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 8f357e5 feat(r): Add scoping + lifecycle helpers (#693)
8f357e5 is described below
commit 8f357e530be7d1db5673e7d406178e46152dc16c
Author: Dewey Dunnington <[email protected]>
AuthorDate: Wed May 24 09:12:38 2023 -0400
feat(r): Add scoping + lifecycle helpers (#693)
See https://github.com/r-dbi/adbc/discussions/4
This PR adds some quality-of-life improvements for managing the
lifecycle of databases, connections, statements, and streams that may
have been generated by any of them. This required quite a bit of
iteration but hopefully saves others working with ADBC in R some
head-scratching trying to get the lifecycles correct.
While poking through the lifecycles, I also fixed some errors (memory
protection, consistency, strict prototypes, order of New -> SetOptions
-> Init) and used the new helpers where appropriate (e.g., to avoid a
dangling garbage-collectible reference if SetOptions errors).
@krlmlr @nbenn do you mind having a quick look though the R bits?
``` r
library(adbcdrivermanager)
stmt <- local({
# Registers exit handlers in the correct order to ensure prompt cleanup
db <- local_adbc(adbc_database_init(adbc_driver_log()))
con <- local_adbc(adbc_connection_init(db))
adbc_connection_join(con, db)
stmt <- local_adbc(adbc_statement_init(con))
adbc_statement_join(stmt, con)
adbc_xptr_move(stmt)
})
#> LogDatabaseNew()
#> LogDatabaseInit()
#> LogConnectionNew()
#> LogConnectionInit()
#> LogStatementNew()
# Everything is released immediately
adbc_statement_release(stmt)
#> LogStatementRelease()
#> LogConnectionRelease()
#> LogDatabaseRelease()
#> LogDriverRelease()
# If an error occurs when creating one of the objects, everything
# is still released immediately
local({
db <- local_adbc(adbc_database_init(adbc_driver_log()))
con <- local_adbc(adbc_connection_init(db))
adbc_connection_join(con, db)
stop("Something happened!")
stmt <- local_adbc(adbc_statement_init(con))
adbc_statement_join(stmt, con)
adbc_xptr_move(stmt)
})
#> LogDatabaseNew()
#> LogDatabaseInit()
#> LogConnectionNew()
#> LogConnectionInit()
#> Error in eval(quote({: Something happened!
#> LogConnectionRelease()
#> LogDatabaseRelease()
#> LogDriverRelease()
```
<sup>Created on 2023-05-19 with [reprex
v2.0.2](https://reprex.tidyverse.org)</sup>
---
.pre-commit-config.yaml | 4 +-
r/adbcdrivermanager/DESCRIPTION | 3 +-
r/adbcdrivermanager/NAMESPACE | 7 +
r/adbcdrivermanager/R/adbc.R | 52 +++++--
r/adbcdrivermanager/R/helpers.R | 170 +++++++++++++++++++++
r/adbcdrivermanager/R/utils.R | 99 ++++++++++++
r/adbcdrivermanager/man/adbc_connection_join.Rd | 56 +++++++
r/adbcdrivermanager/man/adbc_xptr_move.Rd | 41 +++++
r/adbcdrivermanager/man/with_adbc.Rd | 65 ++++++++
r/adbcdrivermanager/src/driver_log.c | 3 +-
r/adbcdrivermanager/src/driver_monkey.c | 2 +-
r/adbcdrivermanager/src/driver_void.c | 2 +-
r/adbcdrivermanager/src/init.c | 20 ++-
r/adbcdrivermanager/src/radbc.cc | 65 +++++++-
r/adbcdrivermanager/src/radbc.h | 20 +++
.../tests/testthat/_snaps/driver_log.md | 4 +-
.../tests/testthat/_snaps/helpers.md | 27 ++++
r/adbcdrivermanager/tests/testthat/test-helpers.R | 131 ++++++++++++++++
r/adbcdrivermanager/tests/testthat/test-utils.R | 33 ++++
r/adbcdrivermanager/tools/make-callentries.R | 4 +-
20 files changed, 772 insertions(+), 36 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8353583..37b5e9c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -28,9 +28,9 @@ repos:
- id: check-yaml
exclude: ci/conda/meta.yaml
- id: end-of-file-fixer
- exclude: r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
+ exclude: "^r/.*?/_snaps/.*?.md$"
- id: trailing-whitespace
- exclude: r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
+ exclude: "^r/.*?/_snaps/.*?.md$"
- repo: https://github.com/pocc/pre-commit-hooks
rev: v1.3.5
hooks:
diff --git a/r/adbcdrivermanager/DESCRIPTION b/r/adbcdrivermanager/DESCRIPTION
index 555fa4c..0fb1799 100644
--- a/r/adbcdrivermanager/DESCRIPTION
+++ b/r/adbcdrivermanager/DESCRIPTION
@@ -17,7 +17,8 @@ Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
SystemRequirements: C++11
Suggests:
- testthat (>= 3.0.0)
+ testthat (>= 3.0.0),
+ withr
Config/testthat/edition: 3
Config/build/bootstrap: TRUE
URL: https://github.com/apache/arrow-adbc
diff --git a/r/adbcdrivermanager/NAMESPACE b/r/adbcdrivermanager/NAMESPACE
index c13dd38..24c0528 100644
--- a/r/adbcdrivermanager/NAMESPACE
+++ b/r/adbcdrivermanager/NAMESPACE
@@ -32,6 +32,7 @@ export(adbc_connection_get_table_schema)
export(adbc_connection_get_table_types)
export(adbc_connection_init)
export(adbc_connection_init_default)
+export(adbc_connection_join)
export(adbc_connection_read_partition)
export(adbc_connection_release)
export(adbc_connection_rollback)
@@ -50,10 +51,16 @@ export(adbc_statement_execute_query)
export(adbc_statement_get_parameter_schema)
export(adbc_statement_init)
export(adbc_statement_init_default)
+export(adbc_statement_join)
export(adbc_statement_prepare)
export(adbc_statement_release)
export(adbc_statement_set_options)
export(adbc_statement_set_sql_query)
export(adbc_statement_set_substrait_plan)
+export(adbc_stream_join)
+export(adbc_xptr_is_valid)
+export(adbc_xptr_move)
+export(local_adbc)
+export(with_adbc)
importFrom(utils,str)
useDynLib(adbcdrivermanager, .registration = TRUE)
diff --git a/r/adbcdrivermanager/R/adbc.R b/r/adbcdrivermanager/R/adbc.R
index f9a05c8..773e9d6 100644
--- a/r/adbcdrivermanager/R/adbc.R
+++ b/r/adbcdrivermanager/R/adbc.R
@@ -53,13 +53,17 @@ adbc_database_init_default <- function(driver, options =
NULL, subclass = charac
}
database$driver <- driver
- adbc_database_set_options(database, options)
- error <- adbc_allocate_error()
- status <- .Call(RAdbcDatabaseInit, database, error)
- stop_for_error(status, error)
- class(database) <- c(subclass, class(database))
- database
+ with_adbc(database, {
+ adbc_database_set_options(database, options)
+
+ error <- adbc_allocate_error()
+ status <- .Call(RAdbcDatabaseInit, database, error)
+ stop_for_error(status, error)
+ class(database) <- c(subclass, class(database))
+
+ adbc_xptr_move(database)
+ })
}
#' @rdname adbc_database_init
@@ -118,13 +122,18 @@ adbc_connection_init.default <- function(database, ...) {
adbc_connection_init_default <- function(database, options = NULL, subclass =
character()) {
connection <- .Call(RAdbcConnectionNew)
connection$database <- database
- error <- adbc_allocate_error()
- status <- .Call(RAdbcConnectionInit, connection, database, error)
- stop_for_error(status, error)
- adbc_connection_set_options(connection, options)
- class(connection) <- c(subclass, class(connection))
- connection
+ with_adbc(connection, {
+ adbc_connection_set_options(connection, options)
+
+ error <- adbc_allocate_error()
+ status <- .Call(RAdbcConnectionInit, connection, database, error)
+ stop_for_error(status, error)
+
+ class(connection) <- c(subclass, class(connection))
+
+ adbc_xptr_move(connection)
+ })
}
#' @rdname adbc_connection_init
@@ -151,6 +160,11 @@ adbc_connection_set_options <- function(connection,
options) {
#' @rdname adbc_connection_init
#' @export
adbc_connection_release <- function(connection) {
+ if (isTRUE(connection$.release_database)) {
+ database <- connection$database
+ on.exit(adbc_database_release(database))
+ }
+
error <- adbc_allocate_error()
status <- .Call(RAdbcConnectionRelease, connection, error)
stop_for_error(status, error)
@@ -308,9 +322,12 @@ adbc_statement_init.default <- function(connection, ...) {
adbc_statement_init_default <- function(connection, options = NULL, subclass =
character()) {
statement <- .Call(RAdbcStatementNew, connection)
statement$connection <- connection
- adbc_statement_set_options(statement, options)
- class(statement) <- c(subclass, class(statement))
- statement
+
+ with_adbc(statement, {
+ adbc_statement_set_options(statement, options)
+ class(statement) <- c(subclass, class(statement))
+ adbc_xptr_move(statement)
+ })
}
#' @rdname adbc_statement_init
@@ -337,6 +354,11 @@ adbc_statement_set_options <- function(statement, options)
{
#' @rdname adbc_statement_init
#' @export
adbc_statement_release <- function(statement) {
+ if (isTRUE(statement$.release_connection)) {
+ connection <- statement$connection
+ on.exit(adbc_connection_release(connection))
+ }
+
error <- adbc_allocate_error()
status <- .Call(RAdbcStatementRelease, statement, error)
stop_for_error(status, error)
diff --git a/r/adbcdrivermanager/R/helpers.R b/r/adbcdrivermanager/R/helpers.R
new file mode 100644
index 0000000..809faa5
--- /dev/null
+++ b/r/adbcdrivermanager/R/helpers.R
@@ -0,0 +1,170 @@
+# 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.
+
+#' Cleanup helpers
+#'
+#' Managing the lifecycle of databases, connections, and statements can
+#' be complex and error-prone. The R objects that wrap the underlying ADBC
+#' pointers will perform cleanup in the correct order if you rely on garbage
+#' collection (i.e., do nothing and let the objects go out of scope); however
+#' it is good practice to explicitly clean up these objects. These helpers
+#' are designed to make explicit and predictable cleanup easy to accomplish.
+#'
+#' Note that you can use [adbc_connection_join()],
+#' [adbc_statement_join()], and [adbc_stream_join()]
+#' to tie the lifecycle of the parent object to that of the child object.
+#' These functions mark any previous references to the parent object as
+#' released so you can still use local and with helpers to manage the parent
+#' object before it is joined.
+#'
+#' @param x An ADBC database, ADBC connection, ADBC statement, or
+#' nanoarrow_array_stream returned from calls to an ADBC function.
+#' @param code Code to execute before cleaning up the input.
+#' @param .local_envir The execution environment whose scope should be tied
+#' to the input.
+#'
+#' @return
+#' - `with_adbc()` returns the result of `code`
+#' - `local_adbc()` returns the input, invisibly.
+#' @export
+#'
+#' @examples
+#' # Using with_adbc():
+#' with_adbc(db <- adbc_database_init(adbc_driver_void()), {
+#' with_adbc(con <- adbc_connection_init(db), {
+#' with_adbc(stmt <- adbc_statement_init(con), {
+#' # adbc_statement_set_sql_query(stmt, "SELECT * FROM foofy")
+#' # adbc_statement_execute_query(stmt)
+#' "some result"
+#' })
+#' })
+#' })
+#'
+#' # Using local_adbc_*() (works best within a function, test, or local())
+#' local({
+#' db <- local_adbc(adbc_database_init(adbc_driver_void()))
+#' con <- local_adbc(adbc_connection_init(db))
+#' stmt <- local_adbc(adbc_statement_init(con))
+#' # adbc_statement_set_sql_query(stmt, "SELECT * FROM foofy")
+#' # adbc_statement_execute_query(stmt)
+#' "some result"
+#' })
+#'
+with_adbc <- function(x, code) {
+ assert_adbc(x)
+
+ on.exit(adbc_release_non_null(x))
+ force(code)
+}
+
+#' @rdname with_adbc
+#' @export
+local_adbc <- function(x, .local_envir = parent.frame()) {
+ assert_adbc(x)
+
+ withr::defer(adbc_release_non_null(x), envir = .local_envir)
+ invisible(x)
+}
+
+#' Join the lifecycle of a unique parent to its child
+#'
+#' It is occasionally useful to return a connection, statement, or stream
+#' from a function that was created from a unique parent. These helpers
+#' tie the lifecycle of a unique parent object to its child such that the
+#' parent object is released predictably and immediately after the child.
+#' These functions will invalidate all references to the previous R object.
+#'
+#' @param database A database created with [adbc_database_init()]
+#' @param connection A connection created with [adbc_connection_init()]
+#' @param statement A statement created with [adbc_statement_init()]
+#' @param stream A
[nanoarrow_array_stream][nanoarrow::as_nanoarrow_array_stream]
+#' @inheritParams with_adbc
+#'
+#' @return The input, invisibly.
+#' @export
+#'
+#' @examples
+#' # Use local_adbc to ensure prompt cleanup on error;
+#' # use join functions to return a single object that manages
+#' # the lifecycle of all three.
+#' stmt <- local({
+#' db <- local_adbc(adbc_database_init(adbc_driver_log()))
+#'
+#' con <- local_adbc(adbc_connection_init(db))
+#' adbc_connection_join(con, db)
+#'
+#' stmt <- local_adbc(adbc_statement_init(con))
+#' adbc_statement_join(stmt, con)
+#'
+#' adbc_xptr_move(stmt)
+#' })
+#'
+#' # Everything is released immediately when the last object is released
+#' adbc_statement_release(stmt)
+#'
+adbc_connection_join <- function(connection, database) {
+ assert_adbc(connection, "adbc_connection")
+ assert_adbc(database, "adbc_database")
+
+ connection$.release_database <- TRUE
+ connection$database <- adbc_xptr_move(database)
+ invisible(connection)
+}
+
+#' @rdname adbc_connection_join
+#' @export
+adbc_statement_join <- function(statement, connection) {
+ assert_adbc(statement, "adbc_statement")
+ assert_adbc(connection, "adbc_connection")
+
+ statement$.release_connection <- TRUE
+ statement$connection <- adbc_xptr_move(connection)
+ invisible(statement)
+}
+
+#' @rdname adbc_connection_join
+#' @export
+adbc_stream_join <- function(stream, x) {
+ if (utils::packageVersion("nanoarrow") >= "0.1.0.9000") {
+ assert_adbc(stream, "nanoarrow_array_stream")
+ assert_adbc(x)
+
+ self_contained_finalizer <- function() {
+ try(adbc_release_non_null(x))
+ }
+
+ # Make sure we don't keep any variables around that aren't needed
+ # for the finalizer and make sure we invalidate the original statement
+ self_contained_finalizer_env <- as.environment(
+ list(x = adbc_xptr_move(x))
+ )
+ parent.env(self_contained_finalizer_env) <-
asNamespace("adbcdrivermanager")
+ environment(self_contained_finalizer) <- self_contained_finalizer_env
+
+ # This finalizer will run immediately on release (if released explicitly
+ # on the main R thread) or on garbage collection otherwise.
+
+ # Until the release version of nanoarrow contains this we will get a check
+ # warning for nanoarrow::array_stream_set_finalizer()
+ set_finalizer <- asNamespace("nanoarrow")[["array_stream_set_finalizer"]]
+ set_finalizer(stream, self_contained_finalizer)
+
+ invisible(stream)
+ } else {
+ stop("adbc_stream_join_statement() requires nanoarrow >= 0.2.0")
+ }
+}
diff --git a/r/adbcdrivermanager/R/utils.R b/r/adbcdrivermanager/R/utils.R
index af9ecaf..e1e7577 100644
--- a/r/adbcdrivermanager/R/utils.R
+++ b/r/adbcdrivermanager/R/utils.R
@@ -94,3 +94,102 @@ str.adbc_xptr <- function(object, ...) {
str(env_proxy, ...)
invisible(object)
}
+
+
+#' Low-level pointer details
+#'
+#' - `adbc_xptr_move()` allocates a fresh R object and moves all values pointed
+#' to by `x` into it. The original R object is invalidated by zeroing its
+#' content. This is useful when returning from a function where
+#' [lifecycle helpers][with_adbc] were used to manage the original
+#' object.
+#' - `adbc_xptr_is_valid()` provides a means by which to test for an
invalidated
+#' pointer.
+#'
+#' @param x An 'adbc_database', 'adbc_connection', 'adbc_statement', or
+#' 'nanoarrow_array_stream'
+#'
+#' @return
+#' - `adbc_xptr_move()`: A freshly-allocated R object identical to `x`
+#' - `adbc_xptr_is_valid()`: Returns FALSE if the ADBC object pointed to by `x`
+#' has been invalidated.
+#' @export
+#'
+#' @examples
+#' db <- adbc_database_init(adbc_driver_void())
+#' adbc_xptr_is_valid(db)
+#' db_new <- adbc_xptr_move(db)
+#' adbc_xptr_is_valid(db)
+#' adbc_xptr_is_valid(db_new)
+#'
+adbc_xptr_move <- function(x) {
+ if (inherits(x, "adbc_database")) {
+ .Call(RAdbcMoveDatabase, x)
+ } else if (inherits(x, "adbc_connection")) {
+ .Call(RAdbcMoveConnection, x)
+ } else if (inherits(x, "adbc_statement")) {
+ .Call(RAdbcMoveStatement, x)
+ } else if (inherits(x, "nanoarrow_array_stream")) {
+ stream <- nanoarrow::nanoarrow_allocate_array_stream()
+ nanoarrow::nanoarrow_pointer_move(x, stream)
+ stream
+ } else {
+ assert_adbc(x)
+ }
+}
+
+#' @rdname adbc_xptr_move
+#' @export
+adbc_xptr_is_valid <- function(x) {
+ if (inherits(x, "adbc_database")) {
+ .Call(RAdbcDatabaseValid, x)
+ } else if (inherits(x, "adbc_connection")) {
+ .Call(RAdbcConnectionValid, x)
+ } else if (inherits(x, "adbc_statement")) {
+ .Call(RAdbcStatementValid, x)
+ } else if (inherits(x, "nanoarrow_array_stream")) {
+ nanoarrow::nanoarrow_pointer_is_valid(x)
+ } else {
+ assert_adbc(x)
+ }
+}
+
+# Usually we want errors for an attempt at double release; however,
+# the helpers we want to be compatible with adbc_xptr_move() which sets the
+# managed pointer to NULL.
+adbc_release_non_null <- function(x) {
+ if (!adbc_xptr_is_valid(x)) {
+ return()
+ }
+
+ if (inherits(x, "adbc_database")) {
+ adbc_database_release(x)
+ } else if (inherits(x, "adbc_connection")) {
+ adbc_connection_release(x)
+ } else if (inherits(x, "adbc_statement")) {
+ adbc_statement_release(x)
+ } else if (inherits(x, "nanoarrow_array_stream")) {
+ nanoarrow::nanoarrow_pointer_release(x)
+ } else {
+ assert_adbc(x)
+ }
+}
+
+adbc_classes <- c(
+ "adbc_database", "adbc_connection", "adbc_statement",
+ "nanoarrow_array_stream"
+)
+
+assert_adbc <- function(x, what = adbc_classes) {
+ if (inherits(x, what)) {
+ return(invisible(x))
+ }
+
+ stop(
+ sprintf(
+ "`x` must inherit from one of: %s",
+ paste0("'", what, "'", collapse = ", ")
+ ),
+ call. = sys.call(-1)
+ )
+}
diff --git a/r/adbcdrivermanager/man/adbc_connection_join.Rd
b/r/adbcdrivermanager/man/adbc_connection_join.Rd
new file mode 100644
index 0000000..823a33b
--- /dev/null
+++ b/r/adbcdrivermanager/man/adbc_connection_join.Rd
@@ -0,0 +1,56 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/helpers.R
+\name{adbc_connection_join}
+\alias{adbc_connection_join}
+\alias{adbc_statement_join}
+\alias{adbc_stream_join}
+\title{Join the lifecycle of a unique parent to its child}
+\usage{
+adbc_connection_join(connection, database)
+
+adbc_statement_join(statement, connection)
+
+adbc_stream_join(stream, x)
+}
+\arguments{
+\item{connection}{A connection created with
\code{\link[=adbc_connection_init]{adbc_connection_init()}}}
+
+\item{database}{A database created with
\code{\link[=adbc_database_init]{adbc_database_init()}}}
+
+\item{statement}{A statement created with
\code{\link[=adbc_statement_init]{adbc_statement_init()}}}
+
+\item{stream}{A
\link[nanoarrow:as_nanoarrow_array_stream]{nanoarrow_array_stream}}
+
+\item{x}{An ADBC database, ADBC connection, ADBC statement, or
+nanoarrow_array_stream returned from calls to an ADBC function.}
+}
+\value{
+The input, invisibly.
+}
+\description{
+It is occasionally useful to return a connection, statement, or stream
+from a function that was created from a unique parent. These helpers
+tie the lifecycle of a unique parent object to its child such that the
+parent object is released predictably and immediately after the child.
+These functions will invalidate all references to the previous R object.
+}
+\examples{
+# Use local_adbc to ensure prompt cleanup on error;
+# use join functions to return a single object that manages
+# the lifecycle of all three.
+stmt <- local({
+ db <- local_adbc(adbc_database_init(adbc_driver_log()))
+
+ con <- local_adbc(adbc_connection_init(db))
+ adbc_connection_join(con, db)
+
+ stmt <- local_adbc(adbc_statement_init(con))
+ adbc_statement_join(stmt, con)
+
+ adbc_xptr_move(stmt)
+})
+
+# Everything is released immediately when the last object is released
+adbc_statement_release(stmt)
+
+}
diff --git a/r/adbcdrivermanager/man/adbc_xptr_move.Rd
b/r/adbcdrivermanager/man/adbc_xptr_move.Rd
new file mode 100644
index 0000000..2dc2cea
--- /dev/null
+++ b/r/adbcdrivermanager/man/adbc_xptr_move.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{adbc_xptr_move}
+\alias{adbc_xptr_move}
+\alias{adbc_xptr_is_valid}
+\title{Low-level pointer details}
+\usage{
+adbc_xptr_move(x)
+
+adbc_xptr_is_valid(x)
+}
+\arguments{
+\item{x}{An 'adbc_database', 'adbc_connection', 'adbc_statement', or
+'nanoarrow_array_stream'}
+}
+\value{
+\itemize{
+\item \code{adbc_xptr_move()}: A freshly-allocated R object identical to
\code{x}
+\item \code{adbc_xptr_is_valid()}: Returns FALSE if the ADBC object pointed to
by \code{x}
+has been invalidated.
+}
+}
+\description{
+\itemize{
+\item \code{adbc_xptr_move()} allocates a fresh R object and moves all values
pointed
+to by \code{x} into it. The original R object is invalidated by zeroing its
+content. This is useful when returning from a function where
+\link[=with_adbc]{lifecycle helpers} were used to manage the original
+object.
+\item \code{adbc_xptr_is_valid()} provides a means by which to test for an
invalidated
+pointer.
+}
+}
+\examples{
+db <- adbc_database_init(adbc_driver_void())
+adbc_xptr_is_valid(db)
+db_new <- adbc_xptr_move(db)
+adbc_xptr_is_valid(db)
+adbc_xptr_is_valid(db_new)
+
+}
diff --git a/r/adbcdrivermanager/man/with_adbc.Rd
b/r/adbcdrivermanager/man/with_adbc.Rd
new file mode 100644
index 0000000..5e16a9c
--- /dev/null
+++ b/r/adbcdrivermanager/man/with_adbc.Rd
@@ -0,0 +1,65 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/helpers.R
+\name{with_adbc}
+\alias{with_adbc}
+\alias{local_adbc}
+\title{Cleanup helpers}
+\usage{
+with_adbc(x, code)
+
+local_adbc(x, .local_envir = parent.frame())
+}
+\arguments{
+\item{x}{An ADBC database, ADBC connection, ADBC statement, or
+nanoarrow_array_stream returned from calls to an ADBC function.}
+
+\item{code}{Code to execute before cleaning up the input.}
+
+\item{.local_envir}{The execution environment whose scope should be tied
+to the input.}
+}
+\value{
+\itemize{
+\item \code{with_adbc()} returns the result of \code{code}
+\item \code{local_adbc()} returns the input, invisibly.
+}
+}
+\description{
+Managing the lifecycle of databases, connections, and statements can
+be complex and error-prone. The R objects that wrap the underlying ADBC
+pointers will perform cleanup in the correct order if you rely on garbage
+collection (i.e., do nothing and let the objects go out of scope); however
+it is good practice to explicitly clean up these objects. These helpers
+are designed to make explicit and predictable cleanup easy to accomplish.
+}
+\details{
+Note that you can use
\code{\link[=adbc_connection_join]{adbc_connection_join()}},
+\code{\link[=adbc_statement_join]{adbc_statement_join()}}, and
\code{\link[=adbc_stream_join]{adbc_stream_join()}}
+to tie the lifecycle of the parent object to that of the child object.
+These functions mark any previous references to the parent object as
+released so you can still use local and with helpers to manage the parent
+object before it is joined.
+}
+\examples{
+# Using with_adbc():
+with_adbc(db <- adbc_database_init(adbc_driver_void()), {
+ with_adbc(con <- adbc_connection_init(db), {
+ with_adbc(stmt <- adbc_statement_init(con), {
+ # adbc_statement_set_sql_query(stmt, "SELECT * FROM foofy")
+ # adbc_statement_execute_query(stmt)
+ "some result"
+ })
+ })
+})
+
+# Using local_adbc_*() (works best within a function, test, or local())
+local({
+ db <- local_adbc(adbc_database_init(adbc_driver_void()))
+ con <- local_adbc(adbc_connection_init(db))
+ stmt <- local_adbc(adbc_statement_init(con))
+ # adbc_statement_set_sql_query(stmt, "SELECT * FROM foofy")
+ # adbc_statement_execute_query(stmt)
+ "some result"
+})
+
+}
diff --git a/r/adbcdrivermanager/src/driver_log.c
b/r/adbcdrivermanager/src/driver_log.c
index 5a0520d..543ae9a 100644
--- a/r/adbcdrivermanager/src/driver_log.c
+++ b/r/adbcdrivermanager/src/driver_log.c
@@ -286,7 +286,6 @@ static AdbcStatusCode LogStatementSetSqlQuery(struct
AdbcStatement* statement,
static AdbcStatusCode LogDriverInitFunc(int version, void* raw_driver,
struct AdbcError* error) {
- Rprintf("LogDriverInitFunc()\n");
if (version != ADBC_VERSION_1_0_0) return ADBC_STATUS_NOT_IMPLEMENTED;
struct AdbcDriver* driver = (struct AdbcDriver*)raw_driver;
memset(driver, 0, sizeof(struct AdbcDriver));
@@ -334,7 +333,7 @@ static AdbcStatusCode LogDriverInitFunc(int version, void*
raw_driver,
return ADBC_STATUS_OK;
}
-SEXP RAdbcLogDriverInitFunc() {
+SEXP RAdbcLogDriverInitFunc(void) {
SEXP xptr =
PROTECT(R_MakeExternalPtrFn((DL_FUNC)LogDriverInitFunc, R_NilValue,
R_NilValue));
Rf_setAttrib(xptr, R_ClassSymbol, Rf_mkString("adbc_driver_init_func"));
diff --git a/r/adbcdrivermanager/src/driver_monkey.c
b/r/adbcdrivermanager/src/driver_monkey.c
index 6dc8738..9076975 100644
--- a/r/adbcdrivermanager/src/driver_monkey.c
+++ b/r/adbcdrivermanager/src/driver_monkey.c
@@ -333,7 +333,7 @@ static AdbcStatusCode MonkeyDriverInitFunc(int version,
void* raw_driver,
return ADBC_STATUS_OK;
}
-SEXP RAdbcMonkeyDriverInitFunc() {
+SEXP RAdbcMonkeyDriverInitFunc(void) {
SEXP xptr =
PROTECT(R_MakeExternalPtrFn((DL_FUNC)MonkeyDriverInitFunc, R_NilValue,
R_NilValue));
Rf_setAttrib(xptr, R_ClassSymbol, Rf_mkString("adbc_driver_init_func"));
diff --git a/r/adbcdrivermanager/src/driver_void.c
b/r/adbcdrivermanager/src/driver_void.c
index b349387..59cfe03 100644
--- a/r/adbcdrivermanager/src/driver_void.c
+++ b/r/adbcdrivermanager/src/driver_void.c
@@ -307,7 +307,7 @@ static AdbcStatusCode VoidDriverInitFunc(int version, void*
raw_driver,
return ADBC_STATUS_OK;
}
-SEXP RAdbcVoidDriverInitFunc() {
+SEXP RAdbcVoidDriverInitFunc(void) {
SEXP xptr =
PROTECT(R_MakeExternalPtrFn((DL_FUNC)VoidDriverInitFunc, R_NilValue,
R_NilValue));
Rf_setAttrib(xptr, R_ClassSymbol, Rf_mkString("adbc_driver_init_func"));
diff --git a/r/adbcdrivermanager/src/init.c b/r/adbcdrivermanager/src/init.c
index de2f95f..0d9f65e 100644
--- a/r/adbcdrivermanager/src/init.c
+++ b/r/adbcdrivermanager/src/init.c
@@ -20,20 +20,24 @@
#include <Rinternals.h>
/* generated by tools/make-callentries.R */
-SEXP RAdbcLogDriverInitFunc();
-SEXP RAdbcMonkeyDriverInitFunc();
-SEXP RAdbcVoidDriverInitFunc();
+SEXP RAdbcLogDriverInitFunc(void);
+SEXP RAdbcMonkeyDriverInitFunc(void);
+SEXP RAdbcVoidDriverInitFunc(void);
SEXP RAdbcAllocateError(SEXP shelter_sexp);
SEXP RAdbcErrorProxy(SEXP error_xptr);
SEXP RAdbcStatusCodeMessage(SEXP status_sexp);
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();
+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);
@@ -54,6 +58,8 @@ SEXP RAdbcConnectionReadPartition(SEXP connection_xptr, SEXP
serialized_partitio
SEXP RAdbcConnectionCommit(SEXP connection_xptr, SEXP error_xptr);
SEXP RAdbcConnectionRollback(SEXP connection_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);
@@ -81,10 +87,14 @@ static const R_CallMethodDef CallEntries[] = {
{"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},
@@ -96,6 +106,8 @@ static const R_CallMethodDef CallEntries[] = {
{"RAdbcConnectionCommit", (DL_FUNC)&RAdbcConnectionCommit, 2},
{"RAdbcConnectionRollback", (DL_FUNC)&RAdbcConnectionRollback, 2},
{"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},
diff --git a/r/adbcdrivermanager/src/radbc.cc b/r/adbcdrivermanager/src/radbc.cc
index 1b977cf..a04fea8 100644
--- a/r/adbcdrivermanager/src/radbc.cc
+++ b/r/adbcdrivermanager/src/radbc.cc
@@ -112,7 +112,7 @@ extern "C" SEXP RAdbcLoadDriverFromInitFunc(SEXP
driver_init_func_xptr) {
}
extern "C" SEXP RAdbcDatabaseNew(SEXP driver_init_func_xptr) {
- SEXP database_xptr = adbc_allocate_xptr<AdbcDatabase>();
+ SEXP database_xptr = PROTECT(adbc_allocate_xptr<AdbcDatabase>());
R_RegisterCFinalizer(database_xptr, &finalize_database_xptr);
AdbcDatabase* database = adbc_from_xptr<AdbcDatabase>(database_xptr);
@@ -132,9 +132,28 @@ extern "C" SEXP RAdbcDatabaseNew(SEXP
driver_init_func_xptr) {
adbc_error_stop(status, &error, "RAdbcDatabaseNew()");
}
+ UNPROTECT(1);
return database_xptr;
}
+extern "C" SEXP RAdbcMoveDatabase(SEXP database_xptr) {
+ AdbcDatabase* database = adbc_from_xptr<AdbcDatabase>(database_xptr);
+ SEXP database_xptr_new = PROTECT(adbc_allocate_xptr<AdbcDatabase>());
+ AdbcDatabase* database_new = adbc_from_xptr<AdbcDatabase>(database_xptr_new);
+
+ memcpy(database_new, database, sizeof(AdbcDatabase));
+ adbc_xptr_move_attrs(database_xptr, database_xptr_new);
+ memset(database, 0, sizeof(AdbcDatabase));
+
+ UNPROTECT(1);
+ return database_xptr_new;
+}
+
+extern "C" SEXP RAdbcDatabaseValid(SEXP database_xptr) {
+ AdbcDatabase* database = adbc_from_xptr<AdbcDatabase>(database_xptr);
+ 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);
@@ -153,8 +172,8 @@ extern "C" SEXP RAdbcDatabaseInit(SEXP database_xptr, SEXP
error_xptr) {
extern "C" SEXP RAdbcDatabaseRelease(SEXP database_xptr, SEXP error_xptr) {
auto database = adbc_from_xptr<AdbcDatabase>(database_xptr);
auto error = adbc_from_xptr<AdbcError>(error_xptr);
- R_SetExternalPtrTag(database_xptr, R_NilValue);
- return adbc_wrap_status(AdbcDatabaseRelease(database, error));
+ int status = AdbcDatabaseRelease(database, error);
+ return adbc_wrap_status(status);
}
static void finalize_connection_xptr(SEXP connection_xptr) {
@@ -172,7 +191,7 @@ static void finalize_connection_xptr(SEXP connection_xptr) {
adbc_xptr_default_finalize<AdbcConnection>(connection_xptr);
}
-extern "C" SEXP RAdbcConnectionNew() {
+extern "C" SEXP RAdbcConnectionNew(void) {
SEXP connection_xptr = PROTECT(adbc_allocate_xptr<AdbcConnection>());
R_RegisterCFinalizer(connection_xptr, &finalize_connection_xptr);
@@ -186,6 +205,24 @@ extern "C" SEXP RAdbcConnectionNew() {
return connection_xptr;
}
+extern "C" SEXP RAdbcMoveConnection(SEXP connection_xptr) {
+ AdbcConnection* connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
+ SEXP connection_xptr_new = PROTECT(adbc_allocate_xptr<AdbcConnection>());
+ AdbcConnection* connection_new =
adbc_from_xptr<AdbcConnection>(connection_xptr_new);
+
+ memcpy(connection_new, connection, sizeof(AdbcConnection));
+ adbc_xptr_move_attrs(connection_xptr, connection_xptr_new);
+ memset(connection, 0, sizeof(AdbcConnection));
+
+ UNPROTECT(1);
+ return connection_xptr_new;
+}
+
+extern "C" SEXP RAdbcConnectionValid(SEXP connection_xptr) {
+ AdbcConnection* connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
+ 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);
@@ -215,7 +252,6 @@ extern "C" SEXP RAdbcConnectionRelease(SEXP
connection_xptr, SEXP error_xptr) {
auto connection = adbc_from_xptr<AdbcConnection>(connection_xptr);
auto error = adbc_from_xptr<AdbcError>(error_xptr);
int status = AdbcConnectionRelease(connection, error);
- R_SetExternalPtrProtected(connection_xptr, R_NilValue);
return adbc_wrap_status(status);
}
@@ -346,6 +382,24 @@ extern "C" SEXP RAdbcStatementNew(SEXP connection_xptr) {
return statement_xptr;
}
+extern "C" SEXP RAdbcMoveStatement(SEXP statement_xptr) {
+ AdbcStatement* statement = adbc_from_xptr<AdbcStatement>(statement_xptr);
+ SEXP statement_xptr_new = PROTECT(adbc_allocate_xptr<AdbcStatement>());
+ AdbcStatement* statement_new =
adbc_from_xptr<AdbcStatement>(statement_xptr_new);
+
+ memcpy(statement_new, statement, sizeof(AdbcStatement));
+ adbc_xptr_move_attrs(statement_xptr, statement_xptr_new);
+ memset(statement, 0, sizeof(AdbcStatement));
+
+ UNPROTECT(1);
+ return statement_xptr_new;
+}
+
+extern "C" SEXP RAdbcStatementValid(SEXP statement_xptr) {
+ AdbcStatement* statement = adbc_from_xptr<AdbcStatement>(statement_xptr);
+ 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);
@@ -359,7 +413,6 @@ 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);
int status = AdbcStatementRelease(statement, error);
- R_SetExternalPtrProtected(statement_xptr, R_NilValue);
return adbc_wrap_status(status);
}
diff --git a/r/adbcdrivermanager/src/radbc.h b/r/adbcdrivermanager/src/radbc.h
index 76cc7df..73b37a3 100644
--- a/r/adbcdrivermanager/src/radbc.h
+++ b/r/adbcdrivermanager/src/radbc.h
@@ -111,6 +111,26 @@ static inline void adbc_xptr_default_finalize(SEXP xptr) {
}
}
+static inline void adbc_xptr_move_attrs(SEXP xptr_old, SEXP xptr_new) {
+ SEXP cls_old = PROTECT(Rf_getAttrib(xptr_old, R_ClassSymbol));
+ SEXP tag_old = PROTECT(R_ExternalPtrTag(xptr_old));
+ SEXP prot_old = PROTECT(R_ExternalPtrProtected(xptr_old));
+
+ SEXP tag_new = PROTECT(R_ExternalPtrTag(xptr_new));
+ SEXP prot_new = PROTECT(R_ExternalPtrProtected(xptr_new));
+
+ Rf_setAttrib(xptr_new, R_ClassSymbol, cls_old);
+ R_SetExternalPtrTag(xptr_new, tag_old);
+ R_SetExternalPtrProtected(xptr_new, prot_old);
+
+ // Don't change the class of the original object...not necessary for
+ // lifecycle management and potentially very confusing
+ R_SetExternalPtrTag(xptr_old, tag_new);
+ R_SetExternalPtrProtected(xptr_old, prot_new);
+
+ UNPROTECT(5);
+}
+
static inline const char* adbc_as_const_char(SEXP sexp) {
if (TYPEOF(sexp) != STRSXP || Rf_length(sexp) != 1) {
Rf_error("Expected character(1) for conversion to const char*");
diff --git a/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
b/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
index 4658a3f..9ddaa7b 100644
--- a/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
+++ b/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
@@ -3,8 +3,6 @@
Code
db <- adbc_database_init(adbc_driver_log(), key = "value")
Output
- LogDriverInitFunc()
- LogDriverInitFunc()
LogDatabaseNew()
LogDatabaseSetOption()
LogDatabaseInit()
@@ -12,8 +10,8 @@
con <- adbc_connection_init(db, key = "value")
Output
LogConnectionNew()
- LogConnectionInit()
LogConnectionSetOption()
+ LogConnectionInit()
Code
stmt <- adbc_statement_init(con, key = "value")
Output
diff --git a/r/adbcdrivermanager/tests/testthat/_snaps/helpers.md
b/r/adbcdrivermanager/tests/testthat/_snaps/helpers.md
new file mode 100644
index 0000000..aef27d0
--- /dev/null
+++ b/r/adbcdrivermanager/tests/testthat/_snaps/helpers.md
@@ -0,0 +1,27 @@
+# joiners work for databases, connections, and statements
+
+ Code
+ stmt <- local({
+ db <- local_adbc(adbc_database_init(adbc_driver_log()))
+ con <- local_adbc(adbc_connection_init(db))
+ adbc_connection_join(con, db)
+ expect_false(adbc_xptr_is_valid(db))
+ stmt <- local_adbc(adbc_statement_init(con))
+ adbc_statement_join(stmt, con)
+ expect_false(adbc_xptr_is_valid(con))
+ adbc_xptr_move(stmt)
+ })
+ Output
+ LogDatabaseNew()
+ LogDatabaseInit()
+ LogConnectionNew()
+ LogConnectionInit()
+ LogStatementNew()
+ Code
+ adbc_statement_release(stmt)
+ Output
+ LogStatementRelease()
+ LogConnectionRelease()
+ LogDatabaseRelease()
+ LogDriverRelease()
+
diff --git a/r/adbcdrivermanager/tests/testthat/test-helpers.R
b/r/adbcdrivermanager/tests/testthat/test-helpers.R
new file mode 100644
index 0000000..c1fb679
--- /dev/null
+++ b/r/adbcdrivermanager/tests/testthat/test-helpers.R
@@ -0,0 +1,131 @@
+# 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("with_adbc() and local_adbc() release databases", {
+ db <- adbc_database_init(adbc_driver_void())
+ expect_identical(with_adbc(db, "value"), "value")
+ expect_error(
+ adbc_database_release(db),
+ "ADBC_STATUS_INVALID_STATE"
+ )
+
+ db <- adbc_database_init(adbc_driver_void())
+ local({
+ expect_identical(local_adbc(db), db)
+ })
+ expect_error(
+ adbc_database_release(db),
+ "ADBC_STATUS_INVALID_STATE"
+ )
+})
+
+test_that("with_adbc() and local_adbc() release connections", {
+ db <- adbc_database_init(adbc_driver_void())
+ con <- adbc_connection_init(db)
+ expect_identical(with_adbc(con, "value"), "value")
+ expect_error(
+ adbc_connection_release(con),
+ "ADBC_STATUS_INVALID_STATE"
+ )
+
+ con <- adbc_connection_init(db)
+ local({
+ expect_identical(local_adbc(con), con)
+ })
+ expect_error(
+ adbc_connection_release(con),
+ "ADBC_STATUS_INVALID_STATE"
+ )
+})
+
+test_that("with_adbc() and local_adbc() release statements", {
+ db <- adbc_database_init(adbc_driver_void())
+ con <- adbc_connection_init(db)
+ stmt <- adbc_statement_init(con)
+ expect_identical(with_adbc(stmt, "value"), "value")
+ expect_error(
+ adbc_statement_release(stmt),
+ "ADBC_STATUS_INVALID_STATE"
+ )
+
+ stmt <- adbc_statement_init(con)
+ local({
+ expect_identical(local_adbc(stmt), stmt)
+ })
+ expect_error(
+ adbc_statement_release(stmt),
+ "ADBC_STATUS_INVALID_STATE"
+ )
+})
+
+test_that("with_adbc() and local_adbc() release streams", {
+ stream <- nanoarrow::basic_array_stream(list(1:5))
+ expect_identical(with_adbc(stream, "value"), "value")
+ expect_false(nanoarrow::nanoarrow_pointer_is_valid(stream))
+
+ stream <- nanoarrow::basic_array_stream(list(1:5))
+ local({
+ expect_identical(local_adbc(stream), stream)
+ })
+ expect_false(nanoarrow::nanoarrow_pointer_is_valid(stream))
+})
+
+test_that("joiners work for databases, connections, and statements", {
+ expect_snapshot({
+ stmt <- local({
+ db <- local_adbc(adbc_database_init(adbc_driver_log()))
+
+ con <- local_adbc(adbc_connection_init(db))
+ adbc_connection_join(con, db)
+ expect_false(adbc_xptr_is_valid(db))
+
+ stmt <- local_adbc(adbc_statement_init(con))
+ adbc_statement_join(stmt, con)
+ expect_false(adbc_xptr_is_valid(con))
+
+ adbc_xptr_move(stmt)
+ })
+
+ adbc_statement_release(stmt)
+ })
+})
+
+test_that("joiners work with streams", {
+ skip_if_not(packageVersion("nanoarrow") >= "0.1.0.9000")
+
+ stream <- local({
+ db <- local_adbc(adbc_database_init(adbc_driver_monkey()))
+
+ con <- local_adbc(adbc_connection_init(db))
+ adbc_connection_join(con, db)
+ expect_false(adbc_xptr_is_valid(db))
+
+ stmt <- local_adbc(adbc_statement_init(con, data.frame(x = 1:5)))
+ adbc_statement_join(stmt, con)
+ expect_false(adbc_xptr_is_valid(con))
+
+ stream <- local_adbc(nanoarrow::nanoarrow_allocate_array_stream())
+ adbc_statement_execute_query(stmt, stream)
+ adbc_stream_join(stream, stmt)
+ expect_false(adbc_xptr_is_valid(stmt))
+
+ adbc_xptr_move(stream)
+ })
+
+ expect_identical(as.data.frame(stream), data.frame(x = 1:5))
+ expect_silent(stream$release())
+})
diff --git a/r/adbcdrivermanager/tests/testthat/test-utils.R
b/r/adbcdrivermanager/tests/testthat/test-utils.R
index 956cc13..938ce95 100644
--- a/r/adbcdrivermanager/tests/testthat/test-utils.R
+++ b/r/adbcdrivermanager/tests/testthat/test-utils.R
@@ -53,3 +53,36 @@ test_that("external pointer embedded environment works", {
db[["key"]] <- "value2"
expect_identical(db[["key"]], "value2")
})
+
+test_that("pointer mover leaves behind an invalid external pointer", {
+ db <- adbc_database_init(adbc_driver_void())
+ con <- adbc_connection_init(db)
+ stmt <- adbc_statement_init(con)
+
+ expect_true(adbc_xptr_is_valid(db))
+ expect_true(adbc_xptr_is_valid(adbc_xptr_move(db)))
+ expect_false(adbc_xptr_is_valid(db))
+
+ expect_true(adbc_xptr_is_valid(con))
+ expect_true(adbc_xptr_is_valid(adbc_xptr_move(con)))
+ expect_false(adbc_xptr_is_valid(con))
+
+ expect_true(adbc_xptr_is_valid(stmt))
+ expect_true(adbc_xptr_is_valid(adbc_xptr_move(stmt)))
+ expect_false(adbc_xptr_is_valid(stmt))
+
+ stream <- nanoarrow::basic_array_stream(list(1:5))
+ expect_true(adbc_xptr_is_valid(stream))
+ expect_true(adbc_xptr_is_valid(adbc_xptr_move(stream)))
+ expect_false(adbc_xptr_is_valid(stream))
+
+ expect_error(
+ adbc_xptr_is_valid(NULL),
+ "must inherit from one of"
+ )
+
+ expect_error(
+ adbc_xptr_move(NULL),
+ "must inherit from one of"
+ )
+})
diff --git a/r/adbcdrivermanager/tools/make-callentries.R
b/r/adbcdrivermanager/tools/make-callentries.R
index 88a4528..cf4d8bb 100644
--- a/r/adbcdrivermanager/tools/make-callentries.R
+++ b/r/adbcdrivermanager/tools/make-callentries.R
@@ -38,7 +38,7 @@ defs <- tibble(
str_remove("SEXP RAdbc[^\\(]+\\(") %>%
str_remove("\\)$") %>%
str_split("\\s*,\\s*") %>%
- map(~{if(identical(.x, "")) character(0) else .x}),
+ map(~{if(identical(.x, "") || identical(.x, "void")) character(0) else
.x}),
n_args = map(args, length)
)
@@ -74,3 +74,5 @@ stopifnot(str_detect(init, pattern))
init %>%
str_replace(pattern, header) %>%
write_file("src/init.c")
+
+system("clang-format -i src/init.c")