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 数组概念,避免开发另一个编组层。
避免需要一对一的适配层,例如基于 JPype 的 Java 和 Python 之间的有限桥接。
无需显式依赖(编译时或运行时)Arrow 软件项目即可实现集成。
理想情况下,Arrow C 数据接口可以成为在运行时共享列式数据的低级*通用语*,并确立 Arrow 作为列式处理生态系统中的通用构建块地位。
非目标#
暴露一个模仿高级运行时(如 C++, Java...)中可用操作的 C API。
不同进程间的数据共享或存储持久化。
与 Arrow IPC 格式的比较#
C 数据接口相对于 IPC 格式的优点
不依赖 Flatbuffers。
无需缓冲区重组(数据已按逻辑 Arrow 格式暴露)。
设计上实现零拷贝。
易于从头实现。
精简的 C 定义,易于复制到其他代码库。
通过自定义释放回调函数进行资源生命周期管理。
IPC 格式相对于数据接口的优点
跨进程和跨机器工作。
支持数据存储和持久化。
作为可流式传输的格式,IPC 格式有空间组合更多功能(例如完整性检查、压缩等)。
无需显式的 C 数据访问。
数据类型描述 – 格式字符串#
数据类型使用格式字符串描述。格式字符串仅编码关于顶层类型的信息;对于嵌套类型,子类型单独描述。此外,元数据编码在单独的字符串中。
格式字符串设计得易于解析,即使是从像 C 这样的语言中。最常见的原始格式使用单字符格式字符串。
格式字符串 |
Arrow 数据类型 |
说明 |
---|---|---|
|
null (空) |
|
|
boolean (布尔) |
|
|
int8 (8位整型) |
|
|
uint8 (8位无符号整型) |
|
|
int16 (16位整型) |
|
|
uint16 (16位无符号整型) |
|
|
int32 (32位整型) |
|
|
uint32 (32位无符号整型) |
|
|
int64 (64位整型) |
|
|
uint64 (64位无符号整型) |
|
|
float16 (16位浮点型) |
|
|
float32 (32位浮点型) |
|
|
float64 (64位浮点型) |
格式字符串 |
Arrow 数据类型 |
说明 |
---|---|---|
|
binary (二进制) |
|
|
large binary (大二进制) |
|
|
binary view (二进制视图) |
|
|
utf-8 string (utf-8 字符串) |
|
|
large utf-8 string (大 utf-8 字符串) |
|
|
utf-8 view (utf-8 视图) |
|
|
decimal128 [精度 19, 刻度 10] |
|
|
decimal 位宽 = NNN [精度 19, 刻度 10] |
|
|
fixed-width binary [42 字节] |
时间类型使用以 t
开头的多字符格式字符串。
格式字符串 |
Arrow 数据类型 |
说明 |
---|---|---|
|
date32 [天] |
|
|
date64 [毫秒] |
|
|
time32 [秒] |
|
|
time32 [毫秒] |
|
|
time64 [微秒] |
|
|
time64 [纳秒] |
|
|
timestamp [秒] 带时区 “…” |
(1) |
|
timestamp [毫秒] 带时区 “…” |
(1) |
|
timestamp [微秒] 带时区 “…” |
(1) |
|
timestamp [纳秒] 带时区 “…” |
(1) |
|
duration [秒] |
|
|
duration [毫秒] |
|
|
duration [微秒] |
|
|
duration [纳秒] |
|
|
interval [月] |
|
|
interval [天, 时间] |
|
|
interval [月, 天, 纳秒] |
字典编码类型没有特定的格式字符串。相反,基础数组的格式字符串表示字典索引类型,并且可以从依赖的字典数组中读取值类型(参见下文“字典编码数组”)。
嵌套类型使用以 +
开头的多字符格式字符串。子字段的名称和类型从子数组中读取。
格式字符串 |
Arrow 数据类型 |
说明 |
---|---|---|
|
list (列表) |
|
|
large list (大列表) |
|
|
list-view (列表视图) |
|
|
large list-view (大列表视图) |
|
|
fixed-sized list [123 项] |
|
|
struct (结构体) |
|
|
map (映射) |
(2) |
|
dense union 带类型 ID I,J… |
|
|
sparse union 带类型 ID I,J… |
|
|
run-end encoded (行程结束编码) |
(3) |
说明
时区字符串在冒号
:
字符后按原样附加,不带任何引号。如果时区为空,冒号:
仍须包含。如 Arrow 列式格式中指定,map 类型有一个名为
entries
的单个子类型,该子类型本身是包含两个子字段(key, value)
的结构体类型。如 Arrow 列式格式中指定,行程结束编码类型有两个子字段,第一个是 (整型)
run_ends
,第二个是values
。
示例#
一个字典编码的
decimal128(precision = 12, scale = 5)
数组,使用int16
索引,其格式字符串为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
;该子字段(即entries
)的两个子字段名称分别为key
和value
,格式字符串分别为u
和g
。一个
sparse_union<ints: int32, floats: float32>
,类型 ID 为4, 5
,格式字符串为+us:4,5
;其两个子字段名称分别为ints
和floats
,格式字符串分别为i
和f
。一个
run_end_encoded<int32, float32>
的格式字符串为+r
;其两个子字段名称分别为run_ends
和values
,格式字符串分别为i
和f
。
结构体定义#
以下独立的定义足以支持您项目中的 Arrow C 数据接口。与 Arrow 项目的其余部分一样,它们根据 Apache License 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#
必须的。描述数据类型的以 null 终止的 UTF8 编码字符串。如果数据类型是嵌套的,子类型不在这里编码,而是在
ArrowSchema.children
结构体中编码。消费者 可以 选择不支持所有数据类型,但他们应该记录此限制。
-
const char *ArrowSchema.name#
可选的。字段或数组名称的以 null 终止的 UTF8 编码字符串。这主要用于重建嵌套类型的子字段。
生产者 可以 选择不提供此信息,消费者 可以 选择忽略它。如果省略, 可以 为 NULL 或空字符串。
-
const char *ArrowSchema.metadata#
可选的。描述类型元数据的二进制字符串。如果数据类型是嵌套的,子类型不在这里编码,而是在
ArrowSchema.children
结构体中编码。此字符串不是以 null 终止的,而是遵循特定格式:
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#
可选的。丰富类型描述的标志位字段。其值通过对标志值进行按位或计算得出。提供以下标志:
ARROW_FLAG_NULLABLE
: 此字段是否在语义上可为空(无论它实际上是否包含 null 值)。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
结构体,数组类型或记录批次 schema 必须已知。这可以通过约定实现(例如,始终产生相同数据类型的生产者 API),或通过在旁边传递一个 ArrowSchema
来实现。
它具有以下字段:
-
int64_t ArrowArray.length#
必须的。数组的逻辑长度(即其项的数量)。
-
int64_t ArrowArray.null_count#
必须的。数组中 null 项的数量。如果尚未计算, 可以 为 -1。
-
int64_t ArrowArray.offset#
必须的。数组内的逻辑偏移量(即从缓冲区的物理开始处算起的项的数量)。 必须 为 0 或正数。
生产者 可以 指定它们仅生成 0 偏移量的数组,以简化消费者代码的实现。消费者 可以 选择不支持非 0 偏移量数组,但他们应该记录此限制。
-
int64_t ArrowArray.n_buffers#
必须的。支持此数组的物理缓冲区数量。缓冲区数量是数据类型的函数,如列式格式规范中所述,但 binary 或 utf-8 view 类型除外,该类型比列式格式规范多一个额外缓冲区(参见Binary view 数组)。
子数组的缓冲区不包含在内。
-
const void **ArrowArray.buffers#
必须的。指向每个物理缓冲区的起始位置的 C 数组。每个 void* 指针是连续缓冲区的物理起始位置。必须有
ArrowArray.n_buffers
个指针。生产者 必须 确保每个连续缓冲区足够大,足以表示根据列式格式规范编码的 length + offset 值。
建议(但非必需)缓冲区的内存地址至少按照它们包含的原始数据类型对齐。消费者 可以 选择不支持未对齐的内存。
缓冲区指针仅在两种情况下 可以 为 null:
对于 null 位图缓冲区,如果
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
结构体仅指向扩展数组的存储数据。
二进制视图数组#
对于 binary 或 utf-8 视图数组,会附加一个额外缓冲区,该缓冲区以 int64_t
存储每个可变长度数据缓冲区的长度。此缓冲区是必需的,因为这些缓冲区长度无法轻易地从 binary 或 utf-8 视图类型的数组中的其他数据中提取。
语义#
内存管理#
ArrowSchema
和 ArrowArray
结构体遵循相同的内存管理约定。下文中的术语“基础结构体”指代在生产者和消费者之间传递的 ArrowSchema
或 ArrowArray
,而非其任何子结构体。
成员分配#
基础结构体旨在由消费者在栈或堆上分配。在这种情况下,生产者 API 应接受指向消费者分配的结构体的指针。
然而,结构体指向的任何数据 必须 由生产者分配和维护。这包括格式和元数据字符串、缓冲区和子字段指针数组等。
因此,消费者 必须 不尝试干预生产者对这些成员生命周期的处理。消费者影响数据生命周期的唯一方式是调用基础结构体的 release
回调函数。
已释放的结构体#
通过将其 release
回调函数设置为 NULL 来指示已释放的结构体。在读取和解释结构体的数据之前,消费者 应该 检查释放回调函数是否为 NULL,并据此处理(可能通过报错处理)。
释放回调函数语义 – 对于消费者#
消费者 在不再使用基础结构体时 必须 调用其释放回调函数,但他们 必须 不调用其任何子结构体(包括可选字典)的释放回调函数。生产者负责释放子结构体。
无论如何,消费者 在调用其释放回调函数后 必须 不尝试访问基础结构体,包括任何相关数据,例如其子结构体。
释放回调函数语义 – 对于生产者#
如果生产者需要额外的生命周期处理信息(例如,C++ 生产者可能想使用 shared_ptr
来管理数组和缓冲区生命周期),他们 必须 使用 private_data
成员来定位所需的簿记信息。
释放回调函数 必须 不假定结构体将位于与最初生成时相同的内存地址。消费者可以自由移动结构体(参见“移动数组”)。
释放回调函数 必须 遍历所有子结构体(包括可选字典)并调用它们自己的释放回调函数。
释放回调 MUST 释放结构体直接拥有的任何数据区域(例如 buffers 和 children 成员)。
释放回调 MUST 通过将其 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
结构体。然后,它 MUST 将源结构体标记为已释放(参见上面的“已释放结构体”了解如何操作),但 不调用 释放回调。这确保了在任何给定时间只有一个结构体的活动副本,并且生命周期被正确地传达给生产者。
照常,当目标结构体不再需要时,将在其上调用释放回调。
移动子数组#
也可以移动一个或多个子数组,但父 ArrowArray
结构体必须在此之后立即被释放,因为它将不再指向有效的子数组。
主要用例是只保持子数组的一个子集(例如,如果您只对数据的某些列感兴趣)的活跃状态,同时释放其他子数组。
注释
为了使移动正常工作,ArrowArray
结构体必须是易于重定位的。因此,ArrowArray
结构体内部的指针成员(包括 private_data
)MUST 不能指向结构体本身内部。此外,生产者 MUST 不能单独存储指向该结构体的外部指针。相反,生产者 MUST 使用 private_data
成员来记住任何必要的簿记信息。
记录批次#
记录批次可以简单地视为等价的结构体数组。在这种情况下,顶层 ArrowSchema
的元数据可以用于记录批次的模式级别元数据。
可变性#
生产者和消费者都 SHOULD 认为导出的数据(即通过 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
};
}
导出 Arrow 兼容布局的 C-malloc() 分配的数组作为 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 data interface v2”。
灵感#
Arrow C 数据接口的灵感来源于 Python 缓冲区协议,该协议已被证明在允许各种 Python 库交换数值数据方面取得了巨大成功,它们之间互不了解,且适应成本接近于零。
语言特定的协议#
某些语言可以在 Arrow C 数据接口之上定义附加协议。