Skip to content

Commit

Permalink
Merge commit '781d630b3ae059e7e8f2176c7261a48ba17a53d3'
Browse files Browse the repository at this point in the history
  • Loading branch information
mgirlich committed Jan 22, 2024
2 parents fd5eaec + 781d630 commit 177af83
Show file tree
Hide file tree
Showing 50 changed files with 854 additions and 245 deletions.
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ S3method(db_copy_to,DBIConnection)
S3method(db_create_index,DBIConnection)
S3method(db_desc,DBIConnection)
S3method(db_explain,DBIConnection)
S3method(db_explain,OraConnection)
S3method(db_explain,Oracle)
S3method(db_query_fields,DBIConnection)
S3method(db_query_fields,PostgreSQLConnection)
S3method(db_save_query,DBIConnection)
S3method(db_sql_render,"Microsoft SQL Server")
S3method(db_sql_render,DBIConnection)
S3method(db_supports_table_alias_with_as,DBIConnection)
S3method(db_supports_table_alias_with_as,OraConnection)
Expand Down
39 changes: 39 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# dbplyr (development version)

* Namespaced dplyr calls now error if the function doesn't exist, or
a translation is not available (#1426).

* `db_sql_render()` correctly passes on `...` when re-calling with
`sql_options` set (#1394).

* `-1 + x` is now translated correctly (#1420).

* SQL server: clear error if you attempt to use `n_distinct()` in `mutate()`
or `filter()` (#1366).

* Add translations for clock functions `add_years()`, `add_days()`,
`date_build()`, `get_year()`, `get_month()`, `get_day()`,
and `base::difftime()` on SQL server, Redshift, Snowflake, and Postgres.

* SQL server: `filter()` does a better job of converting logical vectors
from bit to boolean (@ejneer, #1288).

* Oracle: Added support for `str_replace()` and `str_replace_all()` via
`REGEXP_REPLACE()` (@thomashulst, #1402).

* Allow additional arguments to be passed from `compute()` all the way to
`sql_query_save()`-method (@rsund).

* The class of remote sources now includes all S4 class names, not just
the first (#918).

* `db_explain()` now works for Oracle (@thomashulst, #1353).

* Database errors now show the generated SQL, which hopefully will make it
faster to track down problems (#1401).

* Snowflake (@nathanhaigh, #1406)
* Added support for `str_starts()` and `str_ends()` via `REGEXP_INSTR()`
* Refactored `str_detect()` to use `REGEXP_INSTR()` so now supports
regular expressions.
* Refactored `grepl()` to use `REGEXP_INSTR()` so now supports
case-insensitive matching through `grepl(..., ignore.case = TRUE)`

* Functions qualified with the base namespace are now also translated, e.g.
`base::paste0(x, "_1")` is now translated (@mgirlich, #1022).

Expand Down
91 changes: 84 additions & 7 deletions R/backend-mssql.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
#' details of overall translation technology. Key differences for this backend
#' are:
#'
#' * `SELECT` uses `TOP` not `LIMIT`
#' * Automatically prefixes `#` to create temporary tables. Add the prefix
#' - `SELECT` uses `TOP` not `LIMIT`
#' - Automatically prefixes `#` to create temporary tables. Add the prefix
#' yourself to avoid the message.
#' * String basics: `paste()`, `substr()`, `nchar()`
#' * Custom types for `as.*` functions
#' * Lubridate extraction functions, `year()`, `month()`, `day()` etc
#' * Semi-automated bit <-> boolean translation (see below)
#' - String basics: `paste()`, `substr()`, `nchar()`
#' - Custom types for `as.*` functions
#' - Lubridate extraction functions, `year()`, `month()`, `day()` etc
#' - Semi-automated bit <-> boolean translation (see below)
#'
#' Use `simulate_mssql()` with `lazy_frame()` to see simulated SQL without
#' converting to live access database.
Expand Down Expand Up @@ -350,6 +350,41 @@ simulate_mssql <- function(version = "15.0") {
sql_expr(DATEPART(QUARTER, !!x))
}
},

# clock ---------------------------------------------------------------
add_days = function(x, n, ...) {
check_dots_empty()
sql_expr(DATEADD(DAY, !!n, !!x))
},
add_years = function(x, n, ...) {
check_dots_empty()
sql_expr(DATEADD(YEAR, !!n, !!x))
},
date_build = function(year, month = 1L, day = 1L, ..., invalid = NULL) {
sql_expr(DATEFROMPARTS(!!year, !!month, !!day))
},
get_year = function(x) {
sql_expr(DATEPART('year', !!x))
},
get_month = function(x) {
sql_expr(DATEPART('month', !!x))
},
get_day = function(x) {
sql_expr(DATEPART('day', !!x))
},

difftime = function(time1, time2, tz, units = "days") {

if (!missing(tz)) {
cli::cli_abort("The {.arg tz} argument is not supported for SQL backends.")
}

if (units[1] != "days") {
cli::cli_abort('The only supported value for {.arg units} on SQL backends is "days"')
}

sql_expr(DATEDIFF(day, !!time1, !!time2))
}
)

if (mssql_version(con) >= "11.0") { # MSSQL 2012
Expand Down Expand Up @@ -434,7 +469,13 @@ simulate_mssql <- function(version = "15.0") {
},
all = mssql_bit_int_bit(win_aggregate("MIN")),
any = mssql_bit_int_bit(win_aggregate("MAX")),
row_number = win_rank("ROW_NUMBER", empty_order = TRUE)
row_number = win_rank("ROW_NUMBER", empty_order = TRUE),

n_distinct = function(x) {
cli_abort(
"No translation available in `mutate()`/`filter()` for SQL server."
)
}
)

)}
Expand Down Expand Up @@ -582,4 +623,40 @@ mssql_bit_int_bit <- function(f) {
dplyr::if_else(x, "1", "0", "NULL")
}

#' @export
`db_sql_render.Microsoft SQL Server` <- function(con, sql, ..., cte = FALSE, use_star = TRUE) {
# Post-process WHERE to cast logicals from BIT to BOOLEAN
sql$lazy_query <- purrr::modify_tree(
sql$lazy_query,
is_node = function(x) inherits(x, "lazy_query"),
post = mssql_update_where_clause
)

NextMethod()
}

mssql_update_where_clause <- function(qry) {
if (!has_name(qry, "where")) {
return(qry)
}

qry$where <- lapply(
qry$where,
function(x) set_expr(x, bit_to_boolean(get_expr(x)))
)
qry
}

bit_to_boolean <- function(x_expr) {
if (is_atomic(x_expr) || is_symbol(x_expr)) {
expr(cast(!!x_expr %AS% BIT) == 1L)
} else if (is_call(x_expr, c("|", "&", "||", "&&", "!", "("))) {
idx <- seq2(2, length(x_expr))
x_expr[idx] <- lapply(x_expr[idx], bit_to_boolean)
x_expr
} else {
x_expr
}
}

utils::globalVariables(c("BIT", "CAST", "%AS%", "%is%", "convert", "DATE", "DATENAME", "DATEPART", "IIF", "NOT", "SUBSTRING", "LTRIM", "RTRIM", "CHARINDEX", "SYSDATETIME", "SECOND", "MINUTE", "HOUR", "DAY", "DAYOFWEEK", "DAYOFYEAR", "MONTH", "QUARTER", "YEAR", "BIGINT", "INT", "%AND%", "%BETWEEN%"))
60 changes: 55 additions & 5 deletions R/backend-oracle.R
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,43 @@ sql_translation.Oracle <- function(con) {
paste0 = sql_paste_infix("", "||", function(x) sql_expr(cast(!!x %as% text))),
str_c = sql_paste_infix("", "||", function(x) sql_expr(cast(!!x %as% text))),

# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/REGEXP_REPLACE.html
# 4th argument is starting position (default: 1 => first char of string)
# 5th argument is occurrence (default: 0 => match all occurrences)
str_replace = function(string, pattern, replacement){
sql_expr(regexp_replace(!!string, !!pattern, !!replacement, 1L, 1L))
},
str_replace_all = function(string, pattern, replacement){
sql_expr(regexp_replace(!!string, !!pattern, !!replacement))
},

# lubridate --------------------------------------------------------------
today = function() sql_expr(TRUNC(CURRENT_TIMESTAMP)),
now = function() sql_expr(CURRENT_TIMESTAMP)
now = function() sql_expr(CURRENT_TIMESTAMP),

# clock ------------------------------------------------------------------
add_days = function(x, n, ...) {
check_dots_empty()
sql_expr((!!x + NUMTODSINTERVAL(!!n, 'day')))
},
add_years = function(x, n, ...) {
check_dots_empty()
sql_expr((!!x + NUMTODSINTERVAL(!!n * 365.25, 'day')))
},

difftime = function(time1, time2, tz, units = "days") {

if (!missing(tz)) {
cli::cli_abort("The {.arg tz} argument is not supported for SQL backends.")
}

if (units[1] != "days") {
cli::cli_abort('The only supported value for {.arg units} on SQL backends is "days"')
}

sql_expr(CEIL(CAST(!!time2 %AS% DATE) - CAST(!!time1 %AS% DATE)))
}

),
base_odbc_agg,
base_odbc_win
Expand All @@ -144,10 +178,11 @@ sql_translation.Oracle <- function(con) {

#' @export
sql_query_explain.Oracle <- function(con, sql, ...) {
glue_sql2(
con,
"EXPLAIN PLAN FOR {sql};\n",
"SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY()));",

# https://docs.oracle.com/en/database/oracle/oracle-database/19/tgsql/generating-and-displaying-execution-plans.html
c(
glue_sql2(con, "EXPLAIN PLAN FOR {sql}"),
glue_sql2(con, "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY())")
)
}

Expand Down Expand Up @@ -182,6 +217,18 @@ sql_expr_matches.Oracle <- function(con, x, y, ...) {
glue_sql2(con, "decode({x}, {y}, 0, 1) = 0")
}

#' @export
db_explain.Oracle <- function(con, sql, ...) {
sql <- sql_query_explain(con, sql, ...)

msg <- "Can't explain query."
db_execute(con, sql[[1]], msg) # EXPLAIN PLAN
expl <- db_get_query(con, sql[[2]], msg) # DBMS_XPLAN.DISPLAY

out <- utils::capture.output(print(expl))
paste(out, collapse = "\n")
}

#' @export
db_supports_table_alias_with_as.Oracle <- function(con) {
FALSE
Expand Down Expand Up @@ -219,6 +266,9 @@ setdiff.OraConnection <- setdiff.tbl_Oracle
#' @export
sql_expr_matches.OraConnection <- sql_expr_matches.Oracle

#' @export
db_explain.OraConnection <- db_explain.Oracle

#' @export
db_supports_table_alias_with_as.OraConnection <- db_supports_table_alias_with_as.Oracle

Expand Down
35 changes: 35 additions & 0 deletions R/backend-postgres.R
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,41 @@ sql_translation.PqConnection <- function(con) {
)
sql_expr(DATE_TRUNC(!!unit, !!x))
},

# clock ---------------------------------------------------------------
add_days = function(x, n, ...) {
check_dots_empty()
glue_sql2(sql_current_con(), "({.col x} + {.val n}*INTERVAL'1 day')")
},
add_years = function(x, n, ...) {
check_dots_empty()
glue_sql2(sql_current_con(), "({.col x} + {.val n}*INTERVAL'1 year')")
},
date_build = function(year, month = 1L, day = 1L, ..., invalid = NULL) {
sql_expr(make_date(!!year, !!month, !!day))
},
get_year = function(x) {
sql_expr(date_part('year', !!x))
},
get_month = function(x) {
sql_expr(date_part('month', !!x))
},
get_day = function(x) {
sql_expr(date_part('day', !!x))
},

difftime = function(time1, time2, tz, units = "days") {

if (!missing(tz)) {
cli::cli_abort("The {.arg tz} argument is not supported for SQL backends.")
}

if (units[1] != "days") {
cli::cli_abort('The only supported value for {.arg units} on SQL backends is "days"')
}

sql_expr((CAST(!!time2 %AS% DATE) - CAST(!!time1 %AS% DATE)))
},
),
sql_translator(.parent = base_agg,
cor = sql_aggregate_2("CORR"),
Expand Down
35 changes: 35 additions & 0 deletions R/backend-redshift.R
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,41 @@ sql_translation.RedshiftConnection <- function(con) {
str_replace = sql_not_supported("str_replace"),
str_replace_all = function(string, pattern, replacement) {
sql_expr(REGEXP_REPLACE(!!string, !!pattern, !!replacement))
},

# clock ---------------------------------------------------------------
add_days = function(x, n, ...) {
check_dots_empty()
sql_expr(DATEADD(DAY, !!n, !!x))
},
add_years = function(x, n, ...) {
check_dots_empty()
sql_expr(DATEADD(YEAR, !!n, !!x))
},
date_build = function(year, month = 1L, day = 1L, ..., invalid = NULL) {
glue_sql2(sql_current_con(), "TO_DATE(CAST({.val year} AS TEXT) || '-' CAST({.val month} AS TEXT) || '-' || CAST({.val day} AS TEXT)), 'YYYY-MM-DD')")
},
get_year = function(x) {
sql_expr(DATE_PART('year', !!x))
},
get_month = function(x) {
sql_expr(DATE_PART('month', !!x))
},
get_day = function(x) {
sql_expr(DATE_PART('day', !!x))
},

difftime = function(time1, time2, tz, units = "days") {

if (!missing(tz)) {
cli::cli_abort("The {.arg tz} argument is not supported for SQL backends.")
}

if (units[1] != "days") {
cli::cli_abort('The only supported value for {.arg units} on SQL backends is "days"')
}

sql_expr(DATEDIFF(day, !!time1, !!time2))
}
),
sql_translator(.parent = postgres$aggregate,
Expand Down
Loading

0 comments on commit 177af83

Please sign in to comment.