Skip to content

feat(c): add write/commit C FFI bindings#522

Open
suxiaogang223 wants to merge 1 commit into
apache:mainfrom
suxiaogang223:feat/c-ffi-write-support
Open

feat(c): add write/commit C FFI bindings#522
suxiaogang223 wants to merge 1 commit into
apache:mainfrom
suxiaogang223:feat/c-ffi-write-support

Conversation

@suxiaogang223

Copy link
Copy Markdown
Member

Summary

Add write-side C FFI bindings (bindings/c/src/write.rs) following the same opaque handle / extern "C" patterns as the existing read-side bindings. Closes #520.

API surface (14 functions)

// WriteBuilder
paimon_table_new_write_builder(table)        → paimon_result_write_builder
paimon_write_builder_free(wb)                → void
paimon_write_builder_with_overwrite(wb)      → *mut paimon_error
paimon_write_builder_new_write(wb)           → paimon_result_table_write
paimon_write_builder_new_commit(wb)          → paimon_result_table_commit

// TableWrite
paimon_table_write_free(tw)                  → void
paimon_table_write_write_arrow_batch(tw, array, schema) → *mut paimon_error
paimon_table_write_prepare_commit(tw)        → paimon_result_prepare_commit

// TableCommit
paimon_table_commit_free(tc)                 → void
paimon_table_commit_commit(tc, msgs)         → *mut paimon_error
paimon_table_commit_overwrite(tc, msgs)      → *mut paimon_error
paimon_table_commit_truncate_table(tc)       → *mut paimon_error
paimon_table_commit_abort(tc, msgs)          → *mut paimon_error

// CommitMessages
paimon_commit_messages_free(msgs)            → void

Design decisions

  • In-process transfer: CommitMessages are passed as opaque handles (no serialization), matching Python bindings
  • Shared commit_user: WriteBuilder generates a UUID shared by writer and committer
  • Arrow C Data Interface: Batch import via FFI_ArrowArray/FFI_ArrowSchema raw pointers

Tests (15 cases, all passing)

  • Read path (3), Predicate (3), Write path (7), Error handling (1), Integration (1)

Run: cargo +1.91.0 test -p paimon-c

Add write-side C FFI bindings following the existing read-side patterns:

API surface (14 functions):
- WriteBuilder: new_write_builder, free, with_overwrite, new_write, new_commit
- TableWrite: free, write_arrow_batch (via Arrow C Data Interface), prepare_commit
- TableCommit: free, commit, overwrite, truncate_table, abort
- CommitMessages: free

Tests (15 cases):
- Read path: empty table read, data read, column projection
- Predicate: equal, IS NOT NULL, IS IN, less_than filter, AND combinator
- Write path: builder lifecycle, full roundtrip, multiple batches, empty commit,
  overwrite mode, truncate, abort
- Error handling: null pointer checks
- Integration: two commits with same builder

Design follows Python bindings: in-process commit message transfer,
shared commit_user between writer and committer, Arrow C Data Interface
for zero-copy batch import.

Closes apache#520
Comment thread bindings/c/src/write.rs

let table_write = &mut *((*tw).inner as *mut TableWrite);

let ffi_array = ptr::read(array as *const FFI_ArrowArray);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ptr::read does not clear the caller's release callback. The imported copy owns the same resources while the original struct still appears valid, which can result in a double-free.

Comment thread bindings/c/src/write.rs
let ffi_array = ptr::read(array as *const FFI_ArrowArray);
let ffi_schema = ptr::read(schema as *const FFI_ArrowSchema);

let batch = match unsafe { from_ffi(ffi_array, &ffi_schema) } {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-Struct or nullable root arrays can panic here. The imported schema is also not checked against the table schema, so an incompatible schema may be written directly into data files.

Comment thread bindings/c/src/write.rs
}
let table_write = &mut *((*tw).inner as *mut TableWrite);

match runtime().block_on(table_write.prepare_commit()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The messages do not retain their source table or commit_user. Messages produced for one table can therefore be committed to another table, creating a snapshot that references files under the wrong location.

Comment thread bindings/c/src/write.rs
let table_commit = &*((*tc).inner as *const TableCommit);
let messages = Box::from_raw((*msgs).inner as *mut Vec<CommitMessage>);
// Release the outer wrapper to avoid double-free when caller frees msgs
drop(Box::from_raw(msgs));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This destroys the messages handle even though the API documentation requires callers to free it explicitly. Freeing it after commit becomes a use-after-free, and a failed commit cannot be retried or aborted.

Comment thread bindings/c/src/write.rs
return ptr::null_mut();
}

match runtime().block_on(table_commit.abort(&messages)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prepare_commit can produce new_index_files, but the current abort implementation only deletes data and changelog files. Dynamic-bucket aborts leave index files behind.

Comment thread bindings/c/src/write.rs
};
}
let table_ref = &*((*table).inner as *const Table);
let wb = table_ref.new_write_builder();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated commit_user cannot be supplied by the caller, and the API does not accept a commit_identifier. Separate writers cannot share commit identity, and uncertain commit results cannot be retried idempotently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add write/commit C FFI bindings for bindings/c/

2 participants