Skip to content

fix(rust/ffi): forward original vendor_code as an error detail on the 1.1.0 layout - #32

Open
fornwall wants to merge 1 commit into
mainfrom
rust-ffi-vendor-code-detail
Open

fix(rust/ffi): forward original vendor_code as an error detail on the 1.1.0 layout#32
fornwall wants to merge 1 commit into
mainfrom
rust-ffi-vendor-code-detail

Conversation

@fornwall

@fornwall fornwall commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Problem

In the ADBC 1.1.0 C error layout, AdbcError.vendor_code must hold the ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA sentinel (INT32_MIN) to signal that private_data carries the extended error info. The Rust FFI exporter's export_error therefore stamps the sentinel over the driver's real vendor code on the 1.1.0 path (rust/ffi/src/types.rs), destroying it for every 1.1.0-layout consumer — only Rust-native and 1.0.0-layout callers can still see it.

This bit the adbc-spanner Rust driver (review finding UP-10): it keeps the numeric gRPC status code in vendor_code (e.g. 10 = ABORTED) as a documented contract, but that contract silently does not hold for 1.1.0 C callers.

Fix

When exporting through the 1.1.0 path and the Rust Error carries a meaningful vendor code (nonzero and not the sentinel), export_error now appends it as an additional error detail — the 1.1.0 rich-error-metadata mechanism, readable via AdbcErrorGetDetail:

  • key: adbc.error.vendor_code (exported as adbc_ffi::ERR_DETAIL_VENDOR_CODE) — namespaced adbc.<object>.<field> naming the exact spec field it mirrors, following the existing exporter-synthesized detail-key precedent adbc.drivermanager.driver_load_trace in rust/driver_manager/src/search.rs
  • value: the code as a decimal string in UTF-8 (textual detail values, like the driver-load trace and the postgres driver's PG_DIAG_* details; no -bin suffix since it is not binary proto)

The 1.0.0 path and the spec-mandated sentinel itself are unchanged; pre-existing details are preserved, the synthesized one is appended after them.

How the Go and C++ implementations handle the same collision

Neither forwards the vendor code as an error detail today, so the detail key introduced here has no cross-language precedent — but neither has a lossless answer either:

  • Go (setErrWithDetails in the generated exporters, template go/adbc/pkg/_tmpl/driver.go.tmpl) makes a case-by-case trade:
    • 1.0.0 caller → writes the real VendorCode into vendor_code (same as Rust).
    • 1.1.0 caller, vendor code set but zero details → deliberately downgrades to the plain layout: the real code goes into vendor_code and private_data is left unpopulated, sacrificing the extended layout to keep the code visible.
    • 1.1.0 caller with details → sentinel + details win, and the VendorCode is silently dropped.
  • C++ never faces the problem: the driver framework's Status (c/driver/framework/status.h) carries a status code, message and details but has no vendor-code field at all, and nothing under c/driver/ ever assigns a real vendor_code (the only occurrences are sentinel comparisons; likewise the legacy c/driver/common/utils.c helpers). C++-framework drivers simply never populate a vendor code.

So this PR's behaviour is aligned with Go in intent (don't let the 1.1.0 sentinel destroy the code) but strictly more information-preserving in mechanism: the code survives even when other details are present (Go's lossy case), and the extended layout is never sacrificed (Go's no-details case). The key is deliberately language-neutral so the Go exporter could adopt the same detail to fix its lossy branch if this is proposed upstream.

Tests

  • test_set_error_out_extended_layout_forwards_vendor_code_as_detail — a 1.1.0 consumer reads the code back through the exporter's real ErrorGetDetailCount/ErrorGetDetail functions (made pub(crate) for the test), alongside a preserved pre-existing detail
  • test_set_error_out_extended_layout_skips_meaningless_vendor_codes — no detail for 0 or the sentinel
  • test_set_error_out_v100_layout_keeps_vendor_code_in_place — the 1.0.0 path still passes the code through vendor_code itself

cargo test -p adbc_ffi (11 passed), cargo clippy -p adbc_ffi --all-targets --all-features -- -Dwarnings and cargo fmt --all -- --check are clean. Workspace clippy/test failures in adbc_driver_manager are pre-existing on main (identical 29 dead-code errors with and without this change; they need the sqlite driver test lib).

Upstreaming

This is a fork-internal staging of a fix intended to be proposed upstream to apache/arrow-adbc (see the Go/C++ comparison above — upstream could pair this with the matching fix to the Go exporter's lossy details-present branch).

🤖 Generated with Claude Code

https://claude.ai/code/session_013bxmWF4Z9R4y1kaHEAPgS6

… 1.1.0 layout

The ADBC 1.1.0 error layout requires AdbcError.vendor_code to hold the
ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA sentinel (INT32_MIN) whenever
private_data carries the extended error info, so export_error
unavoidably overwrites the driver's real vendor code (e.g. a gRPC
status code) for 1.1.0-layout consumers. Only Rust-native and
1.0.0-layout callers could still see it.

Preserve it instead as an additional error detail under the key
"adbc.error.vendor_code" (decimal string, UTF-8), readable via
AdbcErrorGetDetail. The detail is only synthesized when the code is
meaningful (nonzero and not the sentinel), the 1.0.0 path is untouched,
and the spec-mandated sentinel stays in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bxmWF4Z9R4y1kaHEAPgS6
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.

1 participant