mbrobbel commented on code in PR #1756: URL: https://github.com/apache/arrow-adbc/pull/1756#discussion_r1579076896
########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { Review Comment: Nit: I think we shouldn't specify the `Default` bound here. I think only specifying bounds where they are needed makes the code easier to understand. ```suggestion struct ExportedDatabase<DriverType: Driver> { ``` ########## rust/core/src/lib.rs: ########## @@ -49,12 +48,18 @@ //! The [driver_manager] module allows loading drivers exposing the C API, //! either from an initialization function (link-time, either static or dynamic) //! or by dynamically finding such a function in a dynamic library (run-time). +//! # Driver Exporter +//! +//! The driver exporter allows exposing native Rust drivers as C drivers to be +//! used by other langages via their own driver manager. Once you have an +//! implementation of [Driver], provided that it also implements [Default], you +//! can build it as an object file implementing the C API with the +//! [export_driver] macro. // TODO(alexandreyc): uncomment these lines during follow-up PRs -// pub mod driver_exporter; // pub mod driver_manager; -// pub use ffi::FFI_AdbcDriverInitFunc as AdbcDriverInitFunc; - +#[doc(hidden)] +pub mod driver_exporter; Review Comment: ```suggestion mod driver_exporter; ``` ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. Review Comment: Maybe we can include/link https://github.com/apache/arrow-adbc/blob/47397a9c919b77de177f8d129fe80ba795ef561b/adbc.h#L2326C1-L2335C58 ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { Review Comment: And here. ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; Review Comment: Maybe you can use `Option::take` and return an error if this was already `None`? ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; + } + ADBC_STATUS_OK +} + +// Option helpers + +unsafe fn copy_string(src: &str, dst: *mut c_char, length: *mut usize) -> Result<()> { + let n = src.len() + 1; // +1 for nul terminator + let src = CString::new(src)?; + if n <= *length { + std::ptr::copy(src.as_ptr(), dst, n); + } + *length = n; + Ok::<(), Error>(()) +} + +unsafe fn copy_bytes(src: &[u8], dst: *mut u8, length: *mut usize) { + let n = src.len(); + if n <= *length { + std::ptr::copy(src.as_ptr(), dst, n); Review Comment: Same here for `dst`. ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; + } + ADBC_STATUS_OK +} + +// Option helpers + +unsafe fn copy_string(src: &str, dst: *mut c_char, length: *mut usize) -> Result<()> { + let n = src.len() + 1; // +1 for nul terminator + let src = CString::new(src)?; + if n <= *length { Review Comment: I think this needs a null check for `length`? ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; + } + ADBC_STATUS_OK +} + +// Option helpers + +unsafe fn copy_string(src: &str, dst: *mut c_char, length: *mut usize) -> Result<()> { + let n = src.len() + 1; // +1 for nul terminator + let src = CString::new(src)?; + if n <= *length { + std::ptr::copy(src.as_ptr(), dst, n); Review Comment: I think this needs a null check for `dst`? ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() Review Comment: Why not use a `enum`? ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { Review Comment: Same questions as above here. ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); Review Comment: I was looking at the `TryFrom` impl for this and this returns an error if `CString::new` fails (if input bytes contain an internal 0 byte), so we can't just `unwrap` here. ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { Review Comment: What about something like this: ```rust trait FFIDriver { fn ffi_driver() -> FFI_AdbcDriver; } impl<DriverType: Driver + Default + 'static> FFIDriver for DriverType { fn ffi_driver() -> FFI_AdbcDriver { ... } } ``` Then: ```rust MyDriverType::fii_driver() ``` ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { Review Comment: Maybe we should add an associated constant for the adbc version to the driver trait? ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; + } + ADBC_STATUS_OK +} + +// Option helpers + +unsafe fn copy_string(src: &str, dst: *mut c_char, length: *mut usize) -> Result<()> { + let n = src.len() + 1; // +1 for nul terminator + let src = CString::new(src)?; + if n <= *length { + std::ptr::copy(src.as_ptr(), dst, n); Review Comment: ```suggestion std::ptr::copy_nonoverlapping(src.as_ptr(), dst, n); ``` ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; + } + ADBC_STATUS_OK +} + +// Option helpers + +unsafe fn copy_string(src: &str, dst: *mut c_char, length: *mut usize) -> Result<()> { + let n = src.len() + 1; // +1 for nul terminator + let src = CString::new(src)?; + if n <= *length { + std::ptr::copy(src.as_ptr(), dst, n); + } + *length = n; + Ok::<(), Error>(()) +} + +unsafe fn copy_bytes(src: &[u8], dst: *mut u8, length: *mut usize) { + let n = src.len(); + if n <= *length { Review Comment: Same here. ########## rust/core/src/driver_exporter.rs: ########## @@ -0,0 +1,1581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::collections::{HashMap, HashSet}; +use std::ffi::{CStr, CString}; +use std::hash::Hash; +use std::os::raw::{c_char, c_int, c_void}; + +use arrow::array::StructArray; +use arrow::datatypes::DataType; +use arrow::ffi::{from_ffi, FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream}; + +use crate::error::{Error, Result, Status}; +use crate::ffi::constants::ADBC_STATUS_OK; +use crate::ffi::{ + types::ErrorPrivateData, FFI_AdbcConnection, FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError, + FFI_AdbcErrorDetail, FFI_AdbcPartitions, FFI_AdbcStatement, FFI_AdbcStatusCode, +}; +use crate::options::{InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionValue}; +use crate::{Connection, Database, Driver, Optionable, Statement}; + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedDatabase<DriverType: Driver + Default> { + options: Option<HashMap<OptionDatabase, OptionValue>>, // Pre-init options + database: Option<DriverType::DatabaseType>, +} + +// Invariant: options.is_none() XOR database.is_none() +struct ExportedConnection<DriverType: Driver + Default> { + options: Option<HashMap<OptionConnection, OptionValue>>, // Pre-init options + connection: Option<<DriverType::DatabaseType as Database>::ConnectionType>, +} + +struct ExportedStatement<DriverType: Driver + Default> { + statement: + <<DriverType::DatabaseType as Database>::ConnectionType as Connection>::StatementType, +} + +pub fn make_ffi_driver<DriverType: Driver + Default + 'static>() -> FFI_AdbcDriver { + FFI_AdbcDriver { + private_data: std::ptr::null_mut(), + private_manager: std::ptr::null(), + release: Some(release_ffi_driver), + DatabaseInit: Some(database_init::<DriverType>), + DatabaseNew: Some(database_new::<DriverType>), + DatabaseSetOption: Some(database_set_option::<DriverType>), + DatabaseRelease: Some(database_release::<DriverType>), + ConnectionCommit: Some(connection_commit::<DriverType>), + ConnectionGetInfo: Some(connection_get_info::<DriverType>), + ConnectionGetObjects: Some(connection_get_objects::<DriverType>), + ConnectionGetTableSchema: Some(connection_get_table_schema::<DriverType>), + ConnectionGetTableTypes: Some(connection_get_table_types::<DriverType>), + ConnectionInit: Some(connection_init::<DriverType>), + ConnectionNew: Some(connection_new::<DriverType>), + ConnectionSetOption: Some(connection_set_option::<DriverType>), + ConnectionReadPartition: Some(connection_read_partition::<DriverType>), + ConnectionRelease: Some(connection_release::<DriverType>), + ConnectionRollback: Some(connection_rollback::<DriverType>), + StatementBind: Some(statement_bind::<DriverType>), + StatementBindStream: Some(statement_bind_stream::<DriverType>), + StatementExecuteQuery: Some(statement_execute_query::<DriverType>), + StatementExecutePartitions: Some(statement_execute_partitions::<DriverType>), + StatementGetParameterSchema: Some(statement_get_parameter_schema::<DriverType>), + StatementNew: Some(statement_new::<DriverType>), + StatementPrepare: Some(statement_prepare::<DriverType>), + StatementRelease: Some(statement_release::<DriverType>), + StatementSetOption: Some(statement_set_option::<DriverType>), + StatementSetSqlQuery: Some(statement_set_sql_query::<DriverType>), + StatementSetSubstraitPlan: Some(statement_set_substrait_plan::<DriverType>), + ErrorGetDetailCount: Some(error_get_detail_count), + ErrorGetDetail: Some(error_get_detail), + ErrorFromArrayStream: None, // TODO(alexandreyc): what to do with this? + DatabaseGetOption: Some(database_get_option::<DriverType>), + DatabaseGetOptionBytes: Some(database_get_option_bytes::<DriverType>), + DatabaseGetOptionDouble: Some(database_get_option_double::<DriverType>), + DatabaseGetOptionInt: Some(database_get_option_int::<DriverType>), + DatabaseSetOptionBytes: Some(database_set_option_bytes::<DriverType>), + DatabaseSetOptionDouble: Some(database_set_option_double::<DriverType>), + DatabaseSetOptionInt: Some(database_set_option_int::<DriverType>), + ConnectionCancel: Some(connection_cancel::<DriverType>), + ConnectionGetOption: Some(connection_get_option::<DriverType>), + ConnectionGetOptionBytes: Some(connection_get_option_bytes::<DriverType>), + ConnectionGetOptionDouble: Some(connection_get_option_double::<DriverType>), + ConnectionGetOptionInt: Some(connection_get_option_int::<DriverType>), + ConnectionGetStatistics: Some(connection_get_statistics::<DriverType>), + ConnectionGetStatisticNames: Some(connection_get_statistic_names::<DriverType>), + ConnectionSetOptionBytes: Some(connection_set_option_bytes::<DriverType>), + ConnectionSetOptionDouble: Some(connection_set_option_double::<DriverType>), + ConnectionSetOptionInt: Some(connection_set_option_int::<DriverType>), + StatementCancel: Some(statement_cancel::<DriverType>), + StatementExecuteSchema: Some(statement_execute_schema::<DriverType>), + StatementGetOption: Some(statement_get_option::<DriverType>), + StatementGetOptionBytes: Some(statement_get_option_bytes::<DriverType>), + StatementGetOptionDouble: Some(statement_get_option_double::<DriverType>), + StatementGetOptionInt: Some(statement_get_option_int::<DriverType>), + StatementSetOptionBytes: Some(statement_set_option_bytes::<DriverType>), + StatementSetOptionDouble: Some(statement_set_option_double::<DriverType>), + StatementSetOptionInt: Some(statement_set_option_int::<DriverType>), + } +} + +/// Export a Rust driver to a C driver. +/// +/// The default name recommended is `AdbcDriverInit` or `<Prefix>DriverInit`. +/// +/// The driver type must implement [Driver] and [Default]. +#[macro_export] +macro_rules! export_driver { + ($func_name:ident, $driver_type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $func_name( + version: std::os::raw::c_int, + driver: *mut std::os::raw::c_void, + error: *mut $crate::ffi::FFI_AdbcError, + ) -> $crate::ffi::FFI_AdbcStatusCode { + if version != $crate::options::AdbcVersion::V110.into() { + let err = $crate::error::Error::with_message_and_status( + format!("Unsupported ADBC version: {version}"), + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + if driver.is_null() { + let err = $crate::error::Error::with_message_and_status( + "Passed null pointer to initialization function", + $crate::error::Status::NotImplemented, + ); + $crate::check_err!(Err(err), error); + } + + let ffi_driver = $crate::driver_exporter::make_ffi_driver::<$driver_type>(); + unsafe { + std::ptr::write_unaligned(driver as *mut $crate::ffi::FFI_AdbcDriver, ffi_driver); + } + $crate::ffi::constants::ADBC_STATUS_OK + } + }; +} + +/// Given a Result, either unwrap the value or handle the error in ADBC function. +/// +/// This macro is for use when implementing ADBC methods that have an out +/// parameter for [FFI_AdbcError] and return [FFI_AdbcStatusCode]. If the result is +/// `Ok`, the expression resolves to the value. Otherwise, it will return early, +/// setting the error and status code appropriately. In order for this to work, +/// the error must be convertible to [crate::error::Error]. +#[doc(hidden)] +#[macro_export] +macro_rules! check_err { + ($res:expr, $err_out:expr) => { + match $res { + Ok(x) => x, + Err(error) => { + let error = $crate::error::Error::from(error); + let status: $crate::ffi::FFI_AdbcStatusCode = error.status.into(); + if !$err_out.is_null() { + let mut ffi_error = $crate::ffi::FFI_AdbcError::try_from(error).unwrap(); + ffi_error.private_driver = (*$err_out).private_driver; + unsafe { std::ptr::write_unaligned($err_out, ffi_error) }; + } + return status; + } + } + }; +} + +/// Check that the given raw pointer is not null. +/// +/// If null, an error is returned from the enclosing function, otherwise this is +/// a no-op. +macro_rules! check_not_null { + ($ptr:ident, $err_out:expr) => { + let res = if $ptr.is_null() { + Err(Error::with_message_and_status( + format!("Passed null pointer for argument {:?}", stringify!($ptr)), + Status::InvalidArguments, + )) + } else { + Ok(()) + }; + check_err!(res, $err_out); + }; +} + +unsafe extern "C" fn release_ffi_driver( + driver: *mut FFI_AdbcDriver, + _error: *mut FFI_AdbcError, +) -> FFI_AdbcStatusCode { + if let Some(driver) = driver.as_mut() { + driver.release = None; + } + ADBC_STATUS_OK +} + +// Option helpers + +unsafe fn copy_string(src: &str, dst: *mut c_char, length: *mut usize) -> Result<()> { + let n = src.len() + 1; // +1 for nul terminator Review Comment: You can use `CString::to_bytes_with_nul` and then `slice::len`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
