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..3523beaf5215 100644 --- a/r/tests/testthat/test-bridge.R +++ b/r/tests/testthat/test-bridge.R @@ -81,3 +81,29 @@ 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$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) +})