From 1c515db4a292f1c17d3ad29e0a681dc747b5ff29 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Wed, 1 Oct 2025 10:58:12 -0700 Subject: [PATCH 01/14] Created MuckDb first go --- DESCRIPTION | 3 +- NAMESPACE | 3 ++ R/Connect.R | 50 +++++++++++++++++++++++++++- R/MuckDb.R | 64 ++++++++++++++++++++++++++++++++++++ man/muckdbConnect.Rd | 31 +++++++++++++++++ tests/testthat/test-MuckDb.R | 36 ++++++++++++++++++++ 6 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 R/MuckDb.R create mode 100644 man/muckdbConnect.Rd create mode 100644 tests/testthat/test-MuckDb.R diff --git a/DESCRIPTION b/DESCRIPTION index 1f2532df..459dda99 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -52,7 +52,8 @@ Suggests: bigrquery, pool, ParallelLogger, - AzureStor + AzureStor, + reticulate License: Apache License VignetteBuilder: knitr URL: https://ohdsi.github.io/DatabaseConnector/, https://github.com/OHDSI/DatabaseConnector diff --git a/NAMESPACE b/NAMESPACE index 28f26cf5..a94f85d3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -26,6 +26,8 @@ export(connect) export(createConnectionDetails) export(createDbiConnectionDetails) export(createZipFile) +export(dbGetQuery.muckdb) +export(dbSendQuery.muckdb) export(dbms) export(disconnect) export(downloadJdbcDrivers) @@ -38,6 +40,7 @@ export(getTableNames) export(inDatabaseSchema) export(insertTable) export(isSqlReservedWord) +export(muckdbConnect) export(querySql) export(querySqlToAndromeda) export(renderTranslateExecuteSql) diff --git a/R/Connect.R b/R/Connect.R index 3b87d83f..f3ab4536 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -31,7 +31,8 @@ checkIfDbmsIsSupported <- function(dbms) { "snowflake", "synapse", "duckdb", - "iris" + "iris", + "muckdb" ) deprecated <- c( "hive", @@ -297,6 +298,8 @@ connect <- function(connectionDetails = NULL, connectSqlite(connectionDetails) } else if (connectionDetails$dbms == "duckdb") { connectDuckdb(connectionDetails) + } else if (connectionDetails$dbms == "muckdb") { + connectMuckdb(connectionDetails) } else if (connectionDetails$dbms == "spark" && is.null(connectionDetails$connectionString())) { connectSparkUsingOdbc(connectionDetails) } else { @@ -871,6 +874,51 @@ connectDuckdb <- function(connectionDetails) { return(connection) } + +#' Connect to a mock DBMS using DuckDB and sqlglot (muckdb) +#' +#' @param connectionDetails A DatabaseConnector connectionDetails object. +#' Should include dbms (mock platform name as the connection string) and server (DuckDB dbdir). +#' @return A muckdb connection object. +#' @keywords internal +#' @noRd +connectMuckdb <- function(connectionDetails) { + inform("Connecting using DuckDB driver with sqlglot platform emulation") + ensure_installed("duckdb") + ensure_installed("reticulate") + + # Check for Python module sqlglot + if (!reticulate::py_module_available("sqlglot")) { + stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot") + } + + # Create muckdb connection using the camelCase DBI constructor + connection <- muckdbConnect( + platform = connectionDetails$connectionString, + dbDir = connectionDetails$server() + ) + + # Check if ICU extension is installed, and if not, try to install it: + isInstalled <- DBI::dbGetQuery( + conn = unclass(connection), + statement = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" + )[1, 1] + if (!isInstalled) { + warning("The ICU extension of DuckDB is not installed. Attempting to install it.") + tryCatch( + DBI::dbExecute(unclass(connection), "INSTALL icu"), + error = function(e) { + warning("Attempting to install the ICU extension of DuckDB failed.\n", + "You may need to check your internet connection.\n", + "For more detail, try 'DBI::dbExecute(connection, \"INSTALL icu\")'.\n", + "Be aware that some time and date functionality will not be available.") + return(NULL) + } + ) + } + return(connection) +} + generateRandomString <- function(length = 20) { return(paste(sample(c(letters, 0:9), length, TRUE), collapse = "")) } diff --git a/R/MuckDb.R b/R/MuckDb.R new file mode 100644 index 00000000..58867a4b --- /dev/null +++ b/R/MuckDb.R @@ -0,0 +1,64 @@ +# Copyright 2025 Observational Health Data Sciences and Informatics +# +# This file is part of DatabaseConnector +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +#' muckdb: Mock DBMS connection using DuckDB and sqlglot +#' +#' This class allows testing SQL logic as if running on any platform (e.g., Hive, BigQuery), +#' but actually executes against DuckDB using Python's sqlglot for SQL dialect translation. +#' @examples +#' # Connect to a mock Hive platform using DuckDB and sqlglot +#' conn <- muckdbConnect(platform = "hive") +#' +#' # Create a table using Hive SQL syntax +#' DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") +#' +#' # Query the table using Hive SQL syntax +#' result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") +#' print(result) +#' +#' # Disconnect when done +#' DBI::dbDisconnect(conn) +#' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark") +#' @param dbDir Path to DuckDB database file (or ":memory:") +#' @export +muckdbConnect <- function(platform = "duckdb", ...) { + if (!reticulate::py_module_available("sqlglot")) + stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") + sqlglot <- reticulate::import("sqlglot") + con <- DBI::dbConnect(duckdb::duckdb(), ...) + attr(con, "muckdbPlatform") <- platform + attr(con, "muckdbSqlglot") <- sqlglot + class(con) <- c("muckdb", class(con)) + con +} + +#' @export +dbSendQuery.muckdb <- function(conn, statement, ...) { + platform <- attr(conn, "muckdbPlatform") + sqlglot <- attr(conn, "muckdbSqlglot") + translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] + DBI::dbSendQuery(conn = unclass(conn), statement = translated, ...) +} + +#' @export +dbGetQuery.muckdb <- function(conn, statement, ...) { + platform <- attr(conn, "muckdbPlatform") + sqlglot <- attr(conn, "muckdbSqlglot") + translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] + DBI::dbGetQuery(conn = unclass(conn), statement = translated, ...) +} + diff --git a/man/muckdbConnect.Rd b/man/muckdbConnect.Rd new file mode 100644 index 00000000..3aef1703 --- /dev/null +++ b/man/muckdbConnect.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/MuckDb.R +\name{muckdbConnect} +\alias{muckdbConnect} +\title{muckdb: Mock DBMS connection using DuckDB and sqlglot} +\usage{ +muckdbConnect(platform = "duckdb", ...) +} +\arguments{ +\item{platform}{The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark")} + +\item{dbDir}{Path to DuckDB database file (or ":memory:")} +} +\description{ +This class allows testing SQL logic as if running on any platform (e.g., Hive, BigQuery), +but actually executes against DuckDB using Python's sqlglot for SQL dialect translation. +} +\examples{ +# Connect to a mock Hive platform using DuckDB and sqlglot +conn <- muckdbConnect(platform = "hive") + +# Create a table using Hive SQL syntax +DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") + +# Query the table using Hive SQL syntax +result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") +print(result) + +# Disconnect when done +DBI::dbDisconnect(conn) +} diff --git a/tests/testthat/test-MuckDb.R b/tests/testthat/test-MuckDb.R new file mode 100644 index 00000000..902d454e --- /dev/null +++ b/tests/testthat/test-MuckDb.R @@ -0,0 +1,36 @@ +test_that("muckdbConnect creates a valid connection and executes SQL", { + testthat::skip_if_not_installed("reticulate") + testthat::skip_if_not(reticulate::py_module_available("sqlglot")) + + conn <- DatabaseConnector::muckdbConnect(platform = "hive", dbDir = ":memory:") + + # Table creation and querying with platform SQL + DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") + result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") + testthat::expect_true(is.data.frame(result)) + testthat::expect_true("x" %in% names(result)) + testthat::expect_equal(result$x, 1) + + DBI::dbDisconnect(conn) +}) + +test_that("connectMuckdb returns a muckdb connection with ICU extension check", { + testthat::skip_if_not_installed("reticulate") + testthat::skip_if_not(reticulate::py_module_available("sqlglot")) + + connectionDetails <- createConnectionDetails( + dbms = "muckdb", + connectionString = "hive", + server = function() ":memory:" + ) + conn <- DatabaseConnector::connect(connectionDetails) + + testthat::expect_true(inherits(conn, "muckdb")) + + # Simple roundtrip + DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 2 AS y") + result <- DBI::dbGetQuery(conn, "SELECT y FROM myTable") + testthat::expect_equal(result$y, 2) + + DBI::dbDisconnect(conn) +}) From 04ca762fb3a09fd26b2f327f0fc04053a0b0cff8 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Tue, 7 Oct 2025 15:15:17 -0400 Subject: [PATCH 02/14] Muck settings --- .Rbuildignore | 2 + R/Connect.R | 7 +- R/MuckDb.R | 8 +- ...aseConnectorConnection-character-method.Rd | 3 +- ...esult-DatabaseConnectorDbiResult-method.Rd | 3 +- ...sult-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...nInfo-DatabaseConnectorDbiResult-method.Rd | 3 +- ...Info-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...able-DatabaseConnectorConnection-method.Rd | 3 +- ...nect-DatabaseConnectorConnection-method.Rd | 3 +- ...ConnectorDbiConnection-character-method.Rd | 3 +- ...onnectorJdbcConnection-character-method.Rd | 3 +- ...aseConnectorConnection-character-method.Rd | 3 +- ...Fetch-DatabaseConnectorDbiResult-method.Rd | 3 +- ...etch-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...Info-DatabaseConnectorConnection-method.Rd | 17 ++-- ...bGetInfo-DatabaseConnectorDriver-method.Rd | 17 ++-- ...ConnectorDbiConnection-character-method.Rd | 3 +- ...onnectorJdbcConnection-character-method.Rd | 3 +- ...Count-DatabaseConnectorDbiResult-method.Rd | 3 +- ...ount-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...ected-DatabaseConnectorDbiResult-method.Rd | 3 +- ...cted-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...ement-DatabaseConnectorDbiResult-method.Rd | 3 +- ...ment-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...leted-DatabaseConnectorDbiResult-method.Rd | 3 +- ...eted-DatabaseConnectorJdbcResult-method.Rd | 3 +- ...d-DatabaseConnectorDbiConnection-method.Rd | 13 +-- ...-DatabaseConnectorJdbcConnection-method.Rd | 13 +-- ...aseConnectorConnection-character-method.Rd | 3 +- ...bles-DatabaseConnectorConnection-method.Rd | 3 +- ...aseConnectorConnection-character-method.Rd | 3 +- ...-DatabaseConnectorConnection-ANY-method.Rd | 3 +- ...ConnectorDbiConnection-character-method.Rd | 5 +- ...onnectorJdbcConnection-character-method.Rd | 5 +- ...aseConnectorConnection-character-method.Rd | 5 +- ...-DatabaseConnectorConnection-ANY-method.Rd | 89 ------------------- 37 files changed, 105 insertions(+), 154 deletions(-) delete mode 100644 man/dbWriteTable-DatabaseConnectorConnection-ANY-method.Rd diff --git a/.Rbuildignore b/.Rbuildignore index f75b4029..b7475913 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,3 +1,5 @@ +^renv$ +^renv\.lock$ ^CRAN-RELEASE$ extras man-roxygen diff --git a/R/Connect.R b/R/Connect.R index f3ab4536..6d310b11 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -897,16 +897,17 @@ connectMuckdb <- function(connectionDetails) { platform = connectionDetails$connectionString, dbDir = connectionDetails$server() ) + # Use the underlying duckdb connection for extension checks + duckdbCon <- attr(connection, "duckdbConnection") - # Check if ICU extension is installed, and if not, try to install it: isInstalled <- DBI::dbGetQuery( - conn = unclass(connection), + conn = duckdbCon, statement = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" )[1, 1] if (!isInstalled) { warning("The ICU extension of DuckDB is not installed. Attempting to install it.") tryCatch( - DBI::dbExecute(unclass(connection), "INSTALL icu"), + DBI::dbExecute(duckdbCon, "INSTALL icu"), error = function(e) { warning("Attempting to install the ICU extension of DuckDB failed.\n", "You may need to check your internet connection.\n", diff --git a/R/MuckDb.R b/R/MuckDb.R index 58867a4b..cbf7c1ee 100644 --- a/R/MuckDb.R +++ b/R/MuckDb.R @@ -39,9 +39,11 @@ muckdbConnect <- function(platform = "duckdb", ...) { if (!reticulate::py_module_available("sqlglot")) stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") sqlglot <- reticulate::import("sqlglot") - con <- DBI::dbConnect(duckdb::duckdb(), ...) + duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) + con <- duckdbCon attr(con, "muckdbPlatform") <- platform attr(con, "muckdbSqlglot") <- sqlglot + attr(con, "duckdbConnection") <- duckdbCon class(con) <- c("muckdb", class(con)) con } @@ -59,6 +61,8 @@ dbGetQuery.muckdb <- function(conn, statement, ...) { platform <- attr(conn, "muckdbPlatform") sqlglot <- attr(conn, "muckdbSqlglot") translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] - DBI::dbGetQuery(conn = unclass(conn), statement = translated, ...) + # Remove "muckdb" class so DBI dispatches to duckdb methods + class(conn) <- setdiff(class(conn), "muckdb") + DBI::dbGetQuery(conn = conn, statement = translated, ...) } diff --git a/man/dbAppendTable-DatabaseConnectorConnection-character-method.Rd b/man/dbAppendTable-DatabaseConnectorConnection-character-method.Rd index 8add657f..ceb1a752 100644 --- a/man/dbAppendTable-DatabaseConnectorConnection-character-method.Rd +++ b/man/dbAppendTable-DatabaseConnectorConnection-character-method.Rd @@ -15,7 +15,7 @@ ) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: @@ -50,6 +50,7 @@ The default implementation calls \code{\link[DBI:sqlAppendTableTemplate]{sqlAppe \code{\link[DBI:dbExecute]{dbExecute()}} with the \code{param} argument. Use \code{\link[DBI:dbAppendTableArrow]{dbAppendTableArrow()}} to append data from an Arrow stream. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbAppendTable")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbClearResult-DatabaseConnectorDbiResult-method.Rd b/man/dbClearResult-DatabaseConnectorDbiResult-method.Rd index 46ecb85e..61592f29 100644 --- a/man/dbClearResult-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbClearResult-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbClearResult}{DatabaseConnectorDbiResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -22,6 +22,7 @@ Frees all resources (local and remote) associated with a result set. This step is mandatory for all objects obtained by calling \code{\link[DBI:dbSendQuery]{dbSendQuery()}} or \code{\link[DBI:dbSendStatement]{dbSendStatement()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbClearResult")} } \seealso{ Other DBIResult generics: diff --git a/man/dbClearResult-DatabaseConnectorJdbcResult-method.Rd b/man/dbClearResult-DatabaseConnectorJdbcResult-method.Rd index c84ba858..3c78a432 100644 --- a/man/dbClearResult-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbClearResult-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbClearResult}{DatabaseConnectorJdbcResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -22,6 +22,7 @@ Frees all resources (local and remote) associated with a result set. This step is mandatory for all objects obtained by calling \code{\link[DBI:dbSendQuery]{dbSendQuery()}} or \code{\link[DBI:dbSendStatement]{dbSendStatement()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbClearResult")} } \seealso{ Other DBIResult generics: diff --git a/man/dbColumnInfo-DatabaseConnectorDbiResult-method.Rd b/man/dbColumnInfo-DatabaseConnectorDbiResult-method.Rd index 424e9aba..2a16fe37 100644 --- a/man/dbColumnInfo-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbColumnInfo-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbColumnInfo}{DatabaseConnectorDbiResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -28,6 +28,7 @@ should have as many rows as there are output fields in the result set, and each column in the data.frame describes an aspect of the result set field (field name, type, etc.) +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbColumnInfo")} } \seealso{ Other DBIResult generics: diff --git a/man/dbColumnInfo-DatabaseConnectorJdbcResult-method.Rd b/man/dbColumnInfo-DatabaseConnectorJdbcResult-method.Rd index 38129110..efca1adb 100644 --- a/man/dbColumnInfo-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbColumnInfo-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbColumnInfo}{DatabaseConnectorJdbcResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -28,6 +28,7 @@ should have as many rows as there are output fields in the result set, and each column in the data.frame describes an aspect of the result set field (field name, type, etc.) +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbColumnInfo")} } \seealso{ Other DBIResult generics: diff --git a/man/dbCreateTable-DatabaseConnectorConnection-method.Rd b/man/dbCreateTable-DatabaseConnectorConnection-method.Rd index f4fdf184..9e5375f5 100644 --- a/man/dbCreateTable-DatabaseConnectorConnection-method.Rd +++ b/man/dbCreateTable-DatabaseConnectorConnection-method.Rd @@ -15,7 +15,7 @@ ) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: @@ -53,6 +53,7 @@ The default \code{dbCreateTable()} method calls \code{\link[DBI:sqlCreateTable]{ \code{\link[DBI:dbExecute]{dbExecute()}}. Use \code{\link[DBI:dbCreateTableArrow]{dbCreateTableArrow()}} to create a table from an Arrow schema. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbCreateTable")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbDisconnect-DatabaseConnectorConnection-method.Rd b/man/dbDisconnect-DatabaseConnectorConnection-method.Rd index e66ae8ed..f60a3cbb 100644 --- a/man/dbDisconnect-DatabaseConnectorConnection-method.Rd +++ b/man/dbDisconnect-DatabaseConnectorConnection-method.Rd @@ -7,7 +7,7 @@ \S4method{dbDisconnect}{DatabaseConnectorConnection}(conn) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} } \value{ @@ -17,6 +17,7 @@ This closes the connection, discards all pending work, and frees resources (e.g., memory, sockets). +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbDisconnect")} } \seealso{ Other DBIConnection generics: diff --git a/man/dbExecute-DatabaseConnectorDbiConnection-character-method.Rd b/man/dbExecute-DatabaseConnectorDbiConnection-character-method.Rd index 7010409b..451cda6e 100644 --- a/man/dbExecute-DatabaseConnectorDbiConnection-character-method.Rd +++ b/man/dbExecute-DatabaseConnectorDbiConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbExecute}{DatabaseConnectorDbiConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -30,6 +30,7 @@ the result is always freed by \code{\link[DBI:dbClearResult]{dbClearResult()}}. For passing query parameters, see \code{\link[DBI:dbBind]{dbBind()}}, in particular the "The command execution flow" section. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbExecute")} } \details{ You can also use \code{dbExecute()} to call a stored procedure diff --git a/man/dbExecute-DatabaseConnectorJdbcConnection-character-method.Rd b/man/dbExecute-DatabaseConnectorJdbcConnection-character-method.Rd index 56e817ef..f1aa455a 100644 --- a/man/dbExecute-DatabaseConnectorJdbcConnection-character-method.Rd +++ b/man/dbExecute-DatabaseConnectorJdbcConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbExecute}{DatabaseConnectorJdbcConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -30,6 +30,7 @@ the result is always freed by \code{\link[DBI:dbClearResult]{dbClearResult()}}. For passing query parameters, see \code{\link[DBI:dbBind]{dbBind()}}, in particular the "The command execution flow" section. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbExecute")} } \details{ You can also use \code{dbExecute()} to call a stored procedure diff --git a/man/dbExistsTable-DatabaseConnectorConnection-character-method.Rd b/man/dbExistsTable-DatabaseConnectorConnection-character-method.Rd index 663b81df..ca864ae9 100644 --- a/man/dbExistsTable-DatabaseConnectorConnection-character-method.Rd +++ b/man/dbExistsTable-DatabaseConnectorConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbExistsTable}{DatabaseConnectorConnection,character}(conn, name, databaseSchema = NULL, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: @@ -33,6 +33,7 @@ This includes temporary tables if supported by the database. \description{ Returns if a table given by name exists in the database. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbExistsTable")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbFetch-DatabaseConnectorDbiResult-method.Rd b/man/dbFetch-DatabaseConnectorDbiResult-method.Rd index d0228c14..d5c5c13a 100644 --- a/man/dbFetch-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbFetch-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbFetch}{DatabaseConnectorDbiResult}(res, n = -1, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}, created by +\item{res}{An object inheriting from \linkS4class{DBIResult}, created by \code{\link[DBI:dbSendQuery]{dbSendQuery()}}.} \item{n}{maximum number of records to retrieve per fetch. Use \code{n = -1} @@ -31,6 +31,7 @@ as specified by the driver, but at most the remaining rows in the result set. Fetch the next \code{n} elements (rows) from the result set and return them as a data.frame. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbFetch")} } \details{ \code{fetch()} is provided for compatibility with older DBI clients - for all diff --git a/man/dbFetch-DatabaseConnectorJdbcResult-method.Rd b/man/dbFetch-DatabaseConnectorJdbcResult-method.Rd index 1863a0c5..a7d003ca 100644 --- a/man/dbFetch-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbFetch-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbFetch}{DatabaseConnectorJdbcResult}(res, n = -1, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}, created by +\item{res}{An object inheriting from \linkS4class{DBIResult}, created by \code{\link[DBI:dbSendQuery]{dbSendQuery()}}.} \item{n}{maximum number of records to retrieve per fetch. Use \code{n = -1} @@ -31,6 +31,7 @@ as specified by the driver, but at most the remaining rows in the result set. Fetch the next \code{n} elements (rows) from the result set and return them as a data.frame. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbFetch")} } \details{ \code{fetch()} is provided for compatibility with older DBI clients - for all diff --git a/man/dbGetInfo-DatabaseConnectorConnection-method.Rd b/man/dbGetInfo-DatabaseConnectorConnection-method.Rd index 98f0aa60..2546173d 100644 --- a/man/dbGetInfo-DatabaseConnectorConnection-method.Rd +++ b/man/dbGetInfo-DatabaseConnectorConnection-method.Rd @@ -7,14 +7,14 @@ \S4method{dbGetInfo}{DatabaseConnectorConnection}(dbObj, ...) } \arguments{ -\item{dbObj}{An object inheriting from \link[DBI:DBIObject-class]{DBIObject}, -i.e. \link[DBI:DBIDriver-class]{DBIDriver}, \link[DBI:DBIConnection-class]{DBIConnection}, -or a \link[DBI:DBIResult-class]{DBIResult}} +\item{dbObj}{An object inheriting from \linkS4class{DBIObject}, +i.e. \linkS4class{DBIDriver}, \linkS4class{DBIConnection}, +or a \linkS4class{DBIResult}} \item{...}{Other arguments to methods.} } \value{ -For objects of class \link[DBI:DBIDriver-class]{DBIDriver}, \code{dbGetInfo()} +For objects of class \linkS4class{DBIDriver}, \code{dbGetInfo()} returns a named list that contains at least the following components: \itemize{ @@ -22,7 +22,7 @@ that contains at least the following components: \item \code{client.version}: the version of the DBMS client library. } -For objects of class \link[DBI:DBIConnection-class]{DBIConnection}, \code{dbGetInfo()} +For objects of class \linkS4class{DBIConnection}, \code{dbGetInfo()} returns a named list that contains at least the following components: \itemize{ @@ -35,7 +35,7 @@ It must not contain a \code{password} component. Components that are not applicable should be set to \code{NA}. } -For objects of class \link[DBI:DBIResult-class]{DBIResult}, \code{dbGetInfo()} +For objects of class \linkS4class{DBIResult}, \code{dbGetInfo()} returns a named list that contains at least the following components: \itemize{ @@ -51,9 +51,10 @@ as returned by \code{\link[DBI:dbHasCompleted]{dbHasCompleted()}}. } } \description{ -Retrieves information on objects of class \link[DBI:DBIDriver-class]{DBIDriver}, -\link[DBI:DBIConnection-class]{DBIConnection} or \link[DBI:DBIResult-class]{DBIResult}. +Retrieves information on objects of class \linkS4class{DBIDriver}, +\linkS4class{DBIConnection} or \linkS4class{DBIResult}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetInfo")} } \seealso{ Other DBIDriver generics: diff --git a/man/dbGetInfo-DatabaseConnectorDriver-method.Rd b/man/dbGetInfo-DatabaseConnectorDriver-method.Rd index 31c30896..f16a2c4c 100644 --- a/man/dbGetInfo-DatabaseConnectorDriver-method.Rd +++ b/man/dbGetInfo-DatabaseConnectorDriver-method.Rd @@ -7,14 +7,14 @@ \S4method{dbGetInfo}{DatabaseConnectorDriver}(dbObj, ...) } \arguments{ -\item{dbObj}{An object inheriting from \link[DBI:DBIObject-class]{DBIObject}, -i.e. \link[DBI:DBIDriver-class]{DBIDriver}, \link[DBI:DBIConnection-class]{DBIConnection}, -or a \link[DBI:DBIResult-class]{DBIResult}} +\item{dbObj}{An object inheriting from \linkS4class{DBIObject}, +i.e. \linkS4class{DBIDriver}, \linkS4class{DBIConnection}, +or a \linkS4class{DBIResult}} \item{...}{Other arguments to methods.} } \value{ -For objects of class \link[DBI:DBIDriver-class]{DBIDriver}, \code{dbGetInfo()} +For objects of class \linkS4class{DBIDriver}, \code{dbGetInfo()} returns a named list that contains at least the following components: \itemize{ @@ -22,7 +22,7 @@ that contains at least the following components: \item \code{client.version}: the version of the DBMS client library. } -For objects of class \link[DBI:DBIConnection-class]{DBIConnection}, \code{dbGetInfo()} +For objects of class \linkS4class{DBIConnection}, \code{dbGetInfo()} returns a named list that contains at least the following components: \itemize{ @@ -35,7 +35,7 @@ It must not contain a \code{password} component. Components that are not applicable should be set to \code{NA}. } -For objects of class \link[DBI:DBIResult-class]{DBIResult}, \code{dbGetInfo()} +For objects of class \linkS4class{DBIResult}, \code{dbGetInfo()} returns a named list that contains at least the following components: \itemize{ @@ -51,9 +51,10 @@ as returned by \code{\link[DBI:dbHasCompleted]{dbHasCompleted()}}. } } \description{ -Retrieves information on objects of class \link[DBI:DBIDriver-class]{DBIDriver}, -\link[DBI:DBIConnection-class]{DBIConnection} or \link[DBI:DBIResult-class]{DBIResult}. +Retrieves information on objects of class \linkS4class{DBIDriver}, +\linkS4class{DBIConnection} or \linkS4class{DBIResult}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetInfo")} } \seealso{ Other DBIDriver generics: diff --git a/man/dbGetQuery-DatabaseConnectorDbiConnection-character-method.Rd b/man/dbGetQuery-DatabaseConnectorDbiConnection-character-method.Rd index 2c09b1e6..c759abdc 100644 --- a/man/dbGetQuery-DatabaseConnectorDbiConnection-character-method.Rd +++ b/man/dbGetQuery-DatabaseConnectorDbiConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetQuery}{DatabaseConnectorDbiConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -32,6 +32,7 @@ For retrieving chunked/paged results or for passing query parameters, see \code{\link[DBI:dbSendQuery]{dbSendQuery()}}, in particular the "The data retrieval flow" section. For retrieving results as an Arrow object, see \code{\link[DBI:dbGetQueryArrow]{dbGetQueryArrow()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetQuery")} } \details{ This method is for \code{SELECT} queries only diff --git a/man/dbGetQuery-DatabaseConnectorJdbcConnection-character-method.Rd b/man/dbGetQuery-DatabaseConnectorJdbcConnection-character-method.Rd index fbfe65c7..73c5fcb4 100644 --- a/man/dbGetQuery-DatabaseConnectorJdbcConnection-character-method.Rd +++ b/man/dbGetQuery-DatabaseConnectorJdbcConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetQuery}{DatabaseConnectorJdbcConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -32,6 +32,7 @@ For retrieving chunked/paged results or for passing query parameters, see \code{\link[DBI:dbSendQuery]{dbSendQuery()}}, in particular the "The data retrieval flow" section. For retrieving results as an Arrow object, see \code{\link[DBI:dbGetQueryArrow]{dbGetQueryArrow()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetQuery")} } \details{ This method is for \code{SELECT} queries only diff --git a/man/dbGetRowCount-DatabaseConnectorDbiResult-method.Rd b/man/dbGetRowCount-DatabaseConnectorDbiResult-method.Rd index 325f67e7..d6c5b4c8 100644 --- a/man/dbGetRowCount-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbGetRowCount-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetRowCount}{DatabaseConnectorDbiResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -33,6 +33,7 @@ and after calling \code{dbFetch()}. Returns the total number of rows actually fetched with calls to \code{\link[DBI:dbFetch]{dbFetch()}} for this result set. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetRowCount")} } \seealso{ Other DBIResult generics: diff --git a/man/dbGetRowCount-DatabaseConnectorJdbcResult-method.Rd b/man/dbGetRowCount-DatabaseConnectorJdbcResult-method.Rd index ece3f12e..2132cd14 100644 --- a/man/dbGetRowCount-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbGetRowCount-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetRowCount}{DatabaseConnectorJdbcResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -33,6 +33,7 @@ and after calling \code{dbFetch()}. Returns the total number of rows actually fetched with calls to \code{\link[DBI:dbFetch]{dbFetch()}} for this result set. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetRowCount")} } \seealso{ Other DBIResult generics: diff --git a/man/dbGetRowsAffected-DatabaseConnectorDbiResult-method.Rd b/man/dbGetRowsAffected-DatabaseConnectorDbiResult-method.Rd index 0677cd1e..8d78f320 100644 --- a/man/dbGetRowsAffected-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbGetRowsAffected-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetRowsAffected}{DatabaseConnectorDbiResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -28,6 +28,7 @@ and after the call to \code{dbFetch()}. This method returns the number of rows that were added, deleted, or updated by a data manipulation statement. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetRowsAffected")} } \seealso{ Other DBIResult generics: diff --git a/man/dbGetRowsAffected-DatabaseConnectorJdbcResult-method.Rd b/man/dbGetRowsAffected-DatabaseConnectorJdbcResult-method.Rd index bf978d4e..7b976480 100644 --- a/man/dbGetRowsAffected-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbGetRowsAffected-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetRowsAffected}{DatabaseConnectorJdbcResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -28,6 +28,7 @@ and after the call to \code{dbFetch()}. This method returns the number of rows that were added, deleted, or updated by a data manipulation statement. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetRowsAffected")} } \seealso{ Other DBIResult generics: diff --git a/man/dbGetStatement-DatabaseConnectorDbiResult-method.Rd b/man/dbGetStatement-DatabaseConnectorDbiResult-method.Rd index 2d91c763..79745b41 100644 --- a/man/dbGetStatement-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbGetStatement-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetStatement}{DatabaseConnectorDbiResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -20,6 +20,7 @@ either \code{\link[DBI:dbSendQuery]{dbSendQuery()}} or Returns the statement that was passed to \code{\link[DBI:dbSendQuery]{dbSendQuery()}} or \code{\link[DBI:dbSendStatement]{dbSendStatement()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetStatement")} } \seealso{ Other DBIResult generics: diff --git a/man/dbGetStatement-DatabaseConnectorJdbcResult-method.Rd b/man/dbGetStatement-DatabaseConnectorJdbcResult-method.Rd index a464f11e..116027d9 100644 --- a/man/dbGetStatement-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbGetStatement-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbGetStatement}{DatabaseConnectorJdbcResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -20,6 +20,7 @@ either \code{\link[DBI:dbSendQuery]{dbSendQuery()}} or Returns the statement that was passed to \code{\link[DBI:dbSendQuery]{dbSendQuery()}} or \code{\link[DBI:dbSendStatement]{dbSendStatement()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbGetStatement")} } \seealso{ Other DBIResult generics: diff --git a/man/dbHasCompleted-DatabaseConnectorDbiResult-method.Rd b/man/dbHasCompleted-DatabaseConnectorDbiResult-method.Rd index 5f1fb095..f4e2a050 100644 --- a/man/dbHasCompleted-DatabaseConnectorDbiResult-method.Rd +++ b/man/dbHasCompleted-DatabaseConnectorDbiResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbHasCompleted}{DatabaseConnectorDbiResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -24,6 +24,7 @@ This method returns if the operation has completed. A \code{SELECT} query is completed if all rows have been fetched. A data manipulation statement is always completed. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbHasCompleted")} } \seealso{ Other DBIResult generics: diff --git a/man/dbHasCompleted-DatabaseConnectorJdbcResult-method.Rd b/man/dbHasCompleted-DatabaseConnectorJdbcResult-method.Rd index d5de8d59..e51aacda 100644 --- a/man/dbHasCompleted-DatabaseConnectorJdbcResult-method.Rd +++ b/man/dbHasCompleted-DatabaseConnectorJdbcResult-method.Rd @@ -7,7 +7,7 @@ \S4method{dbHasCompleted}{DatabaseConnectorJdbcResult}(res, ...) } \arguments{ -\item{res}{An object inheriting from \link[DBI:DBIResult-class]{DBIResult}.} +\item{res}{An object inheriting from \linkS4class{DBIResult}.} \item{...}{Other arguments passed on to methods.} } @@ -24,6 +24,7 @@ This method returns if the operation has completed. A \code{SELECT} query is completed if all rows have been fetched. A data manipulation statement is always completed. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbHasCompleted")} } \seealso{ Other DBIResult generics: diff --git a/man/dbIsValid-DatabaseConnectorDbiConnection-method.Rd b/man/dbIsValid-DatabaseConnectorDbiConnection-method.Rd index a4bce0af..38a0d863 100644 --- a/man/dbIsValid-DatabaseConnectorDbiConnection-method.Rd +++ b/man/dbIsValid-DatabaseConnectorDbiConnection-method.Rd @@ -7,9 +7,9 @@ \S4method{dbIsValid}{DatabaseConnectorDbiConnection}(dbObj, ...) } \arguments{ -\item{dbObj}{An object inheriting from \link[DBI:DBIObject-class]{DBIObject}, -i.e. \link[DBI:DBIDriver-class]{DBIDriver}, \link[DBI:DBIConnection-class]{DBIConnection}, -or a \link[DBI:DBIResult-class]{DBIResult}} +\item{dbObj}{An object inheriting from \linkS4class{DBIObject}, +i.e. \linkS4class{DBIDriver}, \linkS4class{DBIConnection}, +or a \linkS4class{DBIResult}} \item{...}{Other arguments to methods.} } @@ -17,14 +17,14 @@ or a \link[DBI:DBIResult-class]{DBIResult}} \code{dbIsValid()} returns a logical scalar, \code{TRUE} if the object specified by \code{dbObj} is valid, \code{FALSE} otherwise. -A \link[DBI:DBIConnection-class]{DBIConnection} object is initially valid, +A \linkS4class{DBIConnection} object is initially valid, and becomes invalid after disconnecting with \code{\link[DBI:dbDisconnect]{dbDisconnect()}}. For an invalid connection object (e.g., for some drivers if the object is saved to a file and then restored), the method also returns \code{FALSE}. -A \link[DBI:DBIResult-class]{DBIResult} object is valid after a call to \code{\link[DBI:dbSendQuery]{dbSendQuery()}}, +A \linkS4class{DBIResult} object is valid after a call to \code{\link[DBI:dbSendQuery]{dbSendQuery()}}, and stays valid even after all rows have been fetched; only clearing it with \code{\link[DBI:dbClearResult]{dbClearResult()}} invalidates it. -A \link[DBI:DBIResult-class]{DBIResult} object is also valid after a call to \code{\link[DBI:dbSendStatement]{dbSendStatement()}}, +A \linkS4class{DBIResult} object is also valid after a call to \code{\link[DBI:dbSendStatement]{dbSendStatement()}}, and stays valid after querying the number of rows affected; only clearing it with \code{\link[DBI:dbClearResult]{dbClearResult()}} invalidates it. If the connection to the database system is dropped (e.g., due to @@ -35,6 +35,7 @@ connectivity problems, server failure, etc.), \code{dbIsValid()} should return This generic tests whether a database object is still valid (i.e. it hasn't been disconnected or cleared). +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbIsValid")} } \seealso{ Other DBIDriver generics: diff --git a/man/dbIsValid-DatabaseConnectorJdbcConnection-method.Rd b/man/dbIsValid-DatabaseConnectorJdbcConnection-method.Rd index 46170627..63f50661 100644 --- a/man/dbIsValid-DatabaseConnectorJdbcConnection-method.Rd +++ b/man/dbIsValid-DatabaseConnectorJdbcConnection-method.Rd @@ -7,9 +7,9 @@ \S4method{dbIsValid}{DatabaseConnectorJdbcConnection}(dbObj, ...) } \arguments{ -\item{dbObj}{An object inheriting from \link[DBI:DBIObject-class]{DBIObject}, -i.e. \link[DBI:DBIDriver-class]{DBIDriver}, \link[DBI:DBIConnection-class]{DBIConnection}, -or a \link[DBI:DBIResult-class]{DBIResult}} +\item{dbObj}{An object inheriting from \linkS4class{DBIObject}, +i.e. \linkS4class{DBIDriver}, \linkS4class{DBIConnection}, +or a \linkS4class{DBIResult}} \item{...}{Other arguments to methods.} } @@ -17,14 +17,14 @@ or a \link[DBI:DBIResult-class]{DBIResult}} \code{dbIsValid()} returns a logical scalar, \code{TRUE} if the object specified by \code{dbObj} is valid, \code{FALSE} otherwise. -A \link[DBI:DBIConnection-class]{DBIConnection} object is initially valid, +A \linkS4class{DBIConnection} object is initially valid, and becomes invalid after disconnecting with \code{\link[DBI:dbDisconnect]{dbDisconnect()}}. For an invalid connection object (e.g., for some drivers if the object is saved to a file and then restored), the method also returns \code{FALSE}. -A \link[DBI:DBIResult-class]{DBIResult} object is valid after a call to \code{\link[DBI:dbSendQuery]{dbSendQuery()}}, +A \linkS4class{DBIResult} object is valid after a call to \code{\link[DBI:dbSendQuery]{dbSendQuery()}}, and stays valid even after all rows have been fetched; only clearing it with \code{\link[DBI:dbClearResult]{dbClearResult()}} invalidates it. -A \link[DBI:DBIResult-class]{DBIResult} object is also valid after a call to \code{\link[DBI:dbSendStatement]{dbSendStatement()}}, +A \linkS4class{DBIResult} object is also valid after a call to \code{\link[DBI:dbSendStatement]{dbSendStatement()}}, and stays valid after querying the number of rows affected; only clearing it with \code{\link[DBI:dbClearResult]{dbClearResult()}} invalidates it. If the connection to the database system is dropped (e.g., due to @@ -35,6 +35,7 @@ connectivity problems, server failure, etc.), \code{dbIsValid()} should return This generic tests whether a database object is still valid (i.e. it hasn't been disconnected or cleared). +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbIsValid")} } \seealso{ Other DBIDriver generics: diff --git a/man/dbListFields-DatabaseConnectorConnection-character-method.Rd b/man/dbListFields-DatabaseConnectorConnection-character-method.Rd index 9aa1cc58..aefe307e 100644 --- a/man/dbListFields-DatabaseConnectorConnection-character-method.Rd +++ b/man/dbListFields-DatabaseConnectorConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbListFields}{DatabaseConnectorConnection,character}(conn, name, databaseSchema = NULL, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: @@ -35,6 +35,7 @@ The returned names are suitable for quoting with \code{dbQuoteIdentifier()}. \description{ Returns the field names of a remote table as a character vector. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbListFields")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbListTables-DatabaseConnectorConnection-method.Rd b/man/dbListTables-DatabaseConnectorConnection-method.Rd index 5aae0f73..2a4b54aa 100644 --- a/man/dbListTables-DatabaseConnectorConnection-method.Rd +++ b/man/dbListTables-DatabaseConnectorConnection-method.Rd @@ -7,7 +7,7 @@ \S4method{dbListTables}{DatabaseConnectorConnection}(conn, databaseSchema = NULL, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{databaseSchema}{The name of the database schema. See details for platform-specific details.} @@ -35,6 +35,7 @@ connection. This should include views and temporary objects, but not all database backends (in particular \pkg{RMariaDB} and \pkg{RMySQL}) support this. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbListTables")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbReadTable-DatabaseConnectorConnection-character-method.Rd b/man/dbReadTable-DatabaseConnectorConnection-character-method.Rd index ebf135e5..18a3178d 100644 --- a/man/dbReadTable-DatabaseConnectorConnection-character-method.Rd +++ b/man/dbReadTable-DatabaseConnectorConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbReadTable}{DatabaseConnectorConnection,character}(conn, name, databaseSchema = NULL, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: @@ -59,6 +59,7 @@ a column to row names and converting the column names to valid R identifiers. Use \code{\link[DBI:dbReadTableArrow]{dbReadTableArrow()}} instead to obtain an Arrow object. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbReadTable")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbRemoveTable-DatabaseConnectorConnection-ANY-method.Rd b/man/dbRemoveTable-DatabaseConnectorConnection-ANY-method.Rd index 0133fc08..b45536e5 100644 --- a/man/dbRemoveTable-DatabaseConnectorConnection-ANY-method.Rd +++ b/man/dbRemoveTable-DatabaseConnectorConnection-ANY-method.Rd @@ -7,7 +7,7 @@ \S4method{dbRemoveTable}{DatabaseConnectorConnection,ANY}(conn, name, databaseSchema = NULL, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: @@ -31,6 +31,7 @@ given verbatim, e.g. \code{SQL('"my_schema"."table_name"')} Remove a remote table (e.g., created by \code{\link[DBI:dbWriteTable]{dbWriteTable()}}) from the database. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbRemoveTable")} } \details{ The \code{databaseSchema} argument is interpreted differently according to the different platforms: diff --git a/man/dbSendQuery-DatabaseConnectorDbiConnection-character-method.Rd b/man/dbSendQuery-DatabaseConnectorDbiConnection-character-method.Rd index f245363c..4afb4c06 100644 --- a/man/dbSendQuery-DatabaseConnectorDbiConnection-character-method.Rd +++ b/man/dbSendQuery-DatabaseConnectorDbiConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbSendQuery}{DatabaseConnectorDbiConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -16,7 +16,7 @@ } \value{ \code{dbSendQuery()} returns -an S4 object that inherits from \link[DBI:DBIResult-class]{DBIResult}. +an S4 object that inherits from \linkS4class{DBIResult}. The result set can be used with \code{\link[DBI:dbFetch]{dbFetch()}} to extract records. Once you have finished using a result, make sure to clear it with \code{\link[DBI:dbClearResult]{dbClearResult()}}. @@ -31,6 +31,7 @@ For interactive use, you should almost always prefer \code{\link[DBI:dbGetQuery] Use \code{\link[DBI:dbSendQueryArrow]{dbSendQueryArrow()}} or \code{\link[DBI:dbGetQueryArrow]{dbGetQueryArrow()}} instead to retrieve the results as an Arrow object. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbSendQuery")} } \details{ This method is for \code{SELECT} queries only. Some backends may diff --git a/man/dbSendQuery-DatabaseConnectorJdbcConnection-character-method.Rd b/man/dbSendQuery-DatabaseConnectorJdbcConnection-character-method.Rd index 306a15ea..62b80081 100644 --- a/man/dbSendQuery-DatabaseConnectorJdbcConnection-character-method.Rd +++ b/man/dbSendQuery-DatabaseConnectorJdbcConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbSendQuery}{DatabaseConnectorJdbcConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -16,7 +16,7 @@ } \value{ \code{dbSendQuery()} returns -an S4 object that inherits from \link[DBI:DBIResult-class]{DBIResult}. +an S4 object that inherits from \linkS4class{DBIResult}. The result set can be used with \code{\link[DBI:dbFetch]{dbFetch()}} to extract records. Once you have finished using a result, make sure to clear it with \code{\link[DBI:dbClearResult]{dbClearResult()}}. @@ -31,6 +31,7 @@ For interactive use, you should almost always prefer \code{\link[DBI:dbGetQuery] Use \code{\link[DBI:dbSendQueryArrow]{dbSendQueryArrow()}} or \code{\link[DBI:dbGetQueryArrow]{dbGetQueryArrow()}} instead to retrieve the results as an Arrow object. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbSendQuery")} } \details{ This method is for \code{SELECT} queries only. Some backends may diff --git a/man/dbSendStatement-DatabaseConnectorConnection-character-method.Rd b/man/dbSendStatement-DatabaseConnectorConnection-character-method.Rd index cf988b88..41af77ea 100644 --- a/man/dbSendStatement-DatabaseConnectorConnection-character-method.Rd +++ b/man/dbSendStatement-DatabaseConnectorConnection-character-method.Rd @@ -7,7 +7,7 @@ \S4method{dbSendStatement}{DatabaseConnectorConnection,character}(conn, statement, ...) } \arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by +\item{conn}{A \linkS4class{DBIConnection} object, as returned by \code{\link[DBI:dbConnect]{dbConnect()}}.} \item{statement}{a character string containing SQL.} @@ -16,7 +16,7 @@ } \value{ \code{dbSendStatement()} returns -an S4 object that inherits from \link[DBI:DBIResult-class]{DBIResult}. +an S4 object that inherits from \linkS4class{DBIResult}. The result set can be used with \code{\link[DBI:dbGetRowsAffected]{dbGetRowsAffected()}} to determine the number of rows affected by the query. Once you have finished using a result, make sure to clear it @@ -31,6 +31,7 @@ returned result object. You must also call \code{\link[DBI:dbClearResult]{dbCle that. For interactive use, you should almost always prefer \code{\link[DBI:dbExecute]{dbExecute()}}. +\Sexpr[results=rd,stage=render]{DBI:::methods_as_rd("dbSendStatement")} } \details{ \code{\link[DBI:dbSendStatement]{dbSendStatement()}} comes with a default implementation that simply diff --git a/man/dbWriteTable-DatabaseConnectorConnection-ANY-method.Rd b/man/dbWriteTable-DatabaseConnectorConnection-ANY-method.Rd deleted file mode 100644 index 75521ad0..00000000 --- a/man/dbWriteTable-DatabaseConnectorConnection-ANY-method.Rd +++ /dev/null @@ -1,89 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/DBI.R -\name{dbWriteTable,DatabaseConnectorConnection,ANY-method} -\alias{dbWriteTable,DatabaseConnectorConnection,ANY-method} -\title{Copy data frames to database tables} -\usage{ -\S4method{dbWriteTable}{DatabaseConnectorConnection,ANY}( - conn, - name, - value, - databaseSchema = NULL, - overwrite = FALSE, - append = FALSE, - temporary = FALSE, - ... -) -} -\arguments{ -\item{conn}{A \link[DBI:DBIConnection-class]{DBIConnection} object, as returned by -\code{\link[DBI:dbConnect]{dbConnect()}}.} - -\item{name}{The table name, passed on to \code{\link[DBI:dbQuoteIdentifier]{dbQuoteIdentifier()}}. Options are: -\itemize{ -\item a character string with the unquoted DBMS table name, -e.g. \code{"table_name"}, -\item a call to \code{\link[DBI:Id]{Id()}} with components to the fully qualified table name, -e.g. \code{Id(schema = "my_schema", table = "table_name")} -\item a call to \code{\link[DBI:SQL]{SQL()}} with the quoted and fully qualified table name -given verbatim, e.g. \code{SQL('"my_schema"."table_name"')} -}} - -\item{value}{A \link{data.frame} (or coercible to data.frame).} - -\item{databaseSchema}{The name of the database schema. See details for platform-specific details.} - -\item{overwrite}{Overwrite an existing table (if exists)?} - -\item{append}{Append to existing table?} - -\item{temporary}{Should the table created as a temp table?} - -\item{...}{Other parameters passed on to methods.} -} -\value{ -\code{dbWriteTable()} returns \code{TRUE}, invisibly. -} -\description{ -Writes, overwrites or appends a data frame to a database table, optionally -converting row names to a column and specifying SQL data types for fields. - -} -\details{ -The \code{databaseSchema} argument is interpreted differently according to the different platforms: -SQL Server and PDW: The databaseSchema schema should specify both the database and the schema, e.g. -'my_database.dbo'. Impala: the databaseSchema should specify the database. Oracle: -The databaseSchema should specify the Oracle 'user'. All other : The databaseSchema should -specify the schema. -} -\seealso{ -Other DBIConnection generics: -\code{\link[DBI]{DBIConnection-class}}, -\code{\link[DBI]{dbAppendTable}()}, -\code{\link[DBI]{dbAppendTableArrow}()}, -\code{\link[DBI]{dbCreateTable}()}, -\code{\link[DBI]{dbCreateTableArrow}()}, -\code{\link[DBI]{dbDataType}()}, -\code{\link[DBI]{dbDisconnect}()}, -\code{\link[DBI]{dbExecute}()}, -\code{\link[DBI]{dbExistsTable}()}, -\code{\link[DBI]{dbGetException}()}, -\code{\link[DBI]{dbGetInfo}()}, -\code{\link[DBI]{dbGetQuery}()}, -\code{\link[DBI]{dbGetQueryArrow}()}, -\code{\link[DBI]{dbIsReadOnly}()}, -\code{\link[DBI]{dbIsValid}()}, -\code{\link[DBI]{dbListFields}()}, -\code{\link[DBI]{dbListObjects}()}, -\code{\link[DBI]{dbListResults}()}, -\code{\link[DBI]{dbListTables}()}, -\code{\link[DBI]{dbQuoteIdentifier}()}, -\code{\link[DBI]{dbReadTable}()}, -\code{\link[DBI]{dbReadTableArrow}()}, -\code{\link[DBI]{dbRemoveTable}()}, -\code{\link[DBI]{dbSendQuery}()}, -\code{\link[DBI]{dbSendQueryArrow}()}, -\code{\link[DBI]{dbSendStatement}()}, -\code{\link[DBI]{dbUnquoteIdentifier}()}, -\code{\link[DBI]{dbWriteTableArrow}()} -} From 9cf1ad431bea9833f814730724664a7d1ef534a0 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Wed, 8 Oct 2025 12:54:08 -0400 Subject: [PATCH 03/14] reticulate in test enviornment --- .github/workflows/R_CMD_check_Hades.yaml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/R_CMD_check_Hades.yaml b/.github/workflows/R_CMD_check_Hades.yaml index 2c7779ef..eae529cc 100644 --- a/.github/workflows/R_CMD_check_Hades.yaml +++ b/.github/workflows/R_CMD_check_Hades.yaml @@ -99,9 +99,31 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::rcmdcheck + extra-packages: any::rcmdcheck reticulate needs: check + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: setup r-reticulate venv + shell: Rscript {0} + run: | + + if (.Platform$OS.type == "unix" && Sys.info()["sysname"] == "Darwin") { + # macOS + python_path <- "/usr/bin/python3" + } else { + # Windows and Linux + python_path <- Sys.which("python") + } + + path_to_python <- reticulate::virtualenv_create( + envname = "r-reticulate", + python = python_path, + packages = c("sqlglot") + ) + - uses: r-lib/actions/check-r-package@v2 with: args: 'c("--no-manual", "--as-cran")' From 16b3544d56caafe860bcd4fa1305aa02d5ba5f22 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 09:47:33 -0400 Subject: [PATCH 04/14] first working implementation --- NAMESPACE | 5 +- R/Connect.R | 212 +++++++++++++++-------------------- R/MuckDb.R | 71 ++++++++---- tests/testthat/test-MuckDb.R | 19 ++-- 4 files changed, 153 insertions(+), 154 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index a94f85d3..b82d2ee2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -26,8 +26,6 @@ export(connect) export(createConnectionDetails) export(createDbiConnectionDetails) export(createZipFile) -export(dbGetQuery.muckdb) -export(dbSendQuery.muckdb) export(dbms) export(disconnect) export(downloadJdbcDrivers) @@ -53,6 +51,7 @@ export(sql_query_select) exportClasses(DatabaseConnectorDbiResult) exportClasses(DatabaseConnectorDriver) exportClasses(DatabaseConnectorJdbcResult) +exportClasses(muckdb) exportMethods(dbAppendTable) exportMethods(dbCanConnect) exportMethods(dbClearResult) @@ -102,3 +101,5 @@ importFrom(utils,txtProgressBar) importFrom(utils,unzip) importFrom(utils,write.csv) importFrom(utils,write.table) +importMethodsFrom(DBI,dbGetQuery) +importMethodsFrom(DBI,dbSendQuery) diff --git a/R/Connect.R b/R/Connect.R index 6d310b11..c132a2fc 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -50,12 +50,12 @@ checkIfDbmsIsSupported <- function(dbms) { if (dbms %in% deprecated) { warn(sprintf( paste(c("DBMS '%s' has been deprecated. Current functionality is provided as is.", - "No futher support will be provided.", - "Please consider switching to a different database platform."), + "No futher support will be provided.", + "Please consider switching to a different database platform."), collapse = " "), dbms), - .frequency = "regularly", - .frequency_id = "deprecated_dbms" + .frequency = "regularly", + .frequency_id = "deprecated_dbms" ) } } @@ -293,13 +293,13 @@ connect <- function(connectionDetails = NULL, # Using default connectionDetails assertDetailsCanBeValidated(connectionDetails) checkIfDbmsIsSupported(connectionDetails$dbms) - + if (connectionDetails$dbms %in% c("sqlite", "sqlite extended")) { connectSqlite(connectionDetails) } else if (connectionDetails$dbms == "duckdb") { - connectDuckdb(connectionDetails) + return(connectDuckdb(connectionDetails)) } else if (connectionDetails$dbms == "muckdb") { - connectMuckdb(connectionDetails) + return(connectDuckdb(connectionDetails)) } else if (connectionDetails$dbms == "spark" && is.null(connectionDetails$connectionString())) { connectSparkUsingOdbc(connectionDetails) } else { @@ -313,7 +313,9 @@ connectUsingJdbc <- function(connectionDetails) { connectionDetails$pathToDriver <- path.expand(connectionDetails$pathToDriver) checkPathToDriver(connectionDetails$pathToDriver, dbms) - if (dbms == "sql server" || dbms == "synapse" || dbms == "pdw") { + if (dbms == "sql server" || + dbms == "synapse" || + dbms == "pdw") { return(connectSqlServer(connectionDetails)) } else if (dbms == "oracle") { return(connectOracle(connectionDetails)) @@ -364,10 +366,10 @@ connectSqlServer <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } if (connectionDetails$dbms == "pdw") { @@ -421,11 +423,11 @@ connectOracle <- function(connectionDetails) { inform("- Trying using TNSName") connectionString <- paste0("jdbc:oracle:thin:@", connectionDetails$server()) connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } } @@ -433,28 +435,28 @@ connectOracle <- function(connectionDetails) { inform("- using OCI to connect") connectionString <- paste0("jdbc:oracle:oci8:@", connectionDetails$server()) connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } } else { # User has provided the connection string: if (is.null(connectionDetails$user())) { connection <- connectUsingJdbcDriver(driver, - connectionDetails$connectionString(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionDetails$connectionString(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } else { connection <- connectUsingJdbcDriver(driver, - connectionDetails$connectionString(), - user = connectionDetails$user(), - password = connectionDetails$password(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionDetails$connectionString(), + user = connectionDetails$user(), + password = connectionDetails$password(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } } @@ -488,10 +490,10 @@ connectPostgreSql <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } # Used for bulk upload: @@ -538,10 +540,10 @@ connectRedShift <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } return(connection) @@ -617,7 +619,7 @@ connectHive <- function(connectionDetails) { inform("Connecting using Hive driver") jarPath <- findPathToJar("^hive-jdbc-([.0-9]+-)*standalone\\.jar$", connectionDetails$pathToDriver) driver <- getJbcDriverSingleton("org.apache.hive.jdbc.HiveDriver", jarPath) - + if (is.null(connectionDetails$connectionString()) || connectionDetails$connectionString() == "") { connectionString <- paste0("jdbc:hive2://", connectionDetails$server(), ":", connectionDetails$port(), "/") if (!is.null(connectionDetails$extraSettings)) { @@ -652,10 +654,10 @@ connectBigQuery <- function(connectionDetails) { connectionString <- connectionDetails$connectionString() } connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) return(connection) } @@ -722,15 +724,15 @@ connectSnowflake <- function(connectionDetails) { } if (is.null(connectionDetails$user())) { connection <- connectUsingJdbcDriver(driver, connectionDetails$connectionString(), dbms = connectionDetails$dbms, - "CLIENT_TIMESTAMP_TYPE_MAPPING"="TIMESTAMP_NTZ") + "CLIENT_TIMESTAMP_TYPE_MAPPING" = "TIMESTAMP_NTZ") } else { connection <- connectUsingJdbcDriver(driver, - connectionDetails$connectionString(), - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms, - "CLIENT_TIMESTAMP_TYPE_MAPPING"="TIMESTAMP_NTZ", - "QUOTED_IDENTIFIERS_IGNORE_CASE"="FALSE" + connectionDetails$connectionString(), + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms, + "CLIENT_TIMESTAMP_TYPE_MAPPING" = "TIMESTAMP_NTZ", + "QUOTED_IDENTIFIERS_IGNORE_CASE" = "FALSE" ) } return(connection) @@ -771,10 +773,10 @@ connectIris <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } return(connection) @@ -812,11 +814,11 @@ connectUsingJdbcDriver <- function(jdbcDriver, } } connection <- new("DatabaseConnectorJdbcConnection", - jConnection = jConnection, - identifierQuote = "", - stringQuote = "'", - dbms = dbms, - uuid = generateRandomString() + jConnection = jConnection, + identifierQuote = "", + stringQuote = "'", + dbms = dbms, + uuid = generateRandomString() ) registerWithRStudio(connection) attr(connection, "dbms") <- dbms @@ -828,14 +830,14 @@ connectUsingDbi <- function(dbiConnectionDetails) { dbms <- dbiConnectionDetails$dbms dbiConnectionDetails$dbms <- NULL dbiConnection <- do.call(DBI::dbConnect, dbiConnectionDetails) - + connection <- new("DatabaseConnectorDbiConnection", - server = dbms, - dbiConnection = dbiConnection, - identifierQuote = "", - stringQuote = "'", - dbms = dbms, - uuid = generateRandomString() + server = dbms, + dbiConnection = dbiConnection, + identifierQuote = "", + stringQuote = "'", + dbms = dbms, + uuid = generateRandomString() ) registerWithRStudio(connection) attr(connection, "dbms") <- dbms @@ -845,6 +847,13 @@ connectUsingDbi <- function(dbiConnectionDetails) { connectDuckdb <- function(connectionDetails) { inform("Connecting using DuckDB driver") ensure_installed("duckdb") + + if (connectionDetails$dbms == "muckdb") { + ensure_installed("reticulate") + if (!reticulate::py_module_available("sqlglot")) + stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") + } + connection <- connectUsingDbi( createDbiConnectionDetails( dbms = connectionDetails$dbms, @@ -853,9 +862,10 @@ connectDuckdb <- function(connectionDetails) { bigint = "integer64" ) ) + # Check if ICU extension if installed, and if not, try to install it: isInstalled <- querySql( - connection = connection, + connection = connection, sql = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" )[1, 1] if (!isInstalled) { @@ -863,60 +873,20 @@ connectDuckdb <- function(connectionDetails) { tryCatch( executeSql(connection, "INSTALL icu"), error = function(e) { - warning("Attempting to install the ICU extension of DuckDB failed.\n", + warning("Attempting to install the ICU extension of DuckDB failed.\n", "You may need to check your internet connection.\n", "For more detail, try 'executeSql(connection, \"INSTALL icu\")'.\n", - "Be aware that some time and date functionality will not be available.") + "Be aware that some time and date functionality will not be available.") return(NULL) } ) } - return(connection) -} - -#' Connect to a mock DBMS using DuckDB and sqlglot (muckdb) -#' -#' @param connectionDetails A DatabaseConnector connectionDetails object. -#' Should include dbms (mock platform name as the connection string) and server (DuckDB dbdir). -#' @return A muckdb connection object. -#' @keywords internal -#' @noRd -connectMuckdb <- function(connectionDetails) { - inform("Connecting using DuckDB driver with sqlglot platform emulation") - ensure_installed("duckdb") - ensure_installed("reticulate") - - # Check for Python module sqlglot - if (!reticulate::py_module_available("sqlglot")) { - stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot") + # when a dbms(connection) or dbms@connection is called the translation should be for the target test dialect, not duckdb + if (connectionDetails$dbms == "muckdb") { + attr(connection, "dbms") <- connectionDetails$connectionString() } - # Create muckdb connection using the camelCase DBI constructor - connection <- muckdbConnect( - platform = connectionDetails$connectionString, - dbDir = connectionDetails$server() - ) - # Use the underlying duckdb connection for extension checks - duckdbCon <- attr(connection, "duckdbConnection") - - isInstalled <- DBI::dbGetQuery( - conn = duckdbCon, - statement = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" - )[1, 1] - if (!isInstalled) { - warning("The ICU extension of DuckDB is not installed. Attempting to install it.") - tryCatch( - DBI::dbExecute(duckdbCon, "INSTALL icu"), - error = function(e) { - warning("Attempting to install the ICU extension of DuckDB failed.\n", - "You may need to check your internet connection.\n", - "For more detail, try 'DBI::dbExecute(connection, \"INSTALL icu\")'.\n", - "Be aware that some time and date functionality will not be available.") - return(NULL) - } - ) - } return(connection) } @@ -1011,14 +981,14 @@ dbms <- function(connection) { } switch(class(connection), - "Microsoft SQL Server" = "sql server", - "PqConnection" = "postgresql", - "RedshiftConnection" = "redshift", - "BigQueryConnection" = "bigquery", - "SQLiteConnection" = "sqlite", - "duckdb_connection" = "duckdb", - "Snowflake" = "snowflake", - "Spark SQL" = "spark" - # add mappings from various DBI connection classes to SqlRender dbms here + "Microsoft SQL Server" = "sql server", + "PqConnection" = "postgresql", + "RedshiftConnection" = "redshift", + "BigQueryConnection" = "bigquery", + "SQLiteConnection" = "sqlite", + "duckdb_connection" = "duckdb", + "Snowflake" = "snowflake", + "Spark SQL" = "spark" + # add mappings from various DBI connection classes to SqlRender dbms here ) } diff --git a/R/MuckDb.R b/R/MuckDb.R index cbf7c1ee..d5688056 100644 --- a/R/MuckDb.R +++ b/R/MuckDb.R @@ -14,6 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +#' @importMethodsFrom DBI dbSendQuery dbGetQuery +#' @export +setClass( + "muckdb", + contains = "duckdb_connection", + slots = c( + muckdbPlatform = "character", + muckdbSqlglot = "ANY" + ) +) + #' muckdb: Mock DBMS connection using DuckDB and sqlglot #' @@ -34,35 +45,53 @@ #' DBI::dbDisconnect(conn) #' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark") #' @param dbDir Path to DuckDB database file (or ":memory:") +#' @export + #' @export muckdbConnect <- function(platform = "duckdb", ...) { if (!reticulate::py_module_available("sqlglot")) stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") sqlglot <- reticulate::import("sqlglot") + # Create the underlying duckdb connection duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) - con <- duckdbCon - attr(con, "muckdbPlatform") <- platform - attr(con, "muckdbSqlglot") <- sqlglot - attr(con, "duckdbConnection") <- duckdbCon - class(con) <- c("muckdb", class(con)) - con + # Coerce to muckdb and set new slots + new( + "muckdb", + # fill parent slots + conn_ref = duckdbCon@conn_ref, + driver = duckdbCon@driver, + debug = duckdbCon@debug, + convert_opts = duckdbCon@convert_opts, + reserved_words = duckdbCon@reserved_words, + timezone_out = duckdbCon@timezone_out, + tz_out_convert = duckdbCon@tz_out_convert, + bigint = duckdbCon@bigint, + # fill new slots + muckdbPlatform = platform, + muckdbSqlglot = sqlglot + ) } #' @export -dbSendQuery.muckdb <- function(conn, statement, ...) { - platform <- attr(conn, "muckdbPlatform") - sqlglot <- attr(conn, "muckdbSqlglot") - translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] - DBI::dbSendQuery(conn = unclass(conn), statement = translated, ...) -} +setMethod( + "dbSendQuery", + signature(conn = "muckdb", statement = "character"), + function(conn, statement, ...) { + platform <- conn@muckdbPlatform + sqlglot <- conn@muckdbSqlglot + translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] + callNextMethod(conn, translated, ...) + } +) #' @export -dbGetQuery.muckdb <- function(conn, statement, ...) { - platform <- attr(conn, "muckdbPlatform") - sqlglot <- attr(conn, "muckdbSqlglot") - translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] - # Remove "muckdb" class so DBI dispatches to duckdb methods - class(conn) <- setdiff(class(conn), "muckdb") - DBI::dbGetQuery(conn = conn, statement = translated, ...) -} - +setMethod( + "dbGetQuery", + signature(conn = "muckdb", statement = "character"), + function(conn, statement, ...) { + platform <- conn@muckdbPlatform + sqlglot <- conn@muckdbSqlglot + translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] + callNextMethod(conn, translated, ...) + } +) \ No newline at end of file diff --git a/tests/testthat/test-MuckDb.R b/tests/testthat/test-MuckDb.R index 902d454e..67423e8d 100644 --- a/tests/testthat/test-MuckDb.R +++ b/tests/testthat/test-MuckDb.R @@ -1,11 +1,10 @@ -test_that("muckdbConnect creates a valid connection and executes SQL", { +test_that("muckdbConnect creates a valid connection and executes SQL with DBI S4 dispatch", { testthat::skip_if_not_installed("reticulate") testthat::skip_if_not(reticulate::py_module_available("sqlglot")) conn <- DatabaseConnector::muckdbConnect(platform = "hive", dbDir = ":memory:") - # Table creation and querying with platform SQL - DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") + DBI::dbSendQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") testthat::expect_true(is.data.frame(result)) testthat::expect_true("x" %in% names(result)) @@ -14,23 +13,23 @@ test_that("muckdbConnect creates a valid connection and executes SQL", { DBI::dbDisconnect(conn) }) -test_that("connectMuckdb returns a muckdb connection with ICU extension check", { +test_that("connectMuckdb returns a mucked up connection", { testthat::skip_if_not_installed("reticulate") testthat::skip_if_not(reticulate::py_module_available("sqlglot")) connectionDetails <- createConnectionDetails( dbms = "muckdb", connectionString = "hive", - server = function() ":memory:" + server = ":memory:" ) conn <- DatabaseConnector::connect(connectionDetails) + testthat::expect_true(inherits(conn, "DatabaseConnectorConnection")) - testthat::expect_true(inherits(conn, "muckdb")) - + expect_equal(dbms(conn), "hive") # Simple roundtrip - DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 2 AS y") - result <- DBI::dbGetQuery(conn, "SELECT y FROM myTable") + renderTranslateExecuteSql(conn, "CREATE TABLE myTable AS SELECT 2 AS y") + result <- renderTranslateQuerySql(conn, "SELECT y FROM myTable") testthat::expect_equal(result$y, 2) - DBI::dbDisconnect(conn) + disconnect(conn) }) From b9a2cf10e9eaa12bb5089c4c8ecd0e12f1c152d3 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 09:54:15 -0400 Subject: [PATCH 05/14] whitespace reversions --- R/Connect.R | 147 +++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 76 deletions(-) diff --git a/R/Connect.R b/R/Connect.R index c132a2fc..63dfad93 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -50,12 +50,12 @@ checkIfDbmsIsSupported <- function(dbms) { if (dbms %in% deprecated) { warn(sprintf( paste(c("DBMS '%s' has been deprecated. Current functionality is provided as is.", - "No futher support will be provided.", - "Please consider switching to a different database platform."), + "No futher support will be provided.", + "Please consider switching to a different database platform."), collapse = " "), dbms), - .frequency = "regularly", - .frequency_id = "deprecated_dbms" + .frequency = "regularly", + .frequency_id = "deprecated_dbms" ) } } @@ -297,9 +297,7 @@ connect <- function(connectionDetails = NULL, if (connectionDetails$dbms %in% c("sqlite", "sqlite extended")) { connectSqlite(connectionDetails) } else if (connectionDetails$dbms == "duckdb") { - return(connectDuckdb(connectionDetails)) - } else if (connectionDetails$dbms == "muckdb") { - return(connectDuckdb(connectionDetails)) + connectDuckdb(connectionDetails) } else if (connectionDetails$dbms == "spark" && is.null(connectionDetails$connectionString())) { connectSparkUsingOdbc(connectionDetails) } else { @@ -313,9 +311,7 @@ connectUsingJdbc <- function(connectionDetails) { connectionDetails$pathToDriver <- path.expand(connectionDetails$pathToDriver) checkPathToDriver(connectionDetails$pathToDriver, dbms) - if (dbms == "sql server" || - dbms == "synapse" || - dbms == "pdw") { + if (dbms == "sql server" || dbms == "synapse" || dbms == "pdw") { return(connectSqlServer(connectionDetails)) } else if (dbms == "oracle") { return(connectOracle(connectionDetails)) @@ -366,10 +362,10 @@ connectSqlServer <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } if (connectionDetails$dbms == "pdw") { @@ -423,11 +419,11 @@ connectOracle <- function(connectionDetails) { inform("- Trying using TNSName") connectionString <- paste0("jdbc:oracle:thin:@", connectionDetails$server()) connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } } @@ -435,28 +431,28 @@ connectOracle <- function(connectionDetails) { inform("- using OCI to connect") connectionString <- paste0("jdbc:oracle:oci8:@", connectionDetails$server()) connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } } else { # User has provided the connection string: if (is.null(connectionDetails$user())) { connection <- connectUsingJdbcDriver(driver, - connectionDetails$connectionString(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionDetails$connectionString(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } else { connection <- connectUsingJdbcDriver(driver, - connectionDetails$connectionString(), - user = connectionDetails$user(), - password = connectionDetails$password(), - oracle.jdbc.mapDateToTimestamp = "false", - dbms = connectionDetails$dbms + connectionDetails$connectionString(), + user = connectionDetails$user(), + password = connectionDetails$password(), + oracle.jdbc.mapDateToTimestamp = "false", + dbms = connectionDetails$dbms ) } } @@ -490,10 +486,10 @@ connectPostgreSql <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } # Used for bulk upload: @@ -540,10 +536,10 @@ connectRedShift <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } return(connection) @@ -654,10 +650,10 @@ connectBigQuery <- function(connectionDetails) { connectionString <- connectionDetails$connectionString() } connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) return(connection) } @@ -724,15 +720,15 @@ connectSnowflake <- function(connectionDetails) { } if (is.null(connectionDetails$user())) { connection <- connectUsingJdbcDriver(driver, connectionDetails$connectionString(), dbms = connectionDetails$dbms, - "CLIENT_TIMESTAMP_TYPE_MAPPING" = "TIMESTAMP_NTZ") + "CLIENT_TIMESTAMP_TYPE_MAPPING"="TIMESTAMP_NTZ") } else { connection <- connectUsingJdbcDriver(driver, - connectionDetails$connectionString(), - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms, - "CLIENT_TIMESTAMP_TYPE_MAPPING" = "TIMESTAMP_NTZ", - "QUOTED_IDENTIFIERS_IGNORE_CASE" = "FALSE" + connectionDetails$connectionString(), + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms, + "CLIENT_TIMESTAMP_TYPE_MAPPING"="TIMESTAMP_NTZ", + "QUOTED_IDENTIFIERS_IGNORE_CASE"="FALSE" ) } return(connection) @@ -773,10 +769,10 @@ connectIris <- function(connectionDetails) { connection <- connectUsingJdbcDriver(driver, connectionString, dbms = connectionDetails$dbms) } else { connection <- connectUsingJdbcDriver(driver, - connectionString, - user = connectionDetails$user(), - password = connectionDetails$password(), - dbms = connectionDetails$dbms + connectionString, + user = connectionDetails$user(), + password = connectionDetails$password(), + dbms = connectionDetails$dbms ) } return(connection) @@ -814,11 +810,11 @@ connectUsingJdbcDriver <- function(jdbcDriver, } } connection <- new("DatabaseConnectorJdbcConnection", - jConnection = jConnection, - identifierQuote = "", - stringQuote = "'", - dbms = dbms, - uuid = generateRandomString() + jConnection = jConnection, + identifierQuote = "", + stringQuote = "'", + dbms = dbms, + uuid = generateRandomString() ) registerWithRStudio(connection) attr(connection, "dbms") <- dbms @@ -832,12 +828,12 @@ connectUsingDbi <- function(dbiConnectionDetails) { dbiConnection <- do.call(DBI::dbConnect, dbiConnectionDetails) connection <- new("DatabaseConnectorDbiConnection", - server = dbms, - dbiConnection = dbiConnection, - identifierQuote = "", - stringQuote = "'", - dbms = dbms, - uuid = generateRandomString() + server = dbms, + dbiConnection = dbiConnection, + identifierQuote = "", + stringQuote = "'", + dbms = dbms, + uuid = generateRandomString() ) registerWithRStudio(connection) attr(connection, "dbms") <- dbms @@ -862,7 +858,6 @@ connectDuckdb <- function(connectionDetails) { bigint = "integer64" ) ) - # Check if ICU extension if installed, and if not, try to install it: isInstalled <- querySql( connection = connection, @@ -981,14 +976,14 @@ dbms <- function(connection) { } switch(class(connection), - "Microsoft SQL Server" = "sql server", - "PqConnection" = "postgresql", - "RedshiftConnection" = "redshift", - "BigQueryConnection" = "bigquery", - "SQLiteConnection" = "sqlite", - "duckdb_connection" = "duckdb", - "Snowflake" = "snowflake", - "Spark SQL" = "spark" - # add mappings from various DBI connection classes to SqlRender dbms here + "Microsoft SQL Server" = "sql server", + "PqConnection" = "postgresql", + "RedshiftConnection" = "redshift", + "BigQueryConnection" = "bigquery", + "SQLiteConnection" = "sqlite", + "duckdb_connection" = "duckdb", + "Snowflake" = "snowflake", + "Spark SQL" = "spark" + # add mappings from various DBI connection classes to SqlRender dbms here ) } From 0740463ebb8473510f211533cdcd785821d8ceed Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 10:07:26 -0400 Subject: [PATCH 06/14] more elegant solution for inheritance of slots --- R/Connect.R | 2 +- R/MuckDb.R | 35 +++++++++++++++++------------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/R/Connect.R b/R/Connect.R index 63dfad93..1078047d 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -296,7 +296,7 @@ connect <- function(connectionDetails = NULL, if (connectionDetails$dbms %in% c("sqlite", "sqlite extended")) { connectSqlite(connectionDetails) - } else if (connectionDetails$dbms == "duckdb") { + } else if (connectionDetails$dbms %in% c("duckdb", "muckdb")) { connectDuckdb(connectionDetails) } else if (connectionDetails$dbms == "spark" && is.null(connectionDetails$connectionString())) { connectSparkUsingOdbc(connectionDetails) diff --git a/R/MuckDb.R b/R/MuckDb.R index d5688056..ed4fc93e 100644 --- a/R/MuckDb.R +++ b/R/MuckDb.R @@ -43,33 +43,32 @@ setClass( #' #' # Disconnect when done #' DBI::dbDisconnect(conn) -#' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark") +#' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...) #' @param dbDir Path to DuckDB database file (or ":memory:") #' @export #' @export muckdbConnect <- function(platform = "duckdb", ...) { if (!reticulate::py_module_available("sqlglot")) - stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") + stop("Python module 'sqlglot' is required.") sqlglot <- reticulate::import("sqlglot") - # Create the underlying duckdb connection duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) - # Coerce to muckdb and set new slots - new( - "muckdb", - # fill parent slots - conn_ref = duckdbCon@conn_ref, - driver = duckdbCon@driver, - debug = duckdbCon@debug, - convert_opts = duckdbCon@convert_opts, - reserved_words = duckdbCon@reserved_words, - timezone_out = duckdbCon@timezone_out, - tz_out_convert = duckdbCon@tz_out_convert, - bigint = duckdbCon@bigint, - # fill new slots - muckdbPlatform = platform, - muckdbSqlglot = sqlglot + + # Get all slot names/values from parent + parentSlots <- slotNames(duckdbCon) + parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) + names(parentSlotValues) <- parentSlots + + # Add muckdb-specific slots + allSlots <- c( + list( + muckdbPlatform = platform, + muckdbSqlglot = sqlglot + ), + parentSlotValues ) + + do.call("new", c("muckdb", allSlots)) } #' @export From c5bb16e55c607a3b714d6fecb9268c10c8ffc20d Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 12:24:34 -0400 Subject: [PATCH 07/14] Working with documentation errors --- NAMESPACE | 2 + R/Connect.R | 62 ++++++++++++++---------- R/MuckDb.R | 92 ++++++++++++++++++++++++++++++------ man/muckdbConnect.Rd | 2 +- tests/testthat/test-MuckDb.R | 53 ++++++++++++--------- 5 files changed, 149 insertions(+), 62 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index b82d2ee2..51621dcb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -52,6 +52,7 @@ exportClasses(DatabaseConnectorDbiResult) exportClasses(DatabaseConnectorDriver) exportClasses(DatabaseConnectorJdbcResult) exportClasses(muckdb) +exportClasses(muckdb_driver) exportMethods(dbAppendTable) exportMethods(dbCanConnect) exportMethods(dbClearResult) @@ -81,6 +82,7 @@ exportMethods(dbWriteTable) import(DBI) import(methods) import(rJava) +importClassesFrom(duckdb,duckdb_connection) importFrom(bit64,integer64) importFrom(dbplyr,dbplyr_edition) importFrom(dbplyr,sql_escape_logical) diff --git a/R/Connect.R b/R/Connect.R index 1078047d..1e14d3f0 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -844,40 +844,52 @@ connectDuckdb <- function(connectionDetails) { inform("Connecting using DuckDB driver") ensure_installed("duckdb") + # Use muckdb if requested if (connectionDetails$dbms == "muckdb") { ensure_installed("reticulate") if (!reticulate::py_module_available("sqlglot")) stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") - } - connection <- connectUsingDbi( - createDbiConnectionDetails( - dbms = connectionDetails$dbms, - drv = duckdb::duckdb(), - dbdir = connectionDetails$server(), - bigint = "integer64" + checkIfDbmsIsSupported(connectionDetails$connectionString()) + connection <- connectUsingDbi( + createDbiConnectionDetails( + dbms = connectionDetails$dbms, + drv = muckdb(platform = connectionDetails$connectionString()), + dbdir = connectionDetails$server(), + bigint = "integer64" + ) ) - ) - # Check if ICU extension if installed, and if not, try to install it: - isInstalled <- querySql( - connection = connection, - sql = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" - )[1, 1] - if (!isInstalled) { - warning("The ICU extension of DuckDB is not installed. Attempting to install it.") - tryCatch( - executeSql(connection, "INSTALL icu"), - error = function(e) { - warning("Attempting to install the ICU extension of DuckDB failed.\n", - "You may need to check your internet connection.\n", - "For more detail, try 'executeSql(connection, \"INSTALL icu\")'.\n", - "Be aware that some time and date functionality will not be available.") - return(NULL) - } + + } else { + connection <- connectUsingDbi( + createDbiConnectionDetails( + dbms = connectionDetails$dbms, + drv = duckdb::duckdb(), + dbdir = connectionDetails$server(), + bigint = "integer64" + ) ) + # Check if ICU extension is installed, and if not, try to install it: + isInstalled <- querySql( + connection = connection, + sql = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" + )[1, 1] + if (!isInstalled) { + warning("The ICU extension of DuckDB is not installed. Attempting to install it.") + tryCatch( + executeSql(connection, "INSTALL icu"), + error = function(e) { + warning("Attempting to install the ICU extension of DuckDB failed.\n", + "You may need to check your internet connection.\n", + "For more detail, try 'executeSql(connection, \"INSTALL icu\")'.\n", + "Be aware that some time and date functionality will not be available.") + return(NULL) + } + ) + } } - # when a dbms(connection) or dbms@connection is called the translation should be for the target test dialect, not duckdb + # For muckdb, set dbms attribute to the target dialect for translation if (connectionDetails$dbms == "muckdb") { attr(connection, "dbms") <- connectionDetails$connectionString() } diff --git a/R/MuckDb.R b/R/MuckDb.R index ed4fc93e..265c1406 100644 --- a/R/MuckDb.R +++ b/R/MuckDb.R @@ -15,6 +15,7 @@ # limitations under the License. #' @importMethodsFrom DBI dbSendQuery dbGetQuery +#' @importClassesFrom duckdb duckdb_connection #' @export setClass( "muckdb", @@ -45,13 +46,12 @@ setClass( #' DBI::dbDisconnect(conn) #' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...) #' @param dbDir Path to DuckDB database file (or ":memory:") -#' @export - #' @export muckdbConnect <- function(platform = "duckdb", ...) { if (!reticulate::py_module_available("sqlglot")) stop("Python module 'sqlglot' is required.") sqlglot <- reticulate::import("sqlglot") + duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) # Get all slot names/values from parent @@ -71,26 +71,90 @@ muckdbConnect <- function(platform = "duckdb", ...) { do.call("new", c("muckdb", allSlots)) } +.transpile <- function(conn, statement, ...) { + platform <- conn@muckdbPlatform + if (platform == "postgresql") { + platform <- "postgres" + } else if (platform == "sql server") { + platform <- "tsql" + } + + sqlglot <- conn@muckdbSqlglot + translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] + callNextMethod(conn, translated, ...) +} + #' @export setMethod( "dbSendQuery", signature(conn = "muckdb", statement = "character"), - function(conn, statement, ...) { - platform <- conn@muckdbPlatform - sqlglot <- conn@muckdbSqlglot - translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] - callNextMethod(conn, translated, ...) - } + .transpile ) #' @export setMethod( "dbGetQuery", signature(conn = "muckdb", statement = "character"), - function(conn, statement, ...) { - platform <- conn@muckdbPlatform - sqlglot <- conn@muckdbSqlglot - translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] - callNextMethod(conn, translated, ...) + .transpile +) + + +#' @export +setClass( + "muckdb_driver", + contains = "duckdb_driver", + slots = list( + muckdbPlatform = "character", + muckdbSqlglot = "ANY" + ) +) + +muckdb <- function(platform = "duckdb", dbdir = ":memory:", ...) { + ensure_installed("reticulate") + if (!reticulate::py_module_available("sqlglot")) + stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") + sqlglot <- reticulate::import("sqlglot") + + # Construct the underlying duckdb driver + duckdbDrv <- duckdb::duckdb(dbdir = dbdir, ...) + + # Copy all slots from duckdb_driver + parentSlots <- slotNames(duckdbDrv) + parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbDrv, s)) + names(parentSlotValues) <- parentSlots + + # Add muckdb-specific slots + allSlots <- c( + list( + muckdbPlatform = platform, + muckdbSqlglot = sqlglot + ), + parentSlotValues + ) + + do.call("new", c("muckdb_driver", allSlots)) +} + +setMethod( + "dbConnect", + signature(drv = "muckdb_driver"), + function(drv, dbdir = ":memory:", ...) { + # Create the underlying duckdb_connection + duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) + # Copy all parent slots + parent_slots <- slotNames(duckdbCon) + parent_slot_values <- lapply(parent_slots, function(s) slot(duckdbCon, s)) + names(parent_slot_values) <- parent_slots + + # Add muckdb-specific slots from the driver + all_slots <- c( + list( + muckdbPlatform = drv@muckdbPlatform, + muckdbSqlglot = drv@muckdbSqlglot + ), + parent_slot_values + ) + + do.call("new", c("muckdb", all_slots)) } -) \ No newline at end of file +) diff --git a/man/muckdbConnect.Rd b/man/muckdbConnect.Rd index 3aef1703..3ac458a6 100644 --- a/man/muckdbConnect.Rd +++ b/man/muckdbConnect.Rd @@ -7,7 +7,7 @@ muckdbConnect(platform = "duckdb", ...) } \arguments{ -\item{platform}{The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark")} +\item{platform}{The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...)} \item{dbDir}{Path to DuckDB database file (or ":memory:")} } diff --git a/tests/testthat/test-MuckDb.R b/tests/testthat/test-MuckDb.R index 67423e8d..7dbb80a2 100644 --- a/tests/testthat/test-MuckDb.R +++ b/tests/testthat/test-MuckDb.R @@ -1,8 +1,38 @@ +test_that("connectMuckdb returns a mucked up connection", { + testthat::skip_if_not_installed("reticulate") + testthat::skip_if_not(reticulate::py_module_available("sqlglot")) + + platforms <- c("oracle", "postgresql", "bigquery", "spark") + + for (plat in platforms) { + connectionDetails <- createConnectionDetails( + dbms = "muckdb", + connectionString = plat, + server = ":memory:" + ) + + conn <- DatabaseConnector::connect(connectionDetails) + testthat::expect_true(inherits(conn, "DatabaseConnectorConnection")) + + expect_equal(dbms(conn), plat) + renderTranslateExecuteSql(conn, "CREATE TABLE myTable AS SELECT 2 AS y") + result <- renderTranslateQuerySql(conn, "SELECT y FROM myTable") + testthat::expect_equal(result$y, 2) + + expect_error(renderTranslateQuerySql(conn, "SELE foo")) + expect_error(renderTranslateQuerySql(conn, "SELE foo")) + expect_error(querySql(conn, "SELE foo")) + expect_error(executeSql(conn, "SELE foo")) + } + + disconnect(conn) +}) + test_that("muckdbConnect creates a valid connection and executes SQL with DBI S4 dispatch", { testthat::skip_if_not_installed("reticulate") testthat::skip_if_not(reticulate::py_module_available("sqlglot")) - conn <- DatabaseConnector::muckdbConnect(platform = "hive", dbDir = ":memory:") + conn <- DatabaseConnector::muckdbConnect(platform = "bigquery", dbDir = ":memory:") # Table creation and querying with platform SQL DBI::dbSendQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") @@ -12,24 +42,3 @@ test_that("muckdbConnect creates a valid connection and executes SQL with DBI S4 DBI::dbDisconnect(conn) }) - -test_that("connectMuckdb returns a mucked up connection", { - testthat::skip_if_not_installed("reticulate") - testthat::skip_if_not(reticulate::py_module_available("sqlglot")) - - connectionDetails <- createConnectionDetails( - dbms = "muckdb", - connectionString = "hive", - server = ":memory:" - ) - conn <- DatabaseConnector::connect(connectionDetails) - testthat::expect_true(inherits(conn, "DatabaseConnectorConnection")) - - expect_equal(dbms(conn), "hive") - # Simple roundtrip - renderTranslateExecuteSql(conn, "CREATE TABLE myTable AS SELECT 2 AS y") - result <- renderTranslateQuerySql(conn, "SELECT y FROM myTable") - testthat::expect_equal(result$y, 2) - - disconnect(conn) -}) From d7761eea8fc171af6721aa9bad81d64cfef63ce4 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 12:26:03 -0400 Subject: [PATCH 08/14] code cleanup --- R/MuckDb.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/R/MuckDb.R b/R/MuckDb.R index 265c1406..d2f2363a 100644 --- a/R/MuckDb.R +++ b/R/MuckDb.R @@ -142,19 +142,19 @@ setMethod( # Create the underlying duckdb_connection duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) # Copy all parent slots - parent_slots <- slotNames(duckdbCon) - parent_slot_values <- lapply(parent_slots, function(s) slot(duckdbCon, s)) - names(parent_slot_values) <- parent_slots + parentSlots <- slotNames(duckdbCon) + parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) + names(parentSlotValues) <- parentSlots # Add muckdb-specific slots from the driver - all_slots <- c( + allSlots <- c( list( muckdbPlatform = drv@muckdbPlatform, muckdbSqlglot = drv@muckdbSqlglot ), - parent_slot_values + parentSlotValues ) - do.call("new", c("muckdb", all_slots)) + do.call("new", c("muckdb", allSlots)) } ) From 6498d26b0a7128f87e48abdffe6ba352a489f960 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 13:41:33 -0400 Subject: [PATCH 09/14] optional imports --- NAMESPACE | 3 - R/MuckDb.R | 215 +++++++++++++++++++++++++++++------------------------ 2 files changed, 117 insertions(+), 101 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 51621dcb..a00f8605 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -82,7 +82,6 @@ exportMethods(dbWriteTable) import(DBI) import(methods) import(rJava) -importClassesFrom(duckdb,duckdb_connection) importFrom(bit64,integer64) importFrom(dbplyr,dbplyr_edition) importFrom(dbplyr,sql_escape_logical) @@ -103,5 +102,3 @@ importFrom(utils,txtProgressBar) importFrom(utils,unzip) importFrom(utils,write.csv) importFrom(utils,write.table) -importMethodsFrom(DBI,dbGetQuery) -importMethodsFrom(DBI,dbSendQuery) diff --git a/R/MuckDb.R b/R/MuckDb.R index d2f2363a..a1ad36c1 100644 --- a/R/MuckDb.R +++ b/R/MuckDb.R @@ -14,62 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -#' @importMethodsFrom DBI dbSendQuery dbGetQuery -#' @importClassesFrom duckdb duckdb_connection -#' @export -setClass( - "muckdb", - contains = "duckdb_connection", - slots = c( - muckdbPlatform = "character", - muckdbSqlglot = "ANY" - ) -) - - -#' muckdb: Mock DBMS connection using DuckDB and sqlglot -#' -#' This class allows testing SQL logic as if running on any platform (e.g., Hive, BigQuery), -#' but actually executes against DuckDB using Python's sqlglot for SQL dialect translation. -#' @examples -#' # Connect to a mock Hive platform using DuckDB and sqlglot -#' conn <- muckdbConnect(platform = "hive") -#' -#' # Create a table using Hive SQL syntax -#' DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") -#' -#' # Query the table using Hive SQL syntax -#' result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") -#' print(result) -#' -#' # Disconnect when done -#' DBI::dbDisconnect(conn) -#' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...) -#' @param dbDir Path to DuckDB database file (or ":memory:") -#' @export -muckdbConnect <- function(platform = "duckdb", ...) { - if (!reticulate::py_module_available("sqlglot")) - stop("Python module 'sqlglot' is required.") - sqlglot <- reticulate::import("sqlglot") - - duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) - - # Get all slot names/values from parent - parentSlots <- slotNames(duckdbCon) - parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) - names(parentSlotValues) <- parentSlots - - # Add muckdb-specific slots - allSlots <- c( - list( - muckdbPlatform = platform, - muckdbSqlglot = sqlglot - ), - parentSlotValues - ) - - do.call("new", c("muckdb", allSlots)) -} .transpile <- function(conn, statement, ...) { platform <- conn@muckdbPlatform @@ -84,30 +28,85 @@ muckdbConnect <- function(platform = "duckdb", ...) { callNextMethod(conn, translated, ...) } -#' @export -setMethod( - "dbSendQuery", - signature(conn = "muckdb", statement = "character"), - .transpile -) +if (requireNamespace("duckdb", quietly = TRUE)) { + setMethod( + "dbSendQuery", + signature(conn = "muckdb", statement = "character"), + .transpile + ) -#' @export -setMethod( - "dbGetQuery", - signature(conn = "muckdb", statement = "character"), - .transpile -) + setMethod( + "dbGetQuery", + signature(conn = "muckdb", statement = "character"), + .transpile + ) + setClass( + "muckdb_driver", + contains = "duckdb_driver", + slots = list( + muckdbPlatform = "character", + muckdbSqlglot = "ANY" + ) + ) -#' @export -setClass( - "muckdb_driver", - contains = "duckdb_driver", - slots = list( - muckdbPlatform = "character", - muckdbSqlglot = "ANY" + setClass( + "muckdb", + contains = "duckdb_connection", + slots = c( + muckdbPlatform = "character", + muckdbSqlglot = "ANY" + ) ) -) + + setMethod( + "dbConnect", + signature(drv = "muckdb_driver"), + function(drv, dbdir = ":memory:", ...) { + # Create the underlying duckdb_connection + duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) + # Copy all parent slots + parentSlots <- slotNames(duckdbCon) + parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) + names(parentSlotValues) <- parentSlots + + # Add muckdb-specific slots from the driver + allSlots <- c( + list( + muckdbPlatform = drv@muckdbPlatform, + muckdbSqlglot = drv@muckdbSqlglot + ), + parentSlotValues + ) + + do.call("new", c("muckdb", allSlots)) + } + ) + + setMethod( + "dbConnect", + signature(drv = "muckdb_driver"), + function(drv, dbdir = ":memory:", ...) { + # Create the underlying duckdb_connection + duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) + # Copy all parent slots + parentSlots <- slotNames(duckdbCon) + parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) + names(parentSlotValues) <- parentSlots + + # Add muckdb-specific slots from the driver + allSlots <- c( + list( + muckdbPlatform = drv@muckdbPlatform, + muckdbSqlglot = drv@muckdbSqlglot + ), + parentSlotValues + ) + + do.call("new", c("muckdb", allSlots)) + } + ) +} muckdb <- function(platform = "duckdb", dbdir = ":memory:", ...) { ensure_installed("reticulate") @@ -135,26 +134,46 @@ muckdb <- function(platform = "duckdb", dbdir = ":memory:", ...) { do.call("new", c("muckdb_driver", allSlots)) } -setMethod( - "dbConnect", - signature(drv = "muckdb_driver"), - function(drv, dbdir = ":memory:", ...) { - # Create the underlying duckdb_connection - duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) - # Copy all parent slots - parentSlots <- slotNames(duckdbCon) - parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) - names(parentSlotValues) <- parentSlots - - # Add muckdb-specific slots from the driver - allSlots <- c( - list( - muckdbPlatform = drv@muckdbPlatform, - muckdbSqlglot = drv@muckdbSqlglot - ), - parentSlotValues - ) +#' muckdb: Mock DBMS connection using DuckDB and sqlglot +#' +#' This class allows testing SQL logic as if running on any platform (e.g., Hive, BigQuery), +#' but actually executes against DuckDB using Python's sqlglot for SQL dialect translation. +#' @examples +#' # Connect to a mock Hive platform using DuckDB and sqlglot +#' conn <- muckdbConnect(platform = "hive") +#' +#' # Create a table using Hive SQL syntax +#' DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") +#' +#' # Query the table using Hive SQL syntax +#' result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") +#' print(result) +#' +#' # Disconnect when done +#' DBI::dbDisconnect(conn) +#' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...) +#' @param dbDir Path to DuckDB database file (or ":memory:") +#' @export +muckdbConnect <- function(platform = "duckdb", ...) { + if (!reticulate::py_module_available("sqlglot")) + stop("Python module 'sqlglot' is required.") + sqlglot <- reticulate::import("sqlglot") - do.call("new", c("muckdb", allSlots)) - } -) + duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) + + # Get all slot names/values from parent + parentSlots <- slotNames(duckdbCon) + parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) + names(parentSlotValues) <- parentSlots + + # Add muckdb-specific slots + allSlots <- c( + list( + muckdbPlatform = platform, + muckdbSqlglot = sqlglot + ), + parentSlotValues + ) + + do.call("new", c("muckdb", allSlots)) +} \ No newline at end of file From 28b00e8872cf8f87db82a5ce60d6c98925fe98e7 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 14:01:23 -0400 Subject: [PATCH 10/14] Simplified execution --- NAMESPACE | 3 - R/Connect.R | 64 ++++++------- R/MuckDb.R | 179 ----------------------------------- R/Sql.R | 13 ++- man/muckdbConnect.Rd | 31 ------ tests/testthat/test-MuckDb.R | 15 --- 6 files changed, 41 insertions(+), 264 deletions(-) delete mode 100644 R/MuckDb.R delete mode 100644 man/muckdbConnect.Rd diff --git a/NAMESPACE b/NAMESPACE index a00f8605..28f26cf5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -38,7 +38,6 @@ export(getTableNames) export(inDatabaseSchema) export(insertTable) export(isSqlReservedWord) -export(muckdbConnect) export(querySql) export(querySqlToAndromeda) export(renderTranslateExecuteSql) @@ -51,8 +50,6 @@ export(sql_query_select) exportClasses(DatabaseConnectorDbiResult) exportClasses(DatabaseConnectorDriver) exportClasses(DatabaseConnectorJdbcResult) -exportClasses(muckdb) -exportClasses(muckdb_driver) exportMethods(dbAppendTable) exportMethods(dbCanConnect) exportMethods(dbClearResult) diff --git a/R/Connect.R b/R/Connect.R index 1e14d3f0..70c283b5 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -851,47 +851,41 @@ connectDuckdb <- function(connectionDetails) { stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") checkIfDbmsIsSupported(connectionDetails$connectionString()) - connection <- connectUsingDbi( - createDbiConnectionDetails( - dbms = connectionDetails$dbms, - drv = muckdb(platform = connectionDetails$connectionString()), - dbdir = connectionDetails$server(), - bigint = "integer64" - ) - ) - } else { - connection <- connectUsingDbi( - createDbiConnectionDetails( - dbms = connectionDetails$dbms, - drv = duckdb::duckdb(), - dbdir = connectionDetails$server(), - bigint = "integer64" - ) + } + + connection <- connectUsingDbi( + createDbiConnectionDetails( + dbms = connectionDetails$dbms, + drv = duckdb::duckdb(), + dbdir = connectionDetails$server(), + bigint = "integer64" + ) + ) + # Check if ICU extension is installed, and if not, try to install it: + isInstalled <- querySql( + connection = connection, + sql = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" + )[1, 1] + if (!isInstalled) { + warning("The ICU extension of DuckDB is not installed. Attempting to install it.") + tryCatch( + executeSql(connection, "INSTALL icu"), + error = function(e) { + warning("Attempting to install the ICU extension of DuckDB failed.\n", + "You may need to check your internet connection.\n", + "For more detail, try 'executeSql(connection, \"INSTALL icu\")'.\n", + "Be aware that some time and date functionality will not be available.") + return(NULL) + } ) - # Check if ICU extension is installed, and if not, try to install it: - isInstalled <- querySql( - connection = connection, - sql = "SELECT installed FROM duckdb_extensions() WHERE extension_name = 'icu';" - )[1, 1] - if (!isInstalled) { - warning("The ICU extension of DuckDB is not installed. Attempting to install it.") - tryCatch( - executeSql(connection, "INSTALL icu"), - error = function(e) { - warning("Attempting to install the ICU extension of DuckDB failed.\n", - "You may need to check your internet connection.\n", - "For more detail, try 'executeSql(connection, \"INSTALL icu\")'.\n", - "Be aware that some time and date functionality will not be available.") - return(NULL) - } - ) - } } - # For muckdb, set dbms attribute to the target dialect for translation + # For muckdb, set dbms attribute to the target dialect for translation and attaches transpiler if (connectionDetails$dbms == "muckdb") { attr(connection, "dbms") <- connectionDetails$connectionString() + attr(connection, "sqlglot") <- reticulate::import("sqlglot") + attr(connection, "isMuckDb") <- TRUE } return(connection) diff --git a/R/MuckDb.R b/R/MuckDb.R deleted file mode 100644 index a1ad36c1..00000000 --- a/R/MuckDb.R +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright 2025 Observational Health Data Sciences and Informatics -# -# This file is part of DatabaseConnector -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -.transpile <- function(conn, statement, ...) { - platform <- conn@muckdbPlatform - if (platform == "postgresql") { - platform <- "postgres" - } else if (platform == "sql server") { - platform <- "tsql" - } - - sqlglot <- conn@muckdbSqlglot - translated <- sqlglot$transpile(statement, read = platform, write = "duckdb")[[1]] - callNextMethod(conn, translated, ...) -} - -if (requireNamespace("duckdb", quietly = TRUE)) { - setMethod( - "dbSendQuery", - signature(conn = "muckdb", statement = "character"), - .transpile - ) - - setMethod( - "dbGetQuery", - signature(conn = "muckdb", statement = "character"), - .transpile - ) - - setClass( - "muckdb_driver", - contains = "duckdb_driver", - slots = list( - muckdbPlatform = "character", - muckdbSqlglot = "ANY" - ) - ) - - setClass( - "muckdb", - contains = "duckdb_connection", - slots = c( - muckdbPlatform = "character", - muckdbSqlglot = "ANY" - ) - ) - - setMethod( - "dbConnect", - signature(drv = "muckdb_driver"), - function(drv, dbdir = ":memory:", ...) { - # Create the underlying duckdb_connection - duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) - # Copy all parent slots - parentSlots <- slotNames(duckdbCon) - parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) - names(parentSlotValues) <- parentSlots - - # Add muckdb-specific slots from the driver - allSlots <- c( - list( - muckdbPlatform = drv@muckdbPlatform, - muckdbSqlglot = drv@muckdbSqlglot - ), - parentSlotValues - ) - - do.call("new", c("muckdb", allSlots)) - } - ) - - setMethod( - "dbConnect", - signature(drv = "muckdb_driver"), - function(drv, dbdir = ":memory:", ...) { - # Create the underlying duckdb_connection - duckdbCon <- DBI::dbConnect(duckdb::duckdb(), dbdir = dbdir, ...) - # Copy all parent slots - parentSlots <- slotNames(duckdbCon) - parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) - names(parentSlotValues) <- parentSlots - - # Add muckdb-specific slots from the driver - allSlots <- c( - list( - muckdbPlatform = drv@muckdbPlatform, - muckdbSqlglot = drv@muckdbSqlglot - ), - parentSlotValues - ) - - do.call("new", c("muckdb", allSlots)) - } - ) -} - -muckdb <- function(platform = "duckdb", dbdir = ":memory:", ...) { - ensure_installed("reticulate") - if (!reticulate::py_module_available("sqlglot")) - stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") - sqlglot <- reticulate::import("sqlglot") - - # Construct the underlying duckdb driver - duckdbDrv <- duckdb::duckdb(dbdir = dbdir, ...) - - # Copy all slots from duckdb_driver - parentSlots <- slotNames(duckdbDrv) - parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbDrv, s)) - names(parentSlotValues) <- parentSlots - - # Add muckdb-specific slots - allSlots <- c( - list( - muckdbPlatform = platform, - muckdbSqlglot = sqlglot - ), - parentSlotValues - ) - - do.call("new", c("muckdb_driver", allSlots)) -} - -#' muckdb: Mock DBMS connection using DuckDB and sqlglot -#' -#' This class allows testing SQL logic as if running on any platform (e.g., Hive, BigQuery), -#' but actually executes against DuckDB using Python's sqlglot for SQL dialect translation. -#' @examples -#' # Connect to a mock Hive platform using DuckDB and sqlglot -#' conn <- muckdbConnect(platform = "hive") -#' -#' # Create a table using Hive SQL syntax -#' DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") -#' -#' # Query the table using Hive SQL syntax -#' result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") -#' print(result) -#' -#' # Disconnect when done -#' DBI::dbDisconnect(conn) -#' @param platform The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...) -#' @param dbDir Path to DuckDB database file (or ":memory:") -#' @export -muckdbConnect <- function(platform = "duckdb", ...) { - if (!reticulate::py_module_available("sqlglot")) - stop("Python module 'sqlglot' is required.") - sqlglot <- reticulate::import("sqlglot") - - duckdbCon <- DBI::dbConnect(duckdb::duckdb(), ...) - - # Get all slot names/values from parent - parentSlots <- slotNames(duckdbCon) - parentSlotValues <- lapply(parentSlots, function(s) slot(duckdbCon, s)) - names(parentSlotValues) <- parentSlots - - # Add muckdb-specific slots - allSlots <- c( - list( - muckdbPlatform = platform, - muckdbSqlglot = sqlglot - ), - parentSlotValues - ) - - do.call("new", c("muckdb", allSlots)) -} \ No newline at end of file diff --git a/R/Sql.R b/R/Sql.R index 409f94a5..d31fa92e 100644 --- a/R/Sql.R +++ b/R/Sql.R @@ -151,7 +151,12 @@ executeSql <- function(connection, if (!DBI::dbIsValid(connection)) { abort("Connection is closed") } - + + if (isTRUE(attr(connection, "isMuckDb"))) { + sqlglot <- attr(connection, "sqlglot") + sql <- sqlglot$transpile(sql) + } + startTime <- Sys.time() dbms <- dbms(connection) @@ -331,6 +336,12 @@ querySql <- function(connection, if (!DBI::dbIsValid(connection)) { abort("Connection is closed") } + + if (isTRUE(attr(connection, "isMuckDb"))) { + sqlglot <- attr(connection, "sqlglot") + sql <- sqlglot$transpile(sql) + } + # Calling splitSql, because this will also strip trailing semicolons (which cause Oracle to crash). sqlStatements <- SqlRender::splitSql(sql) if (length(sqlStatements) > 1) { diff --git a/man/muckdbConnect.Rd b/man/muckdbConnect.Rd deleted file mode 100644 index 3ac458a6..00000000 --- a/man/muckdbConnect.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/MuckDb.R -\name{muckdbConnect} -\alias{muckdbConnect} -\title{muckdb: Mock DBMS connection using DuckDB and sqlglot} -\usage{ -muckdbConnect(platform = "duckdb", ...) -} -\arguments{ -\item{platform}{The DBMS dialect to emulate (e.g., "hive", "bigquery", "spark", "oracle", ...)} - -\item{dbDir}{Path to DuckDB database file (or ":memory:")} -} -\description{ -This class allows testing SQL logic as if running on any platform (e.g., Hive, BigQuery), -but actually executes against DuckDB using Python's sqlglot for SQL dialect translation. -} -\examples{ -# Connect to a mock Hive platform using DuckDB and sqlglot -conn <- muckdbConnect(platform = "hive") - -# Create a table using Hive SQL syntax -DBI::dbGetQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") - -# Query the table using Hive SQL syntax -result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") -print(result) - -# Disconnect when done -DBI::dbDisconnect(conn) -} diff --git a/tests/testthat/test-MuckDb.R b/tests/testthat/test-MuckDb.R index 7dbb80a2..904b899d 100644 --- a/tests/testthat/test-MuckDb.R +++ b/tests/testthat/test-MuckDb.R @@ -27,18 +27,3 @@ test_that("connectMuckdb returns a mucked up connection", { disconnect(conn) }) - -test_that("muckdbConnect creates a valid connection and executes SQL with DBI S4 dispatch", { - testthat::skip_if_not_installed("reticulate") - testthat::skip_if_not(reticulate::py_module_available("sqlglot")) - - conn <- DatabaseConnector::muckdbConnect(platform = "bigquery", dbDir = ":memory:") - # Table creation and querying with platform SQL - DBI::dbSendQuery(conn, "CREATE TABLE myTable AS SELECT 1 AS x") - result <- DBI::dbGetQuery(conn, "SELECT x FROM myTable") - testthat::expect_true(is.data.frame(result)) - testthat::expect_true("x" %in% names(result)) - testthat::expect_equal(result$x, 1) - - DBI::dbDisconnect(conn) -}) From 1b56da33d73008e68a2fe5841ec48d774dd10b14 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 14:44:28 -0400 Subject: [PATCH 11/14] Removed oracle --- R/Sql.R | 2 +- tests/testthat/test-MuckDb.R | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/R/Sql.R b/R/Sql.R index d31fa92e..7c003e3e 100644 --- a/R/Sql.R +++ b/R/Sql.R @@ -154,7 +154,7 @@ executeSql <- function(connection, if (isTRUE(attr(connection, "isMuckDb"))) { sqlglot <- attr(connection, "sqlglot") - sql <- sqlglot$transpile(sql) + sql <- sqlglot$transpile(sql) |> paste(collapse = ";\n") } startTime <- Sys.time() diff --git a/tests/testthat/test-MuckDb.R b/tests/testthat/test-MuckDb.R index 904b899d..c7277c77 100644 --- a/tests/testthat/test-MuckDb.R +++ b/tests/testthat/test-MuckDb.R @@ -2,7 +2,7 @@ test_that("connectMuckdb returns a mucked up connection", { testthat::skip_if_not_installed("reticulate") testthat::skip_if_not(reticulate::py_module_available("sqlglot")) - platforms <- c("oracle", "postgresql", "bigquery", "spark") + platforms <- c("postgresql", "bigquery", "spark", "sql server", "impala", "snowflake", "redshift", "iris") for (plat in platforms) { connectionDetails <- createConnectionDetails( @@ -15,10 +15,13 @@ test_that("connectMuckdb returns a mucked up connection", { testthat::expect_true(inherits(conn, "DatabaseConnectorConnection")) expect_equal(dbms(conn), plat) - renderTranslateExecuteSql(conn, "CREATE TABLE myTable AS SELECT 2 AS y") + renderTranslateExecuteSql(conn, "CREATE TABLE myTable AS SELECT 2 AS y; CREATE TABLE myTable2 AS SELECT 1 AS y") result <- renderTranslateQuerySql(conn, "SELECT y FROM myTable") testthat::expect_equal(result$y, 2) + result <- renderTranslateQuerySql(conn, "SELECT y FROM myTable2") + testthat::expect_equal(result$y, 1) + expect_error(renderTranslateQuerySql(conn, "SELE foo")) expect_error(renderTranslateQuerySql(conn, "SELE foo")) expect_error(querySql(conn, "SELE foo")) From 557e0bb6d2592f7418fa75ae066db574b357d231 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 15:06:28 -0400 Subject: [PATCH 12/14] added debug log --- R/Connect.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/Connect.R b/R/Connect.R index 70c283b5..62f9d529 100644 --- a/R/Connect.R +++ b/R/Connect.R @@ -851,7 +851,7 @@ connectDuckdb <- function(connectionDetails) { stop("Python module 'sqlglot' is required. Install via pip: pip install sqlglot or reticulate::install_python('sqlglot')") checkIfDbmsIsSupported(connectionDetails$connectionString()) - + inform(paste(connectionDetails$connectionString(), "mucks like \U1f986")) } connection <- connectUsingDbi( From a0880b08428635bec092d1d5f3f064ac230545e2 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 15:26:29 -0400 Subject: [PATCH 13/14] actually transpile the sql --- R/Sql.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/Sql.R b/R/Sql.R index 7c003e3e..394dfa18 100644 --- a/R/Sql.R +++ b/R/Sql.R @@ -154,7 +154,7 @@ executeSql <- function(connection, if (isTRUE(attr(connection, "isMuckDb"))) { sqlglot <- attr(connection, "sqlglot") - sql <- sqlglot$transpile(sql) |> paste(collapse = ";\n") + sql <- sqlglot$transpile(sql, read = dbms(connection), write = "duckdb") |> paste(collapse = ";\n") } startTime <- Sys.time() @@ -339,7 +339,7 @@ querySql <- function(connection, if (isTRUE(attr(connection, "isMuckDb"))) { sqlglot <- attr(connection, "sqlglot") - sql <- sqlglot$transpile(sql) + sql <- sqlglot$transpile(sql, read = dbms(connection), write = "duckdb") } # Calling splitSql, because this will also strip trailing semicolons (which cause Oracle to crash). From af3e21180237e1cc16c691c2255799e591098c61 Mon Sep 17 00:00:00 2001 From: jgilber2 Date: Thu, 9 Oct 2025 15:40:04 -0400 Subject: [PATCH 14/14] remove warning messages --- R/Sql.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/Sql.R b/R/Sql.R index 394dfa18..f67e022e 100644 --- a/R/Sql.R +++ b/R/Sql.R @@ -154,7 +154,8 @@ executeSql <- function(connection, if (isTRUE(attr(connection, "isMuckDb"))) { sqlglot <- attr(connection, "sqlglot") - sql <- sqlglot$transpile(sql, read = dbms(connection), write = "duckdb") |> paste(collapse = ";\n") + sql <- sqlglot$transpile(sql, read = dbms(connection), write = "duckdb", unsupported_level = "IGNORE") |> + paste(collapse = ";\n") } startTime <- Sys.time() @@ -339,7 +340,7 @@ querySql <- function(connection, if (isTRUE(attr(connection, "isMuckDb"))) { sqlglot <- attr(connection, "sqlglot") - sql <- sqlglot$transpile(sql, read = dbms(connection), write = "duckdb") + sql <- sqlglot$transpile(sql, read = dbms(connection), write = "duckdb", unsupported_level = "IGNORE") } # Calling splitSql, because this will also strip trailing semicolons (which cause Oracle to crash).