This is an automated email from the ASF dual-hosted git repository.

paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 09dffb32e refactor(c/driver/framework): Separate C/C++ conversions and 
error handling into minimal "base" framework (#2090)
09dffb32e is described below

commit 09dffb32e8ac8b25be3e2f31554ebe6f2e39c383
Author: Dewey Dunnington <[email protected]>
AuthorDate: Thu Aug 22 07:22:02 2024 -0300

    refactor(c/driver/framework): Separate C/C++ conversions and error handling 
into minimal "base" framework (#2090)
    
    This PR removes the previous `driver/common/driver_base.h` and refactors
    `driver/framework/base_driver.h` such that it can be a (almost) drop-in
    replacement. This is basically to separate the nanoarrow helpers such
    that framework drivers don't have to use them (or can adopt them
    incrementally to improve/simplify their implementation if they would
    like): driver authors can implement the low-level methods with C inputs,
    implement slightly higher level C++-y methods with `Status` error
    handling and inputs already converted to C++ objects, or link to the
    framework. All of our C++ drivers will continue to (or should
    eventually) link to/use the framework.
    
    I am not sure the separation is perfect here and I'm happy to change
    things that don't make sense!
---
 .github/workflows/r-check.yml                      |   6 +-
 c/driver/common/CMakeLists.txt                     |   1 -
 c/driver/common/driver_base.h                      | 770 ---------------------
 c/driver/common/driver_test.cc                     | 271 --------
 c/driver/common/meson.build                        |   1 -
 c/driver/framework/CMakeLists.txt                  |   2 +-
 c/driver/framework/base_driver.cc                  | 178 -----
 c/driver/framework/base_driver.h                   | 532 +++++++++++++-
 c/driver/framework/base_driver_test.cc             | 109 ++-
 .../framework/{base_connection.h => connection.h}  |   8 +-
 c/driver/framework/{base_database.h => database.h} |  34 +-
 c/driver/framework/meson.build                     |   1 -
 .../framework/{base_statement.h => statement.h}    |  10 +-
 c/driver/sqlite/sqlite.cc                          |  18 +-
 r/adbcdrivermanager/bootstrap.R                    |  68 +-
 r/adbcdrivermanager/configure                      |   9 -
 r/adbcdrivermanager/src/.gitignore                 |   5 +-
 r/adbcdrivermanager/src/Makevars                   |  10 +-
 r/adbcdrivermanager/src/driver_test.cc             | 178 +++--
 .../tests/testthat/_snaps/driver_log.md            |  16 +-
 .../tests/testthat/test-driver_log.R               |   6 +-
 r/adbcdrivermanager/tests/testthat/test-error.R    |   4 +-
 r/adbcdrivermanager/tests/testthat/test-options.R  |  29 +-
 r/adbcsqlite/src/Makevars.in                       |   1 -
 24 files changed, 829 insertions(+), 1438 deletions(-)

diff --git a/.github/workflows/r-check.yml b/.github/workflows/r-check.yml
index 39cc965b7..cc4cd9b7c 100644
--- a/.github/workflows/r-check.yml
+++ b/.github/workflows/r-check.yml
@@ -64,15 +64,17 @@ jobs:
       - name: Bootstrap R Package
         run: |
           pushd r/adbcdrivermanager
-          Rscript bootstrap.R
+          R -e 'if (!requireNamespace("nanoarrow", quietly = TRUE)) 
install.packages("nanoarrow", repos = "https://cloud.r-project.org/";)'
+          R CMD INSTALL . --preclean
           popd
           pushd "r/${{ inputs.pkg }}"
           Rscript bootstrap.R
           popd
+        shell: bash
 
       - uses: 
r-lib/actions/setup-r-dependencies@f4937e0dc26f9b99c969cd3e4ca943b576e7f991
         with:
-          extra-packages: any::rcmdcheck, local::../adbcdrivermanager
+          extra-packages: any::rcmdcheck
           needs: check
           working-directory: r/${{ inputs.pkg }}
 
diff --git a/c/driver/common/CMakeLists.txt b/c/driver/common/CMakeLists.txt
index 77999f39d..751eda363 100644
--- a/c/driver/common/CMakeLists.txt
+++ b/c/driver/common/CMakeLists.txt
@@ -29,7 +29,6 @@ if(ADBC_BUILD_TESTS)
                 driver-common
                 SOURCES
                 utils_test.cc
-                driver_test.cc
                 EXTRA_LINK_LIBS
                 adbc_driver_common
                 nanoarrow)
diff --git a/c/driver/common/driver_base.h b/c/driver/common/driver_base.h
deleted file mode 100644
index 0cfb05bd3..000000000
--- a/c/driver/common/driver_base.h
+++ /dev/null
@@ -1,770 +0,0 @@
-// 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 <cstring>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include <arrow-adbc/adbc.h>
-
-// This file defines a developer-friendly way to create an ADBC driver, 
currently intended
-// for testing the R driver manager. It handles errors, option 
getting/setting, and
-// managing the export of the many C callables that compose an AdbcDriver. In 
general,
-// functions or methods intended to be called from C are prefixed with "C" and 
are private
-// (i.e., the public and protected methods are the only ones that driver 
authors should
-// ever interact with).
-//
-// Example:
-// class MyDatabase: public DatabaseObjectBase {};
-// class MyConnection: public ConnectionObjectBase {};
-// class MyStatement: public StatementObjectbase {};
-// AdbcStatusCode VoidDriverInitFunc(int version, void* raw_driver, AdbcError* 
error) {
-//   return Driver<MyDatabase, MyConnection, MyStatement>::Init(
-//     version, raw_driver, error);
-// }
-
-namespace adbc {
-
-namespace common {
-
-class Error {
- public:
-  explicit Error(std::string message) : message_(std::move(message)) {
-    std::memset(sql_state_, 0, sizeof(sql_state_));
-  }
-
-  explicit Error(const char* message) : Error(std::string(message)) {}
-
-  Error(std::string message, std::vector<std::pair<std::string, std::string>> 
details)
-      : message_(std::move(message)), details_(std::move(details)) {
-    std::memset(sql_state_, 0, sizeof(sql_state_));
-  }
-
-  void AddDetail(std::string key, std::string value) {
-    details_.push_back({std::move(key), std::move(value)});
-  }
-
-  void ToAdbc(AdbcError* adbc_error, AdbcDriver* driver = nullptr) {
-    if (adbc_error == nullptr) {
-      return;
-    }
-
-    if (adbc_error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) {
-      auto error_owned_by_adbc_error =
-          new Error(std::move(message_), std::move(details_));
-      adbc_error->message =
-          const_cast<char*>(error_owned_by_adbc_error->message_.c_str());
-      adbc_error->private_data = error_owned_by_adbc_error;
-      adbc_error->private_driver = driver;
-    } else {
-      adbc_error->message = 
reinterpret_cast<char*>(std::malloc(message_.size() + 1));
-      if (adbc_error->message != nullptr) {
-        std::memcpy(adbc_error->message, message_.c_str(), message_.size() + 
1);
-      }
-    }
-
-    std::memcpy(adbc_error->sqlstate, sql_state_, sizeof(sql_state_));
-    adbc_error->release = &CRelease;
-  }
-
- private:
-  std::string message_;
-  std::vector<std::pair<std::string, std::string>> details_;
-  char sql_state_[5];
-
-  // Let the Driver use these to expose C callables wrapping option 
setters/getters
-  template <typename DatabaseT, typename ConnectionT, typename StatementT>
-  friend class Driver;
-
-  int CDetailCount() const { return details_.size(); }
-
-  AdbcErrorDetail CDetail(int index) const {
-    const auto& detail = details_[index];
-    return {detail.first.c_str(), reinterpret_cast<const 
uint8_t*>(detail.second.data()),
-            detail.second.size() + 1};
-  }
-
-  static void CRelease(AdbcError* error) {
-    if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) {
-      auto error_obj = reinterpret_cast<Error*>(error->private_data);
-      delete error_obj;
-    } else {
-      std::free(error->message);
-    }
-
-    std::memset(error, 0, sizeof(AdbcError));
-  }
-};
-
-// Variant that handles the option types that can be get/set by databases,
-// connections, and statements. It currently does not attempt conversion
-// (i.e., getting a double option as a string).
-class Option {
- public:
-  enum Type { TYPE_MISSING, TYPE_STRING, TYPE_BYTES, TYPE_INT, TYPE_DOUBLE };
-
-  Option() : type_(TYPE_MISSING) {}
-  explicit Option(const std::string& value) : type_(TYPE_STRING), 
value_string_(value) {}
-  explicit Option(const std::vector<uint8_t>& value)
-      : type_(TYPE_BYTES), value_bytes_(value) {}
-  explicit Option(double value) : type_(TYPE_DOUBLE), value_double_(value) {}
-  explicit Option(int64_t value) : type_(TYPE_INT), value_int_(value) {}
-
-  Type type() const { return type_; }
-
-  const std::string& GetStringUnsafe() const { return value_string_; }
-
-  const std::vector<uint8_t>& GetBytesUnsafe() const { return value_bytes_; }
-
-  int64_t GetIntUnsafe() const { return value_int_; }
-
-  double GetDoubleUnsafe() const { return value_double_; }
-
- private:
-  Type type_;
-  std::string value_string_;
-  std::vector<uint8_t> value_bytes_;
-  double value_double_;
-  int64_t value_int_;
-
-  // Methods used by trampolines to export option values in C below
-  friend class ObjectBase;
-
-  AdbcStatusCode CGet(char* out, size_t* length) const {
-    switch (type_) {
-      case TYPE_STRING: {
-        const std::string& value = GetStringUnsafe();
-        size_t value_size_with_terminator = value.size() + 1;
-        if (*length < value_size_with_terminator) {
-          *length = value_size_with_terminator;
-        } else {
-          memcpy(out, value.data(), value_size_with_terminator);
-        }
-
-        return ADBC_STATUS_OK;
-      }
-      default:
-        return ADBC_STATUS_NOT_FOUND;
-    }
-  }
-
-  AdbcStatusCode CGet(uint8_t* out, size_t* length) const {
-    switch (type_) {
-      case TYPE_BYTES: {
-        const std::vector<uint8_t>& value = GetBytesUnsafe();
-        if (*length < value.size()) {
-          *length = value.size();
-        } else {
-          memcpy(out, value.data(), value.size());
-        }
-
-        return ADBC_STATUS_OK;
-      }
-      default:
-        return ADBC_STATUS_NOT_FOUND;
-    }
-  }
-
-  AdbcStatusCode CGet(int64_t* value) const {
-    switch (type_) {
-      case TYPE_INT:
-        *value = GetIntUnsafe();
-        return ADBC_STATUS_OK;
-      default:
-        return ADBC_STATUS_NOT_FOUND;
-    }
-  }
-
-  AdbcStatusCode CGet(double* value) const {
-    switch (type_) {
-      case TYPE_DOUBLE:
-        *value = GetDoubleUnsafe();
-        return ADBC_STATUS_OK;
-      default:
-        return ADBC_STATUS_NOT_FOUND;
-    }
-  }
-};
-
-// Base class for private_data of AdbcDatabase, AdbcConnection, and 
AdbcStatement
-// This class handles option setting and getting.
-class ObjectBase {
- public:
-  ObjectBase() : driver_(nullptr) {}
-
-  virtual ~ObjectBase() {}
-
-  // Driver authors can override this method to reject options that are not 
supported or
-  // that are set at a time not supported by the driver (e.g., to reject 
options that are
-  // set after Init() is called if this is not supported).
-  virtual AdbcStatusCode SetOption(const std::string& key, const Option& 
value) {
-    options_[key] = value;
-    return ADBC_STATUS_OK;
-  }
-
-  // Called After zero or more SetOption() calls. The parent is the 
private_data of
-  // the AdbcDriver, AdbcDatabase, or AdbcConnection when initializing a 
subclass of
-  // DatabaseObjectBase, ConnectionObjectBase, and StatementObjectBase 
(respectively).
-  // For example, if you have defined Driver<MyDatabase, MyConnection, 
MyStatement>,
-  // you can reinterpret_cast<MyDatabase>(parent) in MyConnection::Init().
-  virtual AdbcStatusCode Init(void* parent, AdbcError* error) { return 
ADBC_STATUS_OK; }
-
-  // Called when the corresponding AdbcXXXRelease() function is invoked from C.
-  // Driver authors can override this method to return an error if the object 
is
-  // not in a valid state (e.g., if a connection has open statements) or to 
clean
-  // up resources when resource cleanup could fail. Resource cleanup that 
cannot fail
-  // (e.g., releasing memory) should generally be handled in the deleter.
-  virtual AdbcStatusCode Release(AdbcError* error) { return ADBC_STATUS_OK; }
-
-  // Get an option that was previously set, providing an optional default 
value.
-  virtual const Option& GetOption(const std::string& key,
-                                  const Option& default_value = Option()) 
const {
-    auto result = options_.find(key);
-    if (result == options_.end()) {
-      return default_value;
-    } else {
-      return result->second;
-    }
-  }
-
- protected:
-  // Needed to export errors using Error::ToAdbc() that use 1.1.0 extensions
-  // (i.e., error details). This will be nullptr before Init() is called.
-  AdbcDriver* driver() const { return driver_; }
-
- private:
-  AdbcDriver* driver_;
-  std::unordered_map<std::string, Option> options_;
-
-  // Let the Driver use these to expose C callables wrapping option 
setters/getters
-  template <typename DatabaseT, typename ConnectionT, typename StatementT>
-  friend class Driver;
-
-  // The AdbcDriver* struct is set right before Init() is called by the Driver
-  // trampoline.
-  void set_driver(AdbcDriver* driver) { driver_ = driver; }
-
-  template <typename T>
-  AdbcStatusCode CSetOption(const char* key, T value, AdbcError* error) {
-    Option option(value);
-    return SetOption(key, option);
-  }
-
-  AdbcStatusCode CSetOptionBytes(const char* key, const uint8_t* value, size_t 
length,
-                                 AdbcError* error) {
-    std::vector<uint8_t> cppvalue(value, value + length);
-    Option option(cppvalue);
-    return SetOption(key, option);
-  }
-
-  template <typename T>
-  AdbcStatusCode CGetOptionStringLike(const char* key, T* value, size_t* 
length,
-                                      AdbcError* error) const {
-    Option result = GetOption(key);
-    if (result.type() == Option::TYPE_MISSING) {
-      InitErrorNotFound(key, error);
-      return ADBC_STATUS_NOT_FOUND;
-    } else {
-      AdbcStatusCode status = result.CGet(value, length);
-      if (status != ADBC_STATUS_OK) {
-        InitErrorWrongType(key, error);
-      }
-
-      return status;
-    }
-  }
-
-  template <typename T>
-  AdbcStatusCode CGetOptionNumeric(const char* key, T* value, AdbcError* 
error) const {
-    Option result = GetOption(key);
-    if (result.type() == Option::TYPE_MISSING) {
-      InitErrorNotFound(key, error);
-      return ADBC_STATUS_NOT_FOUND;
-    } else {
-      AdbcStatusCode status = result.CGet(value);
-      if (status != ADBC_STATUS_OK) {
-        InitErrorWrongType(key, error);
-      }
-
-      return status;
-    }
-  }
-
-  void InitErrorNotFound(const char* key, AdbcError* error) const {
-    std::stringstream msg_builder;
-    msg_builder << "Option not found for key '" << key << "'";
-    Error cpperror(msg_builder.str());
-    cpperror.AddDetail("adbc.driver_base.option_key", key);
-    cpperror.ToAdbc(error, driver());
-  }
-
-  void InitErrorWrongType(const char* key, AdbcError* error) const {
-    std::stringstream msg_builder;
-    msg_builder << "Wrong type requested for option key '" << key << "'";
-    Error cpperror(msg_builder.str());
-    cpperror.AddDetail("adbc.driver_base.option_key", key);
-    cpperror.ToAdbc(error, driver());
-  }
-};
-
-// Driver authors can subclass DatabaseObjectBase to track driver-specific
-// state pertaining to the AdbcDatbase. The private_data member of an
-// AdbcDatabase initialized by the driver will be a pointer to the
-// subclass of DatbaseObjectBase.
-class DatabaseObjectBase : public ObjectBase {
- public:
-  // (there are no database functions other than option getting/setting)
-};
-
-// Driver authors can subclass ConnectionObjectBase to track driver-specific
-// state pertaining to the AdbcConnection. The private_data member of an
-// AdbcConnection initialized by the driver will be a pointer to the
-// subclass of ConnectionObjectBase. Driver authors can override methods to
-// implement the corresponding ConnectionXXX driver methods.
-class ConnectionObjectBase : public ObjectBase {
- public:
-  virtual AdbcStatusCode Commit(AdbcError* error) { return 
ADBC_STATUS_NOT_IMPLEMENTED; }
-
-  virtual AdbcStatusCode GetInfo(const uint32_t* info_codes, size_t 
info_codes_length,
-                                 ArrowArrayStream* out, AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode GetObjects(int depth, const char* catalog, const 
char* db_schema,
-                                    const char* table_name, const char** 
table_type,
-                                    const char* column_name, ArrowArrayStream* 
out,
-                                    AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode GetTableSchema(const char* catalog, const char* 
db_schema,
-                                        const char* table_name, ArrowSchema* 
schema,
-                                        AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode GetTableTypes(ArrowArrayStream* out, AdbcError* 
error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode ReadPartition(const uint8_t* serialized_partition,
-                                       size_t serialized_length, 
ArrowArrayStream* out,
-                                       AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode Rollback(AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode Cancel(AdbcError* error) { return 
ADBC_STATUS_NOT_IMPLEMENTED; }
-
-  virtual AdbcStatusCode GetStatistics(const char* catalog, const char* 
db_schema,
-                                       const char* table_name, char 
approximate,
-                                       ArrowArrayStream* out, AdbcError* 
error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode GetStatisticNames(ArrowArrayStream* out, AdbcError* 
error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-};
-
-// Driver authors can subclass StatementObjectBase to track driver-specific
-// state pertaining to the AdbcStatement. The private_data member of an
-// AdbcStatement initialized by the driver will be a pointer to the
-// subclass of StatementObjectBase. Driver authors can override methods to
-// implement the corresponding StatementXXX driver methods.
-class StatementObjectBase : public ObjectBase {
- public:
-  virtual AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* 
rows_affected,
-                                      AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode ExecuteSchema(ArrowSchema* schema, AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode Prepare(AdbcError* error) { return 
ADBC_STATUS_NOT_IMPLEMENTED; }
-
-  virtual AdbcStatusCode SetSqlQuery(const char* query, AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode SetSubstraitPlan(const uint8_t* plan, size_t length,
-                                          AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode Bind(ArrowArray* values, ArrowSchema* schema, 
AdbcError* error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode BindStream(ArrowArrayStream* stream, AdbcError* 
error) {
-    return ADBC_STATUS_NOT_IMPLEMENTED;
-  }
-
-  virtual AdbcStatusCode Cancel(AdbcError* error) { return 
ADBC_STATUS_NOT_IMPLEMENTED; }
-};
-
-// 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_1_0) return ADBC_STATUS_NOT_IMPLEMENTED;
-    AdbcDriver* driver = (AdbcDriver*)raw_driver;
-    std::memset(driver, 0, sizeof(AdbcDriver));
-
-    // Driver lifecycle
-    driver->private_data = new Driver();
-    driver->release = &CDriverRelease;
-
-    // Driver functions
-    driver->ErrorGetDetailCount = &CErrorGetDetailCount;
-    driver->ErrorGetDetail = &CErrorGetDetail;
-
-    // Database lifecycle
-    driver->DatabaseNew = &CNew<AdbcDatabase, DatabaseT>;
-    driver->DatabaseInit = &CDatabaseInit;
-    driver->DatabaseRelease = &CRelease<AdbcDatabase, DatabaseT>;
-
-    // Database functions
-    driver->DatabaseSetOption = &CSetOption<AdbcDatabase, DatabaseT>;
-    driver->DatabaseSetOptionBytes = &CSetOptionBytes<AdbcDatabase, DatabaseT>;
-    driver->DatabaseSetOptionInt = &CSetOptionInt<AdbcDatabase, DatabaseT>;
-    driver->DatabaseSetOptionDouble = &CSetOptionDouble<AdbcDatabase, 
DatabaseT>;
-    driver->DatabaseGetOption = &CGetOption<AdbcDatabase, DatabaseT>;
-    driver->DatabaseGetOptionBytes = &CGetOptionBytes<AdbcDatabase, DatabaseT>;
-    driver->DatabaseGetOptionInt = &CGetOptionInt<AdbcDatabase, DatabaseT>;
-    driver->DatabaseGetOptionDouble = &CGetOptionDouble<AdbcDatabase, 
DatabaseT>;
-
-    // Connection lifecycle
-    driver->ConnectionNew = &CNew<AdbcConnection, ConnectionT>;
-    driver->ConnectionInit = &CConnectionInit;
-    driver->ConnectionRelease = &CRelease<AdbcConnection, ConnectionT>;
-
-    // Connection functions
-    driver->ConnectionSetOption = &CSetOption<AdbcConnection, ConnectionT>;
-    driver->ConnectionSetOptionBytes = &CSetOptionBytes<AdbcConnection, 
ConnectionT>;
-    driver->ConnectionSetOptionInt = &CSetOptionInt<AdbcConnection, 
ConnectionT>;
-    driver->ConnectionSetOptionDouble = &CSetOptionDouble<AdbcConnection, 
ConnectionT>;
-    driver->ConnectionGetOption = &CGetOption<AdbcConnection, ConnectionT>;
-    driver->ConnectionGetOptionBytes = &CGetOptionBytes<AdbcConnection, 
ConnectionT>;
-    driver->ConnectionGetOptionInt = &CGetOptionInt<AdbcConnection, 
ConnectionT>;
-    driver->ConnectionGetOptionDouble = &CGetOptionDouble<AdbcConnection, 
ConnectionT>;
-    driver->ConnectionCommit = &CConnectionCommit;
-    driver->ConnectionGetInfo = &CConnectionGetInfo;
-    driver->ConnectionGetObjects = &CConnectionGetObjects;
-    driver->ConnectionGetTableSchema = &CConnectionGetTableSchema;
-    driver->ConnectionGetTableTypes = &CConnectionGetTableTypes;
-    driver->ConnectionReadPartition = &CConnectionReadPartition;
-    driver->ConnectionRollback = &CConnectionRollback;
-    driver->ConnectionCancel = &CConnectionCancel;
-    driver->ConnectionGetStatistics = &CConnectionGetStatistics;
-    driver->ConnectionGetStatisticNames = &CConnectionGetStatisticNames;
-
-    // Statement lifecycle
-    driver->StatementNew = &CStatementNew;
-    driver->StatementRelease = &CRelease<AdbcStatement, StatementT>;
-
-    // Statement functions
-    driver->StatementSetOption = &CSetOption<AdbcStatement, StatementT>;
-    driver->StatementSetOptionBytes = &CSetOptionBytes<AdbcStatement, 
StatementT>;
-    driver->StatementSetOptionInt = &CSetOptionInt<AdbcStatement, StatementT>;
-    driver->StatementSetOptionDouble = &CSetOptionDouble<AdbcStatement, 
StatementT>;
-    driver->StatementGetOption = &CGetOption<AdbcStatement, StatementT>;
-    driver->StatementGetOptionBytes = &CGetOptionBytes<AdbcStatement, 
StatementT>;
-    driver->StatementGetOptionInt = &CGetOptionInt<AdbcStatement, StatementT>;
-    driver->StatementGetOptionDouble = &CGetOptionDouble<AdbcStatement, 
StatementT>;
-
-    driver->StatementExecuteQuery = &CStatementExecuteQuery;
-    driver->StatementExecuteSchema = &CStatementExecuteSchema;
-    driver->StatementPrepare = &CStatementPrepare;
-    driver->StatementSetSqlQuery = &CStatementSetSqlQuery;
-    driver->StatementSetSubstraitPlan = &CStatementSetSubstraitPlan;
-    driver->StatementBind = &CStatementBind;
-    driver->StatementBindStream = &CStatementBindStream;
-    driver->StatementCancel = &CStatementCancel;
-
-    return ADBC_STATUS_OK;
-  }
-
- private:
-  // 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<Error*>(error->private_data);
-    return error_obj->CDetailCount();
-  }
-
-  static AdbcErrorDetail CErrorGetDetail(const AdbcError* error, int index) {
-    auto error_obj = reinterpret_cast<Error*>(error->private_data);
-    return error_obj->CDetail(index);
-  }
-
-  // Templatable trampolines
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CNew(T* obj, AdbcError* error) {
-    auto private_data = new ObjectT();
-    obj->private_data = private_data;
-    return ADBC_STATUS_OK;
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CRelease(T* obj, AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    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, typename ObjectT>
-  static AdbcStatusCode CSetOption(T* obj, const char* key, const char* value,
-                                   AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CSetOption<>(key, value, error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CSetOptionBytes(T* obj, const char* key, const 
uint8_t* value,
-                                        size_t length, AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->CSetOptionBytes(key, value, length, error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CSetOptionInt(T* obj, const char* key, int64_t value,
-                                      AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CSetOption<>(key, value, error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CSetOptionDouble(T* obj, const char* key, double value,
-                                         AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CSetOption<>(key, value, error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CGetOption(T* obj, const char* key, char* value, 
size_t* length,
-                                   AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CGetOptionStringLike<>(key, value, length, 
error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CGetOptionBytes(T* obj, const char* key, uint8_t* 
value,
-                                        size_t* length, AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CGetOptionStringLike<>(key, value, length, 
error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CGetOptionInt(T* obj, const char* key, int64_t* value,
-                                      AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CGetOptionNumeric<>(key, value, error);
-  }
-
-  template <typename T, typename ObjectT>
-  static AdbcStatusCode CGetOptionDouble(T* obj, const char* key, double* 
value,
-                                         AdbcError* error) {
-    auto private_data = reinterpret_cast<ObjectT*>(obj->private_data);
-    return private_data->template CGetOptionNumeric<>(key, value, error);
-  }
-
-  // Database trampolines
-  static AdbcStatusCode CDatabaseInit(AdbcDatabase* database, AdbcError* 
error) {
-    auto private_data = reinterpret_cast<DatabaseT*>(database->private_data);
-    private_data->set_driver(database->private_driver);
-    return private_data->Init(database->private_driver->private_data, error);
-  }
-
-  // Connection trampolines
-  static AdbcStatusCode CConnectionInit(AdbcConnection* connection,
-                                        AdbcDatabase* database, AdbcError* 
error) {
-    auto private_data = 
reinterpret_cast<ConnectionT*>(connection->private_data);
-    private_data->set_driver(connection->private_driver);
-    return private_data->Init(database->private_data, error);
-  }
-
-  static AdbcStatusCode CConnectionCancel(AdbcConnection* connection, 
AdbcError* 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) {
-    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) {
-    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) {
-    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) {
-    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) {
-    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) {
-    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) {
-    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) {
-    auto private_data = 
reinterpret_cast<ConnectionT*>(connection->private_data);
-    return private_data->Commit(error);
-  }
-
-  static AdbcStatusCode CConnectionRollback(AdbcConnection* connection,
-                                            AdbcError* error) {
-    auto private_data = 
reinterpret_cast<ConnectionT*>(connection->private_data);
-    return private_data->Rollback(error);
-  }
-
-  // Statement trampolines
-  static AdbcStatusCode CStatementNew(AdbcConnection* connection,
-                                      AdbcStatement* statement, AdbcError* 
error) {
-    auto private_data = new StatementT();
-    private_data->set_driver(connection->private_driver);
-    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 CStatementExecuteQuery(AdbcStatement* statement,
-                                               ArrowArrayStream* stream,
-                                               int64_t* rows_affected, 
AdbcError* 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) {
-    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
-    return private_data->ExecuteSchema(schema, error);
-  }
-
-  static AdbcStatusCode CStatementPrepare(AdbcStatement* statement, AdbcError* 
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) {
-    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) {
-    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
-    return private_data->SetSubstraitPlan(plan, length, error);
-  }
-
-  static AdbcStatusCode CStatementBind(AdbcStatement* statement, ArrowArray* 
values,
-                                       ArrowSchema* schema, AdbcError* error) {
-    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
-    return private_data->Bind(values, schema, error);
-  }
-
-  static AdbcStatusCode CStatementBindStream(AdbcStatement* statement,
-                                             ArrowArrayStream* stream, 
AdbcError* error) {
-    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
-    return private_data->BindStream(stream, error);
-  }
-
-  static AdbcStatusCode CStatementCancel(AdbcStatement* statement, AdbcError* 
error) {
-    auto private_data = reinterpret_cast<StatementT*>(statement->private_data);
-    return private_data->Cancel(error);
-  }
-};
-
-}  // namespace common
-
-}  // namespace adbc
diff --git a/c/driver/common/driver_test.cc b/c/driver/common/driver_test.cc
deleted file mode 100644
index 85d24826c..000000000
--- a/c/driver/common/driver_test.cc
+++ /dev/null
@@ -1,271 +0,0 @@
-// 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 <gtest/gtest.h>
-#include <cstring>
-
-#include <arrow-adbc/adbc.h>
-#include "driver_base.h"
-
-// Self-contained version of the Handle
-static inline void clean_up(AdbcDriver* ptr) { ptr->release(ptr, nullptr); }
-
-static inline void clean_up(AdbcDatabase* ptr) {
-  ptr->private_driver->DatabaseRelease(ptr, nullptr);
-}
-
-static inline void clean_up(AdbcConnection* ptr) {
-  ptr->private_driver->ConnectionRelease(ptr, nullptr);
-}
-
-static inline void clean_up(AdbcStatement* ptr) {
-  ptr->private_driver->StatementRelease(ptr, nullptr);
-}
-
-static inline void clean_up(AdbcError* ptr) {
-  if (ptr->release != nullptr) {
-    ptr->release(ptr);
-  }
-}
-
-template <typename T>
-class Handle {
- public:
-  explicit Handle(T* value) : value_(value) {}
-
-  ~Handle() { clean_up(value_); }
-
- private:
-  T* value_;
-};
-
-class VoidDatabase : public adbc::common::DatabaseObjectBase {};
-
-class VoidConnection : public adbc::common::ConnectionObjectBase {};
-
-class VoidStatement : public adbc::common::StatementObjectBase {};
-
-using VoidDriver = adbc::common::Driver<VoidDatabase, VoidConnection, 
VoidStatement>;
-
-AdbcStatusCode VoidDriverInitFunc(int version, void* raw_driver, AdbcError* 
error) {
-  return VoidDriver::Init(version, raw_driver, error);
-}
-
-TEST(TestDriverBase, TestVoidDriverOptions) {
-  // Test the get/set option implementation in the base driver
-  struct AdbcDriver driver;
-  memset(&driver, 0, sizeof(driver));
-  ASSERT_EQ(VoidDriverInitFunc(ADBC_VERSION_1_1_0, &driver, nullptr), 
ADBC_STATUS_OK);
-  Handle<AdbcDriver> driver_handle(&driver);
-
-  struct AdbcDatabase database;
-  memset(&database, 0, sizeof(database));
-  ASSERT_EQ(driver.DatabaseNew(&database, nullptr), ADBC_STATUS_OK);
-  database.private_driver = &driver;
-  Handle<AdbcDatabase> database_handle(&database);
-  ASSERT_EQ(driver.DatabaseInit(&database, nullptr), ADBC_STATUS_OK);
-
-  std::vector<char> opt_string;
-  std::vector<uint8_t> opt_bytes;
-  size_t opt_size = 0;
-  int64_t opt_int = 0;
-  double opt_double = 0;
-
-  // Check return codes without an error pointer for non-existent keys
-  ASSERT_EQ(driver.DatabaseGetOption(&database, "key_that_does_not_exist", 
nullptr,
-                                     &opt_size, nullptr),
-            ADBC_STATUS_NOT_FOUND);
-  ASSERT_EQ(driver.DatabaseGetOptionBytes(&database, 
"key_that_does_not_exist", nullptr,
-                                          &opt_size, nullptr),
-            ADBC_STATUS_NOT_FOUND);
-  ASSERT_EQ(driver.DatabaseGetOptionInt(&database, "key_that_does_not_exist", 
&opt_int,
-                                        nullptr),
-            ADBC_STATUS_NOT_FOUND);
-  ASSERT_EQ(driver.DatabaseGetOptionDouble(&database, 
"key_that_does_not_exist",
-                                           &opt_double, nullptr),
-            ADBC_STATUS_NOT_FOUND);
-
-  // Check set/get for string
-  ASSERT_EQ(driver.DatabaseSetOption(&database, "key_string", "value_string", 
nullptr),
-            ADBC_STATUS_OK);
-  opt_size = 0;
-  ASSERT_EQ(
-      driver.DatabaseGetOption(&database, "key_string", nullptr, &opt_size, 
nullptr),
-      ADBC_STATUS_OK);
-  ASSERT_EQ(opt_size, strlen("value_string") + 1);
-  opt_string.resize(opt_size);
-  ASSERT_EQ(driver.DatabaseGetOption(&database, "key_string", 
opt_string.data(),
-                                     &opt_size, nullptr),
-            ADBC_STATUS_OK);
-
-  // Check set/get for bytes
-  const uint8_t test_bytes[] = {0x01, 0x02, 0x03};
-  ASSERT_EQ(driver.DatabaseSetOptionBytes(&database, "key_bytes", test_bytes,
-                                          sizeof(test_bytes), nullptr),
-            ADBC_STATUS_OK);
-  opt_size = 0;
-  ASSERT_EQ(
-      driver.DatabaseGetOptionBytes(&database, "key_bytes", nullptr, 
&opt_size, nullptr),
-      ADBC_STATUS_OK);
-  ASSERT_EQ(opt_size, sizeof(test_bytes));
-  opt_bytes.resize(opt_size);
-  ASSERT_EQ(driver.DatabaseGetOptionBytes(&database, "key_bytes", 
opt_bytes.data(),
-                                          &opt_size, nullptr),
-            ADBC_STATUS_OK);
-
-  // Check set/get for int
-  ASSERT_EQ(driver.DatabaseSetOptionInt(&database, "key_int", 1234, nullptr),
-            ADBC_STATUS_OK);
-  ASSERT_EQ(driver.DatabaseGetOptionInt(&database, "key_int", &opt_int, 
nullptr),
-            ADBC_STATUS_OK);
-  ASSERT_EQ(opt_int, 1234);
-
-  // Check set/get for double
-  ASSERT_EQ(driver.DatabaseSetOptionDouble(&database, "key_double", 1234.5, 
nullptr),
-            ADBC_STATUS_OK);
-  ASSERT_EQ(driver.DatabaseGetOptionDouble(&database, "key_double", 
&opt_double, nullptr),
-            ADBC_STATUS_OK);
-  ASSERT_EQ(opt_double, 1234.5);
-
-  // Check error code for getting a key of an incorrect type
-  opt_size = 0;
-  ASSERT_EQ(driver.DatabaseGetOption(&database, "key_bytes", nullptr, 
&opt_size, nullptr),
-            ADBC_STATUS_NOT_FOUND);
-  ASSERT_EQ(
-      driver.DatabaseGetOptionBytes(&database, "key_string", nullptr, 
&opt_size, nullptr),
-      ADBC_STATUS_NOT_FOUND);
-  ASSERT_EQ(driver.DatabaseGetOptionInt(&database, "key_bytes", &opt_int, 
nullptr),
-            ADBC_STATUS_NOT_FOUND);
-  ASSERT_EQ(driver.DatabaseGetOptionDouble(&database, "key_bytes", 
&opt_double, nullptr),
-            ADBC_STATUS_NOT_FOUND);
-}
-
-TEST(TestDriverBase, TestVoidDriverError) {
-  // Test the extended error detail implementation in the base driver
-  struct AdbcDriver driver;
-  memset(&driver, 0, sizeof(driver));
-  ASSERT_EQ(VoidDriverInitFunc(ADBC_VERSION_1_1_0, &driver, nullptr), 
ADBC_STATUS_OK);
-  Handle<AdbcDriver> driver_handle(&driver);
-
-  struct AdbcDatabase database;
-  memset(&database, 0, sizeof(database));
-  ASSERT_EQ(driver.DatabaseNew(&database, nullptr), ADBC_STATUS_OK);
-  database.private_driver = &driver;
-  Handle<AdbcDatabase> database_handle(&database);
-  ASSERT_EQ(driver.DatabaseInit(&database, nullptr), ADBC_STATUS_OK);
-
-  struct AdbcError error;
-  memset(&error, 0, sizeof(error));
-  Handle<AdbcError> error_handle(&error);
-  size_t opt_size = 0;
-
-  // With zero-initialized error, should populate message but not details
-  ASSERT_EQ(driver.DatabaseGetOption(&database, "key_does_not_exist", nullptr, 
&opt_size,
-                                     &error),
-            ADBC_STATUS_NOT_FOUND);
-  EXPECT_EQ(error.vendor_code, 0);
-  EXPECT_STREQ(error.message, "Option not found for key 'key_does_not_exist'");
-  EXPECT_EQ(error.private_data, nullptr);
-  EXPECT_EQ(error.private_driver, nullptr);
-
-  // Release callback implementation should reset callback
-  error.release(&error);
-  ASSERT_EQ(error.release, nullptr);
-
-  // With the vendor code pre-set, should populate a version with details
-  memset(&error, 0, sizeof(error));
-  error.vendor_code = ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA;
-
-  ASSERT_EQ(driver.DatabaseGetOption(&database, "key_does_not_exist", nullptr, 
&opt_size,
-                                     &error),
-            ADBC_STATUS_NOT_FOUND);
-  EXPECT_NE(error.private_data, nullptr);
-  EXPECT_EQ(error.private_driver, &driver);
-
-  ASSERT_EQ(error.private_driver->ErrorGetDetailCount(&error), 1);
-
-  struct AdbcErrorDetail detail = error.private_driver->ErrorGetDetail(&error, 
0);
-  ASSERT_STREQ(detail.key, "adbc.driver_base.option_key");
-  ASSERT_EQ(detail.value_length, strlen("key_does_not_exist") + 1);
-  ASSERT_STREQ(reinterpret_cast<const char*>(detail.value), 
"key_does_not_exist");
-}
-
-TEST(TestDriverBase, TestVoidDriverMethods) {
-  struct AdbcDriver driver;
-  memset(&driver, 0, sizeof(driver));
-  ASSERT_EQ(VoidDriverInitFunc(ADBC_VERSION_1_1_0, &driver, nullptr), 
ADBC_STATUS_OK);
-  Handle<AdbcDriver> driver_handle(&driver);
-
-  // Database methods are only option related
-  struct AdbcDatabase database;
-  memset(&database, 0, sizeof(database));
-  ASSERT_EQ(driver.DatabaseNew(&database, nullptr), ADBC_STATUS_OK);
-  database.private_driver = &driver;
-  Handle<AdbcDatabase> database_handle(&database);
-  ASSERT_EQ(driver.DatabaseInit(&database, nullptr), ADBC_STATUS_OK);
-
-  // Test connection methods
-  struct AdbcConnection connection;
-  memset(&connection, 0, sizeof(connection));
-  ASSERT_EQ(driver.ConnectionNew(&connection, nullptr), ADBC_STATUS_OK);
-  connection.private_driver = &driver;
-  Handle<AdbcConnection> connection_handle(&connection);
-  ASSERT_EQ(driver.ConnectionInit(&connection, &database, nullptr), 
ADBC_STATUS_OK);
-
-  EXPECT_EQ(driver.ConnectionCommit(&connection, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionGetInfo(&connection, nullptr, 0, nullptr, 
nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionGetObjects(&connection, 0, nullptr, nullptr, 0, 
nullptr,
-                                        nullptr, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionGetTableSchema(&connection, nullptr, nullptr, 
nullptr,
-                                            nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionGetTableTypes(&connection, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionReadPartition(&connection, nullptr, 0, nullptr, 
nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionRollback(&connection, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionCancel(&connection, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionGetStatistics(&connection, nullptr, nullptr, 
nullptr, 0,
-                                           nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.ConnectionGetStatisticNames(&connection, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-
-  // Test statement methods
-  struct AdbcStatement statement;
-  memset(&statement, 0, sizeof(statement));
-  ASSERT_EQ(driver.StatementNew(&connection, &statement, nullptr), 
ADBC_STATUS_OK);
-  statement.private_driver = &driver;
-  Handle<AdbcStatement> statement_handle(&statement);
-
-  EXPECT_EQ(driver.StatementExecuteQuery(&statement, nullptr, nullptr, 
nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementExecuteSchema(&statement, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementPrepare(&statement, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementSetSqlQuery(&statement, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementSetSubstraitPlan(&statement, nullptr, 0, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementBind(&statement, nullptr, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementBindStream(&statement, nullptr, nullptr),
-            ADBC_STATUS_NOT_IMPLEMENTED);
-  EXPECT_EQ(driver.StatementCancel(&statement, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
-}
diff --git a/c/driver/common/meson.build b/c/driver/common/meson.build
index a0999431c..b1423f0e5 100644
--- a/c/driver/common/meson.build
+++ b/c/driver/common/meson.build
@@ -27,7 +27,6 @@ if get_option('tests')
     exc = executable(
         'adbc-driver-common-test',
         'utils_test.cc',
-        'driver_test.cc',
         include_directories: [include_dir],
         link_with: [adbc_common_lib],
         dependencies: [nanoarrow_dep, gtest_main_dep, gmock_dep],
diff --git a/c/driver/framework/CMakeLists.txt 
b/c/driver/framework/CMakeLists.txt
index 1dd81f78a..3efc3f1da 100644
--- a/c/driver/framework/CMakeLists.txt
+++ b/c/driver/framework/CMakeLists.txt
@@ -17,7 +17,7 @@
 
 include(FetchContent)
 
-add_library(adbc_driver_framework STATIC base_driver.cc catalog.cc objects.cc)
+add_library(adbc_driver_framework STATIC 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
diff --git a/c/driver/framework/base_driver.cc 
b/c/driver/framework/base_driver.cc
deleted file mode 100644
index 49b2406c7..000000000
--- a/c/driver/framework/base_driver.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-// 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_driver.h"
-
-namespace adbc::driver {
-Result<bool> Option::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_);
-}
-
-Result<int64_t> Option::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;
-        }
-        return status::InvalidArgument("Invalid integer value ", 
this->Format());
-      },
-      value_);
-}
-
-Result<std::string_view> Option::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;
-        }
-        return status::InvalidArgument("Invalid string value {}", 
this->Format());
-      },
-      value_);
-}
-
-std::string Option::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_);
-}
-
-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<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 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<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 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<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 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<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_);
-}
-
-}  // namespace adbc::driver
diff --git a/c/driver/framework/base_driver.h b/c/driver/framework/base_driver.h
index e241f6e83..b52e47412 100644
--- a/c/driver/framework/base_driver.h
+++ b/c/driver/framework/base_driver.h
@@ -80,26 +80,171 @@ class Option {
   bool has_value() const { return !std::holds_alternative<Unset>(value_); }
 
   /// \brief Try to parse a string value as a boolean.
-  Result<bool> AsBool() const;
+  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;
+  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;
+          }
+          return status::InvalidArgument("Invalid integer value ", 
this->Format());
+        },
+        value_);
+  }
 
   /// \brief Get the value if it is a string.
-  Result<std::string_view> AsString() const;
+  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;
+          }
+          return status::InvalidArgument("Invalid string value ", 
this->Format());
+        },
+        value_);
+  }
 
   /// \brief Provide a human-readable summary of the value
-  std::string Format() const;
+  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;
-  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;
+  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
@@ -122,7 +267,7 @@ class ObjectBase {
   ///
   /// 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.
+  /// Database/Connection/Statement.
   ///
   /// \param[in] parent A pointer to the AdbcDatabase or AdbcConnection
   ///   implementation as appropriate, or nullptr.
@@ -140,7 +285,7 @@ class ObjectBase {
   /// the destructor.
   ///
   /// Generally, you won't need to override this directly. Instead, use the
-  /// typed ReleaseImpl provided by DatabaseBase/ConnectionBase/StatementBase.
+  /// typed ReleaseImpl provided by Database/Connection/Statement.
   virtual AdbcStatusCode Release(AdbcError* error) { return ADBC_STATUS_OK; }
 
   /// \brief Get an option value.
@@ -622,4 +767,371 @@ class Driver {
 #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) {
+    RAISE_RESULT(error, int64_t 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
diff --git a/c/driver/framework/base_driver_test.cc 
b/c/driver/framework/base_driver_test.cc
index dfc1e38ea..1d8d61f60 100644
--- a/c/driver/framework/base_driver_test.cc
+++ b/c/driver/framework/base_driver_test.cc
@@ -19,10 +19,10 @@
 #include <cstring>
 
 #include <arrow-adbc/adbc.h>
-#include "driver/framework/base_connection.h"
-#include "driver/framework/base_database.h"
 #include "driver/framework/base_driver.h"
-#include "driver/framework/base_statement.h"
+#include "driver/framework/connection.h"
+#include "driver/framework/database.h"
+#include "driver/framework/statement.h"
 
 // Self-contained version of the Handle
 static inline void clean_up(AdbcDriver* ptr) { ptr->release(ptr, nullptr); }
@@ -52,17 +52,111 @@ class Handle {
 
 namespace {
 
-class VoidDatabase : public adbc::driver::DatabaseBase<VoidDatabase> {
+class BaseVoidDatabase : public adbc::driver::BaseDatabase<BaseVoidDatabase> {
  public:
   [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
 };
 
-class VoidConnection : public adbc::driver::ConnectionBase<VoidConnection> {
+class BaseVoidConnection : public 
adbc::driver::BaseConnection<BaseVoidConnection> {
  public:
   [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
 };
 
-class VoidStatement : public adbc::driver::StatementBase<VoidStatement> {
+class BaseVoidStatement : public 
adbc::driver::BaseStatement<BaseVoidStatement> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
+};
+
+using BaseVoidDriver =
+    adbc::driver::Driver<BaseVoidDatabase, BaseVoidConnection, 
BaseVoidStatement>;
+}  // namespace
+
+AdbcStatusCode BaseVoidDriverInitFunc(int version, void* raw_driver, 
AdbcError* error) {
+  return BaseVoidDriver::Init(version, raw_driver, error);
+}
+
+TEST(TestDriverBase, TestBaseVoidDriverMethods) {
+  // Checks that wires are plugged in for a framework-based driver based only 
on what is
+  // available in base_driver.h
+
+  struct AdbcDriver driver;
+  memset(&driver, 0, sizeof(driver));
+  ASSERT_EQ(BaseVoidDriverInitFunc(ADBC_VERSION_1_1_0, &driver, nullptr), 
ADBC_STATUS_OK);
+  Handle<AdbcDriver> driver_handle(&driver);
+
+  // Database methods are only option related
+  struct AdbcDatabase database;
+  memset(&database, 0, sizeof(database));
+  ASSERT_EQ(driver.DatabaseNew(&database, nullptr), ADBC_STATUS_OK);
+  database.private_driver = &driver;
+  Handle<AdbcDatabase> database_handle(&database);
+  ASSERT_EQ(driver.DatabaseInit(&database, nullptr), ADBC_STATUS_OK);
+
+  // Test connection methods
+  struct AdbcConnection connection;
+  memset(&connection, 0, sizeof(connection));
+  ASSERT_EQ(driver.ConnectionNew(&connection, nullptr), ADBC_STATUS_OK);
+  connection.private_driver = &driver;
+  Handle<AdbcConnection> connection_handle(&connection);
+  ASSERT_EQ(driver.ConnectionInit(&connection, &database, nullptr), 
ADBC_STATUS_OK);
+
+  EXPECT_EQ(driver.ConnectionCommit(&connection, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionGetInfo(&connection, nullptr, 0, nullptr, 
nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionGetObjects(&connection, 0, nullptr, nullptr, 0, 
nullptr,
+                                        nullptr, nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionGetTableSchema(&connection, nullptr, nullptr, 
nullptr,
+                                            nullptr, nullptr),
+            ADBC_STATUS_INVALID_ARGUMENT);
+  EXPECT_EQ(driver.ConnectionGetTableTypes(&connection, nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionReadPartition(&connection, nullptr, 0, nullptr, 
nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionRollback(&connection, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionCancel(&connection, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionGetStatistics(&connection, nullptr, nullptr, 
nullptr, 0,
+                                           nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.ConnectionGetStatisticNames(&connection, nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+
+  // Test statement methods
+  struct AdbcStatement statement;
+  memset(&statement, 0, sizeof(statement));
+  ASSERT_EQ(driver.StatementNew(&connection, &statement, nullptr), 
ADBC_STATUS_OK);
+  statement.private_driver = &driver;
+  Handle<AdbcStatement> statement_handle(&statement);
+
+  EXPECT_EQ(driver.StatementExecuteQuery(&statement, nullptr, nullptr, 
nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementExecuteSchema(&statement, nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementPrepare(&statement, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementSetSqlQuery(&statement, "", nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementSetSubstraitPlan(&statement, nullptr, 0, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementBind(&statement, nullptr, nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementBindStream(&statement, nullptr, nullptr),
+            ADBC_STATUS_NOT_IMPLEMENTED);
+  EXPECT_EQ(driver.StatementCancel(&statement, nullptr), 
ADBC_STATUS_NOT_IMPLEMENTED);
+}
+
+namespace {
+
+class VoidDatabase : public adbc::driver::Database<VoidDatabase> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
+};
+
+class VoidConnection : public adbc::driver::Connection<VoidConnection> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
+};
+
+class VoidStatement : public adbc::driver::Statement<VoidStatement> {
  public:
   [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
 };
@@ -75,7 +169,8 @@ AdbcStatusCode VoidDriverInitFunc(int version, void* 
raw_driver, AdbcError* erro
 }
 
 TEST(TestDriverBase, TestVoidDriverMethods) {
-  // Checks that wires are plugged in for a framework-based driver
+  // Checks that wires are plugged in for a framework-based driver based on
+  // the more-batteries-included Database, Connection, and Statement
 
   struct AdbcDriver driver;
   memset(&driver, 0, sizeof(driver));
diff --git a/c/driver/framework/base_connection.h 
b/c/driver/framework/connection.h
similarity index 98%
rename from c/driver/framework/base_connection.h
rename to c/driver/framework/connection.h
index d75265bde..f9e329ec1 100644
--- a/c/driver/framework/base_connection.h
+++ b/c/driver/framework/connection.h
@@ -39,9 +39,9 @@ namespace adbc::driver {
 /// define a constexpr static symbol called kErrorPrefix that is used to
 /// construct error messages.
 template <typename Derived>
-class ConnectionBase : public ObjectBase {
+class Connection : public ObjectBase {
  public:
-  using Base = ConnectionBase<Derived>;
+  using Base = Connection<Derived>;
 
   /// \brief Whether autocommit is enabled or not (by default: enabled).
   enum class AutocommitState {
@@ -49,8 +49,8 @@ class ConnectionBase : public ObjectBase {
     kTransaction,
   };
 
-  ConnectionBase() : ObjectBase() {}
-  ~ConnectionBase() = default;
+  Connection() : ObjectBase() {}
+  ~Connection() = default;
 
   /// \internal
   AdbcStatusCode Init(void* parent, AdbcError* error) override {
diff --git a/c/driver/framework/base_database.h b/c/driver/framework/database.h
similarity index 64%
rename from c/driver/framework/base_database.h
rename to c/driver/framework/database.h
index 28201b055..ff0dd756c 100644
--- a/c/driver/framework/base_database.h
+++ b/c/driver/framework/database.h
@@ -34,42 +34,22 @@ namespace adbc::driver {
 /// define a constexpr static symbol called kErrorPrefix that is used to
 /// construct error messages.
 template <typename Derived>
-class DatabaseBase : public ObjectBase {
+class Database : public BaseDatabase<Derived> {
  public:
-  using Base = DatabaseBase<Derived>;
+  using Base = Database<Derived>;
 
-  DatabaseBase() : ObjectBase() {}
-  ~DatabaseBase() = default;
-
-  /// \internal
-  AdbcStatusCode Init(void* parent, AdbcError* error) override {
-    if (auto status = impl().InitImpl(); !status.ok()) {
-      return status.ToAdbc(error);
-    }
-    return ObjectBase::Init(parent, error);
-  }
-
-  /// \internal
-  AdbcStatusCode Release(AdbcError* error) override {
-    return impl().ReleaseImpl().ToAdbc(error);
-  }
-
-  /// \internal
-  AdbcStatusCode SetOption(std::string_view key, Option value,
-                           AdbcError* error) override {
-    return impl().SetOptionImpl(key, std::move(value)).ToAdbc(error);
-  }
+  Database() : BaseDatabase<Derived>() {}
+  ~Database() = default;
 
   /// \brief Initialize the database.
-  virtual Status InitImpl() { return status::Ok(); }
+  virtual Status InitImpl() { return BaseDatabase<Derived>::InitImpl(); }
 
   /// \brief Release the database.
-  virtual Status ReleaseImpl() { return status::Ok(); }
+  virtual Status ReleaseImpl() { return BaseDatabase<Derived>::ReleaseImpl(); }
 
   /// \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());
+    return BaseDatabase<Derived>::SetOptionImpl(key, value);
   }
 
  private:
diff --git a/c/driver/framework/meson.build b/c/driver/framework/meson.build
index dfd32d261..432cc5bb7 100644
--- a/c/driver/framework/meson.build
+++ b/c/driver/framework/meson.build
@@ -18,7 +18,6 @@
 adbc_framework_lib = library(
     'adbc_driver_framework',
     sources: [
-        'base_driver.cc',
         'catalog.cc',
         'objects.cc',
     ],
diff --git a/c/driver/framework/base_statement.h 
b/c/driver/framework/statement.h
similarity index 98%
rename from c/driver/framework/base_statement.h
rename to c/driver/framework/statement.h
index e3bd0fc0f..af9eee03f 100644
--- a/c/driver/framework/base_statement.h
+++ b/c/driver/framework/statement.h
@@ -32,9 +32,9 @@ namespace adbc::driver {
 
 /// \brief A base implementation of a statement.
 template <typename Derived>
-class StatementBase : public ObjectBase {
+class Statement : public BaseStatement<Derived> {
  public:
-  using Base = StatementBase<Derived>;
+  using Base = Statement<Derived>;
 
   /// \brief What to do in ingestion when the table does not exist.
   enum class TableDoesNotExist {
@@ -71,10 +71,10 @@ class StatementBase : public ObjectBase {
   /// \brief Statement state: one of the above.
   using State = std::variant<EmptyState, IngestState, PreparedState, 
QueryState>;
 
-  StatementBase() : ObjectBase() {
+  Statement() : BaseStatement<Derived>() {
     std::memset(&bind_parameters_, 0, sizeof(bind_parameters_));
   }
-  ~StatementBase() = default;
+  ~Statement() = default;
 
   AdbcStatusCode Bind(ArrowArray* values, ArrowSchema* schema, AdbcError* 
error) {
     if (!values || !values->release) {
@@ -183,7 +183,7 @@ class StatementBase : public ObjectBase {
   }
 
   AdbcStatusCode Init(void* parent, AdbcError* error) {
-    lifecycle_state_ = LifecycleState::kInitialized;
+    this->lifecycle_state_ = LifecycleState::kInitialized;
     if (auto status = impl().InitImpl(parent); !status.ok()) {
       return status.ToAdbc(error);
     }
diff --git a/c/driver/sqlite/sqlite.cc b/c/driver/sqlite/sqlite.cc
index debec7193..6628acdd9 100644
--- a/c/driver/sqlite/sqlite.cc
+++ b/c/driver/sqlite/sqlite.cc
@@ -26,11 +26,11 @@
 #define ADBC_FRAMEWORK_USE_FMT
 #include "driver/common/options.h"
 #include "driver/common/utils.h"
-#include "driver/framework/base_connection.h"
-#include "driver/framework/base_database.h"
 #include "driver/framework/base_driver.h"
-#include "driver/framework/base_statement.h"
 #include "driver/framework/catalog.h"
+#include "driver/framework/connection.h"
+#include "driver/framework/database.h"
+#include "driver/framework/statement.h"
 #include "driver/framework/status.h"
 #include "driver/sqlite/statement_reader.h"
 
@@ -502,7 +502,7 @@ struct SqliteGetObjectsHelper : public 
driver::GetObjectsHelper {
   size_t next_constraint = 0;
 };
 
-class SqliteDatabase : public driver::DatabaseBase<SqliteDatabase> {
+class SqliteDatabase : public driver::Database<SqliteDatabase> {
  public:
   [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[SQLite]";
 
@@ -558,7 +558,7 @@ class SqliteDatabase : public 
driver::DatabaseBase<SqliteDatabase> {
   sqlite3* conn_ = nullptr;
 };
 
-class SqliteConnection : public driver::ConnectionBase<SqliteConnection> {
+class SqliteConnection : public driver::Connection<SqliteConnection> {
  public:
   [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[SQLite]";
 
@@ -672,7 +672,7 @@ class SqliteConnection : public 
driver::ConnectionBase<SqliteConnection> {
       }
       conn_ = nullptr;
     }
-    return ConnectionBase::ReleaseImpl();
+    return Connection::ReleaseImpl();
   }
 
   Status RollbackImpl() {
@@ -760,7 +760,7 @@ class SqliteConnection : public 
driver::ConnectionBase<SqliteConnection> {
   std::string extension_path_;
 };
 
-class SqliteStatement : public driver::StatementBase<SqliteStatement> {
+class SqliteStatement : public driver::Statement<SqliteStatement> {
  public:
   [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[SQLite]";
 
@@ -1083,7 +1083,7 @@ class SqliteStatement : public 
driver::StatementBase<SqliteStatement> {
 
   Status InitImpl(void* parent) {
     conn_ = reinterpret_cast<SqliteConnection*>(parent)->conn();
-    return StatementBase::InitImpl(parent);
+    return Statement::InitImpl(parent);
   }
 
   Status PrepareImpl(QueryState& state) {
@@ -1119,7 +1119,7 @@ class SqliteStatement : public 
driver::StatementBase<SqliteStatement> {
       }
     }
     AdbcSqliteBinderRelease(&binder_);
-    return StatementBase::ReleaseImpl();
+    return Statement::ReleaseImpl();
   }
 
   Status SetOptionImpl(std::string_view key, driver::Option value) {
diff --git a/r/adbcdrivermanager/bootstrap.R b/r/adbcdrivermanager/bootstrap.R
index 6eb725adf..73506902a 100644
--- a/r/adbcdrivermanager/bootstrap.R
+++ b/r/adbcdrivermanager/bootstrap.R
@@ -15,60 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# If we are building within the repo, copy the latest adbc.h and driver manager
-# implementation from the repo. We also run this from configure, so do nothing
-# if we aren't sitting within the repo (e.g., installing a source package from 
a
-# tarball).
-dir.create("src/arrow-adbc", showWarnings=FALSE)
-headers_to_vendor <- c(
-    "../../c/include/arrow-adbc/adbc.h",
-    "../../c/include/arrow-adbc/adbc_driver_manager.h"
-)
-if (all(file.exists(headers_to_vendor))) {
-  files_dst <- file.path("src/arrow-adbc", basename(headers_to_vendor))
-
-  n_removed <- sum(file.remove(files_dst))
-  if (n_removed > 0) {
-    cat(sprintf("Removed %d previously vendored files from src/arrow-adbc/\n", 
n_removed))
-  }
-
-  cat(
-    sprintf(
-      "Vendoring headers to src/arrow-adbc/:\n%s\n",
-      paste("-", headers_to_vendor, collapse = "\n")
-    )
-  )
-
-  if (all(file.copy(headers_to_vendor, "src/arrow-adbc"))) {
-    cat("All files successfully copied to src/arrow-adbc/\n")
-  } else {
-    stop("Failed to vendor all headers")
-  }
+source_files <- list.files("../../c", "\\.(h|c|cc|hpp)$", recursive = TRUE)
+source_files <- source_files[!grepl("_test\\.cc", source_files)]
+source_files <- source_files[!grepl("^(build|out)/", source_files)]
+# backward C++ causes CRAN warnings and the drivers do not use it
+source_files <- source_files[!grepl("^vendor/backward", source_files)]
+source_files <- file.path("c", source_files)
+src <- file.path("../..", source_files)
+dst <- file.path("src", source_files)
+
+unlink("src/c", recursive = TRUE)
+for (dir_name in rev(unique(dirname(dst)))) {
+  dir.create(dir_name, showWarnings = FALSE, recursive = TRUE)
 }
 
-files_to_vendor <- c(
-  "../../c/driver_manager/adbc_driver_manager.cc",
-  "../../c/driver/common/driver_base.h"
-)
-
-if (all(file.exists(files_to_vendor))) {
-  files_dst <- file.path("src", basename(files_to_vendor))
-
-  n_removed <- sum(file.remove(files_dst))
-  if (n_removed > 0) {
-    cat(sprintf("Removed %d previously vendored files from src/\n", n_removed))
-  }
-
-  cat(
-    sprintf(
-      "Vendoring files from arrow-adbc to src/:\n%s\n",
-      paste("-", files_to_vendor, collapse = "\n")
-    )
-  )
-
-  if (all(file.copy(files_to_vendor, "src"))) {
-    cat("All files successfully copied to src/\n")
-  } else {
-    stop("Failed to vendor all files")
-  }
-}
+stopifnot(all(file.copy(src, dst)))
diff --git a/r/adbcdrivermanager/configure b/r/adbcdrivermanager/configure
index 0bfdfcd42..a0bec8830 100755
--- a/r/adbcdrivermanager/configure
+++ b/r/adbcdrivermanager/configure
@@ -24,12 +24,3 @@
 if [ -f bootstrap.R ]; then
   $R_HOME/bin/Rscript bootstrap.R --vanilla
 fi
-
-if [ -f "src/arrow-adbc/adbc.h" ] && [ -f 
"src/arrow-adbc/adbc_driver_manager.h" ] && [ -f "src/adbc_driver_manager.cc" 
]; then
-  echo "Found vendored ADBC"
-  exit 0
-fi
-
-echo "Vendored src/arrow-adbc/adbc.h and/or src/adbc_driver_manager.cc are 
missing"
-echo "This source package was probably built incorrectly and it's probably not 
your fault"
-exit 1
diff --git a/r/adbcdrivermanager/src/.gitignore 
b/r/adbcdrivermanager/src/.gitignore
index 7b75640dc..81aa3fbde 100644
--- a/r/adbcdrivermanager/src/.gitignore
+++ b/r/adbcdrivermanager/src/.gitignore
@@ -18,7 +18,4 @@
 *.o
 *.so
 *.dll
-adbc.h
-adbc_driver_manager.h
-adbc_driver_manager.cc
-driver_base.h
+c/
diff --git a/r/adbcdrivermanager/src/Makevars b/r/adbcdrivermanager/src/Makevars
index ffcee27b8..b42e5c457 100644
--- a/r/adbcdrivermanager/src/Makevars
+++ b/r/adbcdrivermanager/src/Makevars
@@ -16,4 +16,12 @@
 # under the License.
 
 CXX_STD = CXX17
-PKG_CPPFLAGS=-I../src -DADBC_EXPORT=""
+PKG_CPPFLAGS=-I../src/c/include -I../src/c -DADBC_EXPORT=""
+
+OBJECTS = driver_test.o \
+    error.o \
+    init.o \
+    options.o \
+    radbc.o \
+    utils.o \
+    c/driver_manager/adbc_driver_manager.o
diff --git a/r/adbcdrivermanager/src/driver_test.cc 
b/r/adbcdrivermanager/src/driver_test.cc
index 5172f004b..7032d38b3 100644
--- a/r/adbcdrivermanager/src/driver_test.cc
+++ b/r/adbcdrivermanager/src/driver_test.cc
@@ -20,21 +20,91 @@
 #include <Rinternals.h>
 
 #include <cstring>
+#include <unordered_map>
 
-#include "driver_base.h"
+#include "driver/framework/base_driver.h"
 
 #include "arrow-adbc/adbc.h"
 
-using adbc::common::ConnectionObjectBase;
-using adbc::common::DatabaseObjectBase;
-using adbc::common::Option;
-using adbc::common::StatementObjectBase;
+using adbc::driver::Option;
+using adbc::driver::Result;
+using adbc::driver::Status;
 
-using VoidDriver =
-    adbc::common::Driver<DatabaseObjectBase, ConnectionObjectBase, 
StatementObjectBase>;
+class VoidDatabase : public adbc::driver::BaseDatabase<VoidDatabase> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
+
+  Status SetOptionImpl(std::string_view key, Option value) override {
+    options_[std::string(key)] = value;
+    return adbc::driver::status::Ok();
+  }
+
+  Result<Option> GetOption(std::string_view key) override {
+    auto result = options_.find(std::string(key));
+    if (result == options_.end()) {
+      Status out(ADBC_STATUS_NOT_FOUND, "option not found");
+      out.AddDetail("r.driver_test.option_key", std::string(key));
+      return out;
+    } else {
+      return result->second;
+    }
+  }
+
+ private:
+  std::unordered_map<std::string, Option> options_;
+};
+
+class VoidConnection : public adbc::driver::BaseConnection<VoidConnection> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
+
+  Status SetOptionImpl(std::string_view key, Option value) override {
+    options_[std::string(key)] = value;
+    return adbc::driver::status::Ok();
+  }
+
+  Result<Option> GetOption(std::string_view key) override {
+    auto result = options_.find(std::string(key));
+    if (result == options_.end()) {
+      Status out(ADBC_STATUS_NOT_FOUND, "option not found");
+      out.AddDetail("r.driver_test.option_key", std::string(key));
+      return out;
+    } else {
+      return result->second;
+    }
+  }
+
+ private:
+  std::unordered_map<std::string, Option> options_;
+};
+
+class VoidStatement : public adbc::driver::BaseStatement<VoidStatement> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[void]";
+
+  Status SetOptionImpl(std::string_view key, Option value) override {
+    options_[std::string(key)] = value;
+    return adbc::driver::status::Ok();
+  }
+
+  Result<Option> GetOption(std::string_view key) override {
+    auto result = options_.find(std::string(key));
+    if (result == options_.end()) {
+      Status out(ADBC_STATUS_NOT_FOUND, "option not found");
+      out.AddDetail("r.driver_test.option_key", std::string(key));
+      return out;
+    } else {
+      return result->second;
+    }
+  }
+
+ private:
+  std::unordered_map<std::string, Option> options_;
+};
 
 static AdbcStatusCode VoidDriverInitFunc(int version, void* raw_driver,
                                          AdbcError* error) {
+  using VoidDriver = adbc::driver::Driver<VoidDatabase, VoidConnection, 
VoidStatement>;
   return VoidDriver::Init(version, raw_driver, error);
 }
 
@@ -46,46 +116,42 @@ extern "C" SEXP RAdbcVoidDriverInitFunc(void) {
   return xptr;
 }
 
-class MonkeyDriverStatement : public StatementObjectBase {
+class MonkeyStatement : public adbc::driver::BaseStatement<MonkeyStatement> {
  public:
-  MonkeyDriverStatement() { stream_.release = nullptr; }
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[monkey]";
 
-  ~MonkeyDriverStatement() {
+  MonkeyStatement() { stream_.release = nullptr; }
+
+  ~MonkeyStatement() {
     if (stream_.release != nullptr) {
       stream_.release(&stream_);
     }
   }
 
-  AdbcStatusCode BindStream(ArrowArrayStream* stream, AdbcError* error) {
+  Status BindStreamImpl(ArrowArrayStream* stream) {
     if (stream_.release != nullptr) {
       stream_.release(&stream_);
     }
 
     std::memcpy(&stream_, stream, sizeof(ArrowArrayStream));
     stream->release = nullptr;
-    return ADBC_STATUS_OK;
+    return adbc::driver::status::Ok();
   }
 
-  AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* rows_affected,
-                              AdbcError* error) {
+  Result<int64_t> ExecuteQueryImpl(ArrowArrayStream* stream) {
     if (stream != nullptr) {
       std::memcpy(stream, &stream_, sizeof(ArrowArrayStream));
       stream_.release = nullptr;
     }
 
-    if (rows_affected != nullptr) {
-      *rows_affected = -1;
-    }
-
-    return ADBC_STATUS_OK;
+    return -1;
   }
 
  private:
   ArrowArrayStream stream_;
 };
 
-using MonkeyDriver =
-    adbc::common::Driver<DatabaseObjectBase, ConnectionObjectBase, 
MonkeyDriverStatement>;
+using MonkeyDriver = adbc::driver::Driver<VoidDatabase, VoidConnection, 
MonkeyStatement>;
 
 static AdbcStatusCode MonkeyDriverInitFunc(int version, void* raw_driver,
                                            AdbcError* error) {
@@ -100,49 +166,46 @@ extern "C" SEXP RAdbcMonkeyDriverInitFunc(void) {
   return xptr;
 }
 
-class LogDriverDatabase : public DatabaseObjectBase {
+class LogDatabase : public adbc::driver::BaseDatabase<LogDatabase> {
  public:
-  LogDriverDatabase() { Rprintf("LogDatabaseNew()\n"); }
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[log]";
 
-  ~LogDriverDatabase() { Rprintf("LogDatabaseRelease()\n"); }
+  LogDatabase() { Rprintf("LogDatabaseNew()\n"); }
 
-  AdbcStatusCode Init(void* parent, AdbcError* error) {
+  ~LogDatabase() { Rprintf("LogDatabaseRelease()\n"); }
+
+  AdbcStatusCode Init(void* parent, AdbcError* error) override {
     Rprintf("LogDatabaseInit()\n");
-    return DatabaseObjectBase::Init(parent, error);
+    return Base::Init(parent, error);
   }
 
-  const Option& GetOption(const std::string& key,
-                          const Option& default_value = Option()) const {
-    Rprintf("LogDatabaseGetOption()\n");
-    return DatabaseObjectBase::GetOption(key, default_value);
+  Status SetOptionImpl(std::string_view key, Option value) override {
+    Rprintf("LogDatabaseSetOption()\n");
+    return adbc::driver::status::Ok();
   }
 
-  AdbcStatusCode SetOption(const std::string& key, const Option& value) {
-    Rprintf("LogDatabaseSetOption()\n");
-    return DatabaseObjectBase::SetOption(key, value);
+  Result<Option> GetOption(std::string_view key) override {
+    Rprintf("LogDatabaseGetOption()\n");
+    return Base::GetOption(key);
   }
 };
 
-class LogDriverConnection : public ConnectionObjectBase {
+class LogConnection : public adbc::driver::BaseConnection<LogConnection> {
  public:
-  LogDriverConnection() { Rprintf("LogConnectionNew()\n"); }
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[log]";
 
-  ~LogDriverConnection() { Rprintf("LogConnectionRelease()\n"); }
+  LogConnection() { Rprintf("LogConnectionNew()\n"); }
 
-  AdbcStatusCode Init(void* parent, AdbcError* error) {
-    Rprintf("LogConnectionInit()\n");
-    return ConnectionObjectBase::Init(parent, error);
-  }
+  ~LogConnection() { Rprintf("LogConnectionRelease()\n"); }
 
-  const Option& GetOption(const std::string& key,
-                          const Option& default_value = Option()) const {
-    Rprintf("LogConnectionGetOption()\n");
-    return ConnectionObjectBase::GetOption(key, default_value);
+  AdbcStatusCode Init(void* parent, AdbcError* error) override {
+    Rprintf("LogConnectionInit()\n");
+    return Base::Init(parent, error);
   }
 
-  AdbcStatusCode SetOption(const std::string& key, const Option& value) {
+  Status SetOptionImpl(std::string_view key, Option value) override {
     Rprintf("LogConnectionSetOption()\n");
-    return ConnectionObjectBase::SetOption(key, value);
+    return adbc::driver::status::Ok();
   }
 
   AdbcStatusCode Commit(AdbcError* error) {
@@ -206,24 +269,20 @@ class LogDriverConnection : public ConnectionObjectBase {
   }
 };
 
-class LogDriverStatement : public StatementObjectBase {
+class LogStatement : public adbc::driver::BaseStatement<LogStatement> {
  public:
-  ~LogDriverStatement() { Rprintf("LogStatementRelease()\n"); }
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = "[log]";
 
-  AdbcStatusCode Init(void* parent, AdbcError* error) {
-    Rprintf("LogStatementNew()\n");
-    return StatementObjectBase::Init(parent, error);
-  }
+  ~LogStatement() { Rprintf("LogStatementRelease()\n"); }
 
-  const Option& GetOption(const std::string& key,
-                          const Option& default_value = Option()) const {
-    Rprintf("LogStatementGetOption()\n");
-    return StatementObjectBase::GetOption(key, default_value);
+  AdbcStatusCode Init(void* parent, AdbcError* error) override {
+    Rprintf("LogStatementNew()\n");
+    return Base::Init(parent, error);
   }
 
-  AdbcStatusCode SetOption(const std::string& key, const Option& value) {
+  Status SetOptionImpl(std::string_view key, Option value) override {
     Rprintf("LogStatementSetOption()\n");
-    return StatementObjectBase::SetOption(key, value);
+    return adbc::driver::status::Ok();
   }
 
   AdbcStatusCode ExecuteQuery(ArrowArrayStream* stream, int64_t* rows_affected,
@@ -268,8 +327,7 @@ class LogDriverStatement : public StatementObjectBase {
   }
 };
 
-using LogDriver =
-    adbc::common::Driver<LogDriverDatabase, LogDriverConnection, 
LogDriverStatement>;
+using LogDriver = adbc::driver::Driver<LogDatabase, LogConnection, 
LogStatement>;
 
 static AdbcStatusCode LogDriverInitFunc(int version, void* raw_driver, 
AdbcError* error) {
   return LogDriver::Init(version, raw_driver, error);
diff --git a/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md 
b/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
index 82b7e6041..268f0cf36 100644
--- a/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
+++ b/r/adbcdrivermanager/tests/testthat/_snaps/driver_log.md
@@ -7,10 +7,10 @@
       LogDatabaseSetOption()
       LogDatabaseInit()
     Code
-      expect_identical(adbc_database_get_option(db, "key"), "value")
+      try(adbc_database_get_option(db, "key"))
     Output
       LogDatabaseGetOption()
-      LogDatabaseGetOption()
+      Error in adbc_database_get_option(db, "key") : NOT_FOUND: Unknown option
     Code
       con <- adbc_connection_init(db, key = "value")
     Output
@@ -18,10 +18,10 @@
       LogConnectionSetOption()
       LogConnectionInit()
     Code
-      expect_identical(adbc_connection_get_option(con, "key"), "value")
+      try(adbc_connection_get_option(con, "key"))
     Output
-      LogConnectionGetOption()
-      LogConnectionGetOption()
+      Error in adbc_connection_get_option(con, "key") : 
+        NOT_FOUND: Unknown option
     Code
       try(adbc_connection_commit(con))
     Output
@@ -80,10 +80,10 @@
       LogStatementNew()
       LogStatementSetOption()
     Code
-      expect_identical(adbc_statement_get_option(stmt, "key"), "value")
+      try(adbc_statement_get_option(stmt, "key"))
     Output
-      LogStatementGetOption()
-      LogStatementGetOption()
+      Error in adbc_statement_get_option(stmt, "key") : 
+        NOT_FOUND: Unknown option
     Code
       try(adbc_statement_execute_query(stmt))
     Output
diff --git a/r/adbcdrivermanager/tests/testthat/test-driver_log.R 
b/r/adbcdrivermanager/tests/testthat/test-driver_log.R
index dd2e097e0..de49ede28 100644
--- a/r/adbcdrivermanager/tests/testthat/test-driver_log.R
+++ b/r/adbcdrivermanager/tests/testthat/test-driver_log.R
@@ -18,10 +18,10 @@
 test_that("The log driver logs", {
   expect_snapshot({
     db <- adbc_database_init(adbc_driver_log(), key = "value")
-    expect_identical(adbc_database_get_option(db, "key"), "value")
+    try(adbc_database_get_option(db, "key"))
 
     con <- adbc_connection_init(db, key = "value")
-    expect_identical(adbc_connection_get_option(con, "key"), "value")
+    try(adbc_connection_get_option(con, "key"))
     try(adbc_connection_commit(con))
     try(adbc_connection_get_info(con))
     try(adbc_connection_get_objects(con))
@@ -34,7 +34,7 @@ test_that("The log driver logs", {
     try(adbc_connection_get_statistic_names(con))
 
     stmt <- adbc_statement_init(con, key = "value")
-    expect_identical(adbc_statement_get_option(stmt, "key"), "value")
+    try(adbc_statement_get_option(stmt, "key"))
 
     try(adbc_statement_execute_query(stmt))
     try(adbc_statement_execute_schema(stmt))
diff --git a/r/adbcdrivermanager/tests/testthat/test-error.R 
b/r/adbcdrivermanager/tests/testthat/test-error.R
index 8f053d375..afffa241e 100644
--- a/r/adbcdrivermanager/tests/testthat/test-error.R
+++ b/r/adbcdrivermanager/tests/testthat/test-error.R
@@ -52,8 +52,8 @@ test_that("stop_for_error() gives a custom error class with 
extra info", {
     expect_s3_class(e, "adbc_status_not_found")
     expect_identical(e$error$status, 3L)
     expect_identical(
-      e$error$detail[["adbc.driver_base.option_key"]],
-      c(charToRaw("this option does not exist"), as.raw(0x00))
+      e$error$detail[["r.driver_test.option_key"]],
+      charToRaw("this option does not exist")
     )
   })
 
diff --git a/r/adbcdrivermanager/tests/testthat/test-options.R 
b/r/adbcdrivermanager/tests/testthat/test-options.R
index 85ee78f72..8d978f838 100644
--- a/r/adbcdrivermanager/tests/testthat/test-options.R
+++ b/r/adbcdrivermanager/tests/testthat/test-options.R
@@ -206,15 +206,10 @@ test_that("get/set option can roundtrip double options 
for statement", {
   )
 })
 
-test_that("void driver errors getting string option of incorrect type", {
+test_that("void driver errors getting string option of incompatible type", {
   db <- adbc_database_init(adbc_driver_void())
   adbc_database_set_options(db, list("some_key" = "some value"))
 
-  expect_error(
-    adbc_database_get_option_bytes(db, "some_key"),
-    class = "adbc_status_not_found"
-  )
-
   expect_error(
     adbc_database_get_option_int(db, "some_key"),
     class = "adbc_status_not_found"
@@ -226,6 +221,16 @@ test_that("void driver errors getting string option of 
incorrect type", {
   )
 })
 
+test_that("void driver can get string option of compatible type", {
+  db <- adbc_database_init(adbc_driver_void())
+  adbc_database_set_options(db, list("some_key" = "some value"))
+
+  expect_identical(
+    adbc_database_get_option_bytes(db, "some_key"),
+    charToRaw("some value")
+  )
+})
+
 test_that("void driver errors getting bytes option of incorrect type", {
   db <- adbc_database_init(adbc_driver_void())
   adbc_database_set_options(db, list("some_key" = charToRaw("some value")))
@@ -260,12 +265,20 @@ test_that("void driver errors getting integer option of 
incorrect type", {
     class = "adbc_status_not_found"
   )
 
-  expect_error(
+
+})
+
+test_that("void driver can get integer option of compatible type", {
+  db <- adbc_database_init(adbc_driver_void())
+  adbc_database_set_options(db, list("some_key" = 123L))
+
+  expect_identical(
     adbc_database_get_option_double(db, "some_key"),
-    class = "adbc_status_not_found"
+    123.0
   )
 })
 
+
 test_that("void driver errors getting double option of incorrect type", {
   db <- adbc_database_init(adbc_driver_void())
   adbc_database_set_options(db, list("some_key" = 123.4))
diff --git a/r/adbcsqlite/src/Makevars.in b/r/adbcsqlite/src/Makevars.in
index 7ed49a9ad..e4da67dc8 100644
--- a/r/adbcsqlite/src/Makevars.in
+++ b/r/adbcsqlite/src/Makevars.in
@@ -21,7 +21,6 @@ PKG_LIBS=@libs@
 
 OBJECTS = init.o \
     c/driver/common/utils.o \
-    c/driver/framework/base_driver.o \
     c/driver/framework/catalog.o \
     c/driver/framework/objects.o \
     c/driver/sqlite/sqlite.o \

Reply via email to