From c74a5e08c806444f2dbcb20e2fe2b7b88f208856 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/cmake_modules/AdbcDefines.cmake | 5 +
c/driver/common/utils.h | 19 -
c/driver/framework/CMakeLists.txt | 44 +
c/driver/framework/base.cc | 160 +
c/driver/framework/base.h | 648 ++
c/driver/framework/base_connection.h | 370 ++
c/driver/framework/base_database.h | 78 +
c/driver/framework/base_statement.h | 413 ++
c/driver/framework/catalog.cc | 263 +
c/driver/framework/catalog.h | 154 +
c/driver/framework/objects.cc | 364 ++
c/driver/framework/objects.h | 40 +
c/driver/framework/status.h | 292 +
c/driver/framework/type_fwd.h | 24 +
c/driver/postgresql/CMakeLists.txt | 2 +
c/driver/postgresql/connection.cc | 37 +-
c/driver/postgresql/copy/copy_common.h | 2 +
c/driver/postgresql/database.cc | 2 +-
c/driver/postgresql/error.cc | 2 +-
c/driver/postgresql/postgresql.cc | 2 +-
c/driver/postgresql/result_helper.cc | 2 +-
c/driver/postgresql/statement.cc | 4 +-
c/driver/postgresql/statement.h | 2 +-
c/driver/sqlite/CMakeLists.txt | 4 +-
c/driver/sqlite/sqlite.c | 2064 ------
c/driver/sqlite/sqlite.cc | 1482 +++++
c/driver/sqlite/sqlite_test.cc | 9 +-
c/driver/sqlite/statement_reader.c | 2 +-
c/driver/sqlite/types.h | 64 -
c/driver_manager/adbc_driver_manager_test.cc | 6 +-
c/validation/adbc_validation_connection.cc | 24 +-
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 | 29 +
ci/linux-packages/debian/rules | 6 +-
ci/scripts/cpp_clang_tidy.sh | 1 +
ci/scripts/cpp_test.sh | 8 +-
dev/release/rat_exclude_files.txt | 1 +
glib/test/test-connection.rb | 26 +-
go/adbc/drivermgr/wrapper_sqlite_test.go | 9 +-
license.tpl | 26 +
.../adbc_driver_manager/tests/test_dbapi.py | 2 +-
.../tests/test_lowlevel.py | 6 +-
r/adbcpostgresql/bootstrap.R | 90 +-
r/adbcpostgresql/src/Makevars.in | 20 +-
r/adbcpostgresql/src/Makevars.ucrt | 19 +-
r/adbcpostgresql/src/Makevars.win | 20 +-
.../src/c/driver/common/.gitignore | 20 +
.../src/c/driver/framework/.gitignore | 20 +
.../src/c/driver/postgresql/.gitignore | 30 +
.../src/c/driver/postgresql/copy/.gitignore | 20 +
.../src/c/vendor/fmt/include/fmt/.gitignore | 30 +
.../src/c/vendor/nanoarrow/.gitignore | 20 +
r/adbcpostgresql/src/{init.c => init.cc} | 2 +
r/adbcsqlite/bootstrap.R | 60 +-
r/adbcsqlite/src/Makevars.in | 15 +-
.../src/c/driver}/common/.gitignore | 3 +-
.../src/c/driver/framework/.gitignore | 27 +
r/adbcsqlite/src/c/driver/sqlite/.gitignore | 19 +
r/adbcsqlite/src/c/vendor/fmt/.gitignore | 22 +
.../src/c/vendor/fmt/include/fmt/.gitignore | 29 +
.../src/c/vendor/fmt/src}/.gitignore | 5 +-
.../src/c/vendor/fmt/support/cmake/.gitignore | 20 +
.../src/c/vendor}/nanoarrow/.gitignore | 3 +-
.../src/c/vendor/sqlite3}/.gitignore | 4 +-
r/adbcsqlite/src/{init.c => init.cc} | 2 +
r/adbcsqlite/src/nanoarrow/.gitignore | 1 +
94 files changed, 27790 insertions(+), 2319 deletions(-)
create mode 100644 c/driver/framework/CMakeLists.txt
create mode 100644 c/driver/framework/base.cc
create mode 100644 c/driver/framework/base.h
create mode 100644 c/driver/framework/base_connection.h
create mode 100644 c/driver/framework/base_database.h
create mode 100644 c/driver/framework/base_statement.h
create mode 100644 c/driver/framework/catalog.cc
create mode 100644 c/driver/framework/catalog.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
delete mode 100644 c/driver/sqlite/sqlite.c
create mode 100644 c/driver/sqlite/sqlite.cc
delete mode 100644 c/driver/sqlite/types.h
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
create mode 100644 r/adbcpostgresql/src/c/driver/common/.gitignore
create mode 100644 r/adbcpostgresql/src/c/driver/framework/.gitignore
create mode 100644 r/adbcpostgresql/src/c/driver/postgresql/.gitignore
create mode 100644 r/adbcpostgresql/src/c/driver/postgresql/copy/.gitignore
create mode 100644 r/adbcpostgresql/src/c/vendor/fmt/include/fmt/.gitignore
create mode 100644 r/adbcpostgresql/src/c/vendor/nanoarrow/.gitignore
rename r/adbcpostgresql/src/{init.c => init.cc} (99%)
rename r/{adbcpostgresql/src => adbcsqlite/src/c/driver}/common/.gitignore (99%)
create mode 100644 r/adbcsqlite/src/c/driver/framework/.gitignore
create mode 100644 r/adbcsqlite/src/c/driver/sqlite/.gitignore
create mode 100644 r/adbcsqlite/src/c/vendor/fmt/.gitignore
create mode 100644 r/adbcsqlite/src/c/vendor/fmt/include/fmt/.gitignore
rename r/{adbcpostgresql/src/copy => adbcsqlite/src/c/vendor/fmt/src}/.gitignore (97%)
create mode 100644 r/adbcsqlite/src/c/vendor/fmt/support/cmake/.gitignore
rename r/{adbcpostgresql/src => adbcsqlite/src/c/vendor}/nanoarrow/.gitignore (99%)
rename r/{adbcpostgresql/src/vendor/portable-snippets => adbcsqlite/src/c/vendor/sqlite3}/.gitignore (97%)
rename r/adbcsqlite/src/{init.c => init.cc} (99%)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 986f26a88d..7acb111a94 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -415,7 +415,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/cmake_modules/AdbcDefines.cmake b/c/cmake_modules/AdbcDefines.cmake
index 8c9c37ee61..4155cd4df4 100644
--- a/c/cmake_modules/AdbcDefines.cmake
+++ b/c/cmake_modules/AdbcDefines.cmake
@@ -85,6 +85,7 @@ if(MSVC)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"
OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ # maybe-uninitialized is flaky
set(ADBC_C_CXX_FLAGS_CHECKIN
-Wall
-Wextra
@@ -93,6 +94,10 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"
-Wno-unused-parameter)
set(ADBC_C_CXX_FLAGS_PRODUCTION -Wall)
+ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ list(APPEND ADBC_C_CXX_FLAGS_CHECKIN -Wno-maybe-uninitialized)
+ endif()
+
if(NOT CMAKE_C_FLAGS_DEBUG MATCHES "-O")
string(APPEND CMAKE_C_FLAGS_DEBUG " -Og")
endif()
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..50f1dafdda
--- /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 base.cc catalog.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.cc b/c/driver/framework/base.cc
new file mode 100644
index 0000000000..ab94caf370
--- /dev/null
+++ b/c/driver/framework/base.cc
@@ -0,0 +1,160 @@
+// 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/base.h"
+
+namespace adbc::driver {
+Result Option::AsBool() const {
+ return std::visit(
+ [&](auto&& value) -> Result {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ if (value == ADBC_OPTION_VALUE_ENABLED) {
+ return true;
+ } else if (value == ADBC_OPTION_VALUE_DISABLED) {
+ return false;
+ }
+ }
+ return status::InvalidArgument("Invalid boolean value {}", *this);
+ },
+ value_);
+}
+
+Result Option::AsInt() const {
+ return std::visit(
+ [&](auto&& value) -> Result {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ return value;
+ } else if constexpr (std::is_same_v) {
+ int64_t parsed = 0;
+ auto begin = value.data();
+ auto end = value.data() + value.size();
+ auto result = std::from_chars(begin, end, parsed);
+ if (result.ec != std::errc()) {
+ return status::InvalidArgument("Invalid integer value '{}': not an integer",
+ value);
+ } else if (result.ptr != end) {
+ return status::InvalidArgument("Invalid integer value '{}': trailing data",
+ value);
+ }
+ return parsed;
+ }
+ return status::InvalidArgument("Invalid integer value {}", *this);
+ },
+ value_);
+}
+
+Result Option::AsString() const {
+ return std::visit(
+ [&](auto&& value) -> Result {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ return value;
+ }
+ return status::InvalidArgument("Invalid string value {}", *this);
+ },
+ value_);
+}
+
+AdbcStatusCode Option::CGet(char* out, size_t* length, AdbcError* error) const {
+ if (!out || !length) {
+ return status::InvalidArgument("Must provide both out and length to GetOption")
+ .ToAdbc(error);
+ }
+ return std::visit(
+ [&](auto&& value) -> AdbcStatusCode {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ 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) {
+ return status::NotFound("Unknown option").ToAdbc(error);
+ } else {
+ return status::NotFound("Option value is not a string").ToAdbc(error);
+ }
+ },
+ value_);
+}
+
+AdbcStatusCode Option::CGet(uint8_t* out, size_t* length, AdbcError* error) const {
+ if (!out || !length) {
+ return status::InvalidArgument("Must provide both out and length to GetOption")
+ .ToAdbc(error);
+ }
+ return std::visit(
+ [&](auto&& value) -> AdbcStatusCode {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v ||
+ std::is_same_v>) {
+ if (*length >= value.size()) {
+ std::memcpy(out, value.data(), value.size());
+ }
+ *length = value.size();
+ return ADBC_STATUS_OK;
+ } else if constexpr (std::is_same_v) {
+ return status::NotFound("Unknown option").ToAdbc(error);
+ } else {
+ return status::NotFound("Option value is not a bytestring").ToAdbc(error);
+ }
+ },
+ value_);
+}
+
+AdbcStatusCode Option::CGet(int64_t* out, AdbcError* error) const {
+ if (!out) {
+ return status::InvalidArgument("Must provide out to GetOption").ToAdbc(error);
+ }
+ return std::visit(
+ [&](auto&& value) -> AdbcStatusCode {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ *out = value;
+ return ADBC_STATUS_OK;
+ } else if constexpr (std::is_same_v) {
+ return status::NotFound("Unknown option").ToAdbc(error);
+ } else {
+ return status::NotFound("Option value is not an integer").ToAdbc(error);
+ }
+ },
+ value_);
+}
+
+AdbcStatusCode Option::CGet(double* out, AdbcError* error) const {
+ if (!out) {
+ return status::InvalidArgument("Must provide out to GetOption").ToAdbc(error);
+ }
+ return std::visit(
+ [&](auto&& value) -> AdbcStatusCode {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v || std::is_same_v) {
+ *out = value;
+ return ADBC_STATUS_OK;
+ } else if constexpr (std::is_same_v) {
+ return status::NotFound("Unknown option").ToAdbc(error);
+ } else {
+ return status::NotFound("Option value is not a double").ToAdbc(error);
+ }
+ },
+ value_);
+}
+} // namespace adbc::driver
diff --git a/c/driver/framework/base.h b/c/driver/framework/base.h
new file mode 100644
index 0000000000..e416f89d0c
--- /dev/null
+++ b/c/driver/framework/base.h
@@ -0,0 +1,648 @@
+// 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
+
+#include "driver/common/utils.h"
+#include "driver/framework/status.h"
+
+/// \file base.h ADBC Driver Framework
+///
+/// A base implementation of an ADBC driver that allows easier driver
+/// development by overriding functions. Databases, connections, and
+/// statements can be defined by subclassing the [CRTP][crtp] base classes.
+///
+/// Generally, base classes provide a set of functions that correspond to the
+/// ADBC functions. These should not be directly overridden, as they provide
+/// the core logic and argument checking/error handling. Instead, override
+/// the -Impl functions that are also exposed by base classes.
+///
+/// [crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
+namespace adbc::driver {
+
+/// \brief 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,
+};
+
+/// \brief A typed option value wrapper. It currently does not attempt
+/// conversion (i.e., getting a double option as a string).
+class Option {
+ public:
+ /// \brief The option is unset.
+ struct Unset {};
+ /// \brief The possible values of an option.
+ using Value = std::variant, int64_t, double>;
+
+ Option() : value_(Unset{}) {}
+ /// \brief Construct an option from a C string.
+ /// NULL strings are treated as unset.
+ explicit Option(const char* value)
+ : value_(value ? Value(std::string(value)) : Value{Unset{}}) {}
+ 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_; }
+
+ /// \brief Check whether this option is set.
+ bool has_value() const { return !std::holds_alternative(value_); }
+
+ /// \brief Try to parse a string value as a boolean.
+ Result AsBool() const;
+
+ /// \brief Try to parse a string or integer value as an integer.
+ Result AsInt() const;
+
+ /// \brief Get the value if it is a string.
+ Result AsString() const;
+
+ private:
+ Value value_;
+
+ // Methods used by trampolines to export option values in C below
+ friend class ObjectBase;
+ AdbcStatusCode CGet(char* out, size_t* length, AdbcError* error) const;
+ AdbcStatusCode CGet(uint8_t* out, size_t* length, AdbcError* error) const;
+ AdbcStatusCode CGet(int64_t* out, AdbcError* error) const;
+ AdbcStatusCode CGet(double* out, AdbcError* error) const;
+};
+
+/// \brief Base class for private_data of AdbcDatabase, AdbcConnection, and
+/// AdbcStatement.
+///
+/// This class handles option setting and getting.
+class ObjectBase {
+ public:
+ ObjectBase() = default;
+ 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().
+
+ /// \brief Initialize the object.
+ ///
+ /// Called after 0 or more SetOption calls. Generally, you won't need to
+ /// override this directly. Instead, use the typed InitImpl provided by
+ /// DatabaseBase/ConnectionBase/StatementBase.
+ ///
+ /// \param[in] parent A pointer to the AdbcDatabase or AdbcConnection
+ /// implementation as appropriate, or nullptr.
+ virtual AdbcStatusCode Init(void* parent, AdbcError* error) {
+ lifecycle_state_ = LifecycleState::kInitialized;
+ return ADBC_STATUS_OK;
+ }
+
+ /// \brief Finalize the object.
+ ///
+ /// This can be used to return an error if the object is not in a valid
+ /// state (e.g. prevent closing a connection with open statements) or to
+ /// clean up resources when resource cleanup could fail. Infallible
+ /// resource cleanup (e.g. releasing memory) should generally be handled in
+ /// the destructor.
+ ///
+ /// Generally, you won't need to override this directly. Instead, use the
+ /// typed ReleaseImpl provided by DatabaseBase/ConnectionBase/StatementBase.
+ virtual AdbcStatusCode Release(AdbcError* error) { return ADBC_STATUS_OK; }
+
+ /// \brief Get an option value.
+ virtual Result