Arrow C 資料介面#

基本原理#

Apache Arrow 旨在成為表格(「欄狀」)資料表示的通用記憶體內格式。然而,某些專案可能會面臨困難的選擇,要在依賴像 Arrow C++ 程式庫這樣快速發展的專案,還是在資料交換方面必須重新實作轉接器,這可能需要大量重複的開發工作。

Arrow C 資料介面定義了一組非常小且穩定的 C 定義,可以輕鬆複製到任何專案的原始碼中,並用於 Arrow 格式的欄狀資料交換。對於非 C/C++ 語言和執行時期環境,將 C 定義轉換為相應的 C FFI 宣告應該幾乎一樣容易。

因此,應用程式和程式庫可以在不必使用 Arrow 程式庫或重新發明輪子的情況下使用 Arrow 記憶體。開發人員可以在與 Arrow 軟體專案緊密整合(受益於 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 資料類型

註解

n

null

b

boolean

c

int8

C

uint8

s

int16

S

uint16

i

int32

I

uint32

l

int64

L

uint64

e

float16

f

float32

g

float64

格式字串

Arrow 資料類型

註解

z

binary

Z

large binary

vz

binary view

u

utf-8 string

U

large utf-8 string

vu

utf-8 view

d:19,10

decimal128 [精度 19,小數位數 10]

d:19,10,NNN

decimal 位元寬度 = NNN [精度 19,小數位數 10]

w:42

固定寬度二進位 [42 位元組]

時間類型具有以 t 開頭的多字元格式字串

格式字串

Arrow 資料類型

註解

tdD

date32 [天]

tdm

date64 [毫秒]

tts

time32 [秒]

ttm

time32 [毫秒]

ttu

time64 [微秒]

ttn

time64 [奈秒]

tss:...

timestamp [秒],時區為「…」

(1)

tsm:...

timestamp [毫秒],時區為「…」

(1)

tsu:...

timestamp [微秒],時區為「…」

(1)

tsn:...

timestamp [奈秒],時區為「…」

(1)

tDs

duration [秒]

tDm

duration [毫秒]

tDu

duration [微秒]

tDn

duration [奈秒]

tiM

interval [月]

tiD

interval [天,時間]

tin

interval [月,天,奈秒]

字典編碼類型沒有特定的格式字串。相反地,基礎陣列的格式字串代表字典索引類型,而值類型可以從相關的字典陣列讀取(請參閱下文「字典編碼陣列」)。

巢狀類型具有以 + 開頭的多字元格式字串。子欄位的名稱和類型從子陣列讀取。

格式字串

Arrow 資料類型

註解

+l

list

+L

large list

+vl

list-view

+vL

large list-view

+w:123

固定大小列表 [123 個項目]

+s

struct

+m

map

(2)

+ud:I,J,...

具有類型 ID I,J…的密集聯集

+us:I,J,...

具有類型 ID I,J…的稀疏聯集

+r

run-end encoded

(3)

註解

  1. 時區字串會附加在冒號字元 : 之後,不帶任何引號。如果時區為空,則冒號 : 仍必須包含。

  2. 如 Arrow 欄狀格式中所指定,map 類型具有名為 entries 的單個子類型,其本身是 (key, value) 的 2 子 struct 類型。

  3. 如 Arrow 欄狀格式中所指定,run-end encoded 類型具有兩個子類型,其中第一個是(整數)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;其兩個子類型具有名稱 intsfloats,以及格式字串 if 分別。

  • map<string, float64> 陣列的格式字串為 +m;其單個子類型具有名稱 entries 和格式字串 +s;其兩個孫子類型具有名稱 keyvalue,以及格式字串 ug 分別。

  • 具有類型 ID 4, 5sparse_union<ints: int32, floats: float32> 的格式字串為 +us:4,5;其兩個子類型具有名稱 intsfloats,以及格式字串 if 分別。

  • run_end_encoded<int32, float32> 的格式字串為 +r;其兩個子類型具有名稱 run_endsvalues,以及格式字串 if 分別。

結構定義#

以下獨立定義足以在您的專案中支援 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#

選用。豐富類型描述的旗標位元欄位。其值透過將旗標值 OR 在一起計算。以下旗標可用

  • ARROW_FLAG_NULLABLE:此欄位在語義上是否可為 null(無論它是否實際具有 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 結構被解釋為類型,必須已知道陣列類型或記錄批次結構描述。這可以透過約定來完成,例如始終產生相同資料類型的生產者 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#

強制性。支援此陣列的物理緩衝區的數量。緩衝區的數量是資料類型的函數,如 欄狀格式規格 中所述,但二進位或 utf-8 view 類型除外,與欄狀格式規格相比,它有一個額外的緩衝區(請參閱 二進位 view 陣列)。

不包括子陣列的緩衝區。

const void **ArrowArray.buffers#

強制性。指向支援此陣列的每個物理緩衝區起點的指標的 C 陣列。每個 void* 指標都是連續緩衝區的物理起點。必須有 ArrowArray.n_buffers 個指標。

生產者必須確保每個連續緩衝區都足夠大,可以表示根據 欄狀格式規格 編碼的 length + offset 值。

建議但不強制要求緩衝區的記憶體位址至少根據它們包含的原始資料類型對齊。消費者可以決定不支援未對齊的記憶體。

緩衝區指標僅在兩種情況下可以為 null

  1. 對於 null 位元圖緩衝區,如果 ArrowArray.null_count 為 0;

  2. 對於任何緩衝區,如果相應緩衝區的大小(以位元組為單位)為 0。

不包括子陣列的緩衝區。

int64_t ArrowArray.n_children#

強制性。此陣列具有的子陣列數量。子陣列的數量是資料類型的函數,如 欄狀格式規格 中所述。

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 格式。具體來說,metadata 鍵 ARROW:extension:name 編碼擴充類型名稱,而 metadata 鍵 ARROW:extension:metadata 編碼擴充類型的實作特定序列化(對於參數化擴充類型)。

從擴充陣列匯出的 ArrowArray 結構僅指向擴充陣列的儲存資料。

二進制視圖陣列#

對於二進制或 utf-8 視圖陣列,會附加一個額外的緩衝區,用於儲存每個可變資料緩衝區的長度,類型為 int64_t。這個緩衝區是必要的,因為這些緩衝區長度無法從二進制或 utf-8 視圖類型陣列中的其他資料中輕易提取。

語意#

記憶體管理#

ArrowSchemaArrowArray 結構遵循相同的記憶體管理慣例。下文中的「基礎結構」一詞指的是在生產者和消費者之間傳遞的 ArrowSchemaArrowArray,而不是其任何子結構。

成員分配#

基礎結構旨在由消費者進行堆疊或堆積分配。在這種情況下,生產者 API 應採用指向消費者分配結構的指標。

然而,結構指向的任何資料都必須由生產者分配和維護。這包括格式和 metadata 字串、緩衝區和子指標陣列等等。

因此,消費者絕不能嘗試干擾生產者對這些成員生命週期的處理。消費者影響資料生命週期的唯一方式是呼叫基礎結構的 release 回呼。

已釋放的結構#

已釋放的結構通過將其 release 回呼設定為 NULL 來指示。在讀取和解釋結構的資料之前,消費者應檢查 NULL release 回呼並相應地處理它(可能通過錯誤處理)。

Release 回呼語意 – 對於消費者#

當消費者不再使用基礎結構時,必須呼叫其 release 回呼,但他們絕不能呼叫其任何子結構(包括可選字典)的 release 回呼。生產者負責釋放子結構。

在任何情況下,消費者在呼叫基礎結構的 release 回呼後,絕不能再嘗試訪問基礎結構,包括任何相關資料,例如其子結構。

Release 回呼語意 – 對於生產者#

如果生產者需要額外資訊來處理生命週期(例如,C++ 生產者可能想要使用 shared_ptr 來管理陣列和緩衝區的生命週期),他們必須使用 private_data 成員來定位所需的簿記資訊。

Release 回呼絕不能假設結構將位於與最初生產時相同的記憶體位置。消費者可以自由移動結構(請參閱「移動陣列」)。

Release 回呼必須遍歷所有子結構(包括可選字典)並呼叫它們自己的 release 回呼。

Release 回呼必須釋放結構直接擁有的任何資料區域(例如緩衝區和子成員)。

Release 回呼必須通過將其 release 成員設定為 NULL,將結構標記為已釋放。

以下是實作 release 回呼的一個好的起點,其中 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 結構。然後,它必須將來源結構標記為已釋放(請參閱上面的「已釋放的結構」以了解如何操作),但呼叫 release 回呼。這確保在任何給定時間只有一個活動的結構副本處於活動狀態,並且生命週期已正確傳達給生產者。

與往常一樣,release 回呼將在目標結構不再需要時被呼叫。

移動子陣列#

也可以移動一個或多個子陣列,但父 ArrowArray 結構必須在之後立即釋放,因為它將不再指向有效的子陣列。

這樣做的主要用例是僅保留一部分子陣列(例如,如果您只對資料的某些列感興趣),同時釋放其他子陣列。

注意

為了使移動正確工作,ArrowArray 結構必須是可輕易重新定位的。因此,ArrowArray 結構內部的指標成員(包括 private_data)絕不能指向結構本身內部。此外,生產者絕不能單獨儲存指向結構的外部指標。相反,生產者必須使用 private_data 成員,以便記住任何必要的簿記資訊。

記錄批次#

記錄批次可以被簡單地視為等效的結構陣列。在這種情況下,頂層 ArrowSchema 的 metadata 可以用於記錄批次的 schema 層級 metadata。

可變性#

生產者和消費者都應將匯出的資料(即,通過 ArrowArraybuffers 成員可訪問的資料)視為不可變的,因為否則任何一方都可能在另一方正在變更資料時看到不一致的資料。

範例用例#

C++ 資料庫引擎想要提供以 Arrow 格式交付結果的選項,但又不想讓自己依賴 Arrow 軟體函式庫。通過 Arrow C 資料介面,引擎可以讓呼叫者傳遞指向 ArrowArray 結構的指標,並用下一塊結果填充它。

它可以做到這一點,而無需包含 Arrow C++ 標頭或與 Arrow DLL 連結。此外,資料庫引擎的 C API 可以通過例如 C FFI 層,使其他運行時和了解 Arrow C 資料介面的函式庫受益。

C 生產者範例#

匯出簡單的 int32 陣列#

匯出具有空 metadata 的不可為 null 的 int32 類型。在這種情況下,所有 ArrowSchema 成員都指向靜態分配的資料,因此 release 回呼是微不足道的。

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
   };
}

匯出與 Arrow 陣列類型相同的 C-malloc() 陣列,通過 release 回呼將所有權轉移給消費者

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;
}

為什麼是兩個不同的結構?#

在許多情況下,相同的類型或 schema 描述適用於多個,可能是短小的,資料批次。為了避免為每個批次匯出和導入類型描述付出成本,ArrowSchema 可以在生產者和消費者之間的對話開始時單獨傳遞一次。

在其他情況下,資料類型由生產者 API 固定,可能根本不需要傳達。

但是,如果生產者專注於一次性交換資料,它可以在同一個 API 呼叫中傳達 ArrowSchemaArrowArray 結構。

更新此規範#

一旦此規範在官方 Arrow 版本中獲得支援,C ABI 就會被凍結。這意味著 ArrowSchemaArrowArray 結構定義不應以任何方式更改,包括添加新成員。

允許向後相容的更改,例如新的 ArrowSchema.flags 值或 ArrowSchema.format 字串的擴展可能性。

任何不相容的更改都應成為新規範的一部分,例如「Arrow C 資料介面 v2」。

靈感#

Arrow C 資料介面受到 Python 緩衝區協議 的啟發,該協議已證明在允許各種 Python 函式庫在彼此不知情的情況下交換數值資料,並且幾乎零適應成本方面非常成功。

特定於語言的協議#

某些語言可能會在 Arrow C 資料介面上定義額外的協議。