This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 7c4f10a02 feat(rust/core): add InfoCode::Other to handle arbitrary
codes (#4510)
7c4f10a02 is described below
commit 7c4f10a02f9f85b1dcbbd6ce047d0b5372a6131a
Author: Fredrik Fornwall <[email protected]>
AuthorDate: Wed Jul 22 02:28:39 2026 +0200
feat(rust/core): add InfoCode::Other to handle arbitrary codes (#4510)
Signed-off-by: Fredrik Fornwall <[email protected]>
---
javascript/src/client.rs | 2 +-
rust/core/src/options.rs | 64 +++++++++++-----
rust/driver/dummy/src/lib.rs | 46 +++++++----
rust/driver/dummy/tests/driver_exporter_dummy.rs | 97 +++++++++++++++++++++++-
rust/ffi/src/driver_exporter.rs | 5 +-
5 files changed, 175 insertions(+), 39 deletions(-)
diff --git a/javascript/src/client.rs b/javascript/src/client.rs
index 5bb4b5b0a..032a5e347 100644
--- a/javascript/src/client.rs
+++ b/javascript/src/client.rs
@@ -304,7 +304,7 @@ impl AdbcConnectionCore {
.map_err(|e| ClientError::Other(e.to_string()))?;
let codes: Option<HashSet<InfoCode>> = info_codes.map(|v| {
v.into_iter()
- .filter_map(|code| InfoCode::try_from(code).ok())
+ .map(InfoCode::from)
.collect::<HashSet<InfoCode>>()
});
let reader = conn.get_info(codes)?;
diff --git a/rust/core/src/options.rs b/rust/core/src/options.rs
index 99fdb5907..dd0e01575 100644
--- a/rust/core/src/options.rs
+++ b/rust/core/src/options.rs
@@ -223,6 +223,19 @@ pub enum InfoCode {
///
/// ADBC API revision 1.1.0
DriverAdbcVersion,
+ /// Any other info code than the ones listed above. The value is the raw
code.
+ ///
+ /// Codes `[0, 10_000)` are reserved for ADBC usage (of which `[500,
1_000)`
+ /// is reserved for "XDBC" information since ADBC API revision 1.1.0),
while
+ /// codes `10_000` and higher are driver/vendor-specific. Drivers ignore
+ /// requests for codes they don't recognize (the row is omitted from the
+ /// result).
+ ///
+ /// Note: to keep equality and hashing consistent, this variant should
never
+ /// be constructed with the value of a code that has a dedicated variant;
+ /// use [InfoCode::from] to convert from a raw `u32`, which only produces
+ /// `Other` for values without one.
+ Other(u32),
}
impl From<&InfoCode> for u32 {
@@ -243,34 +256,30 @@ impl From<&InfoCode> for u32 {
InfoCode::DriverVersion => constants::ADBC_INFO_DRIVER_VERSION,
InfoCode::DriverArrowVersion =>
constants::ADBC_INFO_DRIVER_ARROW_VERSION,
InfoCode::DriverAdbcVersion =>
constants::ADBC_INFO_DRIVER_ADBC_VERSION,
+ InfoCode::Other(v) => *v,
}
}
}
-impl TryFrom<u32> for InfoCode {
- type Error = Error;
-
- fn try_from(value: u32) -> Result<Self, Self::Error> {
+impl From<u32> for InfoCode {
+ fn from(value: u32) -> Self {
match value {
- constants::ADBC_INFO_VENDOR_NAME => Ok(InfoCode::VendorName),
- constants::ADBC_INFO_VENDOR_VERSION => Ok(InfoCode::VendorVersion),
- constants::ADBC_INFO_VENDOR_ARROW_VERSION =>
Ok(InfoCode::VendorArrowVersion),
- constants::ADBC_INFO_VENDOR_SQL => Ok(InfoCode::VendorSql),
- constants::ADBC_INFO_VENDOR_SUBSTRAIT =>
Ok(InfoCode::VendorSubstrait),
+ constants::ADBC_INFO_VENDOR_NAME => InfoCode::VendorName,
+ constants::ADBC_INFO_VENDOR_VERSION => InfoCode::VendorVersion,
+ constants::ADBC_INFO_VENDOR_ARROW_VERSION =>
InfoCode::VendorArrowVersion,
+ constants::ADBC_INFO_VENDOR_SQL => InfoCode::VendorSql,
+ constants::ADBC_INFO_VENDOR_SUBSTRAIT => InfoCode::VendorSubstrait,
constants::ADBC_INFO_VENDOR_SUBSTRAIT_MIN_VERSION => {
- Ok(InfoCode::VendorSubstraitMinVersion)
+ InfoCode::VendorSubstraitMinVersion
}
constants::ADBC_INFO_VENDOR_SUBSTRAIT_MAX_VERSION => {
- Ok(InfoCode::VendorSubstraitMaxVersion)
+ InfoCode::VendorSubstraitMaxVersion
}
- constants::ADBC_INFO_DRIVER_NAME => Ok(InfoCode::DriverName),
- constants::ADBC_INFO_DRIVER_VERSION => Ok(InfoCode::DriverVersion),
- constants::ADBC_INFO_DRIVER_ARROW_VERSION =>
Ok(InfoCode::DriverArrowVersion),
- constants::ADBC_INFO_DRIVER_ADBC_VERSION =>
Ok(InfoCode::DriverAdbcVersion),
- v => Err(Error::with_message_and_status(
- format!("Unknown info code: {v}"),
- Status::InvalidData,
- )),
+ constants::ADBC_INFO_DRIVER_NAME => InfoCode::DriverName,
+ constants::ADBC_INFO_DRIVER_VERSION => InfoCode::DriverVersion,
+ constants::ADBC_INFO_DRIVER_ARROW_VERSION =>
InfoCode::DriverArrowVersion,
+ constants::ADBC_INFO_DRIVER_ADBC_VERSION =>
InfoCode::DriverAdbcVersion,
+ v => InfoCode::Other(v),
}
}
}
@@ -741,3 +750,20 @@ impl std::fmt::Display for Statistics {
write!(f, "{}", self.as_ref())
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn info_code_u32_roundtrip() {
+ for code in (0..20_000_u32).chain([u32::MAX]) {
+ assert_eq!(u32::from(&InfoCode::from(code)), code);
+ }
+ assert_eq!(
+ InfoCode::from(constants::ADBC_INFO_VENDOR_NAME),
+ InfoCode::VendorName
+ );
+ assert_eq!(InfoCode::from(10_042), InfoCode::Other(10_042));
+ }
+}
diff --git a/rust/driver/dummy/src/lib.rs b/rust/driver/dummy/src/lib.rs
index 7879873ca..8c3b92e76 100644
--- a/rust/driver/dummy/src/lib.rs
+++ b/rust/driver/dummy/src/lib.rs
@@ -37,6 +37,8 @@ use adbc_core::{
schemas,
};
+pub const VENDOR_INFO_CODE: u32 = 10_042;
+
#[derive(Debug)]
pub struct SingleBatchReader {
batch: Option<RecordBatch>,
@@ -312,9 +314,9 @@ impl Connection for DummyConnection {
fn get_info(
&self,
- _codes: Option<HashSet<InfoCode>>,
+ codes: Option<HashSet<InfoCode>>,
) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
- let string_value_array = StringArray::from(vec!["MyVendorName"]);
+ let string_value_array = StringArray::from(vec!["MyVendorName",
"MyVendorInfoValue"]);
let bool_value_array = BooleanArray::from(vec![true]);
let int64_value_array = Int64Array::from(vec![42]);
let int32_bitmask_array = Int32Array::from(vec![1337]);
@@ -355,20 +357,34 @@ impl Connection for DummyConnection {
false,
)?;
- let name_array = UInt32Array::from(vec![
- Into::<u32>::into(&InfoCode::VendorName),
- Into::<u32>::into(&InfoCode::VendorVersion),
- Into::<u32>::into(&InfoCode::VendorArrowVersion),
- Into::<u32>::into(&InfoCode::DriverName),
- Into::<u32>::into(&InfoCode::DriverVersion),
- Into::<u32>::into(&InfoCode::DriverArrowVersion),
- ]);
-
- let type_id_buffer = [0_i8, 1, 2, 3, 4, 5]
- .into_iter()
+ // Every info value this driver knows, as (code, union type id, offset
into that type's
+ // child array). Includes a vendor-specific code to exercise
`InfoCode::Other`.
+ let known_info: [(InfoCode, i8, i32); 7] = [
+ (InfoCode::VendorName, 0, 0),
+ (InfoCode::VendorVersion, 1, 0),
+ (InfoCode::VendorArrowVersion, 2, 0),
+ (InfoCode::DriverName, 3, 0),
+ (InfoCode::DriverVersion, 4, 0),
+ (InfoCode::DriverArrowVersion, 5, 0),
+ (InfoCode::Other(VENDOR_INFO_CODE), 0, 1),
+ ];
+ let rows: Vec<&(InfoCode, i8, i32)> = known_info
+ .iter()
+ .filter(|(code, _, _)| codes.as_ref().is_none_or(|codes|
codes.contains(code)))
+ .collect();
+
+ let name_array = UInt32Array::from(
+ rows.iter()
+ .map(|(code, _, _)| code.into())
+ .collect::<Vec<u32>>(),
+ );
+ let type_id_buffer = rows
+ .iter()
+ .map(|(_, type_id, _)| *type_id)
.collect::<ScalarBuffer<i8>>();
- let value_offsets_buffer = [0_i32, 0, 0, 0, 0, 0]
- .into_iter()
+ let value_offsets_buffer = rows
+ .iter()
+ .map(|(_, _, offset)| *offset)
.collect::<ScalarBuffer<i32>>();
let value_array = UnionArray::try_new(
diff --git a/rust/driver/dummy/tests/driver_exporter_dummy.rs
b/rust/driver/dummy/tests/driver_exporter_dummy.rs
index b9bc98941..36e7657f9 100644
--- a/rust/driver/dummy/tests/driver_exporter_dummy.rs
+++ b/rust/driver/dummy/tests/driver_exporter_dummy.rs
@@ -19,11 +19,14 @@
/// directly using the Rust API (native) and through the exported driver via
the
/// driver manager (exported). That allows us to test that data correctly
round-trip
/// between C and Rust.
+use std::collections::HashSet;
use std::ffi::CString;
use std::ops::Deref;
use std::sync::Arc;
-use arrow_array::ffi_stream::FFI_ArrowArrayStream;
+use arrow_array::cast::AsArray;
+use arrow_array::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream};
+use arrow_array::types::UInt32Type;
use arrow_array::{Array, Float64Array, Int64Array, RecordBatch,
RecordBatchReader, StringArray};
use arrow_schema::{DataType, Field, Schema};
use arrow_select::concat::concat_batches;
@@ -365,7 +368,99 @@ fn test_connection_get_info() {
.unwrap(),
);
assert_eq!(exported_info.schema(), *schemas::GET_INFO_SCHEMA.deref());
+ // The dummy driver recognizes `DriverName` but not `DriverAdbcVersion`,
whose row is omitted.
+ assert_eq!(exported_info.num_rows(), 1);
assert_eq!(exported_info, native_info);
+
+ let vendor_codes: HashSet<InfoCode> =
[InfoCode::Other(adbc_dummy::VENDOR_INFO_CODE)].into();
+ let exported_info = concat_reader(
+ exported_connection
+ .get_info(Some(vendor_codes.clone()))
+ .unwrap(),
+ );
+ let native_info =
concat_reader(native_connection.get_info(Some(vendor_codes)).unwrap());
+ assert_eq!(exported_info.num_rows(), 1);
+ assert_eq!(
+ exported_info
+ .column(0)
+ .as_primitive::<UInt32Type>()
+ .value(0),
+ adbc_dummy::VENDOR_INFO_CODE
+ );
+ assert_eq!(exported_info, native_info);
+}
+
+// Driven at the C ABI (unlike the other tests) to exercise the exporter's raw
`u32` code
+// handling: codes without a dedicated `InfoCode` variant must reach the
driver as
+// `InfoCode::Other` (so it can answer vendor-specific codes), and per the
ADBC spec the driver
+// ignores requests for codes it doesn't recognize (the row is omitted) rather
than failing the
+// call.
+#[test]
+fn test_connection_get_info_ignores_unrecognized_codes() {
+ let driver = DummyDriver::ffi_driver();
+ let mut error = FFI_AdbcError::default();
+ let err = &mut error as *mut FFI_AdbcError;
+
+ unsafe {
+ let mut database = FFI_AdbcDatabase::default();
+ assert_eq!(
+ driver.DatabaseNew.unwrap()(&mut database, err),
+ ADBC_STATUS_OK
+ );
+ assert_eq!(
+ driver.DatabaseInit.unwrap()(&mut database, err),
+ ADBC_STATUS_OK
+ );
+
+ let mut connection = FFI_AdbcConnection::default();
+ assert_eq!(
+ driver.ConnectionNew.unwrap()(&mut connection, err),
+ ADBC_STATUS_OK
+ );
+ assert_eq!(
+ driver.ConnectionInit.unwrap()(&mut connection, &mut database,
err),
+ ADBC_STATUS_OK
+ );
+
+ let info_codes: [u32; 3] = [
+ Into::<u32>::into(&InfoCode::DriverName),
+ adbc_dummy::VENDOR_INFO_CODE,
+ u32::MAX,
+ ];
+ let mut stream = FFI_ArrowArrayStream::empty();
+ assert_eq!(
+ driver.ConnectionGetInfo.unwrap()(
+ &mut connection,
+ info_codes.as_ptr(),
+ info_codes.len(),
+ &mut stream,
+ err,
+ ),
+ ADBC_STATUS_OK,
+ "an unrecognized info code must be ignored, not fail the call"
+ );
+
+ let info =
concat_reader(ArrowArrayStreamReader::try_new(stream).unwrap());
+ let returned_codes: HashSet<u32> = info
+ .column(0)
+ .as_primitive::<UInt32Type>()
+ .values()
+ .iter()
+ .copied()
+ .collect();
+ assert_eq!(
+ returned_codes,
+ [
+ Into::<u32>::into(&InfoCode::DriverName),
+ adbc_dummy::VENDOR_INFO_CODE
+ ]
+ .into(),
+ "the vendor-specific code must be answered and the unrecognized
one omitted"
+ );
+
+ driver.ConnectionRelease.unwrap()(&mut connection, err);
+ driver.DatabaseRelease.unwrap()(&mut database, err);
+ }
}
#[test]
diff --git a/rust/ffi/src/driver_exporter.rs b/rust/ffi/src/driver_exporter.rs
index ca483c5a4..429f0caf3 100644
--- a/rust/ffi/src/driver_exporter.rs
+++ b/rust/ffi/src/driver_exporter.rs
@@ -1163,9 +1163,8 @@ extern "C" fn connection_get_info<DriverType: Driver +
'static>(
None
} else {
let info_codes = unsafe { std::slice::from_raw_parts(info_codes,
info_codes_length) };
- let info_codes: Result<HashSet<InfoCode>> =
- info_codes.iter().map(|c| InfoCode::try_from(*c)).collect();
- let info_codes = check_err!(info_codes, error);
+ let info_codes: HashSet<InfoCode> =
+ info_codes.iter().map(|c| InfoCode::from(*c)).collect();
Some(info_codes)
};