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