Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ S3method(union_all,tbl_lazy)
S3method(unique,sql)
S3method(values_prepare,DBIConnection)
S3method(values_prepare,SQLiteConnection)
export(.sql)
export(as.sql)
export(as_table_path)
export(base_agg)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# dbplyr (development version)

* New `.sql` pronoun makes it a little easier to use known SQL functions in packages, requiring only `@importFrom dbplyr .sql` (#1117).
* `join_by(between())` now correctly handles column renames (#1572).
* SQL Server uses `DATEDIFF_BIG` instead of `DATEDIFF` to work regardless of data size (@edward-burn, #1666).
* `na_matches = "na"` now works correctly with inequality and overlap joins, preserving the comparison operator instead of converting to equality (#1505).
Expand Down
41 changes: 38 additions & 3 deletions R/tidyeval.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
#' }
#'
#' You can override the guesses using `local()` and `remote()` to force
#' computation, or by using the `.data` and `.env` pronouns of tidy evaluation.
#' computation, by using the `.data` and `.env` pronouns of tidy evaluation,
#' or by using dbplyr's own `.sql` pronoun.
#'
#' @param call an unevaluated expression, as produced by [quote()]
#' @param data A lazy data frame backed by a database query.
Expand Down Expand Up @@ -49,6 +50,11 @@
#' f <- function(x) x + 1
#' partial_eval(quote(year > f(1980)), lf)
#' partial_eval(quote(year > local(f(1980))), lf)
#'
#' # You can use `.sql` to make it clear that the function comes from SQL,
#' # and inside a package, reduce the number of globalVariables() directives
#' # needed
#' partial_eval(quote(.sql$EXTRACT_YEAR(year)), lf)
partial_eval <- function(
call,
data,
Expand Down Expand Up @@ -193,6 +199,9 @@ partial_eval_sym <- function(sym, data, env) {
is_mask_pronoun <- function(call) {
is_call(call, c("$", "[["), n = 2) && is_symbol(call[[2]], c(".data", ".env"))
}
is_sql_pronoun <- function(call) {
is_call(call, "$", n = 2) && is_symbol(call[[2]], ".sql")
}

partial_eval_call <- function(call, data, env) {
fun <- call[[1]]
Expand All @@ -213,20 +222,22 @@ partial_eval_call <- function(call, data, env) {
call[[1]] <- fun <- sym(fun_name)
}

# Compound calls, apart from `::` aren't translatable
# Compound calls, apart from pronouns and `::` aren't translatable
if (is_call(fun) && !is_call(fun, "::")) {
if (is_mask_pronoun(fun)) {
cli::cli_abort(
"Use local() or remote() to force evaluation of functions",
call = NULL
)
} else if (is_sql_pronoun(fun)) {
call[[1]] <- fun[[3]]
} else {
return(eval_bare(call, env))
}
}

# .data$, .data[[]], .env$, .env[[]] need special handling
if (is_mask_pronoun(call)) {
# special handling for .data$, .data[[]], .env$, .env[[]]
var <- call[[3]]
if (is_call(call, "[[")) {
var <- sym(eval(var, env))
Expand All @@ -237,6 +248,9 @@ partial_eval_call <- function(call, data, env) {
} else {
eval_bare(var, env)
}
} else if (is_sql_pronoun(call)) {
# special handling for .sql$
call[[3]]
} else {
# Process call arguments recursively, unless user has manually called
# remote/local
Expand Down Expand Up @@ -306,3 +320,24 @@ replace_sym <- function(call, sym, replace) {
call
}
}


#' Flag SQL function usage
#'
#' @description
#' Use `.sql$foo(x, y)` to make it clear that you're calling the SQL
#' `foo()` function, not the R `foo()` function. This also makes it easier to
#' reduce `R CMD check` notes in packages; just import `.sql` from dbplyr with
#' e.g. `@importFrom dbplyr .sql`.
#'
#' Note that `.sql` itself does nothing and is just `NULL`; it is automatically
#' removed when dbplyr translates your R code to SQL.
#'
#' @export
#' @format NULL
#' @examples
#' library(dplyr, warn.conflicts = FALSE)
#'
#' db <- lazy_frame(x = 1, y = 2)
#' db |> mutate(z = .sql$CUMULATIVE_SUM(x, 1))
.sql <- NULL
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ reference:
- copy_inline
- pull.tbl_sql
- show_query
- .sql

- subtitle: Verbs that affect rows
contents:
Expand Down
25 changes: 25 additions & 0 deletions man/dot-sql.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion man/partial_eval.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions tests/testthat/test-tidyeval.R
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ test_that("respects tidy evaluation pronouns", {
expect_equal(partial_eval(expr(.env[[x]]), lf), "XX")
})

test_that("respects .sql pronoun", {
lf <- lazy_frame(x = 1)

expect_equal(partial_eval(expr(.sql$foo), lf), expr(foo))
expect_equal(partial_eval(expr(.sql$foo(x, "y")), lf), expr(foo(x, "y")))
})


test_that("fails with multi-classes", {
lf <- lazy_frame(x = 1, y = 2)
x <- structure(list(), class = c('a', 'b'))
Expand Down
11 changes: 11 additions & 0 deletions tests/testthat/test-verb-mutate.R
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ test_that("empty mutate returns input", {
expect_equal(mutate(gf, !!!list()), gf)
})

test_that("can use .sql pronoun", {
lf <- lazy_frame(x = 1)

# Similar capability to existing escaping mechanisms, but more
# convenient for package usage
expect_equal(
lf |> mutate(y = .sql$foo(x), z = .sql$abc + x),
lf |> mutate(y = foo(x), z = remote(abc) + x)
)
})

# .by -------------------------------------------------------------------------

test_that("can group transiently using `.by`", {
Expand Down
7 changes: 7 additions & 0 deletions vignettes/dbplyr.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ db |>
filter(x %LIKE% "%foo%")
```

To make it more clear that you're calling a SQL function, not an R function, you can use the `.sql` pronoun:

```{r}
db |>
mutate(z = .sql$foofify(x, y))
```

SQL functions tend to have a greater variety of syntax than R. That means there are a number of expressions that can't be translated directly from R code. To insert these in your own queries, you can use literal SQL inside `sql()`:

```{r}
Expand Down
Loading
Loading