Arrow C 数据接口#
基本原理#
Apache Arrow 旨在成为一种通用的内存格式,用于表示表格(“列式”)数据。然而,一些项目可能面临艰难的选择:要么依赖于快速发展的项目(例如 Arrow C++ 库),要么不得不重新实现数据交换适配器,这可能需要大量的、冗余的开发工作。
Arrow C 数据接口定义了一组非常小、稳定的 C 定义,可以轻松地_复制_到任何项目的源代码中,并用于 Arrow 格式的列式数据交换。对于非 C/C++ 语言和运行时,将 C 定义转换为相应的 C FFI 声明应该几乎同样容易。
因此,应用程序和库可以使用 Arrow 内存,而不必使用 Arrow 库或重新发明轮子。开发人员可以选择与 Arrow _软件项目_紧密集成(受益于例如 Apache Arrow 的 C++ 或 Java 实现所提供的越来越多的功能,但需要付出依赖性的代价),或者只与 Arrow _格式_进行最小程度的集成。
目标#
公开 ABI 稳定的接口。
使第三方项目能够轻松实现支持(包括在足够的情况下提供部分支持),只需很少的初始投资。
允许在同一进程中运行的独立运行时和组件之间零拷贝共享 Arrow 数据。
与 Arrow 数组概念紧密匹配,以避免开发另一个编组层。
避免需要一对一的适配器层,例如 Java 和 Python 之间基于 JPype 的有限桥梁。
无需显式依赖(在编译时或运行时)Arrow 软件项目即可实现集成。
理想情况下,Arrow C 数据接口可以成为在运行时共享列式数据的低级_通用语言_,并将 Arrow 建立为列式处理生态系统中的通用构建块。
非目标#
公开模仿高级运行时(例如 C++、Java…)中可用操作的 C API。
不同进程之间的数据共享或存储持久性。
与 Arrow IPC 格式的比较#
C 数据接口相对于 IPC 格式的优点
不依赖 Flatbuffers。
无需缓冲区重组(数据已以逻辑 Arrow 格式公开)。
零拷贝设计。
易于从头重新实现。
最小的 C 定义,可以轻松复制到其他代码库中。
通过自定义释放回调进行资源生命周期管理。
IPC 格式相对于数据接口的优点
跨进程和机器工作。
允许数据存储和持久性。
作为一种可流式传输的格式,IPC 格式可以容纳更多功能(例如完整性检查、压缩……)。
不需要显式的 C 数据访问。
数据类型描述 - 格式字符串#
数据类型使用格式字符串进行描述。格式字符串仅编码有关顶级类型的信息;对于嵌套类型,子类型单独描述。此外,元数据编码在单独的字符串中。
格式字符串设计为易于解析,即使是从 C 语言也是如此。最常见的基元格式具有一字符格式字符串
格式字符串 |
Arrow 数据类型 |
备注 |
---|---|---|
|
空 |
|
|
布尔值 |
|
|
int8 |
|
|
uint8 |
|
|
int16 |
|
|
uint16 |
|
|
int32 |
|
|
uint32 |
|
|
int64 |
|
|
uint64 |
|
|
float16 |
|
|
float32 |
|
|
float64 |
格式字符串 |
Arrow 数据类型 |
备注 |
---|---|---|
|
二进制 |
|
|
大二进制 |
|
|
二进制视图 |
|
|
utf-8 字符串 |
|
|
大型 utf-8 字符串 |
|
|
utf-8 视图 |
|
|
decimal128 [精度 19,比例 10] |
|
|
十进制位宽 = NNN [精度 19,比例 10] |
|
|
固定宽度二进制 [42 字节] |
时间类型具有以 t
开头的多字符格式字符串
格式字符串 |
Arrow 数据类型 |
备注 |
---|---|---|
|
date32 [天] |
|
|
date64 [毫秒] |
|
|
time32 [秒] |
|
|
time32 [毫秒] |
|
|
time64 [微秒] |
|
|
time64 [纳秒] |
|
|
带时区的时间戳 [秒] “…” |
(1) |
|
带时区的时间戳 [毫秒] “…” |
(1) |
|
带时区的时间戳 [微秒] “…” |
(1) |
|
带时区的时间戳 [纳秒] “…” |
(1) |
|
持续时间 [秒] |
|
|
持续时间 [毫秒] |
|
|
持续时间 [微秒] |
|
|
持续时间 [纳秒] |
|
|
间隔 [月] |
|
|
间隔 [天,时间] |
|
|
间隔 [月、日、纳秒] |
字典编码类型没有特定的格式字符串。相反,基数组的格式字符串表示字典索引类型,并且可以从依赖字典数组中读取值类型(请参阅下面的“字典编码数组”)。
嵌套类型具有以 +
开头的多字符格式字符串。子字段的名称和类型从子数组中读取。
格式字符串 |
Arrow 数据类型 |
备注 |
---|---|---|
|
列表 |
|
|
大列表 |
|
|
列表视图 |
|
|
大列表视图 |
|
|
固定大小列表 [123 项] |
|
|
结构体 |
|
|
映射 |
(2) |
|
具有类型 ID I、J… 的密集联合 |
|
|
具有类型 ID I、J… 的稀疏联合 |
|
|
游程编码 |
(3) |
备注
时区字符串按原样附加在冒号字符
:
后面,不带任何引号。如果时区为空,则仍必须包含冒号:
。如 Arrow 列式格式中所指定,映射类型具有一个名为
entries
的子类型,它本身是一个(key, value)
的 2 子结构体类型。如 Arrow 列式格式中所指定,游程编码类型有两个子类型,第一个是(整数)
run_ends
,第二个是values
。
示例#
具有
int16
索引的字典编码decimal128(precision = 12, scale = 5)
数组的格式字符串为s
,其依赖字典数组的格式字符串为d:12,5
。list<uint64>
数组的格式字符串为+l
,其单个子级的格式字符串为L
。large_list_view<uint64>
数组的格式字符串为+vL
,其单个子级的格式字符串为L
。struct<ints: int32, floats: float32>
的格式字符串为+s
;它的两个子级的名称分别为ints
和floats
,格式字符串分别为i
和f
。map<string, float64>
数组的格式字符串为+m
;其单个子级的名称为entries
,格式字符串为+s
;它的两个孙级的名称分别为key
和value
,格式字符串分别为u
和g
。类型 ID 为
4, 5
的sparse_union<ints: int32, floats: float32>
的格式字符串为+us:4,5
;它的两个子级的名称分别为ints
和floats
,格式字符串分别为i
和f
。run_end_encoded<int32, float32>
的格式字符串为+r
;它的两个子级的名称分别为run_ends
和values
,格式字符串分别为i
和f
。
结构定义#
以下独立定义足以在您的项目中支持 Arrow C 数据接口。与 Arrow 项目的其余部分一样,它们在 Apache 许可证 2.0 下可用。
#ifndef ARROW_C_DATA_INTERFACE
#define ARROW_C_DATA_INTERFACE
#define ARROW_FLAG_DICTIONARY_ORDERED 1
#define ARROW_FLAG_NULLABLE 2
#define ARROW_FLAG_MAP_KEYS_SORTED 4
struct ArrowSchema {
// Array type description
const char* format;
const char* name;
const char* metadata;
int64_t flags;
int64_t n_children;
struct ArrowSchema** children;
struct ArrowSchema* dictionary;
// Release callback
void (*release)(struct ArrowSchema*);
// Opaque producer-specific data
void* private_data;
};
struct ArrowArray {
// Array data description
int64_t length;
int64_t null_count;
int64_t offset;
int64_t n_buffers;
int64_t n_children;
const void** buffers;
struct ArrowArray** children;
struct ArrowArray* dictionary;
// Release callback
void (*release)(struct ArrowArray*);
// Opaque producer-specific data
void* private_data;
};
#endif // ARROW_C_DATA_INTERFACE
注意
规范性保护符 ARROW_C_DATA_INTERFACE
旨在避免重复定义,即如果两个项目将 C 数据接口定义复制到它们自己的头文件中,而第三方项目包含了这两个项目的头文件。因此,复制这些定义时,务必保持此保护符原样不变。
ArrowSchema 结构#
ArrowSchema
结构描述了导出数组或记录批次的类型和元数据。它具有以下字段
-
const char *ArrowSchema.format#
必填。描述数据类型的以空字符结尾的 UTF8 编码字符串。如果数据类型是嵌套的,则子类型不会在此处编码,而是在
ArrowSchema.children
结构中编码。使用者可以选择不支持所有数据类型,但他们应记录此限制。
-
const char *ArrowSchema.name#
可选。字段或数组名称的以空字符结尾的 UTF8 编码字符串。这主要用于重建嵌套类型的子字段。
生产者可以选择不提供此信息,而使用者可以选择忽略它。如果省略,可以为 NULL 或空字符串。
-
const char *ArrowSchema.metadata#
可选。描述类型元数据的二进制字符串。如果数据类型是嵌套的,则子类型不会在此处编码,而是在
ArrowSchema.children
结构中编码。此字符串不是以空字符结尾的,而是遵循特定格式
int32: number of key/value pairs (noted N below) int32: byte length of key 0 key 0 (not null-terminated) int32: byte length of value 0 value 0 (not null-terminated) ... int32: byte length of key N - 1 key N - 1 (not null-terminated) int32: byte length of value N - 1 value N - 1 (not null-terminated)
整数以本机字节序存储。例如,元数据
[('key1', 'value1')]
在小端机器上编码为\x01\x00\x00\x00\x04\x00\x00\x00key1\x06\x00\x00\x00value1
在大端机器上,相同的示例将编码为
\x00\x00\x00\x01\x00\x00\x00\x04key1\x00\x00\x00\x06value1
如果省略,此字段必须为 NULL(而不是空字符串)。
使用者可以选择忽略此信息。
-
int64_t ArrowSchema.flags#
可选。丰富类型描述的标志位字段。它的值是通过将标志值进行 OR 运算计算得出的。可以使用以下标志
ARROW_FLAG_NULLABLE
:此字段在语义上是否可为空(无论它是否实际具有空值)。ARROW_FLAG_DICTIONARY_ORDERED
:对于字典编码类型,字典索引的顺序是否在语义上有意义。ARROW_FLAG_MAP_KEYS_SORTED
:对于 map 类型,每个 map 值中的键是否已排序。
如果省略,则必须为 0。
使用者可以选择忽略部分或全部标志。即使这样,他们也应该保留此值,以便将其信息传播给自己的使用者。
-
int64_t ArrowSchema.n_children#
必填。此类型具有的子类型数量。
-
ArrowSchema **ArrowSchema.children#
可选。指向此类型的每个子类型的指针的 C 数组。必须有
ArrowSchema.n_children
个指针。仅当
ArrowSchema.n_children
为 0 时,才可为 NULL。
-
ArrowSchema *ArrowSchema.dictionary#
可选。指向字典值类型的指针。
如果 ArrowSchema 表示字典编码类型,则必须存在。否则必须为 NULL。
-
void (*ArrowSchema.release)(struct ArrowSchema*)#
必填。指向生产者提供的释放回调的指针。
有关内存管理和释放回调语义,请参见下文。
-
void *ArrowSchema.private_data#
可选。指向生产者提供的私有数据的不透明指针。
使用者不得处理此成员。此成员的生命周期由生产者处理,尤其由释放回调处理。
ArrowArray 结构#
ArrowArray
描述导出数组或记录批次的数据。为了解释 ArrowArray
结构的类型,必须事先知道数组类型或记录批处理架构。这可以通过约定完成(例如,始终生成相同数据类型的生产者 API),或者通过传递一个 ArrowSchema
来完成。
它具有以下字段
-
int64_t ArrowArray.length#
必填。数组的逻辑长度(即其项目数)。
-
int64_t ArrowArray.null_count#
必填。数组中空项目的数量。如果尚未计算,则可以为 -1。
-
int64_t ArrowArray.offset#
必填。数组内部的逻辑偏移量(即从缓冲区的物理起始位置开始的项目数)。必须为 0 或正数。
生产者可以指定他们只会生成偏移量为 0 的数组,以简化使用者代码的实现。使用者可以选择不支持非 0 偏移数组,但他们应记录此限制。
-
int64_t ArrowArray.n_buffers#
必填。支持此数组的物理缓冲区数量。缓冲区的数量是数据类型的函数,如 列式格式规范 中所述,但二进制或 utf-8 视图类型除外,它比列式格式规范多一个缓冲区(请参阅 二进制视图数组)。
不包括子数组的缓冲区。
-
const void **ArrowArray.buffers#
必填。指向支持此数组的每个物理缓冲区起始位置的指针的 C 数组。每个 void* 指针都是连续缓冲区的物理起始位置。必须有
ArrowArray.n_buffers
个指针。生产者必须确保每个连续缓冲区足够大,以表示根据 列式格式规范 编码的 length + offset 值。
建议(但非必需)缓冲区的内存地址至少根据它们包含的原始数据类型对齐。使用者可以选择不支持未对齐的内存。
缓冲区指针只能在两种情况下为空
对于空位图缓冲区,如果
ArrowArray.null_count
为 0;对于任何缓冲区,如果相应缓冲区的字节大小为 0。
不包括子数组的缓冲区。
-
ArrowArray **ArrowArray.children#
可选的。指向此数组的每个子数组的指针的 C 数组。必须有
ArrowArray.n_children
个指针。仅当
ArrowArray.n_children
为 0 时,可以为 NULL。
-
ArrowArray *ArrowArray.dictionary#
可选的。指向字典值底层数组的指针。
如果 ArrowArray 表示字典编码的数组,则必须存在。否则必须为 NULL。
-
void (*ArrowArray.release)(struct ArrowArray*)#
必填。指向生产者提供的释放回调的指针。
有关内存管理和释放回调语义,请参见下文。
-
void *ArrowArray.private_data#
可选。指向生产者提供的私有数据的不透明指针。
使用者不得处理此成员。此成员的生命周期由生产者处理,尤其由释放回调处理。
字典编码数组#
对于字典编码的数组,ArrowSchema.format
字符串编码_索引_类型。字典_值_类型可以从 ArrowSchema.dictionary
结构中读取。
ArrowArray
结构也是如此:父结构指向索引数据,而 ArrowArray.dictionary
指向字典值数组。
扩展数组#
对于扩展数组,ArrowSchema.format
字符串编码_存储_类型。扩展类型的信息在 ArrowSchema.metadata
字符串中编码,类似于 IPC 格式。具体来说,元数据键 ARROW:extension:name
编码扩展类型名称,元数据键 ARROW:extension:metadata
编码扩展类型的特定于实现的序列化(对于参数化扩展类型)。
从扩展数组导出的 ArrowArray
结构 simplement 指向扩展数组的存储数据。
二进制视图数组#
对于二进制或 utf-8 视图数组,会附加一个额外的缓冲区,该缓冲区将每个可变数据缓冲区的长度存储为 int64_t
。此缓冲区是必需的,因为这些缓冲区长度无法从二进制或 utf-8 视图类型数组中的其他数据中轻松提取。
语义#
内存管理#
ArrowSchema
和 ArrowArray
结构遵循相同的内存管理约定。下面的术语_“基结构”_指的是在生产者和消费者之间传递的 ArrowSchema
或 ArrowArray
,而不是其任何子结构。
成员分配#
基结构旨在由使用者在堆栈或堆上分配。在这种情况下,生产者 API 应接受指向使用者分配的结构的指针。
但是,结构指向的任何数据都必须由生产者分配和维护。这包括格式和元数据字符串、缓冲区和子指针数组等。
因此,使用者不得尝试干扰生产者对这些成员生命周期的处理。使用者影响数据生命周期的唯一方法是调用基结构的 release
回调。
已释放的结构#
已释放的结构通过将其 release
回调设置为 NULL 来指示。在读取和解释结构的数据之前,使用者应该检查 NULL 释放回调并进行相应处理(可能通过报错)。
释放回调语义 - 对于消费者#
当消费者不再使用基结构时,必须调用其释放回调,但不得调用其任何子级的释放回调(包括可选字典)。生产者负责释放子级。
在任何情况下,使用者在调用其释放回调后都不得再尝试访问基结构——包括任何关联的数据,例如其子级。
释放回调语义 - 对于生产者#
如果生产者需要用于生命周期处理的附加信息(例如,C++ 生产者可能希望将 shared_ptr
用于数组和缓冲区生命周期),则必须使用 private_data
成员来定位所需的簿记信息。
释放回调不得假设该结构将位于与其最初生成时相同的内存位置。使用者可以自由地移动结构(请参阅“移动数组”)。
释放回调必须遍历所有子结构(包括可选字典)并调用它们自己的释放回调。
释放回调必须释放结构直接拥有的任何数据区域(例如缓冲区和子成员)。
释放回调必须通过将其 release
成员设置为 NULL 来将结构标记为已释放。
以下是实现释放回调的良好起点,其中 TODO 区域必须填充特定于生产者的释放代码
static void ReleaseExportedArray(struct ArrowArray* array) {
// This should not be called on already released array
assert(array->release != NULL);
// Release children
for (int64_t i = 0; i < array->n_children; ++i) {
struct ArrowArray* child = array->children[i];
if (child->release != NULL) {
child->release(child);
assert(child->release == NULL);
}
}
// Release dictionary
struct ArrowArray* dict = array->dictionary;
if (dict != NULL && dict->release != NULL) {
dict->release(dict);
assert(dict->release == NULL);
}
// TODO here: release and/or deallocate all data directly owned by
// the ArrowArray struct, such as the private_data.
// Mark array released
array->release = NULL;
}
移动数组#
消费者可以通过按位复制或浅层成员复制来_移动_ ArrowArray
结构。然后它必须将源结构标记为已释放(有关如何操作,请参阅上面的“已释放结构”),但_无需_调用释放回调。这可确保在任何给定时间只有一个活动的结构副本,并且生命周期已正确传达给生产者。
像往常一样,当不再需要目标结构时,将调用其释放回调。
移动子数组#
也可以移动一个或多个子数组,但父 ArrowArray
结构必须立即释放,因为它将不再指向有效的子数组。
这样做的主要用例是只保留一部分子数组(例如,如果您只对数据的某些列感兴趣),同时释放其他子数组。
注意
为了使移动正常工作,ArrowArray
结构必须可轻松重定位。因此,ArrowArray
结构内部的指针成员(包括 private_data
)不得指向结构本身内部。此外,指向该结构的外部指针不得由生产者单独存储。相反,生产者必须使用 private_data
成员来记住任何必要的簿记信息。
记录批次#
记录批次可以简单地视为等效的结构数组。在这种情况下,顶级 ArrowSchema
的元数据可以用作记录批次的架构级元数据。
可变性#
生产者和消费者都**应该**将导出的数据(即可通过 ArrowArray
的 buffers
成员访问到的数据)视为不可变的,因为如果一方修改数据,另一方可能会看到不一致的数据。
用例示例#
一个 C++ 数据库引擎希望提供以 Arrow 格式返回结果的选项,但不强制自身依赖 Arrow 软件库。借助 Arrow C 数据接口,引擎可以让调用者传递一个指向 ArrowArray
结构的指针,并用下一批结果填充它。
它可以在不包含 Arrow C++ 头文件或不链接 Arrow DLL 的情况下完成此操作。此外,数据库引擎的 C API 可以通过例如 C FFI 层,使其他了解 Arrow C 数据接口的运行时和库受益。
C 生产者示例#
导出一个简单的 int32
数组#
导出一个没有元数据的非空 int32
类型。在这种情况下,所有 ArrowSchema
成员都指向静态分配的数据,因此释放回调函数很简单。
static void release_int32_type(struct ArrowSchema* schema) {
// Mark released
schema->release = NULL;
}
void export_int32_type(struct ArrowSchema* schema) {
*schema = (struct ArrowSchema) {
// Type description
.format = "i",
.name = "",
.metadata = NULL,
.flags = 0,
.n_children = 0,
.children = NULL,
.dictionary = NULL,
// Bookkeeping
.release = &release_int32_type
};
}
导出一个使用 C-malloc() 分配的与 Arrow 数组类型相同的数组,通过释放回调函数将所有权转移给消费者。
static void release_int32_array(struct ArrowArray* array) {
assert(array->n_buffers == 2);
// Free the buffers and the buffers array
free((void *) array->buffers[1]);
free(array->buffers);
// Mark released
array->release = NULL;
}
void export_int32_array(const int32_t* data, int64_t nitems,
struct ArrowArray* array) {
// Initialize primitive fields
*array = (struct ArrowArray) {
// Data description
.length = nitems,
.offset = 0,
.null_count = 0,
.n_buffers = 2,
.n_children = 0,
.children = NULL,
.dictionary = NULL,
// Bookkeeping
.release = &release_int32_array
};
// Allocate list of buffers
array->buffers = (const void**) malloc(sizeof(void*) * array->n_buffers);
assert(array->buffers != NULL);
array->buffers[0] = NULL; // no nulls, null bitmap can be omitted
array->buffers[1] = data;
}
导出一个 struct<float32, utf8>
数组#
将数组类型导出为具有 C-malloc() 分配的子项的 ArrowSchema
。
static void release_malloced_type(struct ArrowSchema* schema) {
int i;
for (i = 0; i < schema->n_children; ++i) {
struct ArrowSchema* child = schema->children[i];
if (child->release != NULL) {
child->release(child);
}
free(child);
}
free(schema->children);
// Mark released
schema->release = NULL;
}
void export_float32_utf8_type(struct ArrowSchema* schema) {
struct ArrowSchema* child;
//
// Initialize parent type
//
*schema = (struct ArrowSchema) {
// Type description
.format = "+s",
.name = "",
.metadata = NULL,
.flags = 0,
.n_children = 2,
.dictionary = NULL,
// Bookkeeping
.release = &release_malloced_type
};
// Allocate list of children types
schema->children = malloc(sizeof(struct ArrowSchema*) * schema->n_children);
//
// Initialize child type #0
//
child = schema->children[0] = malloc(sizeof(struct ArrowSchema));
*child = (struct ArrowSchema) {
// Type description
.format = "f",
.name = "floats",
.metadata = NULL,
.flags = ARROW_FLAG_NULLABLE,
.n_children = 0,
.dictionary = NULL,
.children = NULL,
// Bookkeeping
.release = &release_malloced_type
};
//
// Initialize child type #1
//
child = schema->children[1] = malloc(sizeof(struct ArrowSchema));
*child = (struct ArrowSchema) {
// Type description
.format = "u",
.name = "strings",
.metadata = NULL,
.flags = ARROW_FLAG_NULLABLE,
.n_children = 0,
.dictionary = NULL,
.children = NULL,
// Bookkeeping
.release = &release_malloced_type
};
}
将使用 C-malloc() 分配的、符合 Arrow 布局的数组导出为 Arrow 结构体数组,并将所有权转移给消费者。
static void release_malloced_array(struct ArrowArray* array) {
int i;
// Free children
for (i = 0; i < array->n_children; ++i) {
struct ArrowArray* child = array->children[i];
if (child->release != NULL) {
child->release(child);
}
free(child);
}
free(array->children);
// Free buffers
for (i = 0; i < array->n_buffers; ++i) {
free((void *) array->buffers[i]);
}
free(array->buffers);
// Mark released
array->release = NULL;
}
void export_float32_utf8_array(
int64_t nitems,
const uint8_t* float32_nulls, const float* float32_data,
const uint8_t* utf8_nulls, const int32_t* utf8_offsets, const uint8_t* utf8_data,
struct ArrowArray* array) {
struct ArrowArray* child;
//
// Initialize parent array
//
*array = (struct ArrowArray) {
// Data description
.length = nitems,
.offset = 0,
.null_count = 0,
.n_buffers = 1,
.n_children = 2,
.dictionary = NULL,
// Bookkeeping
.release = &release_malloced_array
};
// Allocate list of parent buffers
array->buffers = malloc(sizeof(void*) * array->n_buffers);
array->buffers[0] = NULL; // no nulls, null bitmap can be omitted
// Allocate list of children arrays
array->children = malloc(sizeof(struct ArrowArray*) * array->n_children);
//
// Initialize child array #0
//
child = array->children[0] = malloc(sizeof(struct ArrowArray));
*child = (struct ArrowArray) {
// Data description
.length = nitems,
.offset = 0,
.null_count = -1,
.n_buffers = 2,
.n_children = 0,
.dictionary = NULL,
.children = NULL,
// Bookkeeping
.release = &release_malloced_array
};
child->buffers = malloc(sizeof(void*) * child->n_buffers);
child->buffers[0] = float32_nulls;
child->buffers[1] = float32_data;
//
// Initialize child array #1
//
child = array->children[1] = malloc(sizeof(struct ArrowArray));
*child = (struct ArrowArray) {
// Data description
.length = nitems,
.offset = 0,
.null_count = -1,
.n_buffers = 3,
.n_children = 0,
.dictionary = NULL,
.children = NULL,
// Bookkeeping
.release = &release_malloced_array
};
child->buffers = malloc(sizeof(void*) * child->n_buffers);
child->buffers[0] = utf8_nulls;
child->buffers[1] = utf8_offsets;
child->buffers[2] = utf8_data;
}
为什么有两个不同的结构?#
在许多情况下,相同的类型或模式描述适用于多个(可能很短)的数据批次。为了避免为每个批次导出和导入类型描述的成本,可以在生产者和消费者之间的对话开始时单独传递一次 ArrowSchema
。
在其他情况下,数据类型由生产者 API 固定,可能根本不需要进行通信。
但是,如果生产者专注于一次性数据交换,则可以在同一个 API 调用中传递 ArrowSchema
和 ArrowArray
结构。
更新此规范#
一旦此规范在官方 Arrow 版本中得到支持,C ABI 将被冻结。这意味着 ArrowSchema
和 ArrowArray
结构定义不应以任何方式更改,包括添加新成员。
允许向后兼容的更改,例如新的 ArrowSchema.flags
值或 ArrowSchema.format
字符串的扩展可能性。
任何不兼容的更改都应作为新规范的一部分,例如“Arrow C 数据接口 v2”。
灵感#
Arrow C 数据接口的灵感来自 Python 缓冲区协议,该协议已被证明非常成功地允许各种 Python 库交换数值数据,而无需了解彼此且几乎没有适配成本。
特定于语言的协议#
某些语言可能会在 Arrow C 数据接口之上定义其他协议。