This is an automated email from the ASF dual-hosted git repository.

paleolimbot 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 415a7a66 feat(r/adbcdrivermanager): Implement missing function 
mappings (#1206)
415a7a66 is described below

commit 415a7a6606b2e3733c13086a4e5e4fca5ab35890
Author: Dewey Dunnington <[email protected]>
AuthorDate: Wed Oct 18 12:34:18 2023 -0300

    feat(r/adbcdrivermanager): Implement missing function mappings (#1206)
    
    These are the functions that were added in the 1.1.0 version
    specification. The functions added are:
    
    - `adbc_connection_cancel()`
    - `adbc_connection_get_statistic_names()`
    - `adbc_connection_get_statistics()`
    - `adbc_statement_execute_schema()`
    - `adbc_(database|connection|statement)_get_option(|bytes|int|double)()`
    - `adbc_error_from_array_stream()`
    
    Two more bits of functionality were implemented but not directly
    exposed:
    
    - Option setting. There are other option-related improvements I'd like
    to implement and the current option setting helpers have the "convert to
    string" behaviour baked in, so those aren't wired up yet.
    - Error details. I try not to keep the actual error around very long in
    R land, instead preferring to copy the contents and discard the error,
    so the error detail is not directly exposed.
    
    The vast majority of the changes were because of the option setters and
    getters. Most of the other functions do not share signatures that might
    be templatable, but the setters and getters tipped the scale and
    option-related semantics were moved to
    options.cc/options.R/test-options.R. There were also some new types that
    needed converting (bool, int64, double), which tipped the scale on
    separating out all of the tests that check that invalid input won't
    cause a crash or cause the input to be misinterpreted.
    
    To properly test these I need to implement some changes in the dummy
    drivers, and I'd like to save that for a separate PR as this one is
    getting rather unwiedly.
    
    @nbenn I also discovered a typo: if you were having any issues with
    transaction support, `adbc_connection_rollback()` was actually calling
    `AdbcConnectionCommit()` (!!!! 😬 ).
---
 r/adbcdrivermanager/NAMESPACE                      |  17 ++
 r/adbcdrivermanager/R/adbc.R                       | 139 ++++++-----
 r/adbcdrivermanager/R/error.R                      |  31 ++-
 r/adbcdrivermanager/R/options.R                    | 162 ++++++++++++
 .../man/adbc_connection_get_info.Rd                |  30 ++-
 r/adbcdrivermanager/man/adbc_connection_init.Rd    |  20 +-
 r/adbcdrivermanager/man/adbc_database_init.Rd      |  20 +-
 .../man/adbc_error_from_array_stream.Rd            |  29 +++
 r/adbcdrivermanager/man/adbc_statement_init.Rd     |  20 +-
 .../man/adbc_statement_set_sql_query.Rd            |   3 +
 r/adbcdrivermanager/src/error.cc                   |  53 +++-
 r/adbcdrivermanager/src/init.c                     |  84 ++++++-
 r/adbcdrivermanager/src/options.cc                 | 272 +++++++++++++++++++++
 r/adbcdrivermanager/src/radbc.cc                   | 107 ++++----
 r/adbcdrivermanager/src/radbc.h                    | 141 +++++++++--
 r/adbcdrivermanager/tests/testthat/test-error.R    |  18 +-
 r/adbcdrivermanager/tests/testthat/test-options.R  |  90 +++++++
 r/adbcdrivermanager/tests/testthat/test-radbc.R    | 142 ++++++++---
 18 files changed, 1179 insertions(+), 199 deletions(-)

diff --git a/r/adbcdrivermanager/NAMESPACE b/r/adbcdrivermanager/NAMESPACE
index 48a0aa94..74ba9797 100644
--- a/r/adbcdrivermanager/NAMESPACE
+++ b/r/adbcdrivermanager/NAMESPACE
@@ -31,9 +31,16 @@ S3method(str,adbc_driver)
 S3method(str,adbc_error)
 S3method(str,adbc_xptr)
 S3method(write_adbc,default)
+export(adbc_connection_cancel)
 export(adbc_connection_commit)
 export(adbc_connection_get_info)
 export(adbc_connection_get_objects)
+export(adbc_connection_get_option)
+export(adbc_connection_get_option_bytes)
+export(adbc_connection_get_option_double)
+export(adbc_connection_get_option_int)
+export(adbc_connection_get_statistic_names)
+export(adbc_connection_get_statistics)
 export(adbc_connection_get_table_schema)
 export(adbc_connection_get_table_types)
 export(adbc_connection_init)
@@ -45,6 +52,10 @@ export(adbc_connection_read_partition)
 export(adbc_connection_release)
 export(adbc_connection_rollback)
 export(adbc_connection_set_options)
+export(adbc_database_get_option)
+export(adbc_database_get_option_bytes)
+export(adbc_database_get_option_double)
+export(adbc_database_get_option_int)
 export(adbc_database_init)
 export(adbc_database_init_default)
 export(adbc_database_release)
@@ -53,9 +64,15 @@ export(adbc_driver)
 export(adbc_driver_log)
 export(adbc_driver_monkey)
 export(adbc_driver_void)
+export(adbc_error_from_array_stream)
 export(adbc_statement_bind)
 export(adbc_statement_bind_stream)
 export(adbc_statement_execute_query)
+export(adbc_statement_execute_schema)
+export(adbc_statement_get_option)
+export(adbc_statement_get_option_bytes)
+export(adbc_statement_get_option_double)
+export(adbc_statement_get_option_int)
 export(adbc_statement_get_parameter_schema)
 export(adbc_statement_init)
 export(adbc_statement_init_default)
diff --git a/r/adbcdrivermanager/R/adbc.R b/r/adbcdrivermanager/R/adbc.R
index 85f9676b..6be5fceb 100644
--- a/r/adbcdrivermanager/R/adbc.R
+++ b/r/adbcdrivermanager/R/adbc.R
@@ -19,6 +19,7 @@
 #'
 #' @param driver An [adbc_driver()].
 #' @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 options A named `character()` or `list()` whose values are converted
@@ -66,26 +67,6 @@ adbc_database_init_default <- function(driver, options = 
NULL, subclass = charac
   })
 }
 
-#' @rdname adbc_database_init
-#' @export
-adbc_database_set_options <- function(database, options) {
-  options <- key_value_options(options)
-  error <- adbc_allocate_error()
-  for (i in seq_along(options)) {
-    key <- names(options)[i]
-    value <- options[i]
-    status <- .Call(
-      RAdbcDatabaseSetOption,
-      database,
-      key,
-      value,
-      error
-    )
-    stop_for_error(status, error)
-  }
-  invisible(database)
-}
-
 #' @rdname adbc_database_init
 #' @export
 adbc_database_release <- function(database) {
@@ -135,26 +116,6 @@ adbc_connection_init_default <- function(database, options 
= NULL, subclass = ch
   })
 }
 
-#' @rdname adbc_connection_init
-#' @export
-adbc_connection_set_options <- function(connection, options) {
-  options <- key_value_options(options)
-  error <- adbc_allocate_error()
-  for (i in seq_along(options)) {
-    key <- names(options)[i]
-    value <- options[i]
-    status <- .Call(
-      RAdbcConnectionSetOption,
-      connection,
-      key,
-      value,
-      error
-    )
-    stop_for_error(status, error)
-  }
-  invisible(connection)
-}
-
 #' @rdname adbc_connection_init
 #' @export
 adbc_connection_release <- function(connection) {
@@ -184,14 +145,18 @@ adbc_connection_release <- function(connection) {
 #' @param db_schema Only show tables in the given database schema. If NULL, do
 #'   not filter by database schema. If an empty string, only show tables 
without
 #'   a database schema. May be a search pattern.
-#' @param table_name Only show tables with the given name. If NULL, do not
-#'   filter by name. May be a search pattern.
+#' @param table_name Constrain an object or statistics query for a specific 
table.
+#'   If NULL, do not filter by name. May be a search pattern.
 #' @param table_type Only show tables matching one of the given table types. If
 #'   NULL, show tables of any type. Valid table types can be fetched from
 #'   GetTableTypes. Terminate the list with a NULL entry.
 #' @param column_name Only show columns with the given name. If NULL, do not
 #'   filter by name. May be a search pattern.
 #' @param serialized_partition The partition descriptor.
+#' @param approximate If `FALSE`, request exact values of statistics,
+#'   else allow for best-effort, approximate, or cached values. The database
+#'   may return approximate values regardless, as indicated in the result.
+#'   Requesting exact values may be expensive or unsupported.
 #' @param value A string or identifier.
 #'
 #' @return
@@ -303,6 +268,56 @@ adbc_connection_commit <- function(connection) {
   invisible(connection)
 }
 
+
+#' @rdname adbc_connection_get_info
+#' @export
+adbc_connection_rollback <- function(connection) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcConnectionRollback, connection, error)
+  invisible(connection)
+}
+
+#' @rdname adbc_connection_get_info
+#' @export
+adbc_connection_cancel <- function(connection) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcConnectionCancel, connection, error)
+  invisible(connection)
+}
+
+#' @rdname adbc_connection_get_info
+#' @export
+adbc_connection_get_statistic_names <- function(connection) {
+  error <- adbc_allocate_error()
+  out_stream <- nanoarrow::nanoarrow_allocate_array_stream()
+  status <- .Call(RAdbcConnectionGetStatisticNames, connection, out_stream, 
error)
+  stop_for_error(status, error)
+
+  out_stream
+}
+
+#' @rdname adbc_connection_get_info
+#' @export
+adbc_connection_get_statistics <- function(connection, catalog, db_schema,
+                                           table_name, approximate = FALSE) {
+  error <- adbc_allocate_error()
+  out_stream <- nanoarrow::nanoarrow_allocate_array_stream()
+
+  status <- .Call(
+    RAdbcConnectionGetStatistics,
+    connection,
+    catalog,
+    db_schema,
+    table_name,
+    approximate,
+    out_stream,
+    error
+  )
+  stop_for_error(status, error)
+
+  out_stream
+}
+
 #' @rdname adbc_connection_get_info
 #' @export
 adbc_connection_quote_identifier <- function(connection, value, ...) {
@@ -327,14 +342,6 @@ adbc_connection_quote_string.default <- 
function(connection, value, ...) {
   paste0("'", out, "'")
 }
 
-#' @rdname adbc_connection_get_info
-#' @export
-adbc_connection_rollback <- function(connection) {
-  error <- adbc_allocate_error()
-  .Call(RAdbcConnectionRollback, connection, error)
-  invisible(connection)
-}
-
 #' Statements
 #'
 #' @inheritParams adbc_connection_init
@@ -370,26 +377,6 @@ adbc_statement_init_default <- function(connection, 
options = NULL, subclass = c
   })
 }
 
-#' @rdname adbc_statement_init
-#' @export
-adbc_statement_set_options <- function(statement, options) {
-  options <- key_value_options(options)
-  error <- adbc_allocate_error()
-  for (i in seq_along(options)) {
-    key <- names(options)[i]
-    value <- options[i]
-    status <- .Call(
-      RAdbcStatementSetOption,
-      statement,
-      key,
-      value,
-      error
-    )
-    stop_for_error(status, error)
-  }
-  invisible(statement)
-}
-
 #' @rdname adbc_statement_init
 #' @export
 adbc_statement_release <- function(statement) {
@@ -498,3 +485,15 @@ adbc_statement_execute_query <- function(statement, stream 
= NULL) {
   stop_for_error(result$status, error)
   result$rows_affected
 }
+
+#' @rdname adbc_statement_set_sql_query
+#' @export
+adbc_statement_execute_schema <- function(statement) {
+  error <- adbc_allocate_error()
+  out_schema <- nanoarrow::nanoarrow_allocate_schema()
+
+  status <- .Call(RAdbcStatementExecuteSchema, statement, out_schema, error)
+  stop_for_error(status, error)
+
+  out_schema
+}
diff --git a/r/adbcdrivermanager/R/error.R b/r/adbcdrivermanager/R/error.R
index e87e4eae..565f5096 100644
--- a/r/adbcdrivermanager/R/error.R
+++ b/r/adbcdrivermanager/R/error.R
@@ -15,6 +15,33 @@
 # specific language governing permissions and limitations
 # under the License.
 
+
+#' Get extended error information from an array stream
+#'
+#' @param stream A 
[nanoarrow_array_stream][nanoarrow::as_nanoarrow_array_stream]
+#'
+#' @return `NULL` if stream was not created by a driver that supports
+#'   extended error information or a list whose first element is the
+#'   status code and second element is the `adbc_error` object. The
+#'   `acbc_error` must not be accessed if `stream` is explicitly released.
+#' @export
+#'
+#' @examples
+#' db <- adbc_database_init(adbc_driver_monkey())
+#' con <- adbc_connection_init(db)
+#' stmt <- adbc_statement_init(con, mtcars)
+#' stream <- nanoarrow::nanoarrow_allocate_array_stream()
+#' adbc_statement_execute_query(stmt, stream)
+#' adbc_error_from_array_stream(stream)
+#'
+adbc_error_from_array_stream <- function(stream) {
+  if (!inherits(stream, "nanoarrow_array_stream") || 
!adbc_xptr_is_valid(stream)) {
+    stop("`stream` must be a valid nanoarrow_array_stream")
+  }
+
+  .Call(RAdbcErrorFromArrayStream, stream)
+}
+
 adbc_allocate_error <- function(shelter = NULL) {
   .Call(RAdbcAllocateError, shelter)
 }
@@ -62,12 +89,12 @@ str.adbc_error <- function(object, ...) {
 
 #' @export
 length.adbc_error <- function(x, ...) {
-  3L
+  4L
 }
 
 #' @export
 names.adbc_error <- function(x, ...) {
-  c("message", "vendor_code", "sqlstate")
+  c("message", "vendor_code", "sqlstate", "details")
 }
 
 #' @export
diff --git a/r/adbcdrivermanager/R/options.R b/r/adbcdrivermanager/R/options.R
new file mode 100644
index 00000000..be258bbd
--- /dev/null
+++ b/r/adbcdrivermanager/R/options.R
@@ -0,0 +1,162 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+#' @rdname adbc_database_init
+#' @export
+adbc_database_set_options <- function(database, options) {
+  options <- key_value_options(options)
+  error <- adbc_allocate_error()
+  for (i in seq_along(options)) {
+    key <- names(options)[i]
+    value <- options[i]
+    status <- .Call(
+      RAdbcDatabaseSetOption,
+      database,
+      key,
+      value,
+      error
+    )
+    stop_for_error(status, error)
+  }
+  invisible(database)
+}
+
+#' @rdname adbc_connection_init
+#' @export
+adbc_connection_set_options <- function(connection, options) {
+  options <- key_value_options(options)
+  error <- adbc_allocate_error()
+  for (i in seq_along(options)) {
+    key <- names(options)[i]
+    value <- options[i]
+    status <- .Call(
+      RAdbcConnectionSetOption,
+      connection,
+      key,
+      value,
+      error
+    )
+    stop_for_error(status, error)
+  }
+  invisible(connection)
+}
+
+#' @rdname adbc_statement_init
+#' @export
+adbc_statement_set_options <- function(statement, options) {
+  options <- key_value_options(options)
+  error <- adbc_allocate_error()
+  for (i in seq_along(options)) {
+    key <- names(options)[i]
+    value <- options[i]
+    status <- .Call(
+      RAdbcStatementSetOption,
+      statement,
+      key,
+      value,
+      error
+    )
+    stop_for_error(status, error)
+  }
+  invisible(statement)
+}
+
+#' @rdname adbc_database_init
+#' @export
+adbc_database_get_option <- function(database, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcDatabaseGetOption, database, option, error)
+}
+
+#' @rdname adbc_database_init
+#' @export
+adbc_database_get_option_bytes <- function(database, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcDatabaseGetOptionBytes, database, option, error)
+}
+
+#' @rdname adbc_database_init
+#' @export
+adbc_database_get_option_int <- function(database, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcDatabaseGetOptionInt, database, option, error)
+}
+
+#' @rdname adbc_database_init
+#' @export
+adbc_database_get_option_double <- function(database, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcDatabaseGetOptionDouble, database, option, error)
+}
+
+
+#' @rdname adbc_connection_init
+#' @export
+adbc_connection_get_option <- function(connection, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcConnectionGetOption, connection, option, error)
+}
+
+#' @rdname adbc_connection_init
+#' @export
+adbc_connection_get_option_bytes <- function(connection, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcConnectionGetOptionBytes, connection, option, error)
+}
+
+#' @rdname adbc_connection_init
+#' @export
+adbc_connection_get_option_int <- function(connection, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcConnectionGetOptionInt, connection, option, error)
+}
+
+#' @rdname adbc_connection_init
+#' @export
+adbc_connection_get_option_double <- function(connection, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcConnectionGetOptionDouble, connection, option, error)
+}
+
+
+#' @rdname adbc_statement_init
+#' @export
+adbc_statement_get_option <- function(statement, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcStatementGetOption, statement, option, error)
+}
+
+#' @rdname adbc_statement_init
+#' @export
+adbc_statement_get_option_bytes <- function(statement, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcStatementGetOptionBytes, statement, option, error)
+}
+
+#' @rdname adbc_statement_init
+#' @export
+adbc_statement_get_option_int <- function(statement, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcStatementGetOptionInt, statement, option, error)
+}
+
+#' @rdname adbc_statement_init
+#' @export
+adbc_statement_get_option_double <- function(statement, option) {
+  error <- adbc_allocate_error()
+  .Call(RAdbcStatementGetOptionDouble, statement, option, error)
+}
diff --git a/r/adbcdrivermanager/man/adbc_connection_get_info.Rd 
b/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
index 92acb78f..bc90893f 100644
--- a/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
+++ b/r/adbcdrivermanager/man/adbc_connection_get_info.Rd
@@ -7,9 +7,12 @@
 \alias{adbc_connection_get_table_types}
 \alias{adbc_connection_read_partition}
 \alias{adbc_connection_commit}
+\alias{adbc_connection_rollback}
+\alias{adbc_connection_cancel}
+\alias{adbc_connection_get_statistic_names}
+\alias{adbc_connection_get_statistics}
 \alias{adbc_connection_quote_identifier}
 \alias{adbc_connection_quote_string}
-\alias{adbc_connection_rollback}
 \title{Connection methods}
 \usage{
 adbc_connection_get_info(connection, info_codes = NULL)
@@ -32,11 +35,23 @@ adbc_connection_read_partition(connection, 
serialized_partition)
 
 adbc_connection_commit(connection)
 
+adbc_connection_rollback(connection)
+
+adbc_connection_cancel(connection)
+
+adbc_connection_get_statistic_names(connection)
+
+adbc_connection_get_statistics(
+  connection,
+  catalog,
+  db_schema,
+  table_name,
+  approximate = FALSE
+)
+
 adbc_connection_quote_identifier(connection, value, ...)
 
 adbc_connection_quote_string(connection, value, ...)
-
-adbc_connection_rollback(connection)
 }
 \arguments{
 \item{connection}{An \link[=adbc_connection_init]{adbc_connection}}
@@ -56,8 +71,8 @@ a search pattern.}
 not filter by database schema. If an empty string, only show tables without
 a database schema. May be a search pattern.}
 
-\item{table_name}{Only show tables with the given name. If NULL, do not
-filter by name. May be a search pattern.}
+\item{table_name}{Constrain an object or statistics query for a specific table.
+If NULL, do not filter by name. May be a search pattern.}
 
 \item{table_type}{Only show tables matching one of the given table types. If
 NULL, show tables of any type. Valid table types can be fetched from
@@ -68,6 +83,11 @@ filter by name. May be a search pattern.}
 
 \item{serialized_partition}{The partition descriptor.}
 
+\item{approximate}{If \code{FALSE}, request exact values of statistics,
+else allow for best-effort, approximate, or cached values. The database
+may return approximate values regardless, as indicated in the result.
+Requesting exact values may be expensive or unsupported.}
+
 \item{value}{A string or identifier.}
 
 \item{...}{Driver-specific options. For the default method, these are
diff --git a/r/adbcdrivermanager/man/adbc_connection_init.Rd 
b/r/adbcdrivermanager/man/adbc_connection_init.Rd
index 1c545f1a..72edbbd5 100644
--- a/r/adbcdrivermanager/man/adbc_connection_init.Rd
+++ b/r/adbcdrivermanager/man/adbc_connection_init.Rd
@@ -1,19 +1,31 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/adbc.R
+% Please edit documentation in R/adbc.R, R/options.R
 \name{adbc_connection_init}
 \alias{adbc_connection_init}
 \alias{adbc_connection_init_default}
-\alias{adbc_connection_set_options}
 \alias{adbc_connection_release}
+\alias{adbc_connection_set_options}
+\alias{adbc_connection_get_option}
+\alias{adbc_connection_get_option_bytes}
+\alias{adbc_connection_get_option_int}
+\alias{adbc_connection_get_option_double}
 \title{Connections}
 \usage{
 adbc_connection_init(database, ...)
 
 adbc_connection_init_default(database, options = NULL, subclass = character())
 
+adbc_connection_release(connection)
+
 adbc_connection_set_options(connection, options)
 
-adbc_connection_release(connection)
+adbc_connection_get_option(connection, option)
+
+adbc_connection_get_option_bytes(connection, option)
+
+adbc_connection_get_option_int(connection, option)
+
+adbc_connection_get_option_double(connection, option)
 }
 \arguments{
 \item{database}{An \link[=adbc_database_init]{adbc_database}.}
@@ -28,6 +40,8 @@ to strings.}
 finer-grained control over behaviour at the R level.}
 
 \item{connection}{An \link[=adbc_connection_init]{adbc_connection}}
+
+\item{option}{A specific option name}
 }
 \value{
 An object of class 'adbc_connection'
diff --git a/r/adbcdrivermanager/man/adbc_database_init.Rd 
b/r/adbcdrivermanager/man/adbc_database_init.Rd
index 612aaa3a..cc011844 100644
--- a/r/adbcdrivermanager/man/adbc_database_init.Rd
+++ b/r/adbcdrivermanager/man/adbc_database_init.Rd
@@ -1,19 +1,31 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/adbc.R
+% Please edit documentation in R/adbc.R, R/options.R
 \name{adbc_database_init}
 \alias{adbc_database_init}
 \alias{adbc_database_init_default}
-\alias{adbc_database_set_options}
 \alias{adbc_database_release}
+\alias{adbc_database_set_options}
+\alias{adbc_database_get_option}
+\alias{adbc_database_get_option_bytes}
+\alias{adbc_database_get_option_int}
+\alias{adbc_database_get_option_double}
 \title{Databases}
 \usage{
 adbc_database_init(driver, ...)
 
 adbc_database_init_default(driver, options = NULL, subclass = character())
 
+adbc_database_release(database)
+
 adbc_database_set_options(database, options)
 
-adbc_database_release(database)
+adbc_database_get_option(database, option)
+
+adbc_database_get_option_bytes(database, option)
+
+adbc_database_get_option_int(database, option)
+
+adbc_database_get_option_double(database, option)
 }
 \arguments{
 \item{driver}{An \code{\link[=adbc_driver]{adbc_driver()}}.}
@@ -28,6 +40,8 @@ to strings.}
 finer-grained control over behaviour at the R level.}
 
 \item{database}{An \link[=adbc_database_init]{adbc_database}.}
+
+\item{option}{A specific option name}
 }
 \value{
 An object of class adbc_database
diff --git a/r/adbcdrivermanager/man/adbc_error_from_array_stream.Rd 
b/r/adbcdrivermanager/man/adbc_error_from_array_stream.Rd
new file mode 100644
index 00000000..458c2894
--- /dev/null
+++ b/r/adbcdrivermanager/man/adbc_error_from_array_stream.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/error.R
+\name{adbc_error_from_array_stream}
+\alias{adbc_error_from_array_stream}
+\title{Get extended error information from an array stream}
+\usage{
+adbc_error_from_array_stream(stream)
+}
+\arguments{
+\item{stream}{A 
\link[nanoarrow:as_nanoarrow_array_stream]{nanoarrow_array_stream}}
+}
+\value{
+\code{NULL} if stream was not created by a driver that supports
+extended error information or a list whose first element is the
+status code and second element is the \code{adbc_error} object. The
+\code{acbc_error} must not be accessed if \code{stream} is explicitly released.
+}
+\description{
+Get extended error information from an array stream
+}
+\examples{
+db <- adbc_database_init(adbc_driver_monkey())
+con <- adbc_connection_init(db)
+stmt <- adbc_statement_init(con, mtcars)
+stream <- nanoarrow::nanoarrow_allocate_array_stream()
+adbc_statement_execute_query(stmt, stream)
+adbc_error_from_array_stream(stream)
+
+}
diff --git a/r/adbcdrivermanager/man/adbc_statement_init.Rd 
b/r/adbcdrivermanager/man/adbc_statement_init.Rd
index f951555d..75a728dd 100644
--- a/r/adbcdrivermanager/man/adbc_statement_init.Rd
+++ b/r/adbcdrivermanager/man/adbc_statement_init.Rd
@@ -1,19 +1,31 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/adbc.R
+% Please edit documentation in R/adbc.R, R/options.R
 \name{adbc_statement_init}
 \alias{adbc_statement_init}
 \alias{adbc_statement_init_default}
-\alias{adbc_statement_set_options}
 \alias{adbc_statement_release}
+\alias{adbc_statement_set_options}
+\alias{adbc_statement_get_option}
+\alias{adbc_statement_get_option_bytes}
+\alias{adbc_statement_get_option_int}
+\alias{adbc_statement_get_option_double}
 \title{Statements}
 \usage{
 adbc_statement_init(connection, ...)
 
 adbc_statement_init_default(connection, options = NULL, subclass = character())
 
+adbc_statement_release(statement)
+
 adbc_statement_set_options(statement, options)
 
-adbc_statement_release(statement)
+adbc_statement_get_option(statement, option)
+
+adbc_statement_get_option_bytes(statement, option)
+
+adbc_statement_get_option_int(statement, option)
+
+adbc_statement_get_option_double(statement, option)
 }
 \arguments{
 \item{connection}{An \link[=adbc_connection_init]{adbc_connection}}
@@ -28,6 +40,8 @@ to strings.}
 finer-grained control over behaviour at the R level.}
 
 \item{statement}{An \link[=adbc_statement_init]{adbc_statement}}
+
+\item{option}{A specific option name}
 }
 \value{
 An object of class 'adbc_statement'
diff --git a/r/adbcdrivermanager/man/adbc_statement_set_sql_query.Rd 
b/r/adbcdrivermanager/man/adbc_statement_set_sql_query.Rd
index 5102d81d..ea955ddb 100644
--- a/r/adbcdrivermanager/man/adbc_statement_set_sql_query.Rd
+++ b/r/adbcdrivermanager/man/adbc_statement_set_sql_query.Rd
@@ -8,6 +8,7 @@
 \alias{adbc_statement_bind}
 \alias{adbc_statement_bind_stream}
 \alias{adbc_statement_execute_query}
+\alias{adbc_statement_execute_schema}
 \title{Statement methods}
 \usage{
 adbc_statement_set_sql_query(statement, query)
@@ -23,6 +24,8 @@ adbc_statement_bind(statement, values, schema = NULL)
 adbc_statement_bind_stream(statement, stream, schema = NULL)
 
 adbc_statement_execute_query(statement, stream = NULL)
+
+adbc_statement_execute_schema(statement)
 }
 \arguments{
 \item{statement}{An \link[=adbc_statement_init]{adbc_statement}}
diff --git a/r/adbcdrivermanager/src/error.cc b/r/adbcdrivermanager/src/error.cc
index 79f8aaa1..ff24e898 100644
--- a/r/adbcdrivermanager/src/error.cc
+++ b/r/adbcdrivermanager/src/error.cc
@@ -48,9 +48,28 @@ extern "C" SEXP RAdbcAllocateError(SEXP shelter_sexp) {
   return error_xptr;
 }
 
+static SEXP wrap_error_details(AdbcError* error) {
+  int n_details = AdbcErrorGetDetailCount(error);
+  SEXP result_names = PROTECT(Rf_allocVector(STRSXP, n_details));
+  SEXP result = PROTECT(Rf_allocVector(VECSXP, n_details));
+
+  for (int i = 0; i < n_details; i++) {
+    AdbcErrorDetail item = AdbcErrorGetDetail(error, i);
+    SET_STRING_ELT(result_names, i, Rf_mkCharCE(item.key, CE_UTF8));
+    SEXP item_sexp = PROTECT(Rf_allocVector(RAWSXP, item.value_length));
+    memcpy(RAW(item_sexp), item.value, item.value_length);
+    SET_VECTOR_ELT(result, i, item_sexp);
+    UNPROTECT(1);
+  }
+
+  Rf_setAttrib(result, R_NamesSymbol, result_names);
+  UNPROTECT(2);
+  return result;
+}
+
 extern "C" SEXP RAdbcErrorProxy(SEXP error_xptr) {
   AdbcError* error = adbc_from_xptr<AdbcError>(error_xptr);
-  const char* names[] = {"message", "vendor_code", "sqlstate", ""};
+  const char* names[] = {"message", "vendor_code", "sqlstate", "details", ""};
   SEXP result = PROTECT(Rf_mkNamed(VECSXP, names));
 
   if (error->message != nullptr) {
@@ -67,8 +86,38 @@ extern "C" SEXP RAdbcErrorProxy(SEXP error_xptr) {
   SEXP sqlstate = PROTECT(Rf_allocVector(RAWSXP, sizeof(error->sqlstate)));
   memcpy(RAW(sqlstate), error->sqlstate, sizeof(error->sqlstate));
   SET_VECTOR_ELT(result, 2, sqlstate);
+  UNPROTECT(1);
 
-  UNPROTECT(2);
+  SEXP details = PROTECT(wrap_error_details(error));
+  SET_VECTOR_ELT(result, 3, details);
+  UNPROTECT(1);
+
+  UNPROTECT(1);
+  return result;
+}
+
+extern "C" SEXP RAdbcErrorFromArrayStream(SEXP stream_xptr) {
+  struct ArrowArrayStream* stream =
+      reinterpret_cast<ArrowArrayStream*>(R_ExternalPtrAddr(stream_xptr));
+
+  AdbcStatusCode status = ADBC_STATUS_OK;
+  const AdbcError* error = AdbcErrorFromArrayStream(stream, &status);
+  if (error == nullptr) {
+    return R_NilValue;
+  }
+
+  // Not using a normal error_xptr here because the lifecycle is managed by 
the stream.
+  // This logic won't survive accesses to the error following an explicit 
stream release;
+  // however will at least keep a stream from being released via the garbage 
collector.
+  SEXP error_xptr =
+      PROTECT(adbc_borrow_xptr<AdbcError>(const_cast<AdbcError*>(error), 
stream_xptr));
+
+  SEXP status_sexp = PROTECT(adbc_wrap_status(status));
+
+  SEXP result = PROTECT(Rf_allocVector(VECSXP, 2));
+  SET_VECTOR_ELT(result, 0, status_sexp);
+  SET_VECTOR_ELT(result, 1, error_xptr);
+  UNPROTECT(3);
   return result;
 }
 
diff --git a/r/adbcdrivermanager/src/init.c b/r/adbcdrivermanager/src/init.c
index 0d9f65e4..7c5ad3f2 100644
--- a/r/adbcdrivermanager/src/init.c
+++ b/r/adbcdrivermanager/src/init.c
@@ -25,21 +25,54 @@ SEXP RAdbcMonkeyDriverInitFunc(void);
 SEXP RAdbcVoidDriverInitFunc(void);
 SEXP RAdbcAllocateError(SEXP shelter_sexp);
 SEXP RAdbcErrorProxy(SEXP error_xptr);
+SEXP RAdbcErrorFromArrayStream(SEXP stream_xptr);
 SEXP RAdbcStatusCodeMessage(SEXP status_sexp);
+SEXP RAdbcDatabaseSetOption(SEXP database_xptr, SEXP key_sexp, SEXP value_sexp,
+                            SEXP error_xptr);
+SEXP RAdbcDatabaseSetOptionBytes(SEXP database_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                 SEXP error_xptr);
+SEXP RAdbcDatabaseSetOptionInt(SEXP database_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                               SEXP error_xptr);
+SEXP RAdbcDatabaseSetOptionDouble(SEXP database_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                  SEXP error_xptr);
+SEXP RAdbcConnectionSetOption(SEXP connection_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                              SEXP error_xptr);
+SEXP RAdbcConnectionSetOptionBytes(SEXP connection_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                   SEXP error_xptr);
+SEXP RAdbcConnectionSetOptionInt(SEXP connection_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                 SEXP error_xptr);
+SEXP RAdbcConnectionSetOptionDouble(SEXP connection_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                    SEXP error_xptr);
+SEXP RAdbcStatementSetOption(SEXP statement_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                             SEXP error_xptr);
+SEXP RAdbcStatementSetOptionBytes(SEXP statement_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                  SEXP error_xptr);
+SEXP RAdbcStatementSetOptionInt(SEXP statement_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                SEXP error_xptr);
+SEXP RAdbcStatementSetOptionDouble(SEXP statement_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                   SEXP error_xptr);
+SEXP RAdbcDatabaseGetOption(SEXP database_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcDatabaseGetOptionBytes(SEXP database_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcDatabaseGetOptionInt(SEXP database_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcDatabaseGetOptionDouble(SEXP database_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcConnectionGetOption(SEXP connection_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcConnectionGetOptionBytes(SEXP connection_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcConnectionGetOptionInt(SEXP connection_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcConnectionGetOptionDouble(SEXP connection_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcStatementGetOption(SEXP statement_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcStatementGetOptionBytes(SEXP statement_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcStatementGetOptionInt(SEXP statement_xptr, SEXP key_sexp, SEXP 
error_xptr);
+SEXP RAdbcStatementGetOptionDouble(SEXP statement_xptr, SEXP key_sexp, SEXP 
error_xptr);
 SEXP RAdbcLoadDriver(SEXP driver_name_sexp, SEXP entrypoint_sexp);
 SEXP RAdbcLoadDriverFromInitFunc(SEXP driver_init_func_xptr);
 SEXP RAdbcDatabaseNew(SEXP driver_init_func_xptr);
 SEXP RAdbcMoveDatabase(SEXP database_xptr);
 SEXP RAdbcDatabaseValid(SEXP database_xptr);
-SEXP RAdbcDatabaseSetOption(SEXP database_xptr, SEXP key_sexp, SEXP value_sexp,
-                            SEXP error_xptr);
 SEXP RAdbcDatabaseInit(SEXP database_xptr, SEXP error_xptr);
 SEXP RAdbcDatabaseRelease(SEXP database_xptr, SEXP error_xptr);
 SEXP RAdbcConnectionNew(void);
 SEXP RAdbcMoveConnection(SEXP connection_xptr);
 SEXP RAdbcConnectionValid(SEXP connection_xptr);
-SEXP RAdbcConnectionSetOption(SEXP connection_xptr, SEXP key_sexp, SEXP 
value_sexp,
-                              SEXP error_xptr);
 SEXP RAdbcConnectionInit(SEXP connection_xptr, SEXP database_xptr, SEXP 
error_xptr);
 SEXP RAdbcConnectionRelease(SEXP connection_xptr, SEXP error_xptr);
 SEXP RAdbcConnectionGetInfo(SEXP connection_xptr, SEXP info_codes_sexp,
@@ -57,11 +90,16 @@ SEXP RAdbcConnectionReadPartition(SEXP connection_xptr, 
SEXP serialized_partitio
                                   SEXP out_stream_xptr, SEXP error_xptr);
 SEXP RAdbcConnectionCommit(SEXP connection_xptr, SEXP error_xptr);
 SEXP RAdbcConnectionRollback(SEXP connection_xptr, SEXP error_xptr);
+SEXP RAdbcConnectionCancel(SEXP connection_xptr, SEXP error_xptr);
+SEXP RAdbcConnectionGetStatisticNames(SEXP connection_xptr, SEXP 
out_stream_xptr,
+                                      SEXP error_xptr);
+SEXP RAdbcConnectionGetStatistics(SEXP connection_xptr, SEXP catalog_sexp,
+                                  SEXP db_schema_sexp, SEXP table_name_sexp,
+                                  SEXP approximate_sexp, SEXP out_stream_xptr,
+                                  SEXP error_xptr);
 SEXP RAdbcStatementNew(SEXP connection_xptr);
 SEXP RAdbcMoveStatement(SEXP statement_xptr);
 SEXP RAdbcStatementValid(SEXP statement_xptr);
-SEXP RAdbcStatementSetOption(SEXP statement_xptr, SEXP key_sexp, SEXP 
value_sexp,
-                             SEXP error_xptr);
 SEXP RAdbcStatementRelease(SEXP statement_xptr, SEXP error_xptr);
 SEXP RAdbcStatementSetSqlQuery(SEXP statement_xptr, SEXP query_sexp, SEXP 
error_xptr);
 SEXP RAdbcStatementSetSubstraitPlan(SEXP statement_xptr, SEXP plan_sexp, SEXP 
error_xptr);
@@ -73,6 +111,8 @@ SEXP RAdbcStatementBind(SEXP statement_xptr, SEXP 
values_xptr, SEXP schema_xptr,
 SEXP RAdbcStatementBindStream(SEXP statement_xptr, SEXP stream_xptr, SEXP 
error_xptr);
 SEXP RAdbcStatementExecuteQuery(SEXP statement_xptr, SEXP out_stream_xptr,
                                 SEXP error_xptr);
+SEXP RAdbcStatementExecuteSchema(SEXP statement_xptr, SEXP out_schema_xptr,
+                                 SEXP error_xptr);
 SEXP RAdbcStatementExecutePartitions(SEXP statement_xptr, SEXP out_schema_xptr,
                                      SEXP partitions_xptr, SEXP error_xptr);
 SEXP RAdbcXptrEnv(SEXP xptr);
@@ -83,19 +123,42 @@ static const R_CallMethodDef CallEntries[] = {
     {"RAdbcVoidDriverInitFunc", (DL_FUNC)&RAdbcVoidDriverInitFunc, 0},
     {"RAdbcAllocateError", (DL_FUNC)&RAdbcAllocateError, 1},
     {"RAdbcErrorProxy", (DL_FUNC)&RAdbcErrorProxy, 1},
+    {"RAdbcErrorFromArrayStream", (DL_FUNC)&RAdbcErrorFromArrayStream, 1},
     {"RAdbcStatusCodeMessage", (DL_FUNC)&RAdbcStatusCodeMessage, 1},
+    {"RAdbcDatabaseSetOption", (DL_FUNC)&RAdbcDatabaseSetOption, 4},
+    {"RAdbcDatabaseSetOptionBytes", (DL_FUNC)&RAdbcDatabaseSetOptionBytes, 4},
+    {"RAdbcDatabaseSetOptionInt", (DL_FUNC)&RAdbcDatabaseSetOptionInt, 4},
+    {"RAdbcDatabaseSetOptionDouble", (DL_FUNC)&RAdbcDatabaseSetOptionDouble, 
4},
+    {"RAdbcConnectionSetOption", (DL_FUNC)&RAdbcConnectionSetOption, 4},
+    {"RAdbcConnectionSetOptionBytes", (DL_FUNC)&RAdbcConnectionSetOptionBytes, 
4},
+    {"RAdbcConnectionSetOptionInt", (DL_FUNC)&RAdbcConnectionSetOptionInt, 4},
+    {"RAdbcConnectionSetOptionDouble", 
(DL_FUNC)&RAdbcConnectionSetOptionDouble, 4},
+    {"RAdbcStatementSetOption", (DL_FUNC)&RAdbcStatementSetOption, 4},
+    {"RAdbcStatementSetOptionBytes", (DL_FUNC)&RAdbcStatementSetOptionBytes, 
4},
+    {"RAdbcStatementSetOptionInt", (DL_FUNC)&RAdbcStatementSetOptionInt, 4},
+    {"RAdbcStatementSetOptionDouble", (DL_FUNC)&RAdbcStatementSetOptionDouble, 
4},
+    {"RAdbcDatabaseGetOption", (DL_FUNC)&RAdbcDatabaseGetOption, 3},
+    {"RAdbcDatabaseGetOptionBytes", (DL_FUNC)&RAdbcDatabaseGetOptionBytes, 3},
+    {"RAdbcDatabaseGetOptionInt", (DL_FUNC)&RAdbcDatabaseGetOptionInt, 3},
+    {"RAdbcDatabaseGetOptionDouble", (DL_FUNC)&RAdbcDatabaseGetOptionDouble, 
3},
+    {"RAdbcConnectionGetOption", (DL_FUNC)&RAdbcConnectionGetOption, 3},
+    {"RAdbcConnectionGetOptionBytes", (DL_FUNC)&RAdbcConnectionGetOptionBytes, 
3},
+    {"RAdbcConnectionGetOptionInt", (DL_FUNC)&RAdbcConnectionGetOptionInt, 3},
+    {"RAdbcConnectionGetOptionDouble", 
(DL_FUNC)&RAdbcConnectionGetOptionDouble, 3},
+    {"RAdbcStatementGetOption", (DL_FUNC)&RAdbcStatementGetOption, 3},
+    {"RAdbcStatementGetOptionBytes", (DL_FUNC)&RAdbcStatementGetOptionBytes, 
3},
+    {"RAdbcStatementGetOptionInt", (DL_FUNC)&RAdbcStatementGetOptionInt, 3},
+    {"RAdbcStatementGetOptionDouble", (DL_FUNC)&RAdbcStatementGetOptionDouble, 
3},
     {"RAdbcLoadDriver", (DL_FUNC)&RAdbcLoadDriver, 2},
     {"RAdbcLoadDriverFromInitFunc", (DL_FUNC)&RAdbcLoadDriverFromInitFunc, 1},
     {"RAdbcDatabaseNew", (DL_FUNC)&RAdbcDatabaseNew, 1},
     {"RAdbcMoveDatabase", (DL_FUNC)&RAdbcMoveDatabase, 1},
     {"RAdbcDatabaseValid", (DL_FUNC)&RAdbcDatabaseValid, 1},
-    {"RAdbcDatabaseSetOption", (DL_FUNC)&RAdbcDatabaseSetOption, 4},
     {"RAdbcDatabaseInit", (DL_FUNC)&RAdbcDatabaseInit, 2},
     {"RAdbcDatabaseRelease", (DL_FUNC)&RAdbcDatabaseRelease, 2},
     {"RAdbcConnectionNew", (DL_FUNC)&RAdbcConnectionNew, 0},
     {"RAdbcMoveConnection", (DL_FUNC)&RAdbcMoveConnection, 1},
     {"RAdbcConnectionValid", (DL_FUNC)&RAdbcConnectionValid, 1},
-    {"RAdbcConnectionSetOption", (DL_FUNC)&RAdbcConnectionSetOption, 4},
     {"RAdbcConnectionInit", (DL_FUNC)&RAdbcConnectionInit, 3},
     {"RAdbcConnectionRelease", (DL_FUNC)&RAdbcConnectionRelease, 2},
     {"RAdbcConnectionGetInfo", (DL_FUNC)&RAdbcConnectionGetInfo, 4},
@@ -105,10 +168,12 @@ static const R_CallMethodDef CallEntries[] = {
     {"RAdbcConnectionReadPartition", (DL_FUNC)&RAdbcConnectionReadPartition, 
4},
     {"RAdbcConnectionCommit", (DL_FUNC)&RAdbcConnectionCommit, 2},
     {"RAdbcConnectionRollback", (DL_FUNC)&RAdbcConnectionRollback, 2},
+    {"RAdbcConnectionCancel", (DL_FUNC)&RAdbcConnectionCancel, 2},
+    {"RAdbcConnectionGetStatisticNames", 
(DL_FUNC)&RAdbcConnectionGetStatisticNames, 3},
+    {"RAdbcConnectionGetStatistics", (DL_FUNC)&RAdbcConnectionGetStatistics, 
7},
     {"RAdbcStatementNew", (DL_FUNC)&RAdbcStatementNew, 1},
     {"RAdbcMoveStatement", (DL_FUNC)&RAdbcMoveStatement, 1},
     {"RAdbcStatementValid", (DL_FUNC)&RAdbcStatementValid, 1},
-    {"RAdbcStatementSetOption", (DL_FUNC)&RAdbcStatementSetOption, 4},
     {"RAdbcStatementRelease", (DL_FUNC)&RAdbcStatementRelease, 2},
     {"RAdbcStatementSetSqlQuery", (DL_FUNC)&RAdbcStatementSetSqlQuery, 3},
     {"RAdbcStatementSetSubstraitPlan", 
(DL_FUNC)&RAdbcStatementSetSubstraitPlan, 3},
@@ -117,6 +182,7 @@ static const R_CallMethodDef CallEntries[] = {
     {"RAdbcStatementBind", (DL_FUNC)&RAdbcStatementBind, 4},
     {"RAdbcStatementBindStream", (DL_FUNC)&RAdbcStatementBindStream, 3},
     {"RAdbcStatementExecuteQuery", (DL_FUNC)&RAdbcStatementExecuteQuery, 3},
+    {"RAdbcStatementExecuteSchema", (DL_FUNC)&RAdbcStatementExecuteSchema, 3},
     {"RAdbcStatementExecutePartitions", 
(DL_FUNC)&RAdbcStatementExecutePartitions, 4},
     {"RAdbcXptrEnv", (DL_FUNC)&RAdbcXptrEnv, 1},
     {NULL, NULL, 0}};
diff --git a/r/adbcdrivermanager/src/options.cc 
b/r/adbcdrivermanager/src/options.cc
new file mode 100644
index 00000000..377d6330
--- /dev/null
+++ b/r/adbcdrivermanager/src/options.cc
@@ -0,0 +1,272 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#define R_NO_REMAP
+#include <R.h>
+#include <Rinternals.h>
+
+#include <adbc.h>
+
+#include "radbc.h"
+
+template <typename T>
+static inline T adbc_as_c(SEXP sexp);
+
+template <>
+inline const char* adbc_as_c(SEXP sexp) {
+  return adbc_as_const_char(sexp);
+}
+
+template <>
+inline int64_t adbc_as_c(SEXP sexp) {
+  return adbc_as_int64(sexp);
+}
+
+template <>
+inline double adbc_as_c(SEXP sexp) {
+  return adbc_as_double(sexp);
+}
+
+template <typename T, typename ValueT>
+SEXP adbc_set_option(SEXP obj_xptr, SEXP key_sexp, SEXP value_sexp, SEXP 
error_xptr,
+                     AdbcStatusCode (*SetOption)(T*, const char*, ValueT, 
AdbcError*)) {
+  auto obj = adbc_from_xptr<T>(obj_xptr);
+  const char* key = adbc_as_const_char(key_sexp);
+  ValueT value = adbc_as_c<ValueT>(value_sexp);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+  return adbc_wrap_status(SetOption(obj, key, value, error));
+}
+
+template <typename T>
+SEXP adbc_set_option_bytes(SEXP obj_xptr, SEXP key_sexp, SEXP value_sexp, SEXP 
error_xptr,
+                           AdbcStatusCode (*SetOption)(T*, const char*, const 
uint8_t*,
+                                                       size_t, AdbcError*)) {
+  auto obj = adbc_from_xptr<T>(obj_xptr);
+  const char* key = adbc_as_const_char(key_sexp);
+  const uint8_t* value = RAW(value_sexp);
+  size_t value_length = Rf_xlength(value_sexp);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+
+  int status = SetOption(obj, key, value, value_length, error);
+  return adbc_wrap_status(status);
+}
+
+extern "C" SEXP RAdbcDatabaseSetOption(SEXP database_xptr, SEXP key_sexp, SEXP 
value_sexp,
+                                       SEXP error_xptr) {
+  return adbc_set_option<AdbcDatabase, const char*>(database_xptr, key_sexp, 
value_sexp,
+                                                    error_xptr, 
&AdbcDatabaseSetOption);
+}
+
+extern "C" SEXP RAdbcDatabaseSetOptionBytes(SEXP database_xptr, SEXP key_sexp,
+                                            SEXP value_sexp, SEXP error_xptr) {
+  return adbc_set_option_bytes<AdbcDatabase>(database_xptr, key_sexp, 
value_sexp,
+                                             error_xptr, 
&AdbcDatabaseSetOptionBytes);
+}
+
+extern "C" SEXP RAdbcDatabaseSetOptionInt(SEXP database_xptr, SEXP key_sexp,
+                                          SEXP value_sexp, SEXP error_xptr) {
+  return adbc_set_option<AdbcDatabase, int64_t>(database_xptr, key_sexp, 
value_sexp,
+                                                error_xptr, 
&AdbcDatabaseSetOptionInt);
+}
+
+extern "C" SEXP RAdbcDatabaseSetOptionDouble(SEXP database_xptr, SEXP key_sexp,
+                                             SEXP value_sexp, SEXP error_xptr) 
{
+  return adbc_set_option<AdbcDatabase, double>(database_xptr, key_sexp, 
value_sexp,
+                                               error_xptr, 
&AdbcDatabaseSetOptionDouble);
+}
+
+extern "C" SEXP RAdbcConnectionSetOption(SEXP connection_xptr, SEXP key_sexp,
+                                         SEXP value_sexp, SEXP error_xptr) {
+  return adbc_set_option<AdbcConnection, const char*>(
+      connection_xptr, key_sexp, value_sexp, error_xptr, 
&AdbcConnectionSetOption);
+}
+
+extern "C" SEXP RAdbcConnectionSetOptionBytes(SEXP connection_xptr, SEXP 
key_sexp,
+                                              SEXP value_sexp, SEXP 
error_xptr) {
+  return adbc_set_option_bytes<AdbcConnection>(connection_xptr, key_sexp, 
value_sexp,
+                                               error_xptr, 
&AdbcConnectionSetOptionBytes);
+}
+
+extern "C" SEXP RAdbcConnectionSetOptionInt(SEXP connection_xptr, SEXP 
key_sexp,
+                                            SEXP value_sexp, SEXP error_xptr) {
+  return adbc_set_option<AdbcConnection, int64_t>(
+      connection_xptr, key_sexp, value_sexp, error_xptr, 
&AdbcConnectionSetOptionInt);
+}
+
+extern "C" SEXP RAdbcConnectionSetOptionDouble(SEXP connection_xptr, SEXP 
key_sexp,
+                                               SEXP value_sexp, SEXP 
error_xptr) {
+  return adbc_set_option<AdbcConnection, double>(
+      connection_xptr, key_sexp, value_sexp, error_xptr, 
&AdbcConnectionSetOptionDouble);
+}
+
+extern "C" SEXP RAdbcStatementSetOption(SEXP statement_xptr, SEXP key_sexp,
+                                        SEXP value_sexp, SEXP error_xptr) {
+  return adbc_set_option<AdbcStatement, const char*>(statement_xptr, key_sexp, 
value_sexp,
+                                                     error_xptr, 
&AdbcStatementSetOption);
+}
+
+extern "C" SEXP RAdbcStatementSetOptionBytes(SEXP statement_xptr, SEXP 
key_sexp,
+                                             SEXP value_sexp, SEXP error_xptr) 
{
+  return adbc_set_option_bytes<AdbcStatement>(statement_xptr, key_sexp, 
value_sexp,
+                                              error_xptr, 
&AdbcStatementSetOptionBytes);
+}
+
+extern "C" SEXP RAdbcStatementSetOptionInt(SEXP statement_xptr, SEXP key_sexp,
+                                           SEXP value_sexp, SEXP error_xptr) {
+  return adbc_set_option<AdbcStatement, int64_t>(statement_xptr, key_sexp, 
value_sexp,
+                                                 error_xptr, 
&AdbcStatementSetOptionInt);
+}
+
+extern "C" SEXP RAdbcStatementSetOptionDouble(SEXP statement_xptr, SEXP 
key_sexp,
+                                              SEXP value_sexp, SEXP 
error_xptr) {
+  return adbc_set_option<AdbcStatement, double>(
+      statement_xptr, key_sexp, value_sexp, error_xptr, 
&AdbcStatementSetOptionDouble);
+}
+
+template <typename T, typename CharT>
+static inline SEXP adbc_get_option_bytes(SEXP obj_xptr, SEXP key_sexp, SEXP 
error_xptr,
+                                         AdbcStatusCode (*GetOption)(T*, const 
char*,
+                                                                     CharT*, 
size_t*,
+                                                                     
AdbcError*)) {
+  auto obj = adbc_from_xptr<T>(obj_xptr);
+  const char* key = adbc_as_const_char(key_sexp);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+
+  size_t length = 0;
+  int status = GetOption(obj, key, nullptr, &length, error);
+  adbc_error_stop(status, error);
+
+  SEXP result_shelter = PROTECT(Rf_allocVector(RAWSXP, length));
+  auto result = reinterpret_cast<CharT*>(RAW(result_shelter));
+  status = GetOption(obj, key, result, &length, error);
+  adbc_error_stop(status, error);
+
+  UNPROTECT(1);
+  return result_shelter;
+}
+
+template <typename T>
+static inline SEXP adbc_get_option(SEXP obj_xptr, SEXP key_sexp, SEXP 
error_xptr,
+                                   AdbcStatusCode (*GetOption)(T*, const 
char*, char*,
+                                                               size_t*, 
AdbcError*)) {
+  SEXP bytes_sexp =
+      adbc_get_option_bytes<T, char>(obj_xptr, key_sexp, error_xptr, 
GetOption);
+  PROTECT(bytes_sexp);
+
+  char* result = reinterpret_cast<char*>(RAW(bytes_sexp));
+  SEXP result_char = PROTECT(Rf_mkCharLenCE(result, Rf_length(bytes_sexp), 
CE_UTF8));
+  SEXP result_string = PROTECT(Rf_ScalarString(result_char));
+  UNPROTECT(3);
+  return result_string;
+}
+
+static inline SEXP adbc_wrap(int64_t value) {
+  if (value <= NA_INTEGER || value >= INT_MAX) {
+    return Rf_ScalarReal(value);
+  } else {
+    return Rf_ScalarInteger(value);
+  }
+}
+
+static inline SEXP adbc_wrap(double value) { return Rf_ScalarReal(value); }
+
+template <typename T, typename ResultT>
+static inline SEXP adbc_get_option_numeric(SEXP obj_xptr, SEXP key_sexp, SEXP 
error_xptr,
+                                           AdbcStatusCode (*GetOption)(T*, 
const char*,
+                                                                       
ResultT*,
+                                                                       
AdbcError*)) {
+  auto obj = adbc_from_xptr<T>(obj_xptr);
+  const char* key = adbc_as_const_char(key_sexp);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+
+  ResultT value = 0;
+  int status = GetOption(obj, key, &value, error);
+  adbc_error_stop(status, error);
+  return adbc_wrap(value);
+}
+
+extern "C" SEXP RAdbcDatabaseGetOption(SEXP database_xptr, SEXP key_sexp,
+                                       SEXP error_xptr) {
+  return adbc_get_option<AdbcDatabase>(database_xptr, key_sexp, error_xptr,
+                                       &AdbcDatabaseGetOption);
+}
+
+extern "C" SEXP RAdbcDatabaseGetOptionBytes(SEXP database_xptr, SEXP key_sexp,
+                                            SEXP error_xptr) {
+  return adbc_get_option_bytes<AdbcDatabase, uint8_t>(database_xptr, key_sexp, 
error_xptr,
+                                                      
&AdbcDatabaseGetOptionBytes);
+}
+
+extern "C" SEXP RAdbcDatabaseGetOptionInt(SEXP database_xptr, SEXP key_sexp,
+                                          SEXP error_xptr) {
+  return adbc_get_option_numeric<AdbcDatabase, int64_t>(
+      database_xptr, key_sexp, error_xptr, &AdbcDatabaseGetOptionInt);
+}
+
+extern "C" SEXP RAdbcDatabaseGetOptionDouble(SEXP database_xptr, SEXP key_sexp,
+                                             SEXP error_xptr) {
+  return adbc_get_option_numeric<AdbcDatabase, double>(
+      database_xptr, key_sexp, error_xptr, &AdbcDatabaseGetOptionDouble);
+}
+
+extern "C" SEXP RAdbcConnectionGetOption(SEXP connection_xptr, SEXP key_sexp,
+                                         SEXP error_xptr) {
+  return adbc_get_option<AdbcConnection>(connection_xptr, key_sexp, error_xptr,
+                                         &AdbcConnectionGetOption);
+}
+
+extern "C" SEXP RAdbcConnectionGetOptionBytes(SEXP connection_xptr, SEXP 
key_sexp,
+                                              SEXP error_xptr) {
+  return adbc_get_option_bytes<AdbcConnection, uint8_t>(
+      connection_xptr, key_sexp, error_xptr, &AdbcConnectionGetOptionBytes);
+}
+
+extern "C" SEXP RAdbcConnectionGetOptionInt(SEXP connection_xptr, SEXP 
key_sexp,
+                                            SEXP error_xptr) {
+  return adbc_get_option_numeric<AdbcConnection, int64_t>(
+      connection_xptr, key_sexp, error_xptr, &AdbcConnectionGetOptionInt);
+}
+
+extern "C" SEXP RAdbcConnectionGetOptionDouble(SEXP connection_xptr, SEXP 
key_sexp,
+                                               SEXP error_xptr) {
+  return adbc_get_option_numeric<AdbcConnection, double>(
+      connection_xptr, key_sexp, error_xptr, &AdbcConnectionGetOptionDouble);
+}
+
+extern "C" SEXP RAdbcStatementGetOption(SEXP statement_xptr, SEXP key_sexp,
+                                        SEXP error_xptr) {
+  return adbc_get_option<AdbcStatement>(statement_xptr, key_sexp, error_xptr,
+                                        &AdbcStatementGetOption);
+}
+
+extern "C" SEXP RAdbcStatementGetOptionBytes(SEXP statement_xptr, SEXP 
key_sexp,
+                                             SEXP error_xptr) {
+  return adbc_get_option_bytes<AdbcStatement, uint8_t>(
+      statement_xptr, key_sexp, error_xptr, &AdbcStatementGetOptionBytes);
+}
+
+extern "C" SEXP RAdbcStatementGetOptionInt(SEXP statement_xptr, SEXP key_sexp,
+                                           SEXP error_xptr) {
+  return adbc_get_option_numeric<AdbcStatement, int64_t>(
+      statement_xptr, key_sexp, error_xptr, &AdbcStatementGetOptionInt);
+}
+
+extern "C" SEXP RAdbcStatementGetOptionDouble(SEXP statement_xptr, SEXP 
key_sexp,
+                                              SEXP error_xptr) {
+  return adbc_get_option_numeric<AdbcStatement, double>(
+      statement_xptr, key_sexp, error_xptr, &AdbcStatementGetOptionDouble);
+}
diff --git a/r/adbcdrivermanager/src/radbc.cc b/r/adbcdrivermanager/src/radbc.cc
index c10f05c5..fb271296 100644
--- a/r/adbcdrivermanager/src/radbc.cc
+++ b/r/adbcdrivermanager/src/radbc.cc
@@ -41,12 +41,6 @@ static void adbc_error_warn(int code, AdbcError* error, 
const char* context) {
   }
 }
 
-static void adbc_error_stop(int code, AdbcError* error, const char* context) {
-  if (code != ADBC_STATUS_OK) {
-    Rf_error("<%s> %s", context, adbc_error_message(error));
-  }
-}
-
 static void finalize_driver_xptr(SEXP driver_xptr) {
   auto driver = reinterpret_cast<AdbcDriver*>(R_ExternalPtrAddr(driver_xptr));
   if (driver == nullptr) {
@@ -136,7 +130,7 @@ extern "C" SEXP RAdbcDatabaseNew(SEXP 
driver_init_func_xptr) {
   AdbcError error;
   memset(&error, 0, sizeof(AdbcError));
   int status = AdbcDatabaseNew(database, &error);
-  adbc_error_stop(status, &error, "RAdbcDatabaseNew()");
+  adbc_error_stop(status, &error);
 
   if (driver_init_func_xptr != R_NilValue) {
     auto driver_init_func =
@@ -146,7 +140,7 @@ extern "C" SEXP RAdbcDatabaseNew(SEXP 
driver_init_func_xptr) {
     }
 
     status = AdbcDriverManagerDatabaseSetInitFunc(database, driver_init_func, 
&error);
-    adbc_error_stop(status, &error, "RAdbcDatabaseNew()");
+    adbc_error_stop(status, &error);
   }
 
   UNPROTECT(1);
@@ -168,19 +162,10 @@ extern "C" SEXP RAdbcMoveDatabase(SEXP database_xptr) {
 }
 
 extern "C" SEXP RAdbcDatabaseValid(SEXP database_xptr) {
-  AdbcDatabase* database = adbc_from_xptr<AdbcDatabase>(database_xptr, true);
+  AdbcDatabase* database = adbc_from_xptr<AdbcDatabase>(database_xptr, 
/*nullable=*/true);
   return Rf_ScalarLogical(database != nullptr && database->private_data != 
nullptr);
 }
 
-extern "C" SEXP RAdbcDatabaseSetOption(SEXP database_xptr, SEXP key_sexp, SEXP 
value_sexp,
-                                       SEXP error_xptr) {
-  auto database = adbc_from_xptr<AdbcDatabase>(database_xptr);
-  const char* key = adbc_as_const_char(key_sexp);
-  const char* value = adbc_as_const_char(value_sexp);
-  auto error = adbc_from_xptr<AdbcError>(error_xptr);
-  return adbc_wrap_status(AdbcDatabaseSetOption(database, key, value, error));
-}
-
 extern "C" SEXP RAdbcDatabaseInit(SEXP database_xptr, SEXP error_xptr) {
   auto database = adbc_from_xptr<AdbcDatabase>(database_xptr);
   auto error = adbc_from_xptr<AdbcError>(error_xptr);
@@ -219,7 +204,7 @@ extern "C" SEXP RAdbcConnectionNew(void) {
   AdbcError error;
   memset(&error, 0, sizeof(AdbcError));
   int status = AdbcConnectionNew(connection, &error);
-  adbc_error_stop(status, &error, "RAdbcConnectionNew()");
+  adbc_error_stop(status, &error);
 
   UNPROTECT(1);
   return connection_xptr;
@@ -240,19 +225,11 @@ extern "C" SEXP RAdbcMoveConnection(SEXP connection_xptr) 
{
 }
 
 extern "C" SEXP RAdbcConnectionValid(SEXP connection_xptr) {
-  AdbcConnection* connection = adbc_from_xptr<AdbcConnection>(connection_xptr, 
true);
+  AdbcConnection* connection =
+      adbc_from_xptr<AdbcConnection>(connection_xptr, /*nullable=*/true);
   return Rf_ScalarLogical(connection != nullptr && connection->private_data != 
nullptr);
 }
 
-extern "C" SEXP RAdbcConnectionSetOption(SEXP connection_xptr, SEXP key_sexp,
-                                         SEXP value_sexp, SEXP error_xptr) {
-  auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
-  const char* key = adbc_as_const_char(key_sexp);
-  const char* value = adbc_as_const_char(value_sexp);
-  auto error = adbc_from_xptr<AdbcError>(error_xptr);
-  return adbc_wrap_status(AdbcConnectionSetOption(connection, key, value, 
error));
-}
-
 extern "C" SEXP RAdbcConnectionInit(SEXP connection_xptr, SEXP database_xptr,
                                     SEXP error_xptr) {
   auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
@@ -298,13 +275,13 @@ extern "C" SEXP RAdbcConnectionGetObjects(SEXP 
connection_xptr, SEXP depth_sexp,
                                           SEXP error_xptr) {
   auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
   int depth = adbc_as_int(depth_sexp);
-  const char* catalog = adbc_as_const_char(catalog_sexp, true);
-  const char* db_schema = adbc_as_const_char(db_schema_sexp, true);
-  const char* table_name = adbc_as_const_char(table_name_sexp, true);
+  const char* catalog = adbc_as_const_char(catalog_sexp, /*nullable=*/true);
+  const char* db_schema = adbc_as_const_char(db_schema_sexp, 
/*nullable=*/true);
+  const char* table_name = adbc_as_const_char(table_name_sexp, 
/*nullable=*/true);
   std::pair<SEXP, const char**> table_type = 
adbc_as_const_char_list(table_type_sexp);
   PROTECT(table_type.first);
 
-  const char* column_name = adbc_as_const_char(column_name_sexp, true);
+  const char* column_name = adbc_as_const_char(column_name_sexp, 
/*nullable=*/true);
   auto out_stream = adbc_from_xptr<ArrowArrayStream>(out_stream_xptr);
   auto error = adbc_from_xptr<AdbcError>(error_xptr);
 
@@ -319,8 +296,8 @@ extern "C" SEXP RAdbcConnectionGetTableSchema(SEXP 
connection_xptr, SEXP catalog
                                               SEXP db_schema_sexp, SEXP 
table_name_sexp,
                                               SEXP schema_xptr, SEXP 
error_xptr) {
   auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
-  const char* catalog = adbc_as_const_char(catalog_sexp);
-  const char* db_schema = adbc_as_const_char(db_schema_sexp);
+  const char* catalog = adbc_as_const_char(catalog_sexp, /*nullable=*/true);
+  const char* db_schema = adbc_as_const_char(db_schema_sexp, 
/*nullable=*/true);
   const char* table_name = adbc_as_const_char(table_name_sexp);
   auto schema = adbc_from_xptr<ArrowSchema>(schema_xptr);
   auto error = adbc_from_xptr<AdbcError>(error_xptr);
@@ -364,7 +341,41 @@ extern "C" SEXP RAdbcConnectionCommit(SEXP 
connection_xptr, SEXP error_xptr) {
 extern "C" SEXP RAdbcConnectionRollback(SEXP connection_xptr, SEXP error_xptr) 
{
   auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
   auto error = adbc_from_xptr<AdbcError>(error_xptr);
-  int status = AdbcConnectionCommit(connection, error);
+  int status = AdbcConnectionRollback(connection, error);
+  return adbc_wrap_status(status);
+}
+
+extern "C" SEXP RAdbcConnectionCancel(SEXP connection_xptr, SEXP error_xptr) {
+  auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+  int status = AdbcConnectionCancel(connection, error);
+  return adbc_wrap_status(status);
+}
+
+extern "C" SEXP RAdbcConnectionGetStatisticNames(SEXP connection_xptr,
+                                                 SEXP out_stream_xptr, SEXP 
error_xptr) {
+  auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
+  auto out_stream = adbc_from_xptr<ArrowArrayStream>(out_stream_xptr);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+
+  int status = AdbcConnectionGetStatisticNames(connection, out_stream, error);
+  return adbc_wrap_status(status);
+}
+
+extern "C" SEXP RAdbcConnectionGetStatistics(SEXP connection_xptr, SEXP 
catalog_sexp,
+                                             SEXP db_schema_sexp, SEXP 
table_name_sexp,
+                                             SEXP approximate_sexp, SEXP 
out_stream_xptr,
+                                             SEXP error_xptr) {
+  auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
+  const char* catalog = adbc_as_const_char(catalog_sexp, /*nullable=*/true);
+  const char* db_schema = adbc_as_const_char(db_schema_sexp, 
/*nullable=*/true);
+  const char* table_name = adbc_as_const_char(table_name_sexp);
+  char approximate = adbc_as_bool(approximate_sexp);
+  auto out_stream = adbc_from_xptr<ArrowArrayStream>(out_stream_xptr);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+
+  int status = AdbcConnectionGetStatistics(connection, catalog, db_schema, 
table_name,
+                                           approximate, out_stream, error);
   return adbc_wrap_status(status);
 }
 
@@ -394,7 +405,7 @@ extern "C" SEXP RAdbcStatementNew(SEXP connection_xptr) {
   AdbcError error;
   memset(&error, 0, sizeof(AdbcError));
   int status = AdbcStatementNew(connection, statement, &error);
-  adbc_error_stop(status, &error, "RAdbcStatementNew()");
+  adbc_error_stop(status, &error);
 
   R_SetExternalPtrProtected(statement_xptr, connection_xptr);
 
@@ -417,19 +428,11 @@ extern "C" SEXP RAdbcMoveStatement(SEXP statement_xptr) {
 }
 
 extern "C" SEXP RAdbcStatementValid(SEXP statement_xptr) {
-  AdbcStatement* statement = adbc_from_xptr<AdbcStatement>(statement_xptr, 
true);
+  AdbcStatement* statement =
+      adbc_from_xptr<AdbcStatement>(statement_xptr, /*nullable=*/true);
   return Rf_ScalarLogical(statement != nullptr && statement->private_data != 
nullptr);
 }
 
-extern "C" SEXP RAdbcStatementSetOption(SEXP statement_xptr, SEXP key_sexp,
-                                        SEXP value_sexp, SEXP error_xptr) {
-  auto statement = adbc_from_xptr<AdbcStatement>(statement_xptr);
-  const char* key = adbc_as_const_char(key_sexp);
-  const char* value = adbc_as_const_char(value_sexp);
-  auto error = adbc_from_xptr<AdbcError>(error_xptr);
-  return adbc_wrap_status(AdbcStatementSetOption(statement, key, value, 
error));
-}
-
 extern "C" SEXP RAdbcStatementRelease(SEXP statement_xptr, SEXP error_xptr) {
   auto statement = adbc_from_xptr<AdbcStatement>(statement_xptr);
   auto error = adbc_from_xptr<AdbcError>(error_xptr);
@@ -524,6 +527,16 @@ extern "C" SEXP RAdbcStatementExecuteQuery(SEXP 
statement_xptr, SEXP out_stream_
   return result;
 }
 
+extern "C" SEXP RAdbcStatementExecuteSchema(SEXP statement_xptr, SEXP 
out_schema_xptr,
+                                            SEXP error_xptr) {
+  auto statement = adbc_from_xptr<AdbcStatement>(statement_xptr);
+  auto out_schema = adbc_from_xptr<ArrowSchema>(out_schema_xptr);
+  auto error = adbc_from_xptr<AdbcError>(error_xptr);
+
+  int status = AdbcStatementExecuteSchema(statement, out_schema, error);
+  return adbc_wrap_status(status);
+}
+
 extern "C" SEXP RAdbcStatementExecutePartitions(SEXP statement_xptr, SEXP 
out_schema_xptr,
                                                 SEXP partitions_xptr, SEXP 
error_xptr) {
   return adbc_wrap_status(ADBC_STATUS_NOT_IMPLEMENTED);
diff --git a/r/adbcdrivermanager/src/radbc.h b/r/adbcdrivermanager/src/radbc.h
index fa9fb5ff..27772802 100644
--- a/r/adbcdrivermanager/src/radbc.h
+++ b/r/adbcdrivermanager/src/radbc.h
@@ -79,13 +79,7 @@ static inline T* adbc_from_xptr(SEXP xptr, bool null_ok = 
false) {
 }
 
 template <typename T>
-static inline SEXP adbc_allocate_xptr(SEXP shelter_sexp = R_NilValue) {
-  void* ptr = malloc(sizeof(T));
-  if (ptr == nullptr) {
-    Rf_error("Failed to allocate T");
-  }
-
-  memset(ptr, 0, sizeof(T));
+static inline SEXP adbc_borrow_xptr(T* ptr, SEXP shelter_sexp = R_NilValue) {
   SEXP xptr = PROTECT(R_MakeExternalPtr(ptr, R_NilValue, shelter_sexp));
   SEXP xptr_class = PROTECT(Rf_allocVector(STRSXP, 2));
   SET_STRING_ELT(xptr_class, 0, Rf_mkChar(adbc_xptr_class<T>()));
@@ -105,6 +99,17 @@ static inline SEXP adbc_allocate_xptr(SEXP shelter_sexp = 
R_NilValue) {
   return xptr;
 }
 
+template <typename T>
+static inline SEXP adbc_allocate_xptr(SEXP shelter_sexp = R_NilValue) {
+  void* ptr = malloc(sizeof(T));
+  if (ptr == nullptr) {
+    Rf_error("Failed to allocate T");
+  }
+
+  memset(ptr, 0, sizeof(T));
+  return adbc_borrow_xptr<T>(reinterpret_cast<T*>(ptr), shelter_sexp);
+}
+
 template <typename T>
 static inline void adbc_xptr_default_finalize(SEXP xptr) {
   T* ptr = reinterpret_cast<T*>(R_ExternalPtrAddr(xptr));
@@ -138,6 +143,10 @@ static inline const char* adbc_as_const_char(SEXP sexp, 
bool nullable = false) {
     return nullptr;
   }
 
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to const char*");
+  }
+
   if (TYPEOF(sexp) != STRSXP || Rf_length(sexp) != 1) {
     Rf_error("Expected character(1) for conversion to const char*");
   }
@@ -151,24 +160,55 @@ static inline const char* adbc_as_const_char(SEXP sexp, 
bool nullable = false) {
 }
 
 static inline int adbc_as_int(SEXP sexp) {
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to int");
+  }
+
   if (Rf_length(sexp) == 1) {
     switch (TYPEOF(sexp)) {
       case REALSXP: {
         double value = REAL(sexp)[0];
-        if (ISNA(value) || ISNAN(value)) {
-          Rf_error("Can't convert NA_real_ to int");
+        if (!R_finite(value)) {
+          Rf_error("Can't convert non-finite double(1) to int");
         }
 
         return value;
       }
 
-      case INTSXP: {
+      case INTSXP:
+      case LGLSXP:
+        // NA is OK here (or should be handled by the caller for a specific 
ADBC method)
+        return INTEGER(sexp)[0];
+    }
+  }
+
+  Rf_error("Expected integer(1) or double(1) for conversion to int");
+}
+
+static inline bool adbc_as_bool(SEXP sexp) {
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to bool");
+  }
+
+  if (Rf_length(sexp) == 1) {
+    switch (TYPEOF(sexp)) {
+      case REALSXP: {
+        double value = REAL(sexp)[0];
+        if (!R_finite(value)) {
+          Rf_error("Can't convert non-finite double(1) to bool");
+        }
+
+        return value != 0;
+      }
+
+      case INTSXP:
+      case LGLSXP: {
         int value = INTEGER(sexp)[0];
         if (value == NA_INTEGER) {
-          Rf_error("Can't convert NA_integer_ to int");
+          Rf_error("Can't convert NA to bool");
         }
 
-        return value;
+        return value != 0;
       }
     }
   }
@@ -176,7 +216,54 @@ static inline int adbc_as_int(SEXP sexp) {
   Rf_error("Expected integer(1) or double(1) for conversion to int");
 }
 
+static inline int64_t adbc_as_int64(SEXP sexp) {
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to int64");
+  }
+
+  if (Rf_length(sexp) == 1) {
+    switch (TYPEOF(sexp)) {
+      case REALSXP: {
+        double value = REAL(sexp)[0];
+        if (!R_finite(value)) {
+          Rf_error("Can't convert non-finite double(1) to int64");
+        }
+
+        return value;
+      }
+
+      case INTSXP:
+      case LGLSXP:
+        return INTEGER(sexp)[0];
+    }
+  }
+
+  Rf_error("Expected integer(1) or double(1) for conversion to int64");
+}
+
+static inline double adbc_as_double(SEXP sexp) {
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to double");
+  }
+
+  if (Rf_length(sexp) == 1) {
+    switch (TYPEOF(sexp)) {
+      case REALSXP:
+        return REAL(sexp)[0];
+      case INTSXP:
+      case LGLSXP:
+        return INTEGER(sexp)[0];
+    }
+  }
+
+  Rf_error("Expected integer(1) or double(1) for conversion to double");
+}
+
 static inline std::pair<SEXP, const char**> adbc_as_const_char_list(SEXP sexp) 
{
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to const char**");
+  }
+
   switch (TYPEOF(sexp)) {
     case NILSXP:
       return {R_NilValue, nullptr};
@@ -204,6 +291,10 @@ static inline std::pair<SEXP, const char**> 
adbc_as_const_char_list(SEXP sexp) {
 }
 
 static inline std::pair<SEXP, int*> adbc_as_int_list(SEXP sexp) {
+  if (Rf_isObject(sexp)) {
+    Rf_error("Can't convert classed object to int*");
+  }
+
   int result_length = Rf_length(sexp);
 
   switch (TYPEOF(sexp)) {
@@ -212,12 +303,8 @@ static inline std::pair<SEXP, int*> adbc_as_int_list(SEXP 
sexp) {
 
     case INTSXP: {
       int* result = INTEGER(sexp);
-      for (int i = 0; i < result_length; i++) {
-        if (result[i] == NA_INTEGER) {
-          Rf_error("Can't convert NA_integer_ element to int");
-        }
-      }
-
+      // NA is OK here (otherwise it would be hard to work around a driver that
+      // maybe used INT_MIN as a sentinel for something)
       return {sexp, result};
     }
 
@@ -226,8 +313,8 @@ static inline std::pair<SEXP, int*> adbc_as_int_list(SEXP 
sexp) {
       int* result = INTEGER(result_shelter);
       for (int i = 0; i < result_length; i++) {
         double item = REAL(sexp)[i];
-        if (ISNA(item) || ISNAN(item)) {
-          Rf_error("Can't convert NA_real_ or NaN element to int");
+        if (!R_finite(item)) {
+          Rf_error("Can't convert non-finite element to int");
         }
 
         result[i] = item;
@@ -238,10 +325,22 @@ static inline std::pair<SEXP, int*> adbc_as_int_list(SEXP 
sexp) {
     }
 
     default:
-      Rf_error("Expected character for conversion to const char**");
+      Rf_error("Expected integer() or double() for conversion to int*");
   }
 }
 
 static inline SEXP adbc_wrap_status(AdbcStatusCode code) {
   return Rf_ScalarInteger(code);
 }
+
+static inline void adbc_error_stop(int code, AdbcError* error) {
+  SEXP status_sexp = PROTECT(adbc_wrap_status(code));
+  SEXP error_xptr = PROTECT(adbc_borrow_xptr<AdbcError>(error));
+
+  SEXP fun_sym = PROTECT(Rf_install("stop_for_error"));
+  SEXP fun_call = PROTECT(Rf_lang3(fun_sym, status_sexp, error_xptr));
+  SEXP pkg_chr = PROTECT(Rf_mkString("adbcdrivermanager"));
+  SEXP pkg_ns = PROTECT(R_FindNamespace(pkg_chr));
+  Rf_eval(fun_call, pkg_ns);
+  UNPROTECT(6);
+}
diff --git a/r/adbcdrivermanager/tests/testthat/test-error.R 
b/r/adbcdrivermanager/tests/testthat/test-error.R
index e6b1cc18..15a9c21e 100644
--- a/r/adbcdrivermanager/tests/testthat/test-error.R
+++ b/r/adbcdrivermanager/tests/testthat/test-error.R
@@ -15,17 +15,31 @@
 # specific language governing permissions and limitations
 # under the License.
 
+test_that("adbc_error_from_array_stream() errors for invalid streams", {
+  stream <- nanoarrow::nanoarrow_allocate_array_stream()
+  expect_error(
+    adbc_error_from_array_stream(stream),
+    "must be a valid nanoarrow_array_stream"
+  )
+})
+
+test_that("adbc_error_from_array_stream() returns NULL for unrelated streams", 
{
+  stream <- nanoarrow::basic_array_stream(list(1:5))
+  expect_null(adbc_error_from_array_stream(stream))
+})
+
 test_that("error allocator works", {
   err <- adbc_allocate_error()
   expect_s3_class(err, "adbc_error")
 
   expect_output(expect_identical(print(err), err), "adbc_error")
   expect_output(expect_identical(str(err), err), "adbc_error")
-  expect_identical(length(err), 3L)
-  expect_identical(names(err), c("message", "vendor_code", "sqlstate"))
+  expect_identical(length(err), 4L)
+  expect_identical(names(err), c("message", "vendor_code", "sqlstate", 
"details"))
   expect_null(err$message)
   expect_identical(err$vendor_code, 0L)
   expect_identical(err$sqlstate, as.raw(c(0x00, 0x00, 0x00, 0x00, 0x00)))
+  expect_identical(err$details, setNames(list(), character()))
 })
 
 test_that("stop_for_error() gives a custom error class with extra info", {
diff --git a/r/adbcdrivermanager/tests/testthat/test-options.R 
b/r/adbcdrivermanager/tests/testthat/test-options.R
new file mode 100644
index 00000000..74ad63e8
--- /dev/null
+++ b/r/adbcdrivermanager/tests/testthat/test-options.R
@@ -0,0 +1,90 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+test_that("get option methods work on a database for the void driver", {
+  db <- adbc_database_init(adbc_driver_void())
+  expect_error(
+    adbc_database_get_option(db, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_database_get_option_bytes(db, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_database_get_option_int(db, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_database_get_option_double(db, "some_key"),
+    class = "adbc_status_not_found"
+  )
+})
+
+test_that("get option methods work on a connection for the void driver", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
+
+  expect_error(
+    adbc_connection_get_option(con, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_connection_get_option_bytes(con, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_connection_get_option_int(con, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_connection_get_option_double(con, "some_key"),
+    class = "adbc_status_not_found"
+  )
+})
+
+test_that("get option methods work on a statment for the void driver", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
+  stmt <- adbc_statement_init(con)
+
+  expect_error(
+    adbc_statement_get_option(stmt, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_statement_get_option_bytes(stmt, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_statement_get_option_int(stmt, "some_key"),
+    class = "adbc_status_not_found"
+  )
+
+  expect_error(
+    adbc_statement_get_option_double(stmt, "some_key"),
+    class = "adbc_status_not_found"
+  )
+})
diff --git a/r/adbcdrivermanager/tests/testthat/test-radbc.R 
b/r/adbcdrivermanager/tests/testthat/test-radbc.R
index 3d6e6522..b332e711 100644
--- a/r/adbcdrivermanager/tests/testthat/test-radbc.R
+++ b/r/adbcdrivermanager/tests/testthat/test-radbc.R
@@ -93,6 +93,21 @@ test_that("connection methods work for the void driver", {
     con
   )
 
+  expect_identical(
+    adbc_connection_cancel(con),
+    con
+  )
+
+  expect_error(
+    adbc_connection_get_statistic_names(con),
+    "NOT_IMPLEMENTED"
+  )
+
+  expect_error(
+    adbc_connection_get_statistics(con, NULL, NULL, "table name"),
+    "NOT_IMPLEMENTED"
+  )
+
   expect_identical(
     adbc_connection_quote_identifier(con, 'some"identifier'),
     '"some""identifier"'
@@ -153,9 +168,14 @@ test_that("statement methods work for the void driver", {
     adbc_statement_execute_query(stmt),
     "NOT_IMPLEMENTED"
   )
+
+  expect_error(
+    adbc_statement_execute_schema(stmt),
+    "NOT_IMPLEMENTED"
+  )
 })
 
-test_that("invalid parameter types generate errors", {
+test_that("invalid external pointer inputs generate errors", {
   db <- adbc_database_init(adbc_driver_void())
   con <- adbc_connection_init(db)
   stmt <- adbc_statement_init(con)
@@ -170,73 +190,131 @@ test_that("invalid parameter types generate errors", {
     "Expected external pointer with class 'adbc_statement'"
   )
 
+  # (makes a NULL xptr)
+  stmt2 <- unserialize(serialize(stmt, NULL))
   expect_error(
-    adbc_connection_get_objects(
-      con, character(),
-      "catalog", "db_schema",
-      "table_name", "table_type", "column_name"
-    ),
+    adbc_statement_set_sql_query(stmt2, "some query"),
+    "Can't convert external pointer to NULL to T*"
+  )
+})
+
+test_that("invalid integer inputs generate errors", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
+
+  expect_error(
+    adbc_connection_get_objects(con, depth = "abc"),
     "Expected integer(1) or double(1)",
     fixed = TRUE
   )
 
+  expect_error(
+    adbc_connection_get_objects(con, depth = 1:5),
+    "Expected integer(1) or double(1)",
+    fixed = TRUE
+  )
+
+  expect_error(
+    adbc_connection_get_objects(con, structure(1L, class = "non-empty")),
+    "Can't convert classed object"
+  )
+
+  expect_error(
+    adbc_connection_get_objects(con, NA_real_),
+    "Can't convert non-finite"
+  )
+})
+
+test_that("invalid int list inputs generate errors", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
+
+  expect_error(
+    adbc_connection_get_info(con, character()),
+    "Expected integer"
+  )
+
+  expect_error(
+    adbc_connection_get_info(con, structure(integer(), class = "non-empty")),
+    "Can't convert classed object"
+  )
+
+  expect_error(
+    adbc_connection_get_info(con, NA_real_),
+    "Can't convert non-finite element"
+  )
+})
+
+test_that("invalid const char* list inputs generate errors", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
+
   expect_error(
     adbc_connection_get_objects(
-      con, NA_integer_,
-      "catalog", "db_schema",
-      "table_name", "table_type", "column_name"
+      con,
+      table_type = integer()
     ),
-    "Can't convert NA_integer_"
+    "Expected character"
   )
 
   expect_error(
     adbc_connection_get_objects(
-      con, NA_real_,
-      "catalog", "db_schema",
-      "table_name", "table_type", "column_name"
+      con,
+      table_type = NA_character_
     ),
-    "Can't convert NA_real_"
+    "Can't convert NA_character_ element"
   )
 
   expect_error(
     adbc_connection_get_objects(
-      con, 0L,
-      "catalog", "db_schema",
-      "table_name", c("table_type1", NA_character_), "column_name"
+      con,
+      table_type = structure("abc", class = "non-empty")
     ),
-    "Can't convert NA_character_ element"
+    "Can't convert classed object"
   )
+})
+
+test_that("invalid const char* inputs generate errors", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
+  stmt <- adbc_statement_init(con)
 
   expect_error(
     adbc_statement_set_sql_query(stmt, NULL),
-    "Expected character(1)",
-    fixed = TRUE
+    "Expected character"
   )
 
   expect_error(
-    adbc_statement_set_sql_query(stmt, NA_character_),
-    "Can't convert NA_character_"
+    adbc_statement_set_sql_query(stmt, structure("abc", class = "non-empty")),
+    "Can't convert classed object to const char"
   )
 
   expect_error(
-    adbc_connection_get_info(con, NA_integer_),
-    "Can't convert NA_integer_ element"
+    adbc_statement_set_sql_query(stmt, NA_character_),
+    "Can't convert NA_character_"
   )
+})
+
+test_that("invalid bool inputs generate errors", {
+  db <- adbc_database_init(adbc_driver_void())
+  con <- adbc_connection_init(db)
 
   expect_error(
-    adbc_connection_get_info(con, NA_real_),
-    "Can't convert NA_real_ or NaN element"
+    adbc_connection_get_statistics(con, NULL, NULL, "table name", character()),
+    "Expected integer(1) or double(1)",
+    fixed = TRUE
   )
 
   expect_error(
-    adbc_connection_get_info(con, NaN),
-    "Can't convert NA_real_ or NaN element"
+    adbc_connection_get_statistics(con, NULL, NULL, "table name", NA),
+    "Can't convert NA to bool"
   )
 
-  # (makes a NULL xptr)
-  stmt2 <- unserialize(serialize(stmt, NULL))
   expect_error(
-    adbc_statement_set_sql_query(stmt2, "some query"),
-    "Can't convert external pointer to NULL to T*"
+    adbc_connection_get_statistics(
+      con, NULL, NULL, "table name",
+      structure(TRUE, class = "non-empty")
+    ),
+    "Can't convert classed object to bool"
   )
 })

Reply via email to