// 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 <charconv>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

#include <arrow-adbc/adbc.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<Unset, std::string, std::vector<uint8_t>, 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<uint8_t> 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<Unset>(value_); }

  /// \brief Try to parse a string value as a boolean.
  Result<bool> AsBool() const {
    return std::visit(
        [&](auto&& value) -> Result<bool> {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::string>) {
            if (value == ADBC_OPTION_VALUE_ENABLED) {
              return true;
            } else if (value == ADBC_OPTION_VALUE_DISABLED) {
              return false;
            }
          }
          return status::InvalidArgument("Invalid boolean value ", this->Format());
        },
        value_);
  }

  /// \brief Try to parse a string or integer value as an integer.
  Result<int64_t> AsInt() const {
    return std::visit(
        [&](auto&& value) -> Result<int64_t> {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, int64_t>) {
            return value;
          } else if constexpr (std::is_same_v<T, std::string>) {
            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 '", value,
                                             "': not an integer", value);
            } else if (result.ptr != end) {
              return status::InvalidArgument("Invalid integer value '", value,
                                             "': trailing data", value);
            }
            return parsed;
          } else {
            return status::InvalidArgument("Invalid integer value ", this->Format());
          }
        },
        value_);
  }

  /// \brief Get the value if it is a string.
  Result<std::string_view> AsString() const {
    return std::visit(
        [&](auto&& value) -> Result<std::string_view> {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::string>) {
            return value;
          } else {
            return status::InvalidArgument("Invalid string value ", this->Format());
          }
        },
        value_);
  }

  /// \brief Provide a human-readable summary of the value
  std::string Format() const {
    return std::visit(
        [&](auto&& value) -> std::string {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, adbc::driver::Option::Unset>) {
            return "(NULL)";
          } else if constexpr (std::is_same_v<T, std::string>) {
            return std::string("'") + value + "'";
          } else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
            return std::string("(") + std::to_string(value.size()) + " bytes)";
          } else {
            return std::to_string(value);
          }
        },
        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, AdbcError* error) const {
    {
      if (!length || (!out && *length > 0)) {
        return status::InvalidArgument("Must provide both out and length to GetOption")
            .ToAdbc(error);
      }
      return std::visit(
          [&](auto&& value) -> AdbcStatusCode {
            using T = std::decay_t<decltype(value)>;
            if constexpr (std::is_same_v<T, std::string>) {
              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<T, Unset>) {
              return status::NotFound("Unknown option").ToAdbc(error);
            } else {
              return status::NotFound("Option value is not a string").ToAdbc(error);
            }
          },
          value_);
    }
  }
  AdbcStatusCode CGet(uint8_t* out, size_t* length, AdbcError* error) const {
    if (!length || (!out && *length > 0)) {
      return status::InvalidArgument("Must provide both out and length to GetOption")
          .ToAdbc(error);
    }
    return std::visit(
        [&](auto&& value) -> AdbcStatusCode {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::string> ||
                        std::is_same_v<T, std::vector<uint8_t>>) {
            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<T, Unset>) {
            return status::NotFound("Unknown option").ToAdbc(error);
          } else {
            return status::NotFound("Option value is not a bytestring").ToAdbc(error);
          }
        },
        value_);
  }
  AdbcStatusCode 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<decltype(value)>;
            if constexpr (std::is_same_v<T, int64_t>) {
              *out = value;
              return ADBC_STATUS_OK;
            } else if constexpr (std::is_same_v<T, Unset>) {
              return status::NotFound("Unknown option").ToAdbc(error);
            } else {
              return status::NotFound("Option value is not an integer").ToAdbc(error);
            }
          },
          value_);
    }
  }
  AdbcStatusCode 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<decltype(value)>;
          if constexpr (std::is_same_v<T, double> || std::is_same_v<T, int64_t>) {
            *out = static_cast<double>(value);
            return ADBC_STATUS_OK;
          } else if constexpr (std::is_same_v<T, Unset>) {
            return status::NotFound("Unknown option").ToAdbc(error);
          } else {
            return status::NotFound("Option value is not a double").ToAdbc(error);
          }
        },
        value_);
  }
};

/// \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<MyDatabase, MyConnection, MyStatement>, you can
  // reinterpret_cast<MyDatabase>(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
  /// Database/Connection/Statement.
  ///
  /// \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 Database/Connection/Statement.
  virtual AdbcStatusCode Release(AdbcError* error) { return ADBC_STATUS_OK; }

  /// \brief Get an option value.
  virtual Result<Option> GetOption(std::string_view key) {
    Option option(nullptr);
    return option;
  }

  /// \brief Set an option value.
  virtual AdbcStatusCode SetOption(std::string_view key, Option value, AdbcError* error) {
    return ADBC_STATUS_NOT_IMPLEMENTED;
  }

 protected:
  LifecycleState lifecycle_state_;

 private:
  // Let the Driver use these to expose C callables wrapping option setters/getters
  template <typename DatabaseT, typename ConnectionT, typename StatementT>
  friend class Driver;

  template <typename T>
  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<uint8_t> cppvalue(value, value + length);
    Option option(std::move(cppvalue));
    return SetOption(key, std::move(option), error);
  }

  template <typename T>
  AdbcStatusCode CGetOptionStringLike(const char* key, T* value, size_t* length,
                                      AdbcError* error) {
    Option option;
    RAISE_RESULT(error, option, GetOption(key));
    return option.CGet(value, length, error);
  }

  template <typename T>
  AdbcStatusCode CGetOptionNumeric(const char* key, T* value, AdbcError* error) {
    Option option;
    RAISE_RESULT(error, option, GetOption(key));
    return option.CGet(value, error);
  }
};

/// Helper for below: given the ADBC type, pick the right driver type.
template <typename DatabaseT, typename ConnectionT, typename StatementT, typename T>
struct ResolveObjectTImpl {};

template <typename DatabaseT, typename ConnectionT, typename StatementT>
struct ResolveObjectTImpl<DatabaseT, ConnectionT, StatementT, struct AdbcDatabase> {
  using type = DatabaseT;
};
template <typename DatabaseT, typename ConnectionT, typename StatementT>
struct ResolveObjectTImpl<DatabaseT, ConnectionT, StatementT, struct AdbcConnection> {
  using type = ConnectionT;
};
template <typename DatabaseT, typename ConnectionT, typename StatementT>
struct ResolveObjectTImpl<DatabaseT, ConnectionT, StatementT, struct AdbcStatement> {
  using type = StatementT;
};

/// Helper for below: given the ADBC type, pick the right driver type.
template <typename DatabaseT, typename ConnectionT, typename StatementT, typename T>
using ResolveObjectT =
    typename ResolveObjectTImpl<DatabaseT, ConnectionT, StatementT, T>::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 <typename DatabaseT, typename ConnectionT, typename StatementT>
class Driver {
 public:
  static AdbcStatusCode Init(int version, void* raw_driver, AdbcError* error) {
    if (version != ADBC_VERSION_1_0_0 && version != ADBC_VERSION_1_1_0) {
      return ADBC_STATUS_NOT_IMPLEMENTED;
    }

    auto* driver = reinterpret_cast<AdbcDriver*>(raw_driver);
    if (version >= ADBC_VERSION_1_1_0) {
      std::memset(driver, 0, ADBC_DRIVER_1_1_0_SIZE);

      driver->ErrorGetDetailCount = &CErrorGetDetailCount;
      driver->ErrorGetDetail = &CErrorGetDetail;

      driver->DatabaseGetOption = &CGetOption<AdbcDatabase>;
      driver->DatabaseGetOptionBytes = &CGetOptionBytes<AdbcDatabase>;
      driver->DatabaseGetOptionInt = &CGetOptionInt<AdbcDatabase>;
      driver->DatabaseGetOptionDouble = &CGetOptionDouble<AdbcDatabase>;
      driver->DatabaseSetOptionBytes = &CSetOptionBytes<AdbcDatabase>;
      driver->DatabaseSetOptionInt = &CSetOptionInt<AdbcDatabase>;
      driver->DatabaseSetOptionDouble = &CSetOptionDouble<AdbcDatabase>;

      driver->ConnectionCancel = &CConnectionCancel;
      driver->ConnectionGetOption = &CGetOption<AdbcConnection>;
      driver->ConnectionGetOptionBytes = &CGetOptionBytes<AdbcConnection>;
      driver->ConnectionGetOptionInt = &CGetOptionInt<AdbcConnection>;
      driver->ConnectionGetOptionDouble = &CGetOptionDouble<AdbcConnection>;
      driver->ConnectionGetStatistics = &CConnectionGetStatistics;
      driver->ConnectionGetStatisticNames = &CConnectionGetStatisticNames;
      driver->ConnectionSetOptionBytes = &CSetOptionBytes<AdbcConnection>;
      driver->ConnectionSetOptionInt = &CSetOptionInt<AdbcConnection>;
      driver->ConnectionSetOptionDouble = &CSetOptionDouble<AdbcConnection>;

      driver->StatementCancel = &CStatementCancel;
      driver->StatementExecuteSchema = &CStatementExecuteSchema;
      driver->StatementGetOption = &CGetOption<AdbcStatement>;
      driver->StatementGetOptionBytes = &CGetOptionBytes<AdbcStatement>;
      driver->StatementGetOptionInt = &CGetOptionInt<AdbcStatement>;
      driver->StatementGetOptionDouble = &CGetOptionDouble<AdbcStatement>;
      driver->StatementSetOptionBytes = &CSetOptionBytes<AdbcStatement>;
      driver->StatementSetOptionInt = &CSetOptionInt<AdbcStatement>;
      driver->StatementSetOptionDouble = &CSetOptionDouble<AdbcStatement>;
    } else {
      std::memset(driver, 0, ADBC_DRIVER_1_0_0_SIZE);
    }

    driver->private_data = new Driver();
    driver->release = &CDriverRelease;

    driver->DatabaseInit = &CDatabaseInit;
    driver->DatabaseNew = &CNew<AdbcDatabase>;
    driver->DatabaseRelease = &CRelease<AdbcDatabase>;
    driver->DatabaseSetOption = &CSetOption<AdbcDatabase>;

    driver->ConnectionCommit = &CConnectionCommit;
    driver->ConnectionGetInfo = &CConnectionGetInfo;
    driver->ConnectionGetObjects = &CConnectionGetObjects;
    driver->ConnectionGetTableSchema = &CConnectionGetTableSchema;
    driver->ConnectionGetTableTypes = &CConnectionGetTableTypes;
    driver->ConnectionInit = &CConnectionInit;
    driver->ConnectionNew = &CNew<AdbcConnection>;
    driver->ConnectionRelease = &CRelease<AdbcConnection>;
    driver->ConnectionReadPartition = &CConnectionReadPartition;
    driver->ConnectionRollback = &CConnectionRollback;
    driver->ConnectionSetOption = &CSetOption<AdbcConnection>;

    driver->StatementBind = &CStatementBind;
    driver->StatementBindStream = &CStatementBindStream;
    driver->StatementExecutePartitions = &CStatementExecutePartitions;
    driver->StatementExecuteQuery = &CStatementExecuteQuery;
    driver->StatementGetParameterSchema = &CStatementGetParameterSchema;
    driver->StatementNew = &CStatementNew;
    driver->StatementPrepare = &CStatementPrepare;
    driver->StatementRelease = &CRelease<AdbcStatement>;
    driver->StatementSetOption = &CSetOption<AdbcStatement>;
    driver->StatementSetSqlQuery = &CStatementSetSqlQuery;
    driver->StatementSetSubstraitPlan = &CStatementSetSubstraitPlan;

    return ADBC_STATUS_OK;
  }

  // Driver trampolines
  static AdbcStatusCode CDriverRelease(AdbcDriver* driver, AdbcError* error) {
    auto driver_private = reinterpret_cast<Driver*>(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<Status*>(error->private_data);
    if (!error_obj) {
      return 0;
    }
    return error_obj->CDetailCount();
  }

  static AdbcErrorDetail CErrorGetDetail(const AdbcError* error, int index) {
    if (error->vendor_code != ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) {
      return {nullptr, nullptr, 0};
    }

    auto error_obj = reinterpret_cast<Status*>(error->private_data);
    if (!error_obj) {
      return {nullptr, nullptr, 0};
    }

    return error_obj->CDetail(index);
  }

  // Templatable trampolines

  template <typename T>
  static AdbcStatusCode CNew(T* obj, AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = new ObjectT();
    obj->private_data = private_data;
    return ADBC_STATUS_OK;
  }

  template <typename T>
  static AdbcStatusCode CRelease(T* obj, AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    if (obj == nullptr) return ADBC_STATUS_INVALID_STATE;
    auto private_data = reinterpret_cast<ObjectT*>(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 <typename T>
  static AdbcStatusCode CSetOption(T* obj, const char* key, const char* value,
                                   AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CSetOption<>(key, value, error);
  }

  template <typename T>
  static AdbcStatusCode CSetOptionBytes(T* obj, const char* key, const uint8_t* value,
                                        size_t length, AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->CSetOptionBytes(key, value, length, error);
  }

  template <typename T>
  static AdbcStatusCode CSetOptionInt(T* obj, const char* key, int64_t value,
                                      AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CSetOption<>(key, value, error);
  }

  template <typename T>
  static AdbcStatusCode CSetOptionDouble(T* obj, const char* key, double value,
                                         AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CSetOption<>(key, value, error);
  }

  template <typename T>
  static AdbcStatusCode CGetOption(T* obj, const char* key, char* value, size_t* length,
                                   AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CGetOptionStringLike<>(key, value, length, error);
  }

  template <typename T>
  static AdbcStatusCode CGetOptionBytes(T* obj, const char* key, uint8_t* value,
                                        size_t* length, AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CGetOptionStringLike<>(key, value, length, error);
  }

  template <typename T>
  static AdbcStatusCode CGetOptionInt(T* obj, const char* key, int64_t* value,
                                      AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CGetOptionNumeric<>(key, value, error);
  }

  template <typename T>
  static AdbcStatusCode CGetOptionDouble(T* obj, const char* key, double* value,
                                         AdbcError* error) {
    using ObjectT = ResolveObjectT<DatabaseT, ConnectionT, StatementT, T>;
    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
    return private_data->template CGetOptionNumeric<>(key, value, error);
  }

#define CHECK_INIT(DATABASE, ERROR)                                         \
  if (!(DATABASE) || !(DATABASE)->private_data) {                           \
    return status::InvalidState("Database is uninitialized").ToAdbc(ERROR); \
  }

  // Database trampolines
  static AdbcStatusCode CDatabaseInit(AdbcDatabase* database, AdbcError* error) {
    CHECK_INIT(database, error);
    auto private_data = reinterpret_cast<DatabaseT*>(database->private_data);
    return private_data->Init(nullptr, error);
  }

#undef CHECK_INIT
#define CHECK_INIT(CONNECTION, ERROR)                                         \
  if (!(CONNECTION) || !(CONNECTION)->private_data) {                         \
    return status::InvalidState("Connection is uninitialized").ToAdbc(ERROR); \
  }

  // Connection trampolines
  static AdbcStatusCode CConnectionInit(AdbcConnection* connection,
                                        AdbcDatabase* database, AdbcError* error) {
    CHECK_INIT(connection, error);
    if (!database || !database->private_data) {
      return status::InvalidState("Database is uninitialized").ToAdbc(error);
    }
    auto private_data = reinterpret_cast<ConnectionT*>(connection->private_data);
    return private_data->Init(database->private_data, error);
  }

  static AdbcStatusCode CConnectionCancel(AdbcConnection* connection, AdbcError* error) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(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) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(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) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(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) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(connection->private_data);
    return private_data->GetStatistics(catalog, db_schema, table_name, approximate, out,
                                       error);
  }

  static AdbcStatusCode CConnectionGetStatisticNames(AdbcConnection* connection,
                                                     ArrowArrayStream* out,
                                                     AdbcError* error) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(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) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(connection->private_data);
    return private_data->GetTableSchema(catalog, db_schema, table_name, schema, error);
  }

  static AdbcStatusCode CConnectionGetTableTypes(AdbcConnection* connection,
                                                 ArrowArrayStream* out,
                                                 AdbcError* error) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(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) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(connection->private_data);
    return private_data->ReadPartition(serialized_partition, serialized_length, out,
                                       error);
  }

  static AdbcStatusCode CConnectionCommit(AdbcConnection* connection, AdbcError* error) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(connection->private_data);
    return private_data->Commit(error);
  }

  static AdbcStatusCode CConnectionRollback(AdbcConnection* connection,
                                            AdbcError* error) {
    CHECK_INIT(connection, error);
    auto private_data = reinterpret_cast<ConnectionT*>(connection->private_data);
    return private_data->Rollback(error);
  }

#undef CHECK_INIT
#define CHECK_INIT(STATEMENT, ERROR)                                         \
  if (!(STATEMENT) || !(STATEMENT)->private_data) {                          \
    return status::InvalidState("Statement is uninitialized").ToAdbc(ERROR); \
  }

  // Statement trampolines
  static AdbcStatusCode CStatementNew(AdbcConnection* connection,
                                      AdbcStatement* statement, AdbcError* error) {
    if (!connection || !connection->private_data) {
      return status::InvalidState("Connection is uninitialized").ToAdbc(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 CStatementBind(AdbcStatement* statement, ArrowArray* values,
                                       ArrowSchema* schema, AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->Bind(values, schema, error);
  }

  static AdbcStatusCode CStatementBindStream(AdbcStatement* statement,
                                             ArrowArrayStream* stream, AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->BindStream(stream, error);
  }

  static AdbcStatusCode CStatementCancel(AdbcStatement* statement, AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->Cancel(error);
  }

  static AdbcStatusCode CStatementExecutePartitions(AdbcStatement* statement,
                                                    struct ArrowSchema* schema,
                                                    struct AdbcPartitions* partitions,
                                                    int64_t* rows_affected,
                                                    AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(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) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->ExecuteQuery(stream, rows_affected, error);
  }

  static AdbcStatusCode CStatementExecuteSchema(AdbcStatement* statement,
                                                ArrowSchema* schema, AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->ExecuteSchema(schema, error);
  }

  static AdbcStatusCode CStatementGetParameterSchema(AdbcStatement* statement,
                                                     ArrowSchema* schema,
                                                     AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->GetParameterSchema(schema, error);
  }

  static AdbcStatusCode CStatementPrepare(AdbcStatement* statement, AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->Prepare(error);
  }

  static AdbcStatusCode CStatementSetSqlQuery(AdbcStatement* statement, const char* query,
                                              AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->SetSqlQuery(query, error);
  }

  static AdbcStatusCode CStatementSetSubstraitPlan(AdbcStatement* statement,
                                                   const uint8_t* plan, size_t length,
                                                   AdbcError* error) {
    CHECK_INIT(statement, error);
    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
    return private_data->SetSubstraitPlan(plan, length, error);
  }

#undef CHECK_INIT
};

template <typename Derived>
class BaseDatabase : public ObjectBase {
 public:
  using Base = BaseDatabase<Derived>;

  BaseDatabase() : ObjectBase() {}
  ~BaseDatabase() = default;

  /// \internal
  AdbcStatusCode Init(void* parent, AdbcError* error) override {
    RAISE_STATUS(error, impl().InitImpl());
    return ObjectBase::Init(parent, error);
  }

  /// \internal
  AdbcStatusCode Release(AdbcError* error) override {
    RAISE_STATUS(error, impl().ReleaseImpl());
    return ADBC_STATUS_OK;
  }

  /// \internal
  AdbcStatusCode SetOption(std::string_view key, Option value,
                           AdbcError* error) override {
    RAISE_STATUS(error, impl().SetOptionImpl(key, std::move(value)));
    return ADBC_STATUS_OK;
  }

  /// \brief Initialize the database.
  virtual Status InitImpl() { return status::Ok(); }

  /// \brief Release the database.
  virtual Status ReleaseImpl() { return status::Ok(); }

  /// \brief Set an option.  May be called prior to InitImpl.
  virtual Status SetOptionImpl(std::string_view key, Option value) {
    return status::NotImplemented(Derived::kErrorPrefix, " Unknown database option ", key,
                                  "=", value.Format());
  }

 private:
  Derived& impl() { return static_cast<Derived&>(*this); }
};

template <typename Derived>
class BaseConnection : public ObjectBase {
 public:
  using Base = BaseConnection<Derived>;

  /// \brief Whether autocommit is enabled or not (by default: enabled).
  enum class AutocommitState {
    kAutocommit,
    kTransaction,
  };

  BaseConnection() : ObjectBase() {}
  ~BaseConnection() = default;

  /// \internal
  AdbcStatusCode Init(void* parent, AdbcError* error) override {
    RAISE_STATUS(error, impl().InitImpl(parent));
    return ObjectBase::Init(parent, error);
  }

  /// \brief Initialize the database.
  virtual Status InitImpl(void* parent) { return status::Ok(); }

  /// \internal
  AdbcStatusCode Cancel(AdbcError* error) { return impl().CancelImpl().ToAdbc(error); }

  Status CancelImpl() { return status::NotImplemented("Cancel"); }

  /// \internal
  AdbcStatusCode Commit(AdbcError* error) { return impl().CommitImpl().ToAdbc(error); }

  Status CommitImpl() { return status::NotImplemented("Commit"); }

  /// \internal
  AdbcStatusCode GetInfo(const uint32_t* info_codes, size_t info_codes_length,
                         ArrowArrayStream* out, AdbcError* error) {
    std::vector<uint32_t> codes(info_codes, info_codes + info_codes_length);
    RAISE_STATUS(error, impl().GetInfoImpl(codes, out));
    return ADBC_STATUS_OK;
  }

  Status GetInfoImpl(const std::vector<uint32_t> info_codes, ArrowArrayStream* out) {
    return status::NotImplemented("GetInfo");
  }

  /// \internal
  AdbcStatusCode GetObjects(int c_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<std::string_view> table_type_filter;
    while (table_type && *table_type) {
      if (*table_type) {
        table_type_filter.push_back(std::string_view(*table_type));
      }
      table_type++;
    }

    RAISE_STATUS(
        error, impl().GetObjectsImpl(c_depth, catalog_filter, schema_filter, table_filter,
                                     column_filter, table_type_filter, out));

    return ADBC_STATUS_OK;
  }

  Status GetObjectsImpl(int c_depth, std::optional<std::string_view> catalog_filter,
                        std::optional<std::string_view> schema_filter,
                        std::optional<std::string_view> table_filter,
                        std::optional<std::string_view> column_filter,
                        const std::vector<std::string_view>& table_types,
                        struct ArrowArrayStream* out) {
    return status::NotImplemented("GetObjects");
  }

  /// \internal
  AdbcStatusCode GetStatistics(const char* catalog, const char* db_schema,
                               const char* table_name, char approximate,
                               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;
    RAISE_STATUS(error, impl().GetStatisticsImpl(catalog_filter, schema_filter,
                                                 table_filter, approximate != 0, out));
    return ADBC_STATUS_OK;
  }

  Status GetStatisticsImpl(std::optional<std::string_view> catalog,
                           std::optional<std::string_view> db_schema,
                           std::optional<std::string_view> table_name, bool approximate,
                           ArrowArrayStream* out) {
    return status::NotImplemented("GetStatistics");
  }

  /// \internal
  AdbcStatusCode GetStatisticNames(ArrowArrayStream* out, AdbcError* error) {
    RAISE_STATUS(error, impl().GetStatisticNames(out));
    return ADBC_STATUS_OK;
  }

  Status GetStatisticNames(ArrowArrayStream* out) {
    return status::NotImplemented("GetStatisticNames");
  }

  /// \internal
  AdbcStatusCode GetTableSchema(const char* catalog, const char* db_schema,
                                const char* table_name, ArrowSchema* schema,
                                AdbcError* error) {
    if (!table_name) {
      return status::InvalidArgument(Derived::kErrorPrefix,
                                     " GetTableSchema: must provide table_name")
          .ToAdbc(error);
    }

    std::optional<std::string_view> catalog_param =
        catalog ? std::make_optional(std::string_view(catalog)) : std::nullopt;
    std::optional<std::string_view> db_schema_param =
        db_schema ? std::make_optional(std::string_view(db_schema)) : std::nullopt;

    RAISE_STATUS(error, impl().GetTableSchemaImpl(catalog_param, db_schema_param,
                                                  table_name, schema));
    return ADBC_STATUS_OK;
  }

  Status GetTableSchemaImpl(std::optional<std::string_view> catalog,
                            std::optional<std::string_view> db_schema,
                            std::string_view table_name, ArrowSchema* out) {
    return status::NotImplemented("GetTableSchema");
  }

  /// \internal
  AdbcStatusCode GetTableTypes(ArrowArrayStream* out, AdbcError* error) {
    RAISE_STATUS(error, impl().GetTableTypesImpl(out));
    return ADBC_STATUS_OK;
  }

  Status GetTableTypesImpl(ArrowArrayStream* out) {
    return status::NotImplemented("GetTableTypes");
  }

  /// \internal
  AdbcStatusCode ReadPartition(const uint8_t* serialized_partition,
                               size_t serialized_length, ArrowArrayStream* out,
                               AdbcError* error) {
    std::string_view partition(reinterpret_cast<const char*>(serialized_partition),
                               serialized_length);
    RAISE_STATUS(error, impl().ReadPartitionImpl(partition, out));
    return ADBC_STATUS_OK;
  }

  Status ReadPartitionImpl(std::string_view serialized_partition, ArrowArrayStream* out) {
    return status::NotImplemented("ReadPartition");
  }

  /// \internal
  AdbcStatusCode Release(AdbcError* error) override {
    RAISE_STATUS(error, impl().ReleaseImpl());
    return ADBC_STATUS_OK;
  }

  Status ReleaseImpl() { return status::Ok(); }

  /// \internal
  AdbcStatusCode Rollback(AdbcError* error) {
    RAISE_STATUS(error, impl().RollbackImpl());
    return ADBC_STATUS_OK;
  }

  Status RollbackImpl() { return status::NotImplemented("Rollback"); }

  /// \internal
  AdbcStatusCode SetOption(std::string_view key, Option value,
                           AdbcError* error) override {
    RAISE_STATUS(error, impl().SetOptionImpl(key, value));
    return ADBC_STATUS_OK;
  }

  /// \brief Set an option.  May be called prior to InitImpl.
  virtual Status SetOptionImpl(std::string_view key, Option value) {
    return status::NotImplemented(Derived::kErrorPrefix, " Unknown connection option ",
                                  key, "=", value.Format());
  }

 private:
  Derived& impl() { return static_cast<Derived&>(*this); }
};

template <typename Derived>
class BaseStatement : public ObjectBase {
 public:
  using Base = BaseStatement<Derived>;

  /// \internal
  AdbcStatusCode Init(void* parent, AdbcError* error) override {
    RAISE_STATUS(error, impl().InitImpl(parent));
    return ObjectBase::Init(parent, error);
  }

  /// \brief Initialize the statement.
  Status InitImpl(void* parent) { return status::Ok(); }

  /// \internal
  AdbcStatusCode Release(AdbcError* error) override {
    RAISE_STATUS(error, impl().ReleaseImpl());
    return ADBC_STATUS_OK;
  }

  Status ReleaseImpl() { return status::Ok(); }

  /// \internal
  AdbcStatusCode SetOption(std::string_view key, Option value,
                           AdbcError* error) override {
    RAISE_STATUS(error, impl().SetOptionImpl(key, value));
    return ADBC_STATUS_OK;
  }

  /// \brief Set an option.  May be called prior to InitImpl.
  virtual Status SetOptionImpl(std::string_view key, Option value) {
    return status::NotImplemented(Derived::kErrorPrefix, " Unknown statement option ",
                                  key, "=", value.Format());
  }

  AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* rows_affected,
                              AdbcError* error) {
    int64_t rows_affected_result;
    RAISE_RESULT(error, rows_affected_result, impl().ExecuteQueryImpl(stream));
    if (rows_affected) {
      *rows_affected = rows_affected_result;
    }

    return ADBC_STATUS_OK;
  }

  Result<int64_t> ExecuteQueryImpl(ArrowArrayStream* stream) {
    return status::NotImplemented("ExecuteQuery");
  }

  AdbcStatusCode ExecuteSchema(ArrowSchema* schema, AdbcError* error) {
    RAISE_STATUS(error, impl().ExecuteSchemaImpl(schema));
    return ADBC_STATUS_OK;
  }

  Status ExecuteSchemaImpl(ArrowSchema* schema) {
    return status::NotImplemented("ExecuteSchema");
  }

  AdbcStatusCode Prepare(AdbcError* error) {
    RAISE_STATUS(error, impl().PrepareImpl());
    return ADBC_STATUS_OK;
  }

  Status PrepareImpl() { return status::NotImplemented("Prepare"); }

  AdbcStatusCode SetSqlQuery(const char* query, AdbcError* error) {
    RAISE_STATUS(error, impl().SetSqlQueryImpl(query));
    return ADBC_STATUS_OK;
  }

  Status SetSqlQueryImpl(std::string_view query) {
    return status::NotImplemented("SetSqlQuery");
  }

  AdbcStatusCode SetSubstraitPlan(const uint8_t* plan, size_t length, AdbcError* error) {
    RAISE_STATUS(error, impl().SetSubstraitPlanImpl(std::string_view(
                            reinterpret_cast<const char*>(plan), length)));
    return ADBC_STATUS_OK;
  }

  Status SetSubstraitPlanImpl(std::string_view plan) {
    return status::NotImplemented("SetSubstraitPlan");
  }

  AdbcStatusCode Bind(ArrowArray* values, ArrowSchema* schema, AdbcError* error) {
    RAISE_STATUS(error, impl().BindImpl(values, schema));
    return ADBC_STATUS_OK;
  }

  Status BindImpl(ArrowArray* values, ArrowSchema* schema) {
    return status::NotImplemented("Bind");
  }

  AdbcStatusCode BindStream(ArrowArrayStream* stream, AdbcError* error) {
    RAISE_STATUS(error, impl().BindStreamImpl(stream));
    return ADBC_STATUS_OK;
  }

  Status BindStreamImpl(ArrowArrayStream* stream) {
    return status::NotImplemented("BindStream");
  }

  AdbcStatusCode GetParameterSchema(ArrowSchema* schema, AdbcError* error) {
    RAISE_STATUS(error, impl().GetParameterSchemaImpl(schema));
    return ADBC_STATUS_OK;
  }

  Status GetParameterSchemaImpl(struct ArrowSchema* schema) {
    return status::NotImplemented("GetParameterSchema");
  }

  AdbcStatusCode ExecutePartitions(ArrowSchema* schema, AdbcPartitions* partitions,
                                   int64_t* rows_affected, AdbcError* error) {
    return ADBC_STATUS_NOT_IMPLEMENTED;
  }

  AdbcStatusCode Cancel(AdbcError* error) {
    RAISE_STATUS(error, impl().Cancel());
    return ADBC_STATUS_OK;
  }

  Status Cancel() { return status::NotImplemented("Cancel"); }

 private:
  Derived& impl() { return static_cast<Derived&>(*this); }
};

}  // namespace adbc::driver
