From eedb44ac64e2dd24c7a7a5e9643d8b8be022ad17 Mon Sep 17 00:00:00 2001
From: David Li
Date: Fri, 8 Mar 2024 11:41:44 -0500
Subject: [PATCH] refactor(c/driver/sqlite): port to driver base
Fixes #1141.
Fixes #1355.
Fixes #1602.
---
c/CMakeLists.txt | 1 +
c/driver/common/driver_base.h | 254 ++--
c/driver/common/utils.h | 19 -
c/driver/framework/CMakeLists.txt | 50 +
c/driver/framework/base.h | 793 +++++++++++
c/driver/framework/connection.cc | 263 ++++
c/driver/framework/connection.h | 42 +
c/driver/framework/driver.cc | 533 ++++++++
c/driver/framework/driver.h | 331 +++++
c/driver/framework/objects.cc | 305 +++++
c/driver/framework/objects.h | 39 +
c/driver/framework/status.h | 249 ++++
c/driver/framework/type_fwd.h | 24 +
c/driver/postgresql/CMakeLists.txt | 2 +
c/driver/postgresql/connection.cc | 37 +-
c/driver/sqlite/CMakeLists.txt | 4 +-
c/driver/sqlite/sqlite.cc | 1411 ++++++++++++++++++++
c/driver/sqlite/sqlite_test.cc | 9 +-
c/validation/adbc_validation_connection.cc | 27 +-
c/validation/adbc_validation_util.cc | 1 +
20 files changed, 4241 insertions(+), 153 deletions(-)
create mode 100644 c/driver/framework/CMakeLists.txt
create mode 100644 c/driver/framework/base.h
create mode 100644 c/driver/framework/connection.cc
create mode 100644 c/driver/framework/connection.h
create mode 100644 c/driver/framework/driver.cc
create mode 100644 c/driver/framework/driver.h
create mode 100644 c/driver/framework/objects.cc
create mode 100644 c/driver/framework/objects.h
create mode 100644 c/driver/framework/status.h
create mode 100644 c/driver/framework/type_fwd.h
create mode 100644 c/driver/sqlite/sqlite.cc
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index 21d399eee8..174107a92e 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -30,6 +30,7 @@ include(CTest)
add_subdirectory(vendor/nanoarrow)
add_subdirectory(driver/common)
+add_subdirectory(driver/framework)
if(ADBC_BUILD_TESTS)
add_subdirectory(validation)
diff --git a/c/driver/common/driver_base.h b/c/driver/common/driver_base.h
index 8f9fb7d074..297e7cef34 100644
--- a/c/driver/common/driver_base.h
+++ b/c/driver/common/driver_base.h
@@ -15,12 +15,15 @@
// specific language governing permissions and limitations
// under the License.
+#pragma once
+
#include
#include
-#include
#include
+#include
#include
#include
+#include
#include
#include
@@ -40,10 +43,7 @@
// return Driver::Init(
// version, raw_driver, error);
// }
-
-namespace adbc {
-
-namespace common {
+namespace adbc::common {
class Error {
public:
@@ -62,7 +62,7 @@ class Error {
details_.push_back({std::move(key), std::move(value)});
}
- void ToAdbc(AdbcError* adbc_error, AdbcDriver* driver = nullptr) {
+ void ToAdbc(AdbcError* adbc_error) {
if (adbc_error == nullptr) {
return;
}
@@ -73,7 +73,6 @@ class Error {
adbc_error->message =
const_cast(error_owned_by_adbc_error->message_.c_str());
adbc_error->private_data = error_owned_by_adbc_error;
- adbc_error->private_driver = driver;
} else {
adbc_error->message = reinterpret_cast(std::malloc(message_.size() + 1));
if (adbc_error->message != nullptr) {
@@ -106,11 +105,11 @@ class Error {
if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) {
auto error_obj = reinterpret_cast(error->private_data);
delete error_obj;
+ std::memset(error, 0, ADBC_ERROR_1_1_0_SIZE);
} else {
std::free(error->message);
+ std::memset(error, 0, ADBC_ERROR_1_0_0_SIZE);
}
-
- std::memset(error, 0, sizeof(AdbcError));
}
};
@@ -208,23 +207,16 @@ class Option {
// This class handles option setting and getting.
class ObjectBase {
public:
- ObjectBase() : driver_(nullptr) {}
-
- virtual ~ObjectBase() {}
+ ObjectBase() {}
- // Driver authors can override this method to reject options that are not supported or
- // that are set at a time not supported by the driver (e.g., to reject options that are
- // set after Init() is called if this is not supported).
- virtual AdbcStatusCode SetOption(const std::string& key, const Option& value) {
- options_[key] = value;
- return ADBC_STATUS_OK;
- }
+ virtual ~ObjectBase() = default;
- // Called After zero or more SetOption() calls. The parent is the private_data of
- // the AdbcDriver, AdbcDatabase, or AdbcConnection when initializing a subclass of
- // DatabaseObjectBase, ConnectionObjectBase, and StatementObjectBase (respectively).
- // For example, if you have defined Driver,
- // you can reinterpret_cast(parent) in MyConnection::Init().
+ // Called After zero or more SetOption() calls. The parent is the
+ // private_data of the AdbcDatabase, or AdbcConnection when initializing a
+ // subclass of ConnectionObjectBase, and StatementObjectBase (respectively),
+ // or otherwise nullptr. For example, if you have defined
+ // Driver, you can
+ // reinterpret_cast(parent) in MyConnection::Init().
virtual AdbcStatusCode Init(void* parent, AdbcError* error) { return ADBC_STATUS_OK; }
// Called when the corresponding AdbcXXXRelease() function is invoked from C.
@@ -245,34 +237,33 @@ class ObjectBase {
}
}
- protected:
- // Needed to export errors using Error::ToAdbc() that use 1.1.0 extensions
- // (i.e., error details). This will be nullptr before Init() is called.
- AdbcDriver* driver() const { return driver_; }
+ // Driver authors can override this method to reject options that are not supported or
+ // that are set at a time not supported by the driver (e.g., to reject options that are
+ // set after Init() is called if this is not supported).
+ virtual AdbcStatusCode SetOption(const std::string& key, const Option& value,
+ AdbcError* error) {
+ options_[key] = value;
+ return ADBC_STATUS_OK;
+ }
private:
- AdbcDriver* driver_;
std::unordered_map options_;
// Let the Driver use these to expose C callables wrapping option setters/getters
template
friend class Driver;
- // The AdbcDriver* struct is set right before Init() is called by the Driver
- // trampoline.
- void set_driver(AdbcDriver* driver) { driver_ = driver; }
-
template
AdbcStatusCode CSetOption(const char* key, T value, AdbcError* error) {
Option option(value);
- return SetOption(key, option);
+ return SetOption(key, option, error);
}
AdbcStatusCode CSetOptionBytes(const char* key, const uint8_t* value, size_t length,
AdbcError* error) {
std::vector cppvalue(value, value + length);
Option option(cppvalue);
- return SetOption(key, option);
+ return SetOption(key, option, error);
}
template
@@ -309,26 +300,28 @@ class ObjectBase {
}
void InitErrorNotFound(const char* key, AdbcError* error) const {
- std::stringstream msg_builder;
- msg_builder << "Option not found for key '" << key << "'";
- Error cpperror(msg_builder.str());
+ std::string msg = "Option not found for key '";
+ msg += key;
+ msg += "'";
+ Error cpperror(std::move(msg));
cpperror.AddDetail("adbc.driver_base.option_key", key);
- cpperror.ToAdbc(error, driver());
+ cpperror.ToAdbc(error);
}
void InitErrorWrongType(const char* key, AdbcError* error) const {
- std::stringstream msg_builder;
- msg_builder << "Wrong type requested for option key '" << key << "'";
- Error cpperror(msg_builder.str());
+ std::string msg = "Wrong type requested for option key '";
+ msg += key;
+ msg += "'";
+ Error cpperror(std::move(msg));
cpperror.AddDetail("adbc.driver_base.option_key", key);
- cpperror.ToAdbc(error, driver());
+ cpperror.ToAdbc(error);
}
};
// Driver authors can subclass DatabaseObjectBase to track driver-specific
// state pertaining to the AdbcDatbase. The private_data member of an
// AdbcDatabase initialized by the driver will be a pointer to the
-// subclass of DatbaseObjectBase.
+// subclass of DatabaseObjectBase.
class DatabaseObjectBase : public ObjectBase {
public:
// (there are no database functions other than option getting/setting)
@@ -341,6 +334,8 @@ class DatabaseObjectBase : public ObjectBase {
// implement the corresponding ConnectionXXX driver methods.
class ConnectionObjectBase : public ObjectBase {
public:
+ virtual AdbcStatusCode Cancel(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
+
virtual AdbcStatusCode Commit(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
virtual AdbcStatusCode GetInfo(const uint32_t* info_codes, size_t info_codes_length,
@@ -355,6 +350,16 @@ class ConnectionObjectBase : public ObjectBase {
return ADBC_STATUS_NOT_IMPLEMENTED;
}
+ virtual AdbcStatusCode GetStatistics(const char* catalog, const char* db_schema,
+ const char* table_name, char approximate,
+ ArrowArrayStream* out, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode GetStatisticNames(ArrowArrayStream* out, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
virtual AdbcStatusCode GetTableSchema(const char* catalog, const char* db_schema,
const char* table_name, ArrowSchema* schema,
AdbcError* error) {
@@ -374,18 +379,6 @@ class ConnectionObjectBase : public ObjectBase {
virtual AdbcStatusCode Rollback(AdbcError* error) {
return ADBC_STATUS_NOT_IMPLEMENTED;
}
-
- virtual AdbcStatusCode Cancel(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
-
- virtual AdbcStatusCode GetStatistics(const char* catalog, const char* db_schema,
- const char* table_name, char approximate,
- ArrowArrayStream* out, AdbcError* error) {
- return ADBC_STATUS_NOT_IMPLEMENTED;
- }
-
- virtual AdbcStatusCode GetStatisticNames(ArrowArrayStream* out, AdbcError* error) {
- return ADBC_STATUS_NOT_IMPLEMENTED;
- }
};
// Driver authors can subclass StatementObjectBase to track driver-specific
@@ -395,6 +388,16 @@ class ConnectionObjectBase : public ObjectBase {
// implement the corresponding StatementXXX driver methods.
class StatementObjectBase : public ObjectBase {
public:
+ AdbcStatusCode GetParameterSchema(struct ArrowSchema* schema, struct AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode ExecutePartitions(struct ArrowSchema* schema,
+ struct AdbcPartitions* partitions,
+ int64_t* rows_affected, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
virtual AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* rows_affected,
AdbcError* error) {
return ADBC_STATUS_NOT_IMPLEMENTED;
@@ -426,6 +429,26 @@ class StatementObjectBase : public ObjectBase {
virtual AdbcStatusCode Cancel(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
};
+template
+struct ResolveObjectTImpl {};
+
+template
+struct ResolveObjectTImpl {
+ using type = DatabaseT;
+};
+template
+struct ResolveObjectTImpl {
+ using type = ConnectionT;
+};
+template
+struct ResolveObjectTImpl {
+ using type = StatementT;
+};
+
+template
+using ResolveObjectT =
+ typename ResolveObjectTImpl::type;
+
// Driver authors can declare a template specialization of the Driver class
// and use it to provide their driver init function. It is possible, but
// rarely useful, to subclass a driver.
@@ -433,6 +456,7 @@ template
class Driver {
public:
static AdbcStatusCode Init(int version, void* raw_driver, AdbcError* error) {
+ // TODO: support 1_0_0
if (version != ADBC_VERSION_1_1_0) return ADBC_STATUS_NOT_IMPLEMENTED;
AdbcDriver* driver = (AdbcDriver*)raw_driver;
std::memset(driver, 0, sizeof(AdbcDriver));
@@ -446,34 +470,34 @@ class Driver {
driver->ErrorGetDetail = &CErrorGetDetail;
// Database lifecycle
- driver->DatabaseNew = &CNew;
+ driver->DatabaseNew = &CNew;
driver->DatabaseInit = &CDatabaseInit;
- driver->DatabaseRelease = &CRelease;
+ driver->DatabaseRelease = &CRelease;
// Database functions
- driver->DatabaseSetOption = &CSetOption;
- driver->DatabaseSetOptionBytes = &CSetOptionBytes;
- driver->DatabaseSetOptionInt = &CSetOptionInt;
- driver->DatabaseSetOptionDouble = &CSetOptionDouble;
- driver->DatabaseGetOption = &CGetOption;
- driver->DatabaseGetOptionBytes = &CGetOptionBytes;
- driver->DatabaseGetOptionInt = &CGetOptionInt;
- driver->DatabaseGetOptionDouble = &CGetOptionDouble;
+ driver->DatabaseSetOption = &CSetOption;
+ driver->DatabaseSetOptionBytes = &CSetOptionBytes;
+ driver->DatabaseSetOptionInt = &CSetOptionInt;
+ driver->DatabaseSetOptionDouble = &CSetOptionDouble;
+ driver->DatabaseGetOption = &CGetOption;
+ driver->DatabaseGetOptionBytes = &CGetOptionBytes;
+ driver->DatabaseGetOptionInt = &CGetOptionInt;
+ driver->DatabaseGetOptionDouble = &CGetOptionDouble;
// Connection lifecycle
- driver->ConnectionNew = &CNew;
+ driver->ConnectionNew = &CNew;
driver->ConnectionInit = &CConnectionInit;
- driver->ConnectionRelease = &CRelease;
+ driver->ConnectionRelease = &CRelease;
// Connection functions
- driver->ConnectionSetOption = &CSetOption;
- driver->ConnectionSetOptionBytes = &CSetOptionBytes;
- driver->ConnectionSetOptionInt = &CSetOptionInt;
- driver->ConnectionSetOptionDouble = &CSetOptionDouble;
- driver->ConnectionGetOption = &CGetOption;
- driver->ConnectionGetOptionBytes = &CGetOptionBytes;
- driver->ConnectionGetOptionInt = &CGetOptionInt;
- driver->ConnectionGetOptionDouble = &CGetOptionDouble;
+ driver->ConnectionSetOption = &CSetOption;
+ driver->ConnectionSetOptionBytes = &CSetOptionBytes;
+ driver->ConnectionSetOptionInt = &CSetOptionInt;
+ driver->ConnectionSetOptionDouble = &CSetOptionDouble;
+ driver->ConnectionGetOption = &CGetOption;
+ driver->ConnectionGetOptionBytes = &CGetOptionBytes;
+ driver->ConnectionGetOptionInt = &CGetOptionInt;
+ driver->ConnectionGetOptionDouble = &CGetOptionDouble;
driver->ConnectionCommit = &CConnectionCommit;
driver->ConnectionGetInfo = &CConnectionGetInfo;
driver->ConnectionGetObjects = &CConnectionGetObjects;
@@ -487,17 +511,17 @@ class Driver {
// Statement lifecycle
driver->StatementNew = &CStatementNew;
- driver->StatementRelease = &CRelease;
+ driver->StatementRelease = &CRelease;
// Statement functions
- driver->StatementSetOption = &CSetOption;
- driver->StatementSetOptionBytes = &CSetOptionBytes;
- driver->StatementSetOptionInt = &CSetOptionInt;
- driver->StatementSetOptionDouble = &CSetOptionDouble;
- driver->StatementGetOption = &CGetOption;
- driver->StatementGetOptionBytes = &CGetOptionBytes;
- driver->StatementGetOptionInt = &CGetOptionInt;
- driver->StatementGetOptionDouble = &CGetOptionDouble;
+ driver->StatementSetOption = &CSetOption;
+ driver->StatementSetOptionBytes = &CSetOptionBytes;
+ driver->StatementSetOptionInt = &CSetOptionInt;
+ driver->StatementSetOptionDouble = &CSetOptionDouble;
+ driver->StatementGetOption = &CGetOption;
+ driver->StatementGetOptionBytes = &CGetOptionBytes;
+ driver->StatementGetOptionInt = &CGetOptionInt;
+ driver->StatementGetOptionDouble = &CGetOptionDouble;
driver->StatementExecuteQuery = &CStatementExecuteQuery;
driver->StatementExecuteSchema = &CStatementExecuteSchema;
@@ -511,7 +535,6 @@ class Driver {
return ADBC_STATUS_OK;
}
- private:
// Driver trampolines
static AdbcStatusCode CDriverRelease(AdbcDriver* driver, AdbcError* error) {
auto driver_private = reinterpret_cast(driver->private_data);
@@ -535,16 +558,21 @@ class Driver {
}
// Templatable trampolines
- template
+
+ template
static AdbcStatusCode CNew(T* obj, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = new ObjectT();
obj->private_data = private_data;
return ADBC_STATUS_OK;
}
- template
+ template
static AdbcStatusCode CRelease(T* obj, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ if (obj == nullptr) return ADBC_STATUS_INVALID_STATE;
auto private_data = reinterpret_cast(obj->private_data);
+ if (private_data == nullptr) return ADBC_STATUS_INVALID_STATE;
AdbcStatusCode result = private_data->Release(error);
if (result != ADBC_STATUS_OK) {
return result;
@@ -555,74 +583,81 @@ class Driver {
return ADBC_STATUS_OK;
}
- template
+ template
static AdbcStatusCode CSetOption(T* obj, const char* key, const char* value,
AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CSetOption<>(key, value, error);
}
- template
+ template >
static AdbcStatusCode CSetOptionBytes(T* obj, const char* key, const uint8_t* value,
size_t length, AdbcError* error) {
auto private_data = reinterpret_cast(obj->private_data);
return private_data->CSetOptionBytes(key, value, length, error);
}
- template
+ template
static AdbcStatusCode CSetOptionInt(T* obj, const char* key, int64_t value,
AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CSetOption<>(key, value, error);
}
- template
+ template
static AdbcStatusCode CSetOptionDouble(T* obj, const char* key, double value,
AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CSetOption<>(key, value, error);
}
- template
+ template
static AdbcStatusCode CGetOption(T* obj, const char* key, char* value, size_t* length,
AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CGetOptionStringLike<>(key, value, length, error);
}
- template
+ template
static AdbcStatusCode CGetOptionBytes(T* obj, const char* key, uint8_t* value,
size_t* length, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CGetOptionStringLike<>(key, value, length, error);
}
- template
+ template
static AdbcStatusCode CGetOptionInt(T* obj, const char* key, int64_t* value,
AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CGetOptionNumeric<>(key, value, error);
}
- template
+ template
static AdbcStatusCode CGetOptionDouble(T* obj, const char* key, double* value,
AdbcError* error) {
+ using ObjectT = ResolveObjectT;
auto private_data = reinterpret_cast(obj->private_data);
return private_data->template CGetOptionNumeric<>(key, value, error);
}
+ // TODO: all trampolines need to check for database
// Database trampolines
static AdbcStatusCode CDatabaseInit(AdbcDatabase* database, AdbcError* error) {
auto private_data = reinterpret_cast(database->private_data);
- private_data->set_driver(database->private_driver);
- return private_data->Init(database->private_driver->private_data, error);
+ return private_data->Init(nullptr, error);
}
// Connection trampolines
static AdbcStatusCode CConnectionInit(AdbcConnection* connection,
AdbcDatabase* database, AdbcError* error) {
auto private_data = reinterpret_cast(connection->private_data);
- private_data->set_driver(connection->private_driver);
return private_data->Init(database->private_data, error);
}
@@ -706,7 +741,6 @@ class Driver {
static AdbcStatusCode CStatementNew(AdbcConnection* connection,
AdbcStatement* statement, AdbcError* error) {
auto private_data = new StatementT();
- private_data->set_driver(connection->private_driver);
AdbcStatusCode status = private_data->Init(connection->private_data, error);
if (status != ADBC_STATUS_OK) {
delete private_data;
@@ -716,6 +750,15 @@ class Driver {
return ADBC_STATUS_OK;
}
+ static AdbcStatusCode CStatementExecutePartitions(AdbcStatement* statement,
+ struct ArrowSchema* schema,
+ struct AdbcPartitions* partitions,
+ int64_t* rows_affected,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->ExecutePartitions(schema, partitions, rows_affected, error);
+ }
+
static AdbcStatusCode CStatementExecuteQuery(AdbcStatement* statement,
ArrowArrayStream* stream,
int64_t* rows_affected, AdbcError* error) {
@@ -729,6 +772,13 @@ class Driver {
return private_data->ExecuteSchema(schema, error);
}
+ static AdbcStatusCode CStatementGetParameterSchema(AdbcStatement* statement,
+ ArrowSchema* schema,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->GetParameterSchema(schema, error);
+ }
+
static AdbcStatusCode CStatementPrepare(AdbcStatement* statement, AdbcError* error) {
auto private_data = reinterpret_cast(statement->private_data);
return private_data->Prepare(error);
@@ -765,6 +815,4 @@ class Driver {
}
};
-} // namespace common
-
-} // namespace adbc
+} // namespace adbc::common
diff --git a/c/driver/common/utils.h b/c/driver/common/utils.h
index ff75fa7208..cab5ddbe28 100644
--- a/c/driver/common/utils.h
+++ b/c/driver/common/utils.h
@@ -119,25 +119,6 @@ AdbcStatusCode BatchToArrayStream(struct ArrowArray* values, struct ArrowSchema*
if (adbc_status_code != ADBC_STATUS_OK) return adbc_status_code; \
} while (0)
-/// \defgroup adbc-connection-utils Connection Utilities
-/// Utilities for implementing connection-related functions for drivers
-///
-/// @{
-AdbcStatusCode AdbcInitConnectionGetInfoSchema(struct ArrowSchema* schema,
- struct ArrowArray* array,
- struct AdbcError* error);
-AdbcStatusCode AdbcConnectionGetInfoAppendString(struct ArrowArray* array,
- uint32_t info_code,
- const char* info_value,
- struct AdbcError* error);
-AdbcStatusCode AdbcConnectionGetInfoAppendInt(struct ArrowArray* array,
- uint32_t info_code, int64_t info_value,
- struct AdbcError* error);
-
-AdbcStatusCode AdbcInitConnectionObjectsSchema(struct ArrowSchema* schema,
- struct AdbcError* error);
-/// @}
-
struct AdbcGetObjectsUsage {
struct ArrowStringView fk_catalog;
struct ArrowStringView fk_db_schema;
diff --git a/c/driver/framework/CMakeLists.txt b/c/driver/framework/CMakeLists.txt
new file mode 100644
index 0000000000..a206e982df
--- /dev/null
+++ b/c/driver/framework/CMakeLists.txt
@@ -0,0 +1,50 @@
+# 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.
+
+include(FetchContent)
+
+# Common library: fmtlib
+fetchcontent_declare(fmt
+ GIT_REPOSITORY https://github.com/fmtlib/fmt.git
+ GIT_TAG 10.2.1)
+fetchcontent_makeavailable(fmt)
+
+add_library(adbc_driver_framework STATIC connection.cc driver.cc objects.cc)
+adbc_configure_target(adbc_driver_framework)
+set_target_properties(adbc_driver_framework PROPERTIES POSITION_INDEPENDENT_CODE ON)
+target_include_directories(adbc_driver_framework
+ PRIVATE "${REPOSITORY_ROOT}" "${REPOSITORY_ROOT}/c/"
+ "${REPOSITORY_ROOT}/c/vendor")
+target_link_libraries(adbc_driver_framework PUBLIC adbc_driver_common fmt::fmt)
+
+# if(ADBC_BUILD_TESTS)
+# add_test_case(driver_framework_test
+# PREFIX
+# adbc
+# EXTRA_LABELS
+# driver-framework
+# SOURCES
+# utils_test.cc
+# driver_test.cc
+# EXTRA_LINK_LIBS
+# adbc_driver_framework
+# nanoarrow)
+# target_compile_features(adbc-driver-framework-test PRIVATE cxx_std_17)
+# target_include_directories(adbc-driver-framework-test
+# PRIVATE "${REPOSITORY_ROOT}" "${REPOSITORY_ROOT}/c/vendor")
+# adbc_configure_target(adbc-driver-framework-test)
+# endif()
diff --git a/c/driver/framework/base.h b/c/driver/framework/base.h
new file mode 100644
index 0000000000..bf60ee5b2d
--- /dev/null
+++ b/c/driver/framework/base.h
@@ -0,0 +1,793 @@
+// 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.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "driver/common/utils.h"
+#include "driver/framework/status.h"
+
+// \file base.h
+namespace adbc::driver {
+
+// Variant that handles the option types that can be get/set by databases,
+// connections, and statements. It currently does not attempt conversion
+// (i.e., getting a double option as a string).
+class Option {
+ public:
+ struct NotFound {};
+ using Value = std::variant, std::vector,
+ int64_t, double>;
+
+ Option() : value_(NotFound{}) {}
+ explicit Option(const char* value)
+ : value_(value ? std::make_optional(std::string(value)) : std::nullopt) {}
+ explicit Option(std::string value) : value_(std::move(value)) {}
+ explicit Option(std::vector value) : value_(std::move(value)) {}
+ explicit Option(double value) : value_(value) {}
+ explicit Option(int64_t value) : value_(value) {}
+
+ const Value& value() const& { return value_; }
+ Value& value() && { return value_; }
+
+ Result AsBool() const {
+ return std::visit(
+ [&](auto&& value) -> Result {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v>) {
+ if (!value.has_value()) {
+ return status::InvalidArgument("Invalid boolean value (NULL)");
+ } else if (*value == ADBC_OPTION_VALUE_ENABLED) {
+ return true;
+ } else if (*value == ADBC_OPTION_VALUE_DISABLED) {
+ return false;
+ }
+ return status::InvalidArgument("Invalid boolean value '{}'", *value);
+ }
+ return status::InvalidArgument("Value must be 'true' or 'false'");
+ },
+ value_);
+ }
+
+ Result> AsString() const {
+ return std::visit(
+ [&](auto&& value) -> Result> {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v>) {
+ return value;
+ }
+ return status::InvalidArgument("Value must be a string");
+ },
+ value_);
+ }
+
+ private:
+ Value value_;
+
+ // Methods used by trampolines to export option values in C below
+ friend class ObjectBase;
+
+ AdbcStatusCode CGet(char* out, size_t* length) const {
+ // TODO: no way to return error
+ if (!out || !length) {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+ return std::visit(
+ [&](auto&& value) {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v>) {
+ if (!value) {
+ *length = 0;
+ return ADBC_STATUS_OK;
+ }
+ size_t value_size_with_terminator = value->size() + 1;
+ if (*length >= value_size_with_terminator) {
+ std::memcpy(out, value->data(), value->size());
+ out[value->size()] = 0;
+ }
+ *length = value_size_with_terminator;
+ return ADBC_STATUS_OK;
+ } else {
+ return ADBC_STATUS_NOT_FOUND;
+ }
+ },
+ value_);
+ }
+
+ AdbcStatusCode CGet(uint8_t* out, size_t* length) const {
+ if (!out || !length) {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+ return std::visit(
+ [&](auto&& value) {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v>) {
+ if (!value) {
+ *length = 0;
+ return ADBC_STATUS_OK;
+ }
+ size_t value_size_with_terminator = value->size() + 1;
+ if (*length >= value_size_with_terminator) {
+ std::memcpy(out, value->data(), value->size());
+ out[value->size()] = 0;
+ }
+ *length = value_size_with_terminator;
+ return ADBC_STATUS_OK;
+ } else if constexpr (std::is_same_v>) {
+ if (*length >= value.size()) {
+ std::memcpy(out, value.data(), value.size());
+ }
+ *length = value.size();
+ return ADBC_STATUS_OK;
+ } else {
+ return ADBC_STATUS_NOT_FOUND;
+ }
+ },
+ value_);
+ }
+
+ AdbcStatusCode CGet(int64_t* out) const {
+ if (!out) {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+ return std::visit(
+ [&](auto&& value) {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ *out = value;
+ return ADBC_STATUS_OK;
+ } else {
+ return ADBC_STATUS_NOT_FOUND;
+ }
+ },
+ value_);
+ }
+
+ AdbcStatusCode CGet(double* out) const {
+ if (!out) {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+ return std::visit(
+ [&](auto&& value) {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v || std::is_same_v) {
+ *out = value;
+ return ADBC_STATUS_OK;
+ } else {
+ return ADBC_STATUS_NOT_FOUND;
+ }
+ },
+ value_);
+ }
+};
+
+// Base class for private_data of AdbcDatabase, AdbcConnection, and AdbcStatement
+// This class handles option setting and getting.
+class ObjectBase {
+ public:
+ ObjectBase() {}
+
+ virtual ~ObjectBase() = default;
+
+ // Called After zero or more SetOption() calls. The parent is the
+ // private_data of the AdbcDatabase, or AdbcConnection when initializing a
+ // subclass of ConnectionObjectBase, and StatementObjectBase (respectively),
+ // or otherwise nullptr. For example, if you have defined
+ // Driver, you can
+ // reinterpret_cast(parent) in MyConnection::Init().
+ virtual AdbcStatusCode Init(void* parent, AdbcError* error) { return ADBC_STATUS_OK; }
+
+ // Called when the corresponding AdbcXXXRelease() function is invoked from C.
+ // Driver authors can override this method to return an error if the object is
+ // not in a valid state (e.g., if a connection has open statements) or to clean
+ // up resources when resource cleanup could fail. Resource cleanup that cannot fail
+ // (e.g., releasing memory) should generally be handled in the deleter.
+ virtual AdbcStatusCode Release(AdbcError* error) { return ADBC_STATUS_OK; }
+
+ // Get an option value.
+ virtual Result> GetOption(std::string_view key) const {
+ return std::nullopt;
+ }
+
+ // Driver authors can override this method to reject options that are not supported or
+ // that are set at a time not supported by the driver (e.g., to reject options that are
+ // set after Init() is called if this is not supported).
+ virtual AdbcStatusCode SetOption(std::string_view key, Option value, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ private:
+ // Let the Driver use these to expose C callables wrapping option setters/getters
+ template
+ friend class Driver;
+
+ template
+ AdbcStatusCode CSetOption(const char* key, T value, AdbcError* error) {
+ Option option(value);
+ return SetOption(key, std::move(option), error);
+ }
+
+ AdbcStatusCode CSetOptionBytes(const char* key, const uint8_t* value, size_t length,
+ AdbcError* error) {
+ std::vector cppvalue(value, value + length);
+ Option option(std::move(cppvalue));
+ return SetOption(key, std::move(option), error);
+ }
+
+ template
+ AdbcStatusCode CGetOptionStringLike(const char* key, T* value, size_t* length,
+ AdbcError* error) const {
+ RAISE_RESULT(error, auto option, GetOption(key));
+ if (option.has_value()) {
+ // TODO: pass error through
+ return option->CGet(value, length);
+ } else {
+ SetError(error, "option '%s' not found", key);
+ return ADBC_STATUS_NOT_FOUND;
+ }
+ }
+
+ template
+ AdbcStatusCode CGetOptionNumeric(const char* key, T* value, AdbcError* error) const {
+ RAISE_RESULT(error, auto option, GetOption(key));
+ if (option.has_value()) {
+ // TODO: pass error through
+ return option->CGet(value);
+ } else {
+ SetError(error, "option '%s' not found", key);
+ return ADBC_STATUS_NOT_FOUND;
+ }
+ }
+};
+
+// Driver authors can subclass DatabaseObjectBase to track driver-specific
+// state pertaining to the AdbcDatbase. The private_data member of an
+// AdbcDatabase initialized by the driver will be a pointer to the
+// subclass of DatabaseObjectBase.
+class DatabaseObjectBase : public ObjectBase {
+ public:
+ // (there are no database functions other than option getting/setting)
+};
+
+// Driver authors can subclass ConnectionObjectBase to track driver-specific
+// state pertaining to the AdbcConnection. The private_data member of an
+// AdbcConnection initialized by the driver will be a pointer to the
+// subclass of ConnectionObjectBase. Driver authors can override methods to
+// implement the corresponding ConnectionXXX driver methods.
+class ConnectionObjectBase : public ObjectBase {
+ public:
+ virtual AdbcStatusCode Cancel(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
+
+ virtual AdbcStatusCode Commit(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
+
+ virtual AdbcStatusCode GetInfo(const uint32_t* info_codes, size_t info_codes_length,
+ ArrowArrayStream* out, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode GetObjects(int depth, const char* catalog, const char* db_schema,
+ const char* table_name, const char** table_type,
+ const char* column_name, ArrowArrayStream* out,
+ AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode GetStatistics(const char* catalog, const char* db_schema,
+ const char* table_name, char approximate,
+ ArrowArrayStream* out, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode GetStatisticNames(ArrowArrayStream* out, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode GetTableSchema(const char* catalog, const char* db_schema,
+ const char* table_name, ArrowSchema* schema,
+ AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode GetTableTypes(ArrowArrayStream* out, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode ReadPartition(const uint8_t* serialized_partition,
+ size_t serialized_length, ArrowArrayStream* out,
+ AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode Rollback(AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+};
+
+// Driver authors can subclass StatementObjectBase to track driver-specific
+// state pertaining to the AdbcStatement. The private_data member of an
+// AdbcStatement initialized by the driver will be a pointer to the
+// subclass of StatementObjectBase. Driver authors can override methods to
+// implement the corresponding StatementXXX driver methods.
+class StatementObjectBase : public ObjectBase {
+ public:
+ AdbcStatusCode GetParameterSchema(struct ArrowSchema* schema, struct AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode ExecutePartitions(struct ArrowSchema* schema,
+ struct AdbcPartitions* partitions,
+ int64_t* rows_affected, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* rows_affected,
+ AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode ExecuteSchema(ArrowSchema* schema, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode Prepare(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
+
+ virtual AdbcStatusCode SetSqlQuery(const char* query, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode SetSubstraitPlan(const uint8_t* plan, size_t length,
+ AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode Bind(ArrowArray* values, ArrowSchema* schema, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode BindStream(ArrowArrayStream* stream, AdbcError* error) {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+
+ virtual AdbcStatusCode Cancel(AdbcError* error) { return ADBC_STATUS_NOT_IMPLEMENTED; }
+};
+
+template
+struct ResolveObjectTImpl {};
+
+template
+struct ResolveObjectTImpl {
+ using type = DatabaseT;
+};
+template
+struct ResolveObjectTImpl {
+ using type = ConnectionT;
+};
+template
+struct ResolveObjectTImpl {
+ using type = StatementT;
+};
+
+template
+using ResolveObjectT =
+ typename ResolveObjectTImpl::type;
+
+// Driver authors can declare a template specialization of the Driver class
+// and use it to provide their driver init function. It is possible, but
+// rarely useful, to subclass a driver.
+template
+class Driver {
+ public:
+ static AdbcStatusCode Init(int version, void* raw_driver, AdbcError* error) {
+ // TODO: support 1_0_0
+ if (version != ADBC_VERSION_1_1_0) return ADBC_STATUS_NOT_IMPLEMENTED;
+ AdbcDriver* driver = (AdbcDriver*)raw_driver;
+ std::memset(driver, 0, sizeof(AdbcDriver));
+
+ // Driver lifecycle
+ driver->private_data = new Driver();
+ driver->release = &CDriverRelease;
+
+ // Driver functions
+ driver->ErrorGetDetailCount = &CErrorGetDetailCount;
+ driver->ErrorGetDetail = &CErrorGetDetail;
+
+ // Database lifecycle
+ driver->DatabaseNew = &CNew;
+ driver->DatabaseInit = &CDatabaseInit;
+ driver->DatabaseRelease = &CRelease;
+
+ // Database functions
+ driver->DatabaseSetOption = &CSetOption;
+ driver->DatabaseSetOptionBytes = &CSetOptionBytes;
+ driver->DatabaseSetOptionInt = &CSetOptionInt;
+ driver->DatabaseSetOptionDouble = &CSetOptionDouble;
+ driver->DatabaseGetOption = &CGetOption;
+ driver->DatabaseGetOptionBytes = &CGetOptionBytes;
+ driver->DatabaseGetOptionInt = &CGetOptionInt;
+ driver->DatabaseGetOptionDouble = &CGetOptionDouble;
+
+ // Connection lifecycle
+ driver->ConnectionNew = &CNew;
+ driver->ConnectionInit = &CConnectionInit;
+ driver->ConnectionRelease = &CRelease;
+
+ // Connection functions
+ driver->ConnectionSetOption = &CSetOption;
+ driver->ConnectionSetOptionBytes = &CSetOptionBytes;
+ driver->ConnectionSetOptionInt = &CSetOptionInt;
+ driver->ConnectionSetOptionDouble = &CSetOptionDouble;
+ driver->ConnectionGetOption = &CGetOption;
+ driver->ConnectionGetOptionBytes = &CGetOptionBytes;
+ driver->ConnectionGetOptionInt = &CGetOptionInt;
+ driver->ConnectionGetOptionDouble = &CGetOptionDouble;
+ driver->ConnectionCommit = &CConnectionCommit;
+ driver->ConnectionGetInfo = &CConnectionGetInfo;
+ driver->ConnectionGetObjects = &CConnectionGetObjects;
+ driver->ConnectionGetTableSchema = &CConnectionGetTableSchema;
+ driver->ConnectionGetTableTypes = &CConnectionGetTableTypes;
+ driver->ConnectionReadPartition = &CConnectionReadPartition;
+ driver->ConnectionRollback = &CConnectionRollback;
+ driver->ConnectionCancel = &CConnectionCancel;
+ driver->ConnectionGetStatistics = &CConnectionGetStatistics;
+ driver->ConnectionGetStatisticNames = &CConnectionGetStatisticNames;
+
+ // Statement lifecycle
+ driver->StatementNew = &CStatementNew;
+ driver->StatementRelease = &CRelease;
+
+ // Statement functions
+ driver->StatementSetOption = &CSetOption;
+ driver->StatementSetOptionBytes = &CSetOptionBytes;
+ driver->StatementSetOptionInt = &CSetOptionInt;
+ driver->StatementSetOptionDouble = &CSetOptionDouble;
+ driver->StatementGetOption = &CGetOption;
+ driver->StatementGetOptionBytes = &CGetOptionBytes;
+ driver->StatementGetOptionInt = &CGetOptionInt;
+ driver->StatementGetOptionDouble = &CGetOptionDouble;
+
+ driver->StatementExecuteQuery = &CStatementExecuteQuery;
+ driver->StatementExecuteSchema = &CStatementExecuteSchema;
+ driver->StatementPrepare = &CStatementPrepare;
+ driver->StatementSetSqlQuery = &CStatementSetSqlQuery;
+ driver->StatementSetSubstraitPlan = &CStatementSetSubstraitPlan;
+ driver->StatementBind = &CStatementBind;
+ driver->StatementBindStream = &CStatementBindStream;
+ driver->StatementCancel = &CStatementCancel;
+
+ return ADBC_STATUS_OK;
+ }
+
+ // Driver trampolines
+ static AdbcStatusCode CDriverRelease(AdbcDriver* driver, AdbcError* error) {
+ auto driver_private = reinterpret_cast(driver->private_data);
+ delete driver_private;
+ driver->private_data = nullptr;
+ return ADBC_STATUS_OK;
+ }
+
+ static int CErrorGetDetailCount(const AdbcError* error) {
+ if (error->vendor_code != ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) {
+ return 0;
+ }
+
+ auto error_obj = reinterpret_cast(error->private_data);
+ return error_obj->CDetailCount();
+ }
+
+ static AdbcErrorDetail CErrorGetDetail(const AdbcError* error, int index) {
+ auto error_obj = reinterpret_cast(error->private_data);
+ return error_obj->CDetail(index);
+ }
+
+ // Templatable trampolines
+
+ template
+ static AdbcStatusCode CNew(T* obj, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = new ObjectT();
+ obj->private_data = private_data;
+ return ADBC_STATUS_OK;
+ }
+
+ template
+ static AdbcStatusCode CRelease(T* obj, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ if (obj == nullptr) return ADBC_STATUS_INVALID_STATE;
+ auto private_data = reinterpret_cast(obj->private_data);
+ if (private_data == nullptr) return ADBC_STATUS_INVALID_STATE;
+ AdbcStatusCode result = private_data->Release(error);
+ if (result != ADBC_STATUS_OK) {
+ return result;
+ }
+
+ delete private_data;
+ obj->private_data = nullptr;
+ return ADBC_STATUS_OK;
+ }
+
+ template
+ static AdbcStatusCode CSetOption(T* obj, const char* key, const char* value,
+ AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CSetOption<>(key, value, error);
+ }
+
+ template
+ static AdbcStatusCode CSetOptionBytes(T* obj, const char* key, const uint8_t* value,
+ size_t length, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->CSetOptionBytes(key, value, length, error);
+ }
+
+ template
+ static AdbcStatusCode CSetOptionInt(T* obj, const char* key, int64_t value,
+ AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CSetOption<>(key, value, error);
+ }
+
+ template
+ static AdbcStatusCode CSetOptionDouble(T* obj, const char* key, double value,
+ AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CSetOption<>(key, value, error);
+ }
+
+ template
+ static AdbcStatusCode CGetOption(T* obj, const char* key, char* value, size_t* length,
+ AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CGetOptionStringLike<>(key, value, length, error);
+ }
+
+ template
+ static AdbcStatusCode CGetOptionBytes(T* obj, const char* key, uint8_t* value,
+ size_t* length, AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CGetOptionStringLike<>(key, value, length, error);
+ }
+
+ template
+ static AdbcStatusCode CGetOptionInt(T* obj, const char* key, int64_t* value,
+ AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CGetOptionNumeric<>(key, value, error);
+ }
+
+ template
+ static AdbcStatusCode CGetOptionDouble(T* obj, const char* key, double* value,
+ AdbcError* error) {
+ using ObjectT = ResolveObjectT;
+ auto private_data = reinterpret_cast(obj->private_data);
+ return private_data->template CGetOptionNumeric<>(key, value, error);
+ }
+ // TODO: all trampolines need to check for database
+
+ // Database trampolines
+ static AdbcStatusCode CDatabaseInit(AdbcDatabase* database, AdbcError* error) {
+ auto private_data = reinterpret_cast(database->private_data);
+ return private_data->Init(nullptr, error);
+ }
+
+ // Connection trampolines
+ static AdbcStatusCode CConnectionInit(AdbcConnection* connection,
+ AdbcDatabase* database, AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->Init(database->private_data, error);
+ }
+
+ static AdbcStatusCode CConnectionCancel(AdbcConnection* connection, AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->Cancel(error);
+ }
+
+ static AdbcStatusCode CConnectionGetInfo(AdbcConnection* connection,
+ const uint32_t* info_codes,
+ size_t info_codes_length,
+ ArrowArrayStream* out, AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->GetInfo(info_codes, info_codes_length, out, error);
+ }
+
+ static AdbcStatusCode CConnectionGetObjects(AdbcConnection* connection, int depth,
+ const char* catalog, const char* db_schema,
+ const char* table_name,
+ const char** table_type,
+ const char* column_name,
+ ArrowArrayStream* out, AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->GetObjects(depth, catalog, db_schema, table_name, table_type,
+ column_name, out, error);
+ }
+
+ static AdbcStatusCode CConnectionGetStatistics(
+ AdbcConnection* connection, const char* catalog, const char* db_schema,
+ const char* table_name, char approximate, ArrowArrayStream* out, AdbcError* error) {
+ auto private_data = reinterpret_cast