This is an automated email from the ASF dual-hosted git repository.
ianmcook pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 5f105d666 feat(r/adbcdrivermanager): allow driver specification using
string names only (#4535)
5f105d666 is described below
commit 5f105d6666eb94c258ff3e4993c08639f64d77b7
Author: eitsupi <[email protected]>
AuthorDate: Fri Jul 17 10:29:10 2026 +0900
feat(r/adbcdrivermanager): allow driver specification using string names
only (#4535)
Close #4532
The following styles are allowed for driver initialization:
```r
# Infer "postgresql" from the URI scheme
adbc_database_init(uri = "postgresql://localhost/database")
# Load a named or absolute-path connection profile
adbc_database_init(profile = "myprofile")
# Equivalently, use a profile URI as the driver or URI
adbc_database_init("profile://myprofile")
adbc_database_init(uri = "profile://myprofile")
```
---
r/adbcdrivermanager/NAMESPACE | 4 +-
r/adbcdrivermanager/R/adbc.R | 84 ++++++++++++++++++++--
.../man/adbc_connection_get_info.Rd | 4 +-
r/adbcdrivermanager/man/adbc_connection_init.Rd | 4 +-
r/adbcdrivermanager/man/adbc_database_init.Rd | 56 +++++++++++++--
r/adbcdrivermanager/man/adbc_statement_init.Rd | 4 +-
.../tests/testthat/_snaps/profile.md | 24 +++++++
.../tests/testthat/test-driver_void.R | 6 ++
r/adbcdrivermanager/tests/testthat/test-profile.R | 70 ++++++++++++++++--
r/adbcdrivermanager/tests/testthat/test-radbc.R | 4 +-
.../tests/testthat/test-connection-profiles.R | 10 +--
11 files changed, 240 insertions(+), 30 deletions(-)
diff --git a/r/adbcdrivermanager/NAMESPACE b/r/adbcdrivermanager/NAMESPACE
index 80d17d54b..2bdca1a20 100644
--- a/r/adbcdrivermanager/NAMESPACE
+++ b/r/adbcdrivermanager/NAMESPACE
@@ -11,9 +11,11 @@ S3method(adbc_connection_init,adbc_database_monkey)
S3method(adbc_connection_init,default)
S3method(adbc_connection_quote_identifier,default)
S3method(adbc_connection_quote_string,default)
+S3method(adbc_database_init,"NULL")
+S3method(adbc_database_init,adbc_driver)
S3method(adbc_database_init,adbc_driver_log)
S3method(adbc_database_init,adbc_driver_monkey)
-S3method(adbc_database_init,default)
+S3method(adbc_database_init,character)
S3method(adbc_statement_init,adbc_connection_log)
S3method(adbc_statement_init,adbc_connection_monkey)
S3method(adbc_statement_init,default)
diff --git a/r/adbcdrivermanager/R/adbc.R b/r/adbcdrivermanager/R/adbc.R
index 643b8c007..0fa29b00c 100644
--- a/r/adbcdrivermanager/R/adbc.R
+++ b/r/adbcdrivermanager/R/adbc.R
@@ -17,28 +17,100 @@
#' Databases
#'
-#' @param driver An [adbc_driver()].
+#' @param driver The driver to use. This can be one of the following:
+#'
+#' * A non-missing `character(1)` containing a driver or manifest name, a
+#' relative or absolute path to a driver or manifest, or a URI. For a URI,
+#' the driver manager uses the URI scheme as the driver name and passes the
+#' URI to that driver. A `profile://` URI loads a connection profile.
+#' * `NULL`, which leaves driver selection to the driver manager. In this
+#' case, `...` must normally contain `uri` or `profile`; the driver is
+#' inferred from the URI or loaded from the connection profile.
+#' * An object that inherits from the `adbc_driver` class, such as one
created
+#' by [adbc_driver()]. This includes drivers provided by R packages, such
as
+#' `adbcsqlite::adbcsqlite()`.
#' @param database An [adbc_database][adbc_database_init].
#' @param option A specific option name
-#' @param ... Driver-specific options. For the default method, these are
-#' named values that are converted to strings.
+#' @param ... Driver-specific options. These are generally named values that
+#' are converted to strings.
#' @param options A named `character()` or `list()` whose values are converted
#' to strings.
#' @param subclass An extended class for an object so that drivers can specify
#' finer-grained control over behaviour at the R level.
#'
+#' @section Driver inference and connection profiles:
+#' A driver can be selected by name, inferred from a URI, or loaded from a
+#' connection profile:
+#'
+#' ```r
+#' # Load a driver by name and pass it a URI
+#' adbc_database_init("postgresql", uri = "postgresql://localhost/database")
+#'
+#' # Infer "postgresql" from the URI scheme
+#' adbc_database_init(uri = "postgresql://localhost/database")
+#'
+#' # Load a named or absolute-path connection profile
+#' adbc_database_init(profile = "myprofile")
+#'
+#' # Equivalently, use a profile URI as the driver or URI
+#' adbc_database_init("profile://myprofile")
+#' adbc_database_init(uri = "profile://myprofile")
+#' ```
+#'
+#' A minimal connection profile is a TOML file containing a driver and its
+#' database options:
+#'
+#' ```toml
+#' profile_version = 1
+#' driver = "postgresql"
+#'
+#' [Options]
+#' uri = "postgresql://localhost/database"
+#' ```
+#'
+#' Named profiles are located using the driver manager's standard profile
+#' search paths. See the
+#' [ADBC connection profile
documentation](https://arrow.apache.org/adbc/current/connection_profiles.html)
+#' for the file format, search locations, option precedence, and environment
+#' variable substitution.
+#'
#' @return An object of class adbc_database
#' @export
#'
#' @examples
#' adbc_database_init(adbc_driver_void())
#'
-adbc_database_init <- function(driver, ...) {
- UseMethod("adbc_database_init")
+adbc_database_init <- function(driver = NULL, ...) {
+ UseMethod("adbc_database_init", driver)
+}
+
+#' @export
+adbc_database_init.NULL <- function(driver, ...) {
+ adbc_database_init_driver_manager(list(...))
+}
+
+#' @export
+adbc_database_init.character <- function(driver, ...) {
+ if (length(driver) != 1L || is.na(driver)) {
+ stop(
+ "When `driver` is a character vector, it must have length 1 and must not
be `NA`.",
+ call. = FALSE
+ )
+ }
+
+ adbc_database_init_driver_manager(c(list(driver = driver), list(...)))
+}
+
+adbc_database_init_driver_manager <- function(options) {
+ driver <- list(
+ driver_init_func = NULL,
+ load_flags = adbc_load_flags()
+ )
+ adbc_database_init_default(driver, options)
}
#' @export
-adbc_database_init.default <- function(driver, ...) {
+adbc_database_init.adbc_driver <- function(driver, ...) {
adbc_database_init_default(driver, list(...))
}
diff --git a/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
b/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
index 5318df031..46ded005e 100644
--- a/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
+++ b/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
@@ -91,8 +91,8 @@ Requesting exact values may be expensive or unsupported.}
\item{value}{A string or identifier.}
-\item{...}{Driver-specific options. For the default method, these are
-named values that are converted to strings.}
+\item{...}{Driver-specific options. These are generally named values that
+are converted to strings.}
}
\value{
\itemize{
diff --git a/r/adbcdrivermanager/man/adbc_connection_init.Rd
b/r/adbcdrivermanager/man/adbc_connection_init.Rd
index 72edbbd5c..3a20a9029 100644
--- a/r/adbcdrivermanager/man/adbc_connection_init.Rd
+++ b/r/adbcdrivermanager/man/adbc_connection_init.Rd
@@ -30,8 +30,8 @@ adbc_connection_get_option_double(connection, option)
\arguments{
\item{database}{An \link[=adbc_database_init]{adbc_database}.}
-\item{...}{Driver-specific options. For the default method, these are
-named values that are converted to strings.}
+\item{...}{Driver-specific options. These are generally named values that
+are converted to strings.}
\item{options}{A named \code{character()} or \code{list()} whose values are
converted
to strings.}
diff --git a/r/adbcdrivermanager/man/adbc_database_init.Rd
b/r/adbcdrivermanager/man/adbc_database_init.Rd
index cc011844d..1ca918f84 100644
--- a/r/adbcdrivermanager/man/adbc_database_init.Rd
+++ b/r/adbcdrivermanager/man/adbc_database_init.Rd
@@ -11,7 +11,7 @@
\alias{adbc_database_get_option_double}
\title{Databases}
\usage{
-adbc_database_init(driver, ...)
+adbc_database_init(driver = NULL, ...)
adbc_database_init_default(driver, options = NULL, subclass = character())
@@ -28,10 +28,22 @@ adbc_database_get_option_int(database, option)
adbc_database_get_option_double(database, option)
}
\arguments{
-\item{driver}{An \code{\link[=adbc_driver]{adbc_driver()}}.}
+\item{driver}{The driver to use. This can be one of the following:
+\itemize{
+\item A non-missing \code{character(1)} containing a driver or manifest name, a
+relative or absolute path to a driver or manifest, or a URI. For a URI,
+the driver manager uses the URI scheme as the driver name and passes the
+URI to that driver. A \verb{profile://} URI loads a connection profile.
+\item \code{NULL}, which leaves driver selection to the driver manager. In this
+case, \code{...} must normally contain \code{uri} or \code{profile}; the
driver is
+inferred from the URI or loaded from the connection profile.
+\item An object that inherits from the \code{adbc_driver} class, such as one
created
+by \code{\link[=adbc_driver]{adbc_driver()}}. This includes drivers provided
by R packages, such as
+\code{adbcsqlite::adbcsqlite()}.
+}}
-\item{...}{Driver-specific options. For the default method, these are
-named values that are converted to strings.}
+\item{...}{Driver-specific options. These are generally named values that
+are converted to strings.}
\item{options}{A named \code{character()} or \code{list()} whose values are
converted
to strings.}
@@ -49,6 +61,42 @@ An object of class adbc_database
\description{
Databases
}
+\section{Driver inference and connection profiles}{
+
+A driver can be selected by name, inferred from a URI, or loaded from a
+connection profile:
+
+\if{html}{\out{<div class="sourceCode r">}}\preformatted{# Load a driver by
name and pass it a URI
+adbc_database_init("postgresql", uri = "postgresql://localhost/database")
+
+# Infer "postgresql" from the URI scheme
+adbc_database_init(uri = "postgresql://localhost/database")
+
+# Load a named or absolute-path connection profile
+adbc_database_init(profile = "myprofile")
+
+# Equivalently, use a profile URI as the driver or URI
+adbc_database_init("profile://myprofile")
+adbc_database_init(uri = "profile://myprofile")
+}\if{html}{\out{</div>}}
+
+A minimal connection profile is a TOML file containing a driver and its
+database options:
+
+\if{html}{\out{<div class="sourceCode toml">}}\preformatted{profile_version = 1
+driver = "postgresql"
+
+[Options]
+uri = "postgresql://localhost/database"
+}\if{html}{\out{</div>}}
+
+Named profiles are located using the driver manager's standard profile
+search paths. See the
+\href{https://arrow.apache.org/adbc/current/connection_profiles.html}{ADBC
connection profile documentation}
+for the file format, search locations, option precedence, and environment
+variable substitution.
+}
+
\examples{
adbc_database_init(adbc_driver_void())
diff --git a/r/adbcdrivermanager/man/adbc_statement_init.Rd
b/r/adbcdrivermanager/man/adbc_statement_init.Rd
index 75a728dd0..91c5fa342 100644
--- a/r/adbcdrivermanager/man/adbc_statement_init.Rd
+++ b/r/adbcdrivermanager/man/adbc_statement_init.Rd
@@ -30,8 +30,8 @@ adbc_statement_get_option_double(statement, option)
\arguments{
\item{connection}{An \link[=adbc_connection_init]{adbc_connection}}
-\item{...}{Driver-specific options. For the default method, these are
-named values that are converted to strings.}
+\item{...}{Driver-specific options. These are generally named values that
+are converted to strings.}
\item{options}{A named \code{character()} or \code{list()} whose values are
converted
to strings.}
diff --git a/r/adbcdrivermanager/tests/testthat/_snaps/profile.md
b/r/adbcdrivermanager/tests/testthat/_snaps/profile.md
new file mode 100644
index 000000000..eeb9051eb
--- /dev/null
+++ b/r/adbcdrivermanager/tests/testthat/_snaps/profile.md
@@ -0,0 +1,24 @@
+# character driver must be one non-missing string
+
+ Code
+ adbc_database_init(character())
+ Condition
+ Error:
+ ! When `driver` is a character vector, it must have length 1 and must
not be `NA`.
+
+---
+
+ Code
+ adbc_database_init(c("one", "two"))
+ Condition
+ Error:
+ ! When `driver` is a character vector, it must have length 1 and must
not be `NA`.
+
+---
+
+ Code
+ adbc_database_init(NA_character_)
+ Condition
+ Error:
+ ! When `driver` is a character vector, it must have length 1 and must
not be `NA`.
+
diff --git a/r/adbcdrivermanager/tests/testthat/test-driver_void.R
b/r/adbcdrivermanager/tests/testthat/test-driver_void.R
index 657f42118..dd5fd8899 100644
--- a/r/adbcdrivermanager/tests/testthat/test-driver_void.R
+++ b/r/adbcdrivermanager/tests/testthat/test-driver_void.R
@@ -25,6 +25,12 @@ test_that("drivers can be loaded by name/entrypoint", {
driver <- adbc_driver(shared, "AdbcTestVoidDriverInit")
expect_s3_class(driver, "adbc_driver")
expect_identical(driver$name, shared)
+
+ database <- adbc_database_init(
+ shared,
+ entrypoint = "AdbcTestVoidDriverInit"
+ )
+ expect_s3_class(database, "adbc_database")
})
test_that("drivers are loaded using load_flags", {
diff --git a/r/adbcdrivermanager/tests/testthat/test-profile.R
b/r/adbcdrivermanager/tests/testthat/test-profile.R
index e78703b67..a96386486 100644
--- a/r/adbcdrivermanager/tests/testthat/test-profile.R
+++ b/r/adbcdrivermanager/tests/testthat/test-profile.R
@@ -39,6 +39,53 @@ write_profile <- function(dir, name) {
path
}
+write_test_driver_manifest <- function(dir, name) {
+ driver_path <- adbcdrivermanager_shared()
+ content <- sprintf(
+ "
+manifest_version = 1
+
+[ADBC]
+version = 'v1.1.0'
+
+[Driver]
+shared = '%s'
+entrypoint = 'AdbcTestVoidDriverInit'
+",
+ driver_path
+ )
+ path <- file.path(dir, paste0(name, ".toml"))
+ writeLines(content, path)
+ path
+}
+
+test_that("can initialize a database using a profile", {
+ dir <- tempfile()
+ dir.create(dir)
+ on.exit(unlink(dir, recursive = TRUE))
+
+ manifest_path <- write_test_driver_manifest(dir, "test_driver")
+ profile_path <- file.path(dir, "test_profile.toml")
+ writeLines(
+ c(
+ "profile_version = 1",
+ sprintf("driver = '%s'", manifest_path),
+ "",
+ "[Options]",
+ "profile_option = 'profile value'"
+ ),
+ profile_path
+ )
+
+ db <- adbc_database_init(profile = profile_path)
+ on.exit(adbc_database_release(db), add = TRUE)
+
+ expect_identical(
+ adbc_database_get_option(db, "profile_option"),
+ "profile value"
+ )
+})
+
test_that("can load a profile by absolute path via 'profile' option", {
dir <- tempfile()
dir.create(dir)
@@ -48,7 +95,6 @@ test_that("can load a profile by absolute path via 'profile'
option", {
expect_error(
adbc_database_init(
- adbc_driver_for_profile(),
profile = profile_path
),
regexp = "nonexistent"
@@ -64,7 +110,6 @@ test_that("can load a profile by name via
additional_profile_search_path_list",
expect_error(
adbc_database_init(
- adbc_driver_for_profile(),
profile = "myprofile",
additional_profile_search_path_list = dir
),
@@ -83,7 +128,6 @@ test_that("can load a profile by name via ADBC_PROFILE_PATH
env var", {
list(ADBC_PROFILE_PATH = dir),
expect_error(
adbc_database_init(
- adbc_driver_for_profile(),
profile = "myprofile"
),
regexp = "nonexistent"
@@ -100,13 +144,25 @@ test_that("can load a profile via profile:// URI in 'uri'
option", {
expect_error(
adbc_database_init(
- adbc_driver_for_profile(),
uri = paste0("profile://", profile_path)
),
regexp = "nonexistent"
)
})
+test_that("can load a profile via profile:// URI in 'driver' argument", {
+ dir <- tempfile()
+ dir.create(dir)
+ on.exit(unlink(dir, recursive = TRUE))
+
+ profile_path <- write_profile(dir, "myprofile")
+
+ expect_error(
+ adbc_database_init(paste0("profile://", profile_path)),
+ regexp = "nonexistent"
+ )
+})
+
test_that("can load a profile via profile:// URI in 'driver' option", {
dir <- tempfile()
dir.create(dir)
@@ -123,6 +179,12 @@ test_that("can load a profile via profile:// URI in
'driver' option", {
)
})
+test_that("character driver must be one non-missing string", {
+ expect_snapshot(error = TRUE, adbc_database_init(character()))
+ expect_snapshot(error = TRUE, adbc_database_init(c("one", "two")))
+ expect_snapshot(error = TRUE, adbc_database_init(NA_character_))
+})
+
test_that("missing profile returns an error", {
dir <- tempfile()
dir.create(dir)
diff --git a/r/adbcdrivermanager/tests/testthat/test-radbc.R
b/r/adbcdrivermanager/tests/testthat/test-radbc.R
index 3ba63ed45..24a762812 100644
--- a/r/adbcdrivermanager/tests/testthat/test-radbc.R
+++ b/r/adbcdrivermanager/tests/testthat/test-radbc.R
@@ -186,7 +186,9 @@ test_that("invalid external pointer inputs generate
errors", {
stmt <- adbc_statement_init(con)
expect_error(
- adbc_database_init(list(driver_init_func = character(), load_flags = 0L)),
+ adbc_database_init_default(
+ list(driver_init_func = character(), load_flags = 0L)
+ ),
"Expected external pointer with class 'adbc_driver_init_func'"
)
diff --git a/r/adbcsqlite/tests/testthat/test-connection-profiles.R
b/r/adbcsqlite/tests/testthat/test-connection-profiles.R
index e96113fdd..19ce2fd9d 100644
--- a/r/adbcsqlite/tests/testthat/test-connection-profiles.R
+++ b/r/adbcsqlite/tests/testthat/test-connection-profiles.R
@@ -31,13 +31,6 @@ adbcsqlite_shared <- function() {
system.file("src", lib_name, package = "adbcsqlite")
}
-adbc_driver_for_profile <- function() {
- driver <- new.env(parent = emptyenv())
- driver$load_flags <- adbcdrivermanager:::adbc_load_flags()
- class(driver) <- "adbc_driver"
- driver
-}
-
write_sqlite_profile <- function(dir, name) {
driver_path <- adbcsqlite_shared()
stopifnot(file.exists(driver_path))
@@ -57,6 +50,8 @@ write_sqlite_profile <- function(dir, name) {
}
test_that("can open a sqlite database via a profile from path via env var", {
+ skip_if_not(packageVersion("adbcdrivermanager") >= "0.23.0.9000")
+
dir <- tempfile()
dir.create(dir)
on.exit(unlink(dir, recursive = TRUE))
@@ -67,7 +62,6 @@ test_that("can open a sqlite database via a profile from path
via env var", {
withr::with_envvar(
list(ADBC_PROFILE_PATH = dir),
db <- adbc_database_init(
- adbc_driver_for_profile(),
uri = "profile://my_sqlite"
),
)