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