From 19b05691d24df3cdb0a3c69f81d97d6e9d7e4d4a Mon Sep 17 00:00:00 2001 From: singhpratech Date: Tue, 8 Sep 2026 08:26:36 -0400 Subject: [PATCH 1/2] GH-39793: [R] Export the C Data Interface allocators and document $export_to_c() / $import_from_c() allocate_arrow_schema(), delete_arrow_schema(), allocate_arrow_array(), delete_arrow_array(), allocate_arrow_array_stream() and delete_arrow_array_stream() are now exported, with a help page (?c-data-interface) that shows the round trip, and the Array methods list names $export_to_c() and $import_from_c(). Two tests cover the exports and an Array round trip through the exported functions. Closes #39793. --- r/DESCRIPTION | 1 + r/NAMESPACE | 6 +++ r/NEWS.md | 7 ++++ r/R/array.R | 5 +++ r/R/c-data-interface.R | 69 ++++++++++++++++++++++++++++++++++ r/man/array-class.Rd | 5 +++ r/man/c-data-interface.Rd | 51 +++++++++++++++++++++++++ r/tests/testthat/test-bridge.R | 23 ++++++++++++ 8 files changed, 167 insertions(+) create mode 100644 r/R/c-data-interface.R create mode 100644 r/man/c-data-interface.Rd diff --git a/r/DESCRIPTION b/r/DESCRIPTION index 12494b49cc8e..f8252bf04fff 100644 --- a/r/DESCRIPTION +++ b/r/DESCRIPTION @@ -85,6 +85,7 @@ Collate: 'arrow-package.R' 'arrow-tabular.R' 'buffer.R' + 'c-data-interface.R' 'chunked-array.R' 'io.R' 'compression.R' diff --git a/r/NAMESPACE b/r/NAMESPACE index a96b77fdda89..293cce6a1969 100644 --- a/r/NAMESPACE +++ b/r/NAMESPACE @@ -278,6 +278,9 @@ export(TimestampParser) export(Type) export(UnionDataset) export(all_of) +export(allocate_arrow_array) +export(allocate_arrow_array_stream) +export(allocate_arrow_schema) export(arrow_array) export(arrow_info) export(arrow_table) @@ -324,6 +327,9 @@ export(decimal256) export(decimal32) export(decimal64) export(default_memory_pool) +export(delete_arrow_array) +export(delete_arrow_array_stream) +export(delete_arrow_schema) export(dictionary) export(duration) export(ends_with) diff --git a/r/NEWS.md b/r/NEWS.md index 37ed6c6de66a..6ad413ca9ee6 100644 --- a/r/NEWS.md +++ b/r/NEWS.md @@ -19,6 +19,13 @@ # arrow 25.0.1.9000 +## Minor improvements and fixes + +- `allocate_arrow_schema()`, `delete_arrow_schema()`, `allocate_arrow_array()`, + `delete_arrow_array()`, `allocate_arrow_array_stream()` and `delete_arrow_array_stream()` + are now exported and documented, so a package can use `$export_to_c()` and + `$import_from_c()` without reaching into the namespace (#39793). + # arrow 25.0.1 ## Minor improvements and fixes diff --git a/r/R/array.R b/r/R/array.R index f2b34fc03f8b..b175faa1a74a 100644 --- a/r/R/array.R +++ b/r/R/array.R @@ -82,6 +82,11 @@ #' - `$View(type)`: Construct a zero-copy view of this array with the given type. #' - `$Validate()` : Perform any validation checks to determine obvious inconsistencies #' within the array's internal data. This can be an expensive check, potentially `O(length)` +#' - `$export_to_c(array_ptr, schema_ptr)`: Fill an `ArrowArray` and an `ArrowSchema` struct +#' from this array for the Arrow C Data Interface; the pointers come from +#' [allocate_arrow_array()] and [allocate_arrow_schema()]. +#' - `Array$import_from_c(array_ptr, schema_ptr)`: Build an `Array` from structs +#' another library filled (a class method, not an instance method). #' #' @rdname array-class #' @examples diff --git a/r/R/c-data-interface.R b/r/R/c-data-interface.R new file mode 100644 index 000000000000..c3c5196fe4e8 --- /dev/null +++ b/r/R/c-data-interface.R @@ -0,0 +1,69 @@ +# 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. + +#' Allocate and free Arrow C Data Interface structs +#' +#' The [Arrow C Data Interface](https://arrow.apache.org/docs/format/CDataInterface.html) +#' passes a column between two libraries in the same process through a pair of +#' C structs, `ArrowSchema` and `ArrowArray`, and a stream of batches through +#' `ArrowArrayStream`. These functions allocate one of those structs and return an +#' external pointer to it; the matching `delete_*()` function frees the struct. +#' +#' The pointers are what the `$export_to_c()` and `$import_from_c()` methods take: +#' `Array$export_to_c(array_ptr, schema_ptr)` fills the two structs from an +#' [Array] (likewise for [RecordBatch], and `$export_to_c(schema_ptr)` for a +#' [Schema], [DataType] or [Field]), and `Array$import_from_c(array_ptr, schema_ptr)` +#' builds an [Array] from structs another library filled. A consumer that has +#' imported a struct releases it through the struct's own release callback; call +#' `delete_*()` only to free the allocation itself, after the other side is done +#' with it. +#' +#' @return `allocate_*()` return an external pointer to a zero-initialised struct. +#' `delete_*()` return `NULL`, invisibly. +#' @name c-data-interface +#' @rdname c-data-interface +#' @aliases allocate_arrow_schema delete_arrow_schema allocate_arrow_array +#' delete_arrow_array allocate_arrow_array_stream delete_arrow_array_stream +#' @usage +#' allocate_arrow_schema() +#' delete_arrow_schema(ptr) +#' allocate_arrow_array() +#' delete_arrow_array(ptr) +#' allocate_arrow_array_stream() +#' delete_arrow_array_stream(ptr) +#' @param ptr an external pointer returned by the matching `allocate_*()` function +#' @examples +#' array_ptr <- allocate_arrow_array() +#' schema_ptr <- allocate_arrow_schema() +#' Array$create(c(1, 2, 3))$export_to_c(array_ptr, schema_ptr) +#' Array$import_from_c(array_ptr, schema_ptr) +#' delete_arrow_array(array_ptr) +#' delete_arrow_schema(schema_ptr) +#' @export allocate_arrow_schema +#' @export delete_arrow_schema +#' @export allocate_arrow_array +#' @export delete_arrow_array +#' @export allocate_arrow_array_stream +#' @export delete_arrow_array_stream +NULL +# The functions themselves are generated into arrowExports.R from the C++ side. +# +# A note for consumers that check buffer alignment: Array$create() on an R double or +# integer vector borrows the vector's memory rather than copying it, so the values buffer +# an export hands over starts where R's data does, 48 bytes into R's allocation. Buffers +# arrow allocates itself (a cast to another type, concat_arrays(), anything read from +# a file) are aligned by arrow's own allocator. diff --git a/r/man/array-class.Rd b/r/man/array-class.Rd index c10e9de96708..7cf62a4c0ae0 100644 --- a/r/man/array-class.Rd +++ b/r/man/array-class.Rd @@ -81,6 +81,11 @@ data in the array to change its type. \item \verb{$View(type)}: Construct a zero-copy view of this array with the given type. \item \verb{$Validate()} : Perform any validation checks to determine obvious inconsistencies within the array's internal data. This can be an expensive check, potentially \code{O(length)} +\item \verb{$export_to_c(array_ptr, schema_ptr)}: Fill an \code{ArrowArray} and an \code{ArrowSchema} struct +from this array for the Arrow C Data Interface; the pointers come from +\code{\link[=allocate_arrow_array]{allocate_arrow_array()}} and \code{\link[=allocate_arrow_schema]{allocate_arrow_schema()}}. +\item \code{Array$import_from_c(array_ptr, schema_ptr)}: Build an \code{Array} from structs +another library filled (a class method, not an instance method). } } diff --git a/r/man/c-data-interface.Rd b/r/man/c-data-interface.Rd new file mode 100644 index 000000000000..426b80d71239 --- /dev/null +++ b/r/man/c-data-interface.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/c-data-interface.R +\name{c-data-interface} +\alias{c-data-interface} +\alias{allocate_arrow_schema} +\alias{delete_arrow_schema} +\alias{allocate_arrow_array} +\alias{delete_arrow_array} +\alias{allocate_arrow_array_stream} +\alias{delete_arrow_array_stream} +\title{Allocate and free Arrow C Data Interface structs} +\usage{ +allocate_arrow_schema() +delete_arrow_schema(ptr) +allocate_arrow_array() +delete_arrow_array(ptr) +allocate_arrow_array_stream() +delete_arrow_array_stream(ptr) +} +\arguments{ +\item{ptr}{an external pointer returned by the matching \verb{allocate_*()} function} +} +\value{ +\verb{allocate_*()} return an external pointer to a zero-initialised struct. +\verb{delete_*()} return \code{NULL}, invisibly. +} +\description{ +The \href{https://arrow.apache.org/docs/format/CDataInterface.html}{Arrow C Data Interface} +passes a column between two libraries in the same process through a pair of +C structs, \code{ArrowSchema} and \code{ArrowArray}, and a stream of batches through +\code{ArrowArrayStream}. These functions allocate one of those structs and return an +external pointer to it; the matching \verb{delete_*()} function frees the struct. +} +\details{ +The pointers are what the \verb{$export_to_c()} and \verb{$import_from_c()} methods take: +\code{Array$export_to_c(array_ptr, schema_ptr)} fills the two structs from an +\link{Array} (likewise for \link{RecordBatch}, and \verb{$export_to_c(schema_ptr)} for a +\link{Schema}, \link{DataType} or \link{Field}), and \code{Array$import_from_c(array_ptr, schema_ptr)} +builds an \link{Array} from structs another library filled. A consumer that has +imported a struct releases it through the struct's own release callback; call +\verb{delete_*()} only to free the allocation itself, after the other side is done +with it. +} +\examples{ +array_ptr <- allocate_arrow_array() +schema_ptr <- allocate_arrow_schema() +Array$create(c(1, 2, 3))$export_to_c(array_ptr, schema_ptr) +Array$import_from_c(array_ptr, schema_ptr) +delete_arrow_array(array_ptr) +delete_arrow_schema(schema_ptr) +} diff --git a/r/tests/testthat/test-bridge.R b/r/tests/testthat/test-bridge.R index e6bb517f6fce..920d70660441 100644 --- a/r/tests/testthat/test-bridge.R +++ b/r/tests/testthat/test-bridge.R @@ -81,3 +81,26 @@ test_that("Pointer wrapper errors for unknown object", { "Can't parse 'this is not an integer'" ) }) + +test_that("the C Data Interface allocators are exported", { + exported <- getNamespaceExports("arrow") + for (f in c( + "allocate_arrow_schema", "delete_arrow_schema", + "allocate_arrow_array", "delete_arrow_array", + "allocate_arrow_array_stream", "delete_arrow_array_stream" + )) { + expect_true(f %in% exported, label = paste(f, "is exported")) + } +}) + +test_that("an Array round-trips through the exported C Data Interface functions", { + array_ptr <- arrow::allocate_arrow_array() + schema_ptr <- arrow::allocate_arrow_schema() + on.exit({ + arrow::delete_arrow_array(array_ptr) + arrow::delete_arrow_schema(schema_ptr) + }) + a <- Array(c(1.5, NA, 3)) + a(array_ptr, schema_ptr) + expect_equal(Array(array_ptr, schema_ptr), a) +}) From 49038982792da5d39d4ef1f776dbf4821702f54a Mon Sep 17 00:00:00 2001 From: singhpratech Date: Tue, 8 Sep 2026 10:23:45 -0400 Subject: [PATCH 2/2] GH-39793: [R] Fix the round-trip test's method calls and the vector formatting --- r/tests/testthat/test-bridge.R | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/r/tests/testthat/test-bridge.R b/r/tests/testthat/test-bridge.R index 920d70660441..3523beaf5215 100644 --- a/r/tests/testthat/test-bridge.R +++ b/r/tests/testthat/test-bridge.R @@ -85,9 +85,12 @@ test_that("Pointer wrapper errors for unknown object", { test_that("the C Data Interface allocators are exported", { exported <- getNamespaceExports("arrow") for (f in c( - "allocate_arrow_schema", "delete_arrow_schema", - "allocate_arrow_array", "delete_arrow_array", - "allocate_arrow_array_stream", "delete_arrow_array_stream" + "allocate_arrow_schema", + "delete_arrow_schema", + "allocate_arrow_array", + "delete_arrow_array", + "allocate_arrow_array_stream", + "delete_arrow_array_stream" )) { expect_true(f %in% exported, label = paste(f, "is exported")) } @@ -100,7 +103,7 @@ test_that("an Array round-trips through the exported C Data Interface functions" arrow::delete_arrow_array(array_ptr) arrow::delete_arrow_schema(schema_ptr) }) - a <- Array(c(1.5, NA, 3)) - a(array_ptr, schema_ptr) - expect_equal(Array(array_ptr, schema_ptr), a) + a <- Array$create(c(1.5, NA, 3)) + a$export_to_c(array_ptr, schema_ptr) + expect_equal(Array$import_from_c(array_ptr, schema_ptr), a) })