diff --git a/R/backend-dbplyr__duckdb_connection.R b/R/backend-dbplyr__duckdb_connection.R index f0a060712..29226c2c2 100644 --- a/R/backend-dbplyr__duckdb_connection.R +++ b/R/backend-dbplyr__duckdb_connection.R @@ -307,6 +307,16 @@ sql_translation.duckdb_connection <- function(con) { build_sql("DATEDIFF('day', ", !!start, ", " ,!!end, ")") }, + date_build = function(year, month = 1L, day = 1L, ..., invalid = NULL) { + check_unsupported_arg(invalid, allow_null = TRUE) + rlang::check_dots_empty() + dbplyr::sql_expr(MAKE_DATE(!!year, !!month, !!day)) + }, + difftime = function(time1, time2, tz, units = "days") { + check_unsupported_arg(tz) + check_unsupported_arg(units, allowed = "days") + dbplyr::sql_expr(DATEDIFF("day", !!time2, !!time1)) + }, # stringr functions str_c = sql_paste(""), diff --git a/R/check_unsupported_arg.R b/R/check_unsupported_arg.R new file mode 100644 index 000000000..5f3b4de52 --- /dev/null +++ b/R/check_unsupported_arg.R @@ -0,0 +1,50 @@ +#' Check for unsupported argument values +#' +#' The function checks whether a given argument is unsupported +#' It throws an error message if the argument is not among the allowed values. +#' Function is taken from dplyr. +#' +#' @param x The argument to check. +#' @param allowed A value or vector of values that are allowed for `x`. If `NULL`, no value is allowed. +#' @param allow_null Logical. If `TRUE`, allows `x` to be `NULL`. +#' @param backend Optional character string indicating the back-end. +#' @param arg The name of the argument being checked (automatically inferred via `rlang::caller_arg()`). +#' @param call The calling environment used for error reporting (automatically inferred via `rlang::caller_env()`). +#' +#' @noRd +check_unsupported_arg <- function (x, allowed = NULL, allow_null = FALSE, backend = NULL, + arg = caller_arg(x), call = caller_env()) +{ + if (rlang::is_missing(x)) { + return() + } + if (allow_null && rlang::is_null(x)) { + return() + } + if (identical(x, allowed)) { + return() + } + if (rlang::is_null(allowed)) { + msg <- "Argument {.arg {arg}} isn't supported" + } + else { + msg <- "{.code {arg} = {.val {x}}} isn't supported" + } + if (is.null(backend)) { + msg <- paste0(msg, " on database backends.") + } + else { + msg <- paste0(msg, " in {backend} translation.") + } + if (!rlang::is_null(allowed)) { + if (allow_null) { + allow_msg <- "It must be {.val {allowed}} or {.code NULL} instead." + } + else { + allow_msg <- "It must be {.val {allowed}} instead." + } + msg <- c(msg, i = allow_msg) + } + cli_abort(msg, call = call) +} + diff --git a/src/duckdb/ub_src_function_table_system-6d8b1e5a.o.tmp b/src/duckdb/ub_src_function_table_system-6d8b1e5a.o.tmp new file mode 100644 index 000000000..e69de29bb diff --git a/tests/testthat/test-backend-dbplyr__duckdb_connection.R b/tests/testthat/test-backend-dbplyr__duckdb_connection.R index 2bfb56af6..8c3a5b9b2 100644 --- a/tests/testthat/test-backend-dbplyr__duckdb_connection.R +++ b/tests/testthat/test-backend-dbplyr__duckdb_connection.R @@ -152,6 +152,10 @@ test_that("custom clock functions translated correctly", { precision = "day")), sql(r"{DATEDIFF('day', start, end)}")) + expect_equal(translate(difftime(time1 = "time1", time2 = "time2", units = "days")), sql(r"{DATEDIFF('day', 'time2', 'time1')}")) + expect_equal(translate(date_build(year = 2000L, month = 8L, day = 8L)), sql(r"{MAKE_DATE(2000, 8, 8)}")) + expect_equal(translate(date_build(year = 2000)), sql(r"{MAKE_DATE(2000.0, 1, 1)}")) + test_data <- data.frame( person = 1L, date_1 = as.Date("2000-01-01"), @@ -193,6 +197,9 @@ test_that("custom clock functions translated correctly", { precision = "day", n = 5))) + expect_error(translate(date_build(year = 2000, month = 08, day = 08, invalid = "next-day"))) + expect_error(translate(difftime(time1, time2, units = "secs"))) + expect_error(translate(difftime(time1, time2, tz = Sys.timezone()))) }) # stringr functions