From 264d15463f590991d9e594fdcf661740f553648e 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.
---
CONTRIBUTING.md | 2 +
LICENSE.txt | 26 +
c/CMakeLists.txt | 3 +
c/driver/common/utils.h | 19 -
c/driver/framework/CMakeLists.txt | 44 +
c/driver/framework/base.h | 797 +++
c/driver/framework/base_database.h | 81 +
c/driver/framework/catalog.cc | 263 +
c/driver/framework/catalog.h | 145 +
c/driver/framework/driver.cc | 527 ++
c/driver/framework/driver.h | 202 +
c/driver/framework/objects.cc | 305 +
c/driver/framework/objects.h | 39 +
c/driver/framework/status.h | 253 +
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 | 1414 +++++
c/driver/sqlite/sqlite_test.cc | 9 +-
c/driver_manager/adbc_driver_manager_test.cc | 6 +-
c/validation/adbc_validation_connection.cc | 27 +-
c/vendor/fmt/.clang-format | 8 +
c/vendor/fmt/CMakeLists.txt | 453 ++
c/vendor/fmt/CONTRIBUTING.md | 20 +
c/vendor/fmt/ChangeLog.md | 5533 +++++++++++++++++
c/vendor/fmt/LICENSE | 27 +
c/vendor/fmt/README.md | 490 ++
c/vendor/fmt/include/fmt/args.h | 235 +
c/vendor/fmt/include/fmt/chrono.h | 2240 +++++++
c/vendor/fmt/include/fmt/color.h | 643 ++
c/vendor/fmt/include/fmt/compile.h | 535 ++
c/vendor/fmt/include/fmt/core.h | 2969 +++++++++
c/vendor/fmt/include/fmt/format-inl.h | 1678 +++++
c/vendor/fmt/include/fmt/format.h | 4535 ++++++++++++++
c/vendor/fmt/include/fmt/os.h | 455 ++
c/vendor/fmt/include/fmt/ostream.h | 245 +
c/vendor/fmt/include/fmt/printf.h | 675 ++
c/vendor/fmt/include/fmt/ranges.h | 738 +++
c/vendor/fmt/include/fmt/std.h | 537 ++
c/vendor/fmt/include/fmt/xchar.h | 259 +
c/vendor/fmt/src/fmt.cc | 108 +
c/vendor/fmt/src/format.cc | 43 +
c/vendor/fmt/src/os.cc | 402 ++
c/vendor/fmt/support/cmake/FindSetEnv.cmake | 7 +
c/vendor/fmt/support/cmake/JoinPaths.cmake | 26 +
.../fmt/support/cmake/fmt-config.cmake.in | 7 +
c/vendor/fmt/support/cmake/fmt.pc.in | 11 +
c/vendor/vendor_fmt.sh | 28 +
dev/release/rat_exclude_files.txt | 1 +
license.tpl | 26 +
51 files changed, 27112 insertions(+), 51 deletions(-)
create mode 100644 c/driver/framework/CMakeLists.txt
create mode 100644 c/driver/framework/base.h
create mode 100644 c/driver/framework/base_database.h
create mode 100644 c/driver/framework/catalog.cc
create mode 100644 c/driver/framework/catalog.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
create mode 100644 c/vendor/fmt/.clang-format
create mode 100644 c/vendor/fmt/CMakeLists.txt
create mode 100644 c/vendor/fmt/CONTRIBUTING.md
create mode 100644 c/vendor/fmt/ChangeLog.md
create mode 100644 c/vendor/fmt/LICENSE
create mode 100644 c/vendor/fmt/README.md
create mode 100644 c/vendor/fmt/include/fmt/args.h
create mode 100644 c/vendor/fmt/include/fmt/chrono.h
create mode 100644 c/vendor/fmt/include/fmt/color.h
create mode 100644 c/vendor/fmt/include/fmt/compile.h
create mode 100644 c/vendor/fmt/include/fmt/core.h
create mode 100644 c/vendor/fmt/include/fmt/format-inl.h
create mode 100644 c/vendor/fmt/include/fmt/format.h
create mode 100644 c/vendor/fmt/include/fmt/os.h
create mode 100644 c/vendor/fmt/include/fmt/ostream.h
create mode 100644 c/vendor/fmt/include/fmt/printf.h
create mode 100644 c/vendor/fmt/include/fmt/ranges.h
create mode 100644 c/vendor/fmt/include/fmt/std.h
create mode 100644 c/vendor/fmt/include/fmt/xchar.h
create mode 100644 c/vendor/fmt/src/fmt.cc
create mode 100644 c/vendor/fmt/src/format.cc
create mode 100644 c/vendor/fmt/src/os.cc
create mode 100644 c/vendor/fmt/support/cmake/FindSetEnv.cmake
create mode 100644 c/vendor/fmt/support/cmake/JoinPaths.cmake
create mode 100644 c/vendor/fmt/support/cmake/fmt-config.cmake.in
create mode 100644 c/vendor/fmt/support/cmake/fmt.pc.in
create mode 100755 c/vendor/vendor_fmt.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c1cd405e5d..97277c48c9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -406,7 +406,9 @@ $ cd go/adbc && go-licenses report ./... \
--ignore github.com/apache/arrow/go/v11 \
--ignore github.com/apache/arrow/go/v12 \
--ignore github.com/apache/arrow/go/v13 \
+ --ignore github.com/apache/arrow/go/v14 \
--ignore github.com/apache/arrow/go/v15 \
+ --ignore github.com/apache/arrow/go/v16 \
--template ../../license.tpl > ../../LICENSE.txt 2> /dev/null
```
diff --git a/LICENSE.txt b/LICENSE.txt
index 49f8560e32..6b1a9f794b 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -246,6 +246,32 @@ All rights reserved.
--------------------------------------------------------------------------------
+3rdparty dependency {fmt} is statically linked in certain binary
+distributions, like the Python wheels. {fmt} is under the MIT license:
+
+Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--------------------------------------------------------------------------------
+
3rdparty dependency Go is statically linked in certain binary distributions,
like the Python wheels. The Go project is under the BSD 3-clause license +
PATENTS weak patent termination clause
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index 21d399eee8..06be814267 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -28,8 +28,11 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(CTest)
+add_subdirectory(vendor/fmt EXCLUDE_FROM_ALL)
+set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON)
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/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..3fc905ecee
--- /dev/null
+++ b/c/driver/framework/CMakeLists.txt
@@ -0,0 +1,44 @@
+# 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)
+
+add_library(adbc_driver_framework STATIC catalog.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..aaa4202138
--- /dev/null
+++ b/c/driver/framework/base.h
@@ -0,0 +1,797 @@
+// 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 driver.h ADBC Driver Framework
+///
+/// A base implementation of an ADBC driver that allows easier driver
+/// development just by overriding functions.
+namespace adbc::driver {
+
+/// \brief A typed option value wrapper. It currently does not attempt
+/// conversion (i.e., getting a double option as a string).
+class Option {
+ public:
+ /// The option is unset.
+ struct NotFound {};
+ // TODO: use NotFound instead of std::nullopt, and rename to Unset or something.
+ 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(connection->private_data);
+ return private_data->GetStatistics(catalog, db_schema, table_name, approximate, out,
+ error);
+ }
+
+ static AdbcStatusCode CConnectionGetStatisticNames(AdbcConnection* connection,
+ ArrowArrayStream* out,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->GetStatisticNames(out, error);
+ }
+
+ static AdbcStatusCode CConnectionGetTableSchema(AdbcConnection* connection,
+ const char* catalog,
+ const char* db_schema,
+ const char* table_name,
+ ArrowSchema* schema, AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->GetTableSchema(catalog, db_schema, table_name, schema, error);
+ }
+
+ static AdbcStatusCode CConnectionGetTableTypes(AdbcConnection* connection,
+ ArrowArrayStream* out,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->GetTableTypes(out, error);
+ }
+
+ static AdbcStatusCode CConnectionReadPartition(AdbcConnection* connection,
+ const uint8_t* serialized_partition,
+ size_t serialized_length,
+ ArrowArrayStream* out,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->ReadPartition(serialized_partition, serialized_length, out,
+ error);
+ }
+
+ static AdbcStatusCode CConnectionCommit(AdbcConnection* connection, AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->Commit(error);
+ }
+
+ static AdbcStatusCode CConnectionRollback(AdbcConnection* connection,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(connection->private_data);
+ return private_data->Rollback(error);
+ }
+
+ // Statement trampolines
+ static AdbcStatusCode CStatementNew(AdbcConnection* connection,
+ AdbcStatement* statement, AdbcError* error) {
+ auto private_data = new StatementT();
+ AdbcStatusCode status = private_data->Init(connection->private_data, error);
+ if (status != ADBC_STATUS_OK) {
+ delete private_data;
+ }
+
+ statement->private_data = private_data;
+ 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) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->ExecuteQuery(stream, rows_affected, error);
+ }
+
+ static AdbcStatusCode CStatementExecuteSchema(AdbcStatement* statement,
+ ArrowSchema* schema, AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ 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);
+ }
+
+ static AdbcStatusCode CStatementSetSqlQuery(AdbcStatement* statement, const char* query,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->SetSqlQuery(query, error);
+ }
+
+ static AdbcStatusCode CStatementSetSubstraitPlan(AdbcStatement* statement,
+ const uint8_t* plan, size_t length,
+ AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->SetSubstraitPlan(plan, length, error);
+ }
+
+ static AdbcStatusCode CStatementBind(AdbcStatement* statement, ArrowArray* values,
+ ArrowSchema* schema, AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->Bind(values, schema, error);
+ }
+
+ static AdbcStatusCode CStatementBindStream(AdbcStatement* statement,
+ ArrowArrayStream* stream, AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->BindStream(stream, error);
+ }
+
+ static AdbcStatusCode CStatementCancel(AdbcStatement* statement, AdbcError* error) {
+ auto private_data = reinterpret_cast(statement->private_data);
+ return private_data->Cancel(error);
+ }
+};
+
+} // namespace adbc::driver
+
+template <>
+struct fmt::formatter : fmt::nested_formatter {
+ auto format(const adbc::driver::Option& option, fmt::format_context& ctx) {
+ return write_padded(ctx, [=](auto out) {
+ return std::visit(
+ [&](auto&& value) {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ return fmt::format_to(out, "(missing option)");
+ } else if constexpr (std::is_same_v>) {
+ if (value) {
+ return fmt::format_to(out, "'{}'", *value);
+ } else {
+ return fmt::format_to(out, "(NULL)");
+ }
+ } else if constexpr (std::is_same_v>) {
+ return fmt::format_to(out, "({} bytes)", value.size());
+ } else {
+ return fmt::format_to(out, "{}", value);
+ }
+ },
+ option.value());
+ });
+ }
+};
diff --git a/c/driver/framework/base_database.h b/c/driver/framework/base_database.h
new file mode 100644
index 0000000000..edb6c4b6dd
--- /dev/null
+++ b/c/driver/framework/base_database.h
@@ -0,0 +1,81 @@
+// 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 "driver/framework/driver.h"
+#include "driver/framework/status.h"
+
+namespace adbc::driver {
+/// \brief The CRTP base implementation of an AdbcDatabase.
+///
+/// Derived should override and implement the Impl methods, but not others.
+/// Overridden methods should defer to the superclass version at the end.
+/// (The Base typedef is provided to make this easier.) Derived should also
+/// define a constexpr static symbol called kErrorPrefix that is used to
+/// construct error messages.
+template
+class DatabaseBase : public ObjectBase {
+ public:
+ using Base = driver::DatabaseBase;
+
+ DatabaseBase() : ObjectBase(), lifecycle_state_(LifecycleState::kUninitialized) {}
+
+ /// \internal
+ AdbcStatusCode Init(void* parent, AdbcError* error) override {
+ lifecycle_state_ = LifecycleState::kInitialized;
+ if (auto status = impl().InitImpl(); !status.ok()) {
+ return status.ToAdbc(error);
+ }
+ return ObjectBase::Init(parent, error);
+ }
+
+ /// \internal
+ AdbcStatusCode Release(AdbcError* error) override {
+ return impl().ReleaseImpl().ToAdbc(error);
+ }
+
+ /// \internal
+ AdbcStatusCode SetOption(std::string_view key, Option value,
+ AdbcError* error) override {
+ return impl().SetOptionImpl(key, std::move(value)).ToAdbc(error);
+ }
+
+ /// \brief Initialize the database.
+ virtual Status InitImpl() { return status::kOk; }
+
+ /// \brief Release the database.
+ virtual Status ReleaseImpl() { return status::kOk; }
+
+ /// \brief Set an option. May be called prior to InitImpl.
+ virtual Status SetOptionImpl(std::string_view key, Option value) {
+ return status::NotImplemented("{} Unknown database option {}={}",
+ Derived::kErrorPrefix, key, value);
+ }
+
+ protected:
+ LifecycleState lifecycle_state_;
+
+ private:
+ Derived& impl() { return static_cast(*this); }
+};
+} // namespace adbc::driver
diff --git a/c/driver/framework/catalog.cc b/c/driver/framework/catalog.cc
new file mode 100644
index 0000000000..5c5b791e49
--- /dev/null
+++ b/c/driver/framework/catalog.cc
@@ -0,0 +1,263 @@
+// 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 "driver/framework/catalog.h"
+
+#include
+
+namespace adbc::driver {
+Status AdbcInitConnectionGetInfoSchema(struct ArrowSchema* schema,
+ struct ArrowArray* array) {
+ ArrowSchemaInit(schema);
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetTypeStruct(schema, /*num_columns=*/2));
+
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetType(schema->children[0], NANOARROW_TYPE_UINT32));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(schema->children[0], "info_name"));
+ schema->children[0]->flags &= ~ARROW_FLAG_NULLABLE;
+
+ struct ArrowSchema* info_value = schema->children[1];
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetTypeUnion(info_value, NANOARROW_TYPE_DENSE_UNION, 6));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(info_value, "info_value"));
+
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(info_value->children[0], "string_value"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[1], NANOARROW_TYPE_BOOL));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(info_value->children[1], "bool_value"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[2], NANOARROW_TYPE_INT64));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(info_value->children[2], "int64_value"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[3], NANOARROW_TYPE_INT32));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(info_value->children[3], "int32_bitmask"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[4], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(info_value->children[4], "string_list"));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetType(info_value->children[5], NANOARROW_TYPE_MAP));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(info_value->children[5], "int32_to_int32_list_map"));
+
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetType(info_value->children[4]->children[0],
+ NANOARROW_TYPE_STRING));
+
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[5]->children[0]->children[0],
+ NANOARROW_TYPE_INT32));
+ info_value->children[5]->children[0]->children[0]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(info_value->children[5]->children[0]->children[1],
+ NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(
+ Internal,
+ ArrowSchemaSetType(info_value->children[5]->children[0]->children[1]->children[0],
+ NANOARROW_TYPE_INT32));
+
+ struct ArrowError na_error = {0};
+ UNWRAP_NANOARROW(na_error, Internal,
+ ArrowArrayInitFromSchema(array, schema, &na_error));
+ UNWRAP_ERRNO(Internal, ArrowArrayStartAppending(array));
+
+ return status::kOk;
+}
+
+Status AdbcConnectionGetInfoAppendString(struct ArrowArray* array, uint32_t info_code,
+ std::string_view info_value) {
+ UNWRAP_ERRNO(Internal, ArrowArrayAppendUInt(array->children[0], info_code));
+ // Append to type variant
+ struct ArrowStringView value;
+ value.data = info_value.data();
+ value.size_bytes = static_cast(info_value.size());
+ UNWRAP_ERRNO(Internal, ArrowArrayAppendString(array->children[1]->children[0], value));
+ // Append type code/offset
+ UNWRAP_ERRNO(Internal, ArrowArrayFinishUnionElement(array->children[1], /*type_id=*/0));
+ return status::kOk;
+}
+
+Status AdbcConnectionGetInfoAppendInt(struct ArrowArray* array, uint32_t info_code,
+ int64_t info_value) {
+ UNWRAP_ERRNO(Internal, ArrowArrayAppendUInt(array->children[0], info_code));
+ // Append to type variant
+ UNWRAP_ERRNO(Internal,
+ ArrowArrayAppendInt(array->children[1]->children[2], info_value));
+ // Append type code/offset
+ UNWRAP_ERRNO(Internal, ArrowArrayFinishUnionElement(array->children[1], /*type_id=*/2));
+ return status::kOk;
+}
+
+Status AdbcInitConnectionObjectsSchema(struct ArrowSchema* schema) {
+ ArrowSchemaInit(schema);
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetTypeStruct(schema, /*num_columns=*/2));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetType(schema->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(schema->children[0], "catalog_name"));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetType(schema->children[1], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(schema->children[1], "catalog_db_schemas"));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetTypeStruct(schema->children[1]->children[0], 2));
+
+ struct ArrowSchema* db_schema_schema = schema->children[1]->children[0];
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(db_schema_schema->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(db_schema_schema->children[0], "db_schema_name"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(db_schema_schema->children[1], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(db_schema_schema->children[1], "db_schema_tables"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetTypeStruct(db_schema_schema->children[1]->children[0], 4));
+
+ struct ArrowSchema* table_schema = db_schema_schema->children[1]->children[0];
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(table_schema->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(table_schema->children[0], "table_name"));
+ table_schema->children[0]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(table_schema->children[1], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(table_schema->children[1], "table_type"));
+ table_schema->children[1]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(table_schema->children[2], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(table_schema->children[2], "table_columns"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetTypeStruct(table_schema->children[2]->children[0], 19));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(table_schema->children[3], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(table_schema->children[3], "table_constraints"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetTypeStruct(table_schema->children[3]->children[0], 4));
+
+ struct ArrowSchema* column_schema = table_schema->children[2]->children[0];
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(column_schema->children[0], "column_name"));
+ column_schema->children[0]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[1], NANOARROW_TYPE_INT32));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[1], "ordinal_position"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[2], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(column_schema->children[2], "remarks"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[3], NANOARROW_TYPE_INT16));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[3], "xdbc_data_type"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[4], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[4], "xdbc_type_name"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[5], NANOARROW_TYPE_INT32));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[5], "xdbc_column_size"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[6], NANOARROW_TYPE_INT16));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[6], "xdbc_decimal_digits"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[7], NANOARROW_TYPE_INT16));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[7], "xdbc_num_prec_radix"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[8], NANOARROW_TYPE_INT16));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(column_schema->children[8], "xdbc_nullable"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[9], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[9], "xdbc_column_def"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[10], NANOARROW_TYPE_INT16));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[10], "xdbc_sql_data_type"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[11], NANOARROW_TYPE_INT16));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[11], "xdbc_datetime_sub"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[12], NANOARROW_TYPE_INT32));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[12], "xdbc_char_octet_length"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[13], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[13], "xdbc_is_nullable"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[14], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[14], "xdbc_scope_catalog"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[15], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[15], "xdbc_scope_schema"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[16], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[16], "xdbc_scope_table"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[17], NANOARROW_TYPE_BOOL));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(column_schema->children[17], "xdbc_is_autoincrement"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(column_schema->children[18], NANOARROW_TYPE_BOOL));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(column_schema->children[18],
+ "xdbc_is_generatedcolumn"));
+
+ struct ArrowSchema* constraint_schema = table_schema->children[3]->children[0];
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(constraint_schema->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(constraint_schema->children[0], "constraint_name"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(constraint_schema->children[1], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetName(constraint_schema->children[1], "constraint_type"));
+ constraint_schema->children[1]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(constraint_schema->children[2], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(constraint_schema->children[2],
+ "constraint_column_names"));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetType(constraint_schema->children[2]->children[0],
+ NANOARROW_TYPE_STRING));
+ constraint_schema->children[2]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(constraint_schema->children[3], NANOARROW_TYPE_LIST));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(constraint_schema->children[3],
+ "constraint_column_usage"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetTypeStruct(constraint_schema->children[3]->children[0], 4));
+
+ struct ArrowSchema* usage_schema = constraint_schema->children[3]->children[0];
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(usage_schema->children[0], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(usage_schema->children[0], "fk_catalog"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(usage_schema->children[1], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(usage_schema->children[1], "fk_db_schema"));
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(usage_schema->children[2], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(usage_schema->children[2], "fk_table"));
+ usage_schema->children[2]->flags &= ~ARROW_FLAG_NULLABLE;
+ UNWRAP_ERRNO(Internal,
+ ArrowSchemaSetType(usage_schema->children[3], NANOARROW_TYPE_STRING));
+ UNWRAP_ERRNO(Internal, ArrowSchemaSetName(usage_schema->children[3], "fk_column_name"));
+ usage_schema->children[3]->flags &= ~ARROW_FLAG_NULLABLE;
+
+ return status::kOk;
+}
+} // namespace adbc::driver
diff --git a/c/driver/framework/catalog.h b/c/driver/framework/catalog.h
new file mode 100644
index 0000000000..be868447ae
--- /dev/null
+++ b/c/driver/framework/catalog.h
@@ -0,0 +1,145 @@
+// 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 "driver/framework/status.h"
+
+namespace adbc::driver {
+
+/// \defgroup adbc-framework-catalog Catalog Utilities
+/// Utilities for implementing catalog/metadata-related functions.
+///
+/// @{
+
+/// \brief Helper to implement GetObjects.
+struct GetObjectsHelper {
+ virtual ~GetObjectsHelper() = default;
+
+ struct Table {
+ std::string_view name;
+ std::string_view type;
+ };
+
+ struct ColumnXdbc {
+ int16_t xdbc_data_type;
+ std::string_view xdbc_type_name;
+ int32_t xdbc_column_size;
+ int16_t xdbc_decimal_digits;
+ int16_t xdbc_num_prec_radix;
+ int16_t xdbc_nullable;
+ std::string_view xdbc_column_def;
+ int16_t xdbc_sql_data_type;
+ int16_t xdbc_datetime_sub;
+ int32_t xdbc_char_octet_length;
+ std::string_view xdbc_is_nullable;
+ std::string_view xdbc_scope_catalog;
+ std::string_view xdbc_scope_schema;
+ std::string_view xdbc_scope_table;
+ bool xdbc_is_autoincrement;
+ bool xdbc_is_generatedcolumn;
+ };
+
+ struct Column {
+ std::string_view column_name;
+ int32_t ordinal_position;
+ std::optional remarks;
+ std::optional xdbc;
+ };
+
+ struct ConstraintUsage {
+ std::optional catalog;
+ std::optional schema;
+ std::string_view table;
+ std::string_view column;
+ };
+
+ struct Constraint {
+ std::optional name;
+ std::string_view type;
+ std::vector column_names;
+ std::vector usage;
+ };
+
+ Status Close() { return status::kOk; }
+
+ /// \brief Fetch all metadata needed. The driver is free to delay loading
+ /// but this gives it a chance to load data up front.
+ virtual Status Load(int depth, std::optional catalog_filter,
+ std::optional schema_filter,
+ std::optional table_filter,
+ std::optional column_filter,
+ std::vector table_types) {
+ return status::NotImplemented("GetObjects");
+ }
+
+ virtual Status LoadCatalogs() {
+ return status::NotImplemented("GetObjects at depth = catalog");
+ };
+
+ virtual Result> NextCatalog() { return std::nullopt; }
+
+ virtual Status LoadSchemas(std::string_view catalog) {
+ return status::NotImplemented("GetObjects at depth = schema");
+ };
+
+ virtual Result> NextSchema() { return std::nullopt; }
+
+ virtual Status LoadTables(std::string_view catalog, std::string_view schema) {
+ return status::NotImplemented("GetObjects at depth = table");
+ };
+
+ virtual Result> NextTable() { return std::nullopt; }
+
+ virtual Status LoadColumns(std::string_view catalog, std::string_view schema,
+ std::string_view table) {
+ return status::NotImplemented("GetObjects at depth = column");
+ };
+
+ virtual Result> NextColumn() { return std::nullopt; }
+
+ virtual Result> NextConstraint() { return std::nullopt; }
+};
+
+struct InfoValue {
+ uint32_t code;
+ std::variant value;
+
+ explicit InfoValue(uint32_t code, std::variant value)
+ : code(code), value(std::move(value)) {}
+};
+
+Status AdbcInitConnectionGetInfoSchema(struct ArrowSchema* schema,
+ struct ArrowArray* array);
+Status AdbcConnectionGetInfoAppendString(struct ArrowArray* array, uint32_t info_code,
+ std::string_view info_value);
+Status AdbcConnectionGetInfoAppendInt(struct ArrowArray* array, uint32_t info_code,
+ int64_t info_value);
+Status AdbcInitConnectionObjectsSchema(struct ArrowSchema* schema);
+/// @}
+
+} // namespace adbc::driver
diff --git a/c/driver/framework/driver.cc b/c/driver/framework/driver.cc
new file mode 100644
index 0000000000..d083b5ed22
--- /dev/null
+++ b/c/driver/framework/driver.cc
@@ -0,0 +1,527 @@
+// 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 "driver/framework/driver.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "adbc.h"
+#include "driver/common/options.h"
+#include "driver/common/utils.h"
+#include "driver/framework/base.h"
+#include "driver/framework/catalog.h"
+#include "driver/framework/objects.h"
+
+namespace adbc::driver {
+
+namespace {
+/// One-value ArrowArrayStream used to unify the implementations of Bind
+struct OneValueStream {
+ struct ArrowSchema schema;
+ struct ArrowArray array;
+
+ static int GetSchema(struct ArrowArrayStream* self, struct ArrowSchema* out) {
+ OneValueStream* stream = static_cast(self->private_data);
+ return ArrowSchemaDeepCopy(&stream->schema, out);
+ }
+ static int GetNext(struct ArrowArrayStream* self, struct ArrowArray* out) {
+ OneValueStream* stream = static_cast(self->private_data);
+ *out = stream->array;
+ stream->array.release = nullptr;
+ return 0;
+ }
+ static const char* GetLastError(struct ArrowArrayStream* self) { return NULL; }
+ static void Release(struct ArrowArrayStream* self) {
+ OneValueStream* stream = static_cast(self->private_data);
+ if (stream->schema.release) {
+ stream->schema.release(&stream->schema);
+ stream->schema.release = nullptr;
+ }
+ if (stream->array.release) {
+ stream->array.release(&stream->array);
+ stream->array.release = nullptr;
+ }
+ delete stream;
+ self->release = nullptr;
+ }
+};
+} // namespace
+
+AdbcStatusCode ConnectionBase::Commit(AdbcError* error) {
+ switch (autocommit_) {
+ case AutocommitState::kAutocommit:
+ return status::InvalidState("no active transaction, cannot commit").ToAdbc(error);
+ case AutocommitState::kTransaction:
+ return CommitImpl().ToAdbc(error);
+ }
+ assert(false);
+ return ADBC_STATUS_INTERNAL;
+}
+
+AdbcStatusCode ConnectionBase::GetInfo(const uint32_t* info_codes,
+ size_t info_codes_length, ArrowArrayStream* out,
+ AdbcError* error) {
+ std::vector codes(info_codes, info_codes + info_codes_length);
+ RAISE_RESULT(error, auto infos, InfoImpl(codes));
+
+ nanoarrow::UniqueSchema schema;
+ nanoarrow::UniqueArray array;
+ RAISE_STATUS(error, AdbcInitConnectionGetInfoSchema(schema.get(), array.get()));
+
+ for (const auto& info : infos) {
+ RAISE_STATUS(
+ error,
+ std::visit(
+ [&](auto&& value) -> Status {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ return AdbcConnectionGetInfoAppendString(array.get(), info.code, value);
+ } else if constexpr (std::is_same_v) {
+ return AdbcConnectionGetInfoAppendInt(array.get(), info.code, value);
+ } else {
+ static_assert(!sizeof(T), "info value type not implemented");
+ }
+ return status::kOk;
+ },
+ info.value));
+ CHECK_NA(INTERNAL, ArrowArrayFinishElement(array.get()), error);
+ }
+
+ struct ArrowError na_error = {0};
+ CHECK_NA_DETAIL(INTERNAL, ArrowArrayFinishBuildingDefault(array.get(), &na_error),
+ &na_error, error);
+ return BatchToArrayStream(array.get(), schema.get(), out, error);
+}
+
+AdbcStatusCode ConnectionBase::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) {
+ const auto catalog_filter =
+ catalog ? std::make_optional(std::string_view(catalog)) : std::nullopt;
+ const auto schema_filter =
+ db_schema ? std::make_optional(std::string_view(db_schema)) : std::nullopt;
+ const auto table_filter =
+ table_name ? std::make_optional(std::string_view(table_name)) : std::nullopt;
+ const auto column_filter =
+ column_name ? std::make_optional(std::string_view(column_name)) : std::nullopt;
+ std::vector table_type_filter;
+ while (table_type && *table_type) {
+ if (*table_type) {
+ table_type_filter.push_back(std::string_view(*table_type));
+ }
+ table_type++;
+ }
+
+ RAISE_RESULT(error, auto helper, GetObjectsImpl());
+ // TODO: parse depth to an enum
+ nanoarrow::UniqueSchema schema;
+ nanoarrow::UniqueArray array;
+ RAISE_STATUS(error, BuildGetObjects(helper.get(), depth, catalog_filter, schema_filter,
+ table_filter, column_filter, table_type_filter,
+ schema.get(), array.get()));
+ // TODO: always call helper->Close even on error
+ RAISE_STATUS(error, helper->Close());
+ return BatchToArrayStream(array.get(), schema.get(), out, error);
+}
+
+Result> ConnectionBase::GetObjectsImpl() {
+ return std::make_unique();
+}
+
+Result> ConnectionBase::GetOption(std::string_view key) const {
+ if (key == ADBC_CONNECTION_OPTION_AUTOCOMMIT) {
+ switch (autocommit_) {
+ case AutocommitState::kAutocommit:
+ return driver::Option(ADBC_OPTION_VALUE_ENABLED);
+ case AutocommitState::kTransaction:
+ return driver::Option(ADBC_OPTION_VALUE_DISABLED);
+ }
+ } else if (key == ADBC_CONNECTION_OPTION_CURRENT_CATALOG) {
+ return driver::Option("main");
+ } else if (key == ADBC_CONNECTION_OPTION_CURRENT_DB_SCHEMA) {
+ return driver::Option();
+ }
+ return std::nullopt;
+}
+
+AdbcStatusCode ConnectionBase::GetTableSchema(const char* catalog, const char* db_schema,
+ const char* table_name, ArrowSchema* schema,
+ AdbcError* error) {
+ if (!table_name) {
+ return status::InvalidArgument("GetTableSchema: must provide table_name")
+ .ToAdbc(error);
+ }
+ std::memset(schema, 0, sizeof(*schema));
+ std::optional catalog_param =
+ catalog ? std::make_optional(std::string_view(catalog)) : std::nullopt;
+ std::optional db_schema_param =
+ db_schema ? std::make_optional(std::string_view(db_schema)) : std::nullopt;
+ std::string_view table_name_param = table_name;
+
+ return GetTableSchemaImpl(catalog_param, db_schema_param, table_name_param, schema)
+ .ToAdbc(error);
+}
+
+Status ConnectionBase::GetTableSchemaImpl(std::optional catalog,
+ std::optional db_schema,
+ std::string_view table_name,
+ ArrowSchema* schema) {
+ return status::NotImplemented("GetTableSchema");
+}
+
+AdbcStatusCode ConnectionBase::GetTableTypes(ArrowArrayStream* out, AdbcError* error) {
+ RAISE_RESULT(error, std::vector table_types, GetTableTypesImpl());
+
+ nanoarrow::UniqueArray array;
+ nanoarrow::UniqueSchema schema;
+ ArrowSchemaInit(schema.get());
+
+ CHECK_NA(INTERNAL, ArrowSchemaSetType(schema.get(), NANOARROW_TYPE_STRUCT), error);
+ CHECK_NA(INTERNAL, ArrowSchemaAllocateChildren(schema.get(), /*num_columns=*/1), error);
+ ArrowSchemaInit(schema.get()->children[0]);
+ CHECK_NA(INTERNAL, ArrowSchemaSetType(schema.get()->children[0], NANOARROW_TYPE_STRING),
+ error);
+ CHECK_NA(INTERNAL, ArrowSchemaSetName(schema.get()->children[0], "table_type"), error);
+ schema.get()->children[0]->flags &= ~ARROW_FLAG_NULLABLE;
+
+ CHECK_NA(INTERNAL, ArrowArrayInitFromSchema(array.get(), schema.get(), NULL), error);
+ CHECK_NA(INTERNAL, ArrowArrayStartAppending(array.get()), error);
+
+ for (std::string const& table_type : table_types) {
+ CHECK_NA(
+ INTERNAL,
+ ArrowArrayAppendString(array->children[0], ArrowCharView(table_type.c_str())),
+ error);
+ CHECK_NA(INTERNAL, ArrowArrayFinishElement(array.get()), error);
+ }
+
+ CHECK_NA(INTERNAL, ArrowArrayFinishBuildingDefault(array.get(), NULL), error);
+
+ return BatchToArrayStream(array.get(), schema.get(), out, error);
+}
+
+Result> ConnectionBase::GetTableTypesImpl() {
+ return std::vector();
+}
+
+AdbcStatusCode ConnectionBase::Init(void* parent, AdbcError* error) {
+ lifecycle_state_ = LifecycleState::kInitialized;
+ if (auto status = InitImpl(parent); !status.ok()) {
+ return status.ToAdbc(error);
+ }
+ return ConnectionObjectBase::Init(parent, error);
+}
+
+AdbcStatusCode ConnectionBase::Release(AdbcError* error) {
+ return ReleaseImpl().ToAdbc(error);
+}
+
+AdbcStatusCode ConnectionBase::Rollback(AdbcError* error) {
+ switch (autocommit_) {
+ case AutocommitState::kAutocommit:
+ return status::InvalidState("no active transaction, cannot rollback").ToAdbc(error);
+ case AutocommitState::kTransaction:
+ return RollbackImpl().ToAdbc(error);
+ }
+ assert(false);
+ return ADBC_STATUS_INTERNAL;
+}
+
+AdbcStatusCode ConnectionBase::SetOption(std::string_view key, Option value,
+ AdbcError* error) {
+ return SetOptionImpl(key, value).ToAdbc(error);
+}
+
+Status ConnectionBase::CommitImpl() {
+ return status::NotImplemented("driver does not implement AdbcConnectionCommit");
+}
+
+Result> ConnectionBase::InfoImpl(
+ const std::vector& codes) {
+ return std::vector{};
+}
+
+Status ConnectionBase::InitImpl(void* parent) { return status::kOk; }
+
+Status ConnectionBase::ReleaseImpl() { return status::kOk; }
+
+Status ConnectionBase::RollbackImpl() {
+ return status::NotImplemented("driver does not implement AdbcConnectionRollback");
+}
+
+Status ConnectionBase::SetOptionImpl(std::string_view key, Option value) {
+ if (key == ADBC_CONNECTION_OPTION_AUTOCOMMIT) {
+ UNWRAP_RESULT(auto enabled, value.AsBool());
+ switch (autocommit_) {
+ case AutocommitState::kAutocommit: {
+ if (!enabled) {
+ UNWRAP_STATUS(ToggleAutocommitImpl(false));
+ autocommit_ = AutocommitState::kTransaction;
+ }
+ break;
+ }
+ case AutocommitState::kTransaction: {
+ if (enabled) {
+ UNWRAP_STATUS(ToggleAutocommitImpl(true));
+ autocommit_ = AutocommitState::kAutocommit;
+ }
+ break;
+ }
+ }
+ return status::kOk;
+ }
+ return status::NotImplemented("unknown connection option {}={}", key, value);
+}
+
+Status ConnectionBase::ToggleAutocommitImpl(bool enable_autocommit) {
+ return status::NotImplemented("driver does not support changing autocommit");
+}
+
+AdbcStatusCode StatementBase::Bind(ArrowArray* values, ArrowSchema* schema,
+ AdbcError* error) {
+ if (!values || !values->release) {
+ return status::InvalidArgument("Bind: must provide non-NULL array").ToAdbc(error);
+ } else if (!schema || !schema->release) {
+ return status::InvalidArgument("Bind: must provide non-NULL stream").ToAdbc(error);
+ }
+ if (bind_parameters_.release) bind_parameters_.release(&bind_parameters_);
+ // Make a one-value stream
+ bind_parameters_.private_data = new OneValueStream{*schema, *values};
+ bind_parameters_.get_schema = &OneValueStream::GetSchema;
+ bind_parameters_.get_next = &OneValueStream::GetNext;
+ bind_parameters_.get_last_error = &OneValueStream::GetLastError;
+ bind_parameters_.release = &OneValueStream::Release;
+ std::memset(values, 0, sizeof(*values));
+ std::memset(schema, 0, sizeof(*schema));
+ return ADBC_STATUS_OK;
+}
+
+AdbcStatusCode StatementBase::BindStream(ArrowArrayStream* stream, AdbcError* error) {
+ if (!stream || !stream->release) {
+ return status::InvalidArgument("BindStream: must provide non-NULL stream")
+ .ToAdbc(error);
+ }
+ if (bind_parameters_.release) bind_parameters_.release(&bind_parameters_);
+ // Move stream
+ bind_parameters_ = *stream;
+ std::memset(stream, 0, sizeof(*stream));
+ return ADBC_STATUS_OK;
+}
+
+Result StatementBase::ExecuteIngestImpl(IngestState& state) {
+ return status::NotImplemented("bulk ingest is not implemented");
+}
+
+AdbcStatusCode StatementBase::ExecuteQuery(ArrowArrayStream* stream,
+ int64_t* rows_affected, AdbcError* error) {
+ // TODO: we could introduce a state to track when we're in the middle of a
+ // query and prevent operations
+ return std::visit(
+ [&](auto&& state) -> AdbcStatusCode {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ return status::InvalidState("cannot ExecuteQuery without setting the query")
+ .ToAdbc(error);
+ } else if constexpr (std::is_same_v) {
+ if (stream) {
+ return status::InvalidState("cannot ingest with result set").ToAdbc(error);
+ }
+ RAISE_RESULT(error, int64_t rows, ExecuteIngestImpl(state));
+ if (rows_affected) {
+ *rows_affected = rows;
+ }
+ return ADBC_STATUS_OK;
+ } else if constexpr (std::is_same_v ||
+ std::is_same_v) {
+ int64_t rows = 0;
+ if (stream) {
+ RAISE_RESULT(error, rows, ExecuteQueryImpl(state, stream));
+ } else {
+ RAISE_RESULT(error, rows, ExecuteUpdateImpl(state));
+ }
+ if (rows_affected) {
+ *rows_affected = rows;
+ }
+ return ADBC_STATUS_OK;
+ } else {
+ static_assert(!sizeof(T), "case not implemented");
+ }
+ },
+ state_);
+}
+
+Result StatementBase::ExecuteQueryImpl(PreparedState& state,
+ ArrowArrayStream* stream) {
+ return status::NotImplemented("ExecuteQuery is not implemented");
+}
+
+Result StatementBase::ExecuteQueryImpl(QueryState& state,
+ ArrowArrayStream* stream) {
+ return status::NotImplemented("ExecuteQuery is not implemented");
+}
+
+Result StatementBase::ExecuteUpdateImpl(PreparedState& state) {
+ return status::NotImplemented("ExecuteQuery (update) is not implemented");
+}
+
+Result StatementBase::ExecuteUpdateImpl(QueryState& state) {
+ return status::NotImplemented("ExecuteQuery (update) is not implemented");
+}
+
+AdbcStatusCode StatementBase::Init(void* parent, AdbcError* error) {
+ lifecycle_state_ = LifecycleState::kInitialized;
+ if (auto status = InitImpl(parent); !status.ok()) {
+ return status.ToAdbc(error);
+ }
+ return StatementObjectBase::Init(parent, error);
+}
+
+Status StatementBase::InitImpl(void* parent) { return status::kOk; }
+
+AdbcStatusCode StatementBase::Prepare(AdbcError* error) {
+ RAISE_STATUS(
+ error,
+ std::visit(
+ [&](auto&& state) -> Status {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ return status::InvalidState("cannot Prepare without setting the query");
+ } else if constexpr (std::is_same_v) {
+ return status::InvalidState("cannot Prepare without setting the query");
+ } else if constexpr (std::is_same_v) {
+ // No-op
+ return status::kOk;
+ } else if constexpr (std::is_same_v) {
+ UNWRAP_STATUS(PrepareImpl(state));
+ state_ = PreparedState{std::move(state.query)};
+ return status::kOk;
+ } else {
+ static_assert(!sizeof(T), "case not implemented");
+ }
+ },
+ state_));
+ return ADBC_STATUS_OK;
+}
+
+Status StatementBase::PrepareImpl(QueryState& state) { return status::kOk; }
+
+AdbcStatusCode StatementBase::SetOption(std::string_view key, Option value,
+ AdbcError* error) {
+ auto ensure_ingest = [&]() -> IngestState& {
+ if (!std::holds_alternative(state_)) {
+ state_ = IngestState{};
+ }
+ return std::get(state_);
+ };
+ if (key == ADBC_INGEST_OPTION_MODE) {
+ RAISE_RESULT(error, auto mode, value.AsString());
+ if (mode == ADBC_INGEST_OPTION_MODE_APPEND) {
+ auto& state = ensure_ingest();
+ state.table_does_not_exist_ = TableDoesNotExist::kFail;
+ state.table_exists_ = TableExists::kAppend;
+ } else if (mode == ADBC_INGEST_OPTION_MODE_CREATE) {
+ auto& state = ensure_ingest();
+ state.table_does_not_exist_ = TableDoesNotExist::kCreate;
+ state.table_exists_ = TableExists::kFail;
+ } else if (mode == ADBC_INGEST_OPTION_MODE_CREATE_APPEND) {
+ auto& state = ensure_ingest();
+ state.table_does_not_exist_ = TableDoesNotExist::kCreate;
+ state.table_exists_ = TableExists::kAppend;
+ } else if (mode == ADBC_INGEST_OPTION_MODE_REPLACE) {
+ auto& state = ensure_ingest();
+ state.table_does_not_exist_ = TableDoesNotExist::kCreate;
+ state.table_exists_ = TableExists::kReplace;
+ } else {
+ return status::InvalidArgument("invalid ingest mode '{}'", key, value)
+ .ToAdbc(error);
+ }
+ return ADBC_STATUS_OK;
+ } else if (key == ADBC_INGEST_OPTION_TARGET_CATALOG) {
+ RAISE_RESULT(error, auto catalog, value.AsString());
+ ensure_ingest().target_catalog = catalog;
+ return ADBC_STATUS_OK;
+ } else if (key == ADBC_INGEST_OPTION_TARGET_DB_SCHEMA) {
+ RAISE_RESULT(error, auto schema, value.AsString());
+ ensure_ingest().target_schema = schema;
+ return ADBC_STATUS_OK;
+ } else if (key == ADBC_INGEST_OPTION_TARGET_TABLE) {
+ RAISE_RESULT(error, auto table, value.AsString());
+ ensure_ingest().target_table = table;
+ return ADBC_STATUS_OK;
+ } else if (key == ADBC_INGEST_OPTION_TEMPORARY) {
+ RAISE_RESULT(error, auto temporary, value.AsBool());
+ ensure_ingest().temporary = temporary;
+ return ADBC_STATUS_OK;
+ }
+ return SetOptionImpl(key, value).ToAdbc(error);
+}
+
+Status StatementBase::SetOptionImpl(std::string_view key, Option value) {
+ return status::NotImplemented("unknown statement option {}={}", key, value);
+}
+
+AdbcStatusCode StatementBase::SetSqlQuery(const char* query, AdbcError* error) {
+ RAISE_STATUS(error, std::visit(
+ [&](auto&& state) -> Status {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ state_ = QueryState{
+ std::string(query),
+ };
+ return status::kOk;
+ } else if constexpr (std::is_same_v) {
+ state_ = QueryState{
+ std::string(query),
+ };
+ return status::kOk;
+ } else if constexpr (std::is_same_v) {
+ state_ = QueryState{
+ std::string(query),
+ };
+ return status::kOk;
+ } else if constexpr (std::is_same_v) {
+ state.query = std::string(query);
+ return status::kOk;
+ } else {
+ static_assert(!sizeof(T),
+ "info value type not implemented");
+ }
+ },
+ state_));
+ return ADBC_STATUS_OK;
+}
+
+AdbcStatusCode StatementBase::Release(AdbcError* error) {
+ return ReleaseImpl().ToAdbc(error);
+}
+
+Status StatementBase::ReleaseImpl() {
+ if (bind_parameters_.release) {
+ bind_parameters_.release(&bind_parameters_);
+ bind_parameters_.release = nullptr;
+ }
+ return status::kOk;
+}
+
+} // namespace adbc::driver
diff --git a/c/driver/framework/driver.h b/c/driver/framework/driver.h
new file mode 100644
index 0000000000..36a27699ef
--- /dev/null
+++ b/c/driver/framework/driver.h
@@ -0,0 +1,202 @@
+// 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 "driver/framework/base.h"
+#include "driver/framework/catalog.h"
+#include "driver/framework/status.h"
+
+namespace adbc::driver {
+
+/// \brief Track the state of a database/connection/statement.
+enum class LifecycleState {
+ /// \brief New has been called but not Init.
+ kUninitialized,
+ /// \brief Init has been called.
+ kInitialized,
+};
+
+class ConnectionBase : public ConnectionObjectBase {
+ public:
+ ConnectionBase()
+ : ConnectionObjectBase(), lifecycle_state_(LifecycleState::kUninitialized) {}
+
+ AdbcStatusCode Init(void* parent, AdbcError* error) override;
+
+ AdbcStatusCode Commit(AdbcError* error) override;
+
+ AdbcStatusCode GetInfo(const uint32_t* info_codes, size_t info_codes_length,
+ ArrowArrayStream* out, AdbcError* error) override;
+
+ 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) override;
+
+ Result> GetOption(std::string_view key) const override;
+
+ AdbcStatusCode GetTableSchema(const char* catalog, const char* db_schema,
+ const char* table_name, ArrowSchema* schema,
+ AdbcError* error) override;
+
+ AdbcStatusCode GetTableTypes(ArrowArrayStream* out, AdbcError* error) override;
+
+ AdbcStatusCode Release(AdbcError* error) override;
+
+ AdbcStatusCode Rollback(AdbcError* error) override;
+
+ AdbcStatusCode SetOption(std::string_view key, Option value, AdbcError* error) override;
+
+ protected:
+ enum class AutocommitState {
+ kAutocommit,
+ kTransaction,
+ };
+
+ // struct GetObjectsTable {
+ // std::string table_name;
+ // std::string table_type;
+ // };
+
+ virtual Status CommitImpl();
+
+ virtual Result> GetObjectsImpl();
+
+ // /// \brief Get a list of catalogs according to the given filter.
+ // virtual Result> GetObjectsCatalogsImpl(
+ // std::string_view catalog_filter);
+
+ // /// \brief Get a list of schemas in the given catalog according to the given
+ // /// filter.
+ // virtual Result> GetObjectsDbSchemasImpl(
+ // std::string_view catalog, std::string_view schema_filter);
+
+ // /// \brief Get a list of tables in the given catalog/schema according to the
+ // /// given filter.
+ // virtual Result> GetObjectsTablesImpl(
+ // std::string_view catalog, std::string_view schema, std::string_view table_filter,
+ // const std::vector& table_types);
+
+ virtual Status GetTableSchemaImpl(std::optional catalog,
+ std::optional db_schema,
+ std::string_view table_name, ArrowSchema* schema);
+
+ virtual Result> GetTableTypesImpl();
+
+ virtual Result> InfoImpl(const std::vector& codes);
+
+ virtual Status InitImpl(void* parent);
+
+ virtual Status ReleaseImpl();
+
+ virtual Status RollbackImpl();
+
+ virtual Status SetOptionImpl(std::string_view key, Option value);
+
+ virtual Status ToggleAutocommitImpl(bool enable_autocommit);
+
+ AutocommitState autocommit_ = AutocommitState::kAutocommit;
+ LifecycleState lifecycle_state_;
+};
+
+class StatementBase : public StatementObjectBase {
+ public:
+ StatementBase()
+ : StatementObjectBase(), lifecycle_state_(LifecycleState::kUninitialized) {
+ std::memset(&bind_parameters_, 0, sizeof(bind_parameters_));
+ }
+
+ AdbcStatusCode Bind(ArrowArray* values, ArrowSchema* schema, AdbcError* error) override;
+
+ AdbcStatusCode BindStream(ArrowArrayStream* stream, AdbcError* error) override;
+
+ AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* rows_affected,
+ AdbcError* error) override;
+
+ AdbcStatusCode Init(void* parent, AdbcError* error) override;
+
+ AdbcStatusCode Prepare(AdbcError* error) override;
+
+ AdbcStatusCode Release(AdbcError* error) override;
+
+ AdbcStatusCode SetOption(std::string_view key, Option value, AdbcError* error) override;
+
+ AdbcStatusCode SetSqlQuery(const char* query, AdbcError* error) override;
+
+ protected:
+ enum class TableDoesNotExist {
+ kCreate,
+ kFail,
+ };
+ enum class TableExists {
+ kAppend,
+ kFail,
+ kReplace,
+ };
+
+ struct EmptyState {};
+ struct IngestState {
+ std::optional target_catalog;
+ std::optional target_schema;
+ std::optional target_table;
+ bool temporary = false;
+ TableDoesNotExist table_does_not_exist_ = TableDoesNotExist::kCreate;
+ TableExists table_exists_ = TableExists::kFail;
+ };
+ struct PreparedState {
+ std::string query;
+ };
+ struct QueryState {
+ std::string query;
+ };
+
+ using State = std::variant;
+
+ virtual Result ExecuteIngestImpl(IngestState& state);
+
+ virtual Result ExecuteQueryImpl(PreparedState& state,
+ ArrowArrayStream* stream);
+
+ virtual Result ExecuteQueryImpl(QueryState& state, ArrowArrayStream* stream);
+
+ virtual Result ExecuteUpdateImpl(PreparedState& state);
+
+ virtual Result ExecuteUpdateImpl(QueryState& state);
+
+ virtual Status InitImpl(void* parent);
+
+ virtual Status PrepareImpl(QueryState& state);
+
+ virtual Status ReleaseImpl();
+
+ virtual Status SetOptionImpl(std::string_view key, Option value);
+
+ ArrowArrayStream bind_parameters_;
+ LifecycleState lifecycle_state_;
+ State state_ = State(EmptyState{});
+};
+
+} // namespace adbc::driver
diff --git a/c/driver/framework/objects.cc b/c/driver/framework/objects.cc
new file mode 100644
index 0000000000..0fca30ff5f
--- /dev/null
+++ b/c/driver/framework/objects.cc
@@ -0,0 +1,305 @@
+// 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 "driver/framework/objects.h"
+#include
+
+#include "driver/framework/catalog.h"
+#include "driver/framework/driver.h"
+#include "driver/framework/status.h"
+
+namespace adbc::driver {
+
+namespace {
+ArrowStringView ToStringView(std::string_view s) {
+ return {
+ s.data(),
+ static_cast(s.size()),
+ };
+}
+
+struct GetObjectsBuilder {
+ GetObjectsBuilder(GetObjectsHelper* helper, int depth,
+ std::optional