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"
     ),
   )

Reply via email to