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 e93d70fa3 docs(c/driver/framework): Add documentation for building a 
driver using the driver framework (#2186)
e93d70fa3 is described below

commit e93d70fa3cd47df97a153d0aa942f27ad3884d28
Author: Dewey Dunnington <[email protected]>
AuthorDate: Tue Oct 29 15:20:11 2024 +0000

    docs(c/driver/framework): Add documentation for building a driver using the 
driver framework (#2186)
    
    This PR adds a tutorial with how to build a basic driver using the C++
    framework. You can read the recipe by downloading the docs.tgz asset
    from the CI job:
    
    
    
https://github.com/apache/arrow-adbc/actions/runs/11431710883/artifacts/2080179525
    
    Example driver in action:
    
    ``` python
    import os
    import pyarrow
    from pathlib import Path
    from adbc_driver_manager import dbapi
    
    
    def connect(uri: str):
        build_dir = Path(__file__).parent / "build"
        for lib in [
            "libdriver_example.dylib",
            "libdriver_example.so",
            "driver_example.dll",
        ]:
            driver_lib = build_dir / lib
            if driver_lib.exists():
                return dbapi.connect(
                    driver=str(driver_lib.resolve()), db_kwargs={"uri": uri}
                )
    
        raise RuntimeError("Can't find driver shared object")
    
    
    with connect(uri=Path(__file__).parent.as_uri()) as con:
        data = pyarrow.table({"col": [1, 2, 3]})
        with con.cursor() as cur:
            cur.adbc_ingest("example.arrows", data, mode="create")
    
        with con.cursor() as cur:
            cur.execute("SELECT * FROM example.arrows")
            print(cur.fetchall())
    
        os.unlink(Path(__file__).parent / "example.arrows")
    #> [(1,), (2,), (3,)]
    ```
    
    ---------
    
    Co-authored-by: David Li <[email protected]>
---
 .gitignore                                         |   1 +
 c/apidoc/Doxyfile                                  |  11 +-
 c/driver/framework/status.h                        |   1 +
 ci/scripts/cpp_recipe.sh                           |  17 +-
 ci/scripts/r_build.sh                              |   4 +
 docs/source/cpp/{index.rst => driver_example.rst}  |  39 ++-
 docs/source/cpp/index.rst                          |   1 +
 docs/source/cpp/recipe_driver/CMakeLists.txt       |  69 +++++
 docs/source/cpp/recipe_driver/driver_example.cc    | 304 +++++++++++++++++++++
 docs/source/cpp/recipe_driver/driver_example.h     |  21 ++
 docs/source/cpp/recipe_driver/driver_example.py    |  67 +++++
 .../cpp/recipe_driver/driver_example_test.cc       |  64 +++++
 docs/source/ext/doxygen_inventory.py               |  26 +-
 .../python/recipe/postgresql_get_table_schema.py   |   4 +-
 docs/source/r/index.rst                            |   1 +
 15 files changed, 598 insertions(+), 32 deletions(-)

diff --git a/.gitignore b/.gitignore
index 235d9a0a0..8716f5d7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,7 @@ cpp/.idea/
 c/apidoc/html/
 c/apidoc/latex/
 c/apidoc/xml/
+c/apidoc/objects.inv
 docs/example.gz
 docs/example1.dat
 docs/example3.dat
diff --git a/c/apidoc/Doxyfile b/c/apidoc/Doxyfile
index ddb99888f..2f4924281 100644
--- a/c/apidoc/Doxyfile
+++ b/c/apidoc/Doxyfile
@@ -500,7 +500,7 @@ EXTRACT_ALL            = NO
 # be included in the documentation.
 # The default value is: NO.
 
-EXTRACT_PRIVATE        = NO
+EXTRACT_PRIVATE        = YES
 
 # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
 # methods of a class will be included in the documentation.
@@ -891,7 +891,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = ../../c/include/arrow-adbc/adbc.h ../../README.md 
../../c/include/arrow-adbc/adbc_driver_manager.h
+INPUT                  = ../../c/include/arrow-adbc/adbc.h ../../README.md 
../../c/include/arrow-adbc/adbc_driver_manager.h ../../c/driver/framework/
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -920,12 +920,7 @@ INPUT_ENCODING         = UTF-8
 # comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
 # *.vhdl, *.ucf, *.qsf and *.ice.
 
-FILE_PATTERNS          = *.c \
-                         *.cc \
-                         *.cxx \
-                         *.cpp \
-                         *.c++ \
-                         *.java \
+FILE_PATTERNS          = *.java \
                          *.ii \
                          *.ixx \
                          *.ipp \
diff --git a/c/driver/framework/status.h b/c/driver/framework/status.h
index cfdca6ebb..22e484dd3 100644
--- a/c/driver/framework/status.h
+++ b/c/driver/framework/status.h
@@ -147,6 +147,7 @@ class Status {
 #undef STATUS_CTOR
 
  private:
+  /// \brief Private Status implementation details
   struct Impl {
     // invariant: code is never OK
     AdbcStatusCode code;
diff --git a/ci/scripts/cpp_recipe.sh b/ci/scripts/cpp_recipe.sh
index b3cf2f3ce..7309e7ee3 100755
--- a/ci/scripts/cpp_recipe.sh
+++ b/ci/scripts/cpp_recipe.sh
@@ -23,10 +23,11 @@ set -e
 : ${ADBC_CMAKE_ARGS:=""}
 : ${CMAKE_BUILD_TYPE:=Debug}
 
-main() {
-    local -r source_dir="${1}"
-    local -r install_dir="${2}"
-    local -r build_dir="${3}"
+test_recipe() {
+    local -r recipe="${1}"
+    local -r source_dir="${2}"
+    local -r install_dir="${3}"
+    local -r build_dir="${4}"
 
     export DYLD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${install_dir}/lib"
     export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${install_dir}/lib"
@@ -36,11 +37,12 @@ main() {
     pushd "${build_dir}"
 
     set -x
-    cmake "${source_dir}/docs/source/cpp/recipe/" \
+    cmake "${source_dir}/${recipe}/" \
           ${ADBC_CMAKE_ARGS} \
           -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
           -DCMAKE_INSTALL_LIBDIR=lib \
-          -DCMAKE_PREFIX_PATH="${install_dir}"
+          -DCMAKE_PREFIX_PATH="${install_dir}" \
+          -DADBC_DRIVER_EXAMPLE_BUILD_TESTS=ON
     set +x
 
     cmake --build . -j
@@ -49,4 +51,5 @@ main() {
         --no-tests=error
 }
 
-main "$@"
+test_recipe "docs/source/cpp/recipe" "$@"
+test_recipe "docs/source/cpp/recipe_driver" "$@"
diff --git a/ci/scripts/r_build.sh b/ci/scripts/r_build.sh
index 79af0e899..fbf4d039c 100755
--- a/ci/scripts/r_build.sh
+++ b/ci/scripts/r_build.sh
@@ -63,6 +63,10 @@ main() {
     if [[ "${BUILD_DRIVER_SNOWFLAKE}" -gt 0 ]]; then
         install_pkg "${source_dir}" "${install_dir}" adbcsnowflake
     fi
+
+    if [[ "${BUILD_DRIVER_BIGQUERY}" -gt 0 ]]; then
+        install_pkg "${source_dir}" "${install_dir}" adbcbigquery
+    fi
 }
 
 main "$@"
diff --git a/docs/source/cpp/index.rst b/docs/source/cpp/driver_example.rst
similarity index 51%
copy from docs/source/cpp/index.rst
copy to docs/source/cpp/driver_example.rst
index add0e29ef..0776b5e8e 100644
--- a/docs/source/cpp/index.rst
+++ b/docs/source/cpp/driver_example.rst
@@ -15,14 +15,35 @@
 .. specific language governing permissions and limitations
 .. under the License.
 
-=========
-C and C++
-=========
+==============
+Driver Example
+==============
 
-.. toctree::
-   :maxdepth: 2
+.. recipe:: recipe_driver/driver_example.cc
+   :language: cpp
 
-   quickstart
-   driver_manager
-   concurrency
-   api/index
+Low-level testing
+=================
+
+.. recipe:: recipe_driver/driver_example_test.cc
+   :language: cpp
+
+High-level testing
+==================
+
+.. recipe:: recipe_driver/driver_example.py
+
+High-level tests can also be written in R using the ``adbcdrivermanager``
+package.
+
+.. code-block:: r
+
+   library(adbcdrivermanager)
+
+   drv <- adbc_driver("build/libdriver_example.dylib")
+   db <- adbc_database_init(drv, uri = paste0("file://", getwd()))
+   con <- adbc_connection_init(db)
+
+   data.frame(col = 1:3) |> write_adbc(con, "example.arrows")
+   con |> read_adbc("SELECT * FROM example.arrows") |> as.data.frame()
+   unlink("example.arrows")
diff --git a/docs/source/cpp/index.rst b/docs/source/cpp/index.rst
index add0e29ef..29bbfc920 100644
--- a/docs/source/cpp/index.rst
+++ b/docs/source/cpp/index.rst
@@ -25,4 +25,5 @@ C and C++
    quickstart
    driver_manager
    concurrency
+   driver_example
    api/index
diff --git a/docs/source/cpp/recipe_driver/CMakeLists.txt 
b/docs/source/cpp/recipe_driver/CMakeLists.txt
new file mode 100644
index 000000000..8e1159a85
--- /dev/null
+++ b/docs/source/cpp/recipe_driver/CMakeLists.txt
@@ -0,0 +1,69 @@
+# 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.
+
+cmake_minimum_required(VERSION 3.18)
+
+project(adbc_cookbook_recipes_driver
+        VERSION "1.0.0"
+        LANGUAGES CXX)
+
+include(CTest)
+include(FetchContent)
+
+set(CMAKE_CXX_STANDARD 17)
+
+set(NANOARROW_IPC ON)
+set(NANOARROW_NAMESPACE "DriverExamplePrivate")
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+fetchcontent_declare(nanoarrow
+                     URL 
"https://www.apache.org/dyn/closer.lua?action=download&filename=arrow/apache-arrow-nanoarrow-0.6.0/apache-arrow-nanoarrow-0.6.0.tar.gz";
+                     URL_HASH 
SHA256=e4a02ac51002ad1875bf09317e70adb959005fad52b240ff59f73b970fa485d1
+)
+fetchcontent_makeavailable(nanoarrow)
+
+# TODO: We could allow this to be installed + linked to as a target; however,
+# fetchcontent is a little nicer for this kind of thing (statically linked
+# pinned version of something that doesn't rely on a system library).
+add_library(adbc_driver_framework ../../../../c/driver/framework/utility.cc
+                                  ../../../../c/driver/framework/objects.cc)
+target_include_directories(adbc_driver_framework PRIVATE ../../../../c
+                                                         ../../../../c/include)
+target_link_libraries(adbc_driver_framework PRIVATE nanoarrow::nanoarrow)
+
+add_library(driver_example SHARED driver_example.cc)
+target_include_directories(driver_example PRIVATE ../../../../c 
../../../../c/include)
+target_link_libraries(driver_example PRIVATE adbc_driver_framework
+                                             nanoarrow::nanoarrow_ipc)
+
+if(ADBC_DRIVER_EXAMPLE_BUILD_TESTS)
+  fetchcontent_declare(googletest
+                       URL 
https://github.com/google/googletest/archive/refs/tags/v1.15.1.tar.gz
+                       URL_HASH 
SHA256=5052e088b16bdd8c6f0c7f9cafc942fd4f7c174f1dac6b15a8dd83940ed35195
+  )
+  fetchcontent_makeavailable(googletest)
+
+  find_package(AdbcDriverManager REQUIRED)
+
+  add_executable(driver_example_test driver_example_test.cc)
+  target_link_libraries(driver_example_test
+                        PRIVATE gtest_main driver_example
+                                AdbcDriverManager::adbc_driver_manager_shared)
+
+  include(GoogleTest)
+  gtest_discover_tests(driver_example_test)
+
+endif()
diff --git a/docs/source/cpp/recipe_driver/driver_example.cc 
b/docs/source/cpp/recipe_driver/driver_example.cc
new file mode 100644
index 000000000..9c7ae75f8
--- /dev/null
+++ b/docs/source/cpp/recipe_driver/driver_example.cc
@@ -0,0 +1,304 @@
+// 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.
+
+// RECIPE STARTS HERE
+
+/// Here we'll show the structure of building an ADBC driver in C++ using
+/// the ADBC driver framework library. This is the same library that ADBC
+/// uses to build its SQLite and PostgreSQL drivers and abstracts away
+/// the details of C callables and catalog/metadata functions that can be
+/// difficult to implement but are essential for efficiently leveraging
+/// the rest of the ADBC ecosystem.
+///
+/// At a high level, we'll be building a driver whose "database" is a directory
+/// where each "table" in the database is a file containing an Arrow IPC 
stream.
+/// Tables can be written using the bulk ingest feature and tables can be read
+/// with a simple query in the form ``SELECT * FROM (the file)``.
+///
+/// Installation
+/// ============
+///
+/// This quickstart is actually a literate C++ file.  You can clone
+/// the repository, build the sample, and follow along.
+///
+/// We'll assume you're using conda-forge_ for dependencies.  CMake, a
+/// C++17 compiler, and the ADBC libraries are required.  They can be
+/// installed as follows:
+///
+/// .. code-block:: shell
+///
+///    mamba install cmake compilers libadbc-driver-manager
+///
+/// .. _conda-forge: https://conda-forge.org/
+///
+/// Building
+/// ========
+///
+/// We'll use CMake_ here.  From a source checkout of the ADBC repository:
+///
+/// .. code-block:: shell
+///
+///    mkdir build
+///    cd build
+///    cmake ../docs/source/cpp/recipe_driver 
-DADBC_DRIVER_EXAMPLE_BUILD_TESTS=ON
+///    cmake --build .
+///    ctest
+///
+/// .. _CMake: https://cmake.org/
+///
+/// Building an ADBC Driver using C++
+/// =================================
+///
+/// Let's start with some includes. Notably, we'll need the driver framework
+/// header files and nanoarrow_, which we'll use to create and consume the
+/// Arrow C data interface structures in this example driver.
+
+/// .. _nanoarrow: https://arrow.apache.org/nanoarrow
+
+#include "driver_example.h"
+
+#include <cstdio>
+#include <string>
+
+#include "driver/framework/connection.h"
+#include "driver/framework/database.h"
+#include "driver/framework/statement.h"
+
+#include "nanoarrow/nanoarrow.hpp"
+#include "nanoarrow/nanoarrow_ipc.hpp"
+
+#include "arrow-adbc/adbc.h"
+
+/// Next, we'll bring a few essential framework types into the namespace
+/// to reduce the verbosity of the implementation:
+///
+/// * :cpp:class:`adbc::driver::Option` : Options can be set on an ADBC 
database,
+///   connection, and statmenent. They can be strings, opaque binary, doubles, 
or
+///   integers.  The ``Option`` class abstracts the details of how to get, set,
+///   and parse these values.
+/// * :cpp:class:`adbc::driver::Status`: The ``Status`` is the ADBC driver
+///   framework's error handling mechanism: functions with no return value that
+///   can fail return a ``Status``.  You can use 
``UNWRAP_STATUS(some_call())`` as
+///   shorthand for ``Status status = some_call(); if (!status.ok()) return
+///   status;`` to succinctly propagate errors.
+/// * :cpp:class:`adbc::driver::Result`: The ``Result<T>`` is used as a return
+///   value for functions that on success return a value of type ``T`` and on
+///   failure communicate their error using a ``Status``. You can use
+///   ``UNWRAP_RESULT(some_type value, some_call())`` as shorthand for
+///
+///   .. code-block:: cpp
+///
+///      some_type value;
+///      Result<some_type> maybe_value = some_call();
+///      if (!maybe_value.status().ok()) {
+///        return maybe_value.status();
+///      } else {
+///        value = *maybe_value;
+///      }
+
+using adbc::driver::Option;
+using adbc::driver::Result;
+using adbc::driver::Status;
+
+namespace {
+
+/// Next, we'll provide the database implementation. The driver framework uses
+/// the Curiously Recurring Template Pattern (CRTP_). The details of this are
+/// handled by the framework, but functionally this is still just overriding
+/// methods from a base class that handles the details.
+///
+/// Here, our database implementation will simply record the ``uri`` passed
+/// by the user. Our interpretation of this will be a ``file://`` uri to
+/// a directory to which our IPC files should be written and/or IPC files
+/// should be read. This is the role of the database in ADBC: a shared
+/// handle to a database that potentially caches some shared state among
+/// connections, but which still allows multiple connections to execute
+/// against the database concurrently.
+///
+/// .. _CRTP: 
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
+
+class DriverExampleDatabase : public 
adbc::driver::Database<DriverExampleDatabase> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = 
"[example]";
+
+  Status SetOptionImpl(std::string_view key, Option value) override {
+    // Handle and validate options implemented by this driver
+    if (key == "uri") {
+      UNWRAP_RESULT(std::string_view uri, value.AsString());
+
+      if (uri.find("file://") != 0) {
+        return adbc::driver::status::InvalidArgument(
+            "[example] uri must start with 'file://'");
+      }
+
+      uri_ = uri;
+      return adbc::driver::status::Ok();
+    }
+
+    // Defer to the base implementation to handle state managed by the base
+    // class (and error for all other options).
+    return Base::SetOptionImpl(key, value);
+  }
+
+  Result<Option> GetOption(std::string_view key) override {
+    // Return the value of options implemented by this driver
+    if (key == "uri") {
+      return Option(uri_);
+    }
+
+    // Defer to the base implementation to handle state managed by the base
+    // class (and error for all other options).
+    return Base::GetOption(key);
+  }
+
+  // This is called after zero or more calls to SetOption() on
+  Status InitImpl() override {
+    if (uri_.empty()) {
+      return adbc::driver::status::InvalidArgument(
+          "[example] Must set uri to a non-empty value");
+    }
+
+    return Base::InitImpl();
+  }
+
+  // Getters for members needed by the connection and/or statement:
+  const std::string& uri() { return uri_; }
+
+ private:
+  std::string uri_;
+};
+
+/// Next, we implement the connection. While the role of the database is 
typically
+/// to store or cache information, the role of the connection is to provide
+/// resource handles that might be expensive to obtain (e.g., negotiating 
authentication
+/// when connecting to a database). Because our example "database" is just a 
directory, we
+/// don't need to do much in our connection in terms of resource management 
except to
+/// provide a way for child statements to access the database's uri.
+///
+/// Another role of the connection is to provide metadata about tables, 
columns,
+/// statistics, and other catalog-like information a caller might want to know 
before
+/// issuing a query. The driver framework base classes provide helpers to 
implement these
+/// functions such that you can mostly implement them in terms of the C++17 
standard
+/// library (as opposed to building the C-level arrays yourself).
+
+class DriverExampleConnection : public 
adbc::driver::Connection<DriverExampleConnection> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = 
"[example]";
+
+  // Get information from the database and/or store a reference if needed.
+  Status InitImpl(void* parent) {
+    auto& database = *reinterpret_cast<DriverExampleDatabase*>(parent);
+    uri_ = database.uri();
+    return Base::InitImpl(parent);
+  }
+
+  // Getters for members needed by the statement:
+  const std::string& uri() { return uri_; }
+
+ private:
+  std::string uri_;
+};
+
+/// Next, we provide the statement implementation. The statement is where 
query execution
+/// is managed. Because our data source is quite literally Arrow data, we 
don't have to
+/// provide a layer that manages type or value conversion. The SQLite and 
PostgreSQL
+/// drivers both dedicate many lines of code to implementing and testing these 
conversions
+/// efficiently. The nanoarrow library can be used to implement conversions in 
both
+/// directions and is the scope of a separate article.
+
+class DriverExampleStatement : public 
adbc::driver::Statement<DriverExampleStatement> {
+ public:
+  [[maybe_unused]] constexpr static std::string_view kErrorPrefix = 
"[example]";
+
+  // Get information from the connection and/or store a reference if needed.
+  Status InitImpl(void* parent) {
+    auto& connection = *reinterpret_cast<DriverExampleConnection*>(parent);
+    uri_ = connection.uri();
+    return Base::InitImpl(parent);
+  }
+
+  // Our implementation of a bulk ingestion is to write an Arrow IPC stream as 
a file
+  // using the target table as the filename.
+  Result<int64_t> ExecuteIngestImpl(IngestState& state) {
+    std::string directory = uri_.substr(strlen("file://"));
+    std::string filename = directory + "/" + *state.target_table;
+
+    nanoarrow::ipc::UniqueOutputStream output_stream;
+    FILE* c_file = std::fopen(filename.c_str(), "wb");
+    UNWRAP_ERRNO(Internal, ArrowIpcOutputStreamInitFile(output_stream.get(), 
c_file,
+                                                        /*close_on_release*/ 
true));
+
+    nanoarrow::ipc::UniqueWriter writer;
+    UNWRAP_ERRNO(Internal, ArrowIpcWriterInit(writer.get(), 
output_stream.get()));
+
+    ArrowError nanoarrow_error;
+    ArrowErrorInit(&nanoarrow_error);
+    UNWRAP_NANOARROW(nanoarrow_error, Internal,
+                     ArrowIpcWriterWriteArrayStream(writer.get(), 
&bind_parameters_,
+                                                    &nanoarrow_error));
+
+    return -1;
+  }
+
+  // Our implementation of query execution is to accept a simple query in the 
form
+  // SELECT * FROM (the filename).
+  Result<int64_t> ExecuteQueryImpl(QueryState& state, ArrowArrayStream* 
stream) {
+    std::string prefix("SELECT * FROM ");
+    if (state.query.find(prefix) != 0) {
+      return adbc::driver::status::InvalidArgument(
+          "[example] Query must be in the form 'SELECT * FROM filename'");
+    }
+
+    std::string directory = uri_.substr(strlen("file://"));
+    std::string filename = directory + "/" + state.query.substr(prefix.size());
+
+    nanoarrow::ipc::UniqueInputStream input_stream;
+    FILE* c_file = std::fopen(filename.c_str(), "rb");
+    UNWRAP_ERRNO(Internal, ArrowIpcInputStreamInitFile(input_stream.get(), 
c_file,
+                                                       /*close_on_release*/ 
true));
+
+    UNWRAP_ERRNO(Internal,
+                 ArrowIpcArrayStreamReaderInit(stream, input_stream.get(), 
nullptr));
+    return -1;
+  }
+
+  // This path is taken when the user calls Prepare() first.
+  Result<int64_t> ExecuteQueryImpl(PreparedState& state, ArrowArrayStream* 
stream) {
+    QueryState query_state{state.query};
+    return ExecuteQueryImpl(query_state, stream);
+  }
+
+ private:
+  std::string uri_;
+};
+
+}  // namespace
+
+/// Finally, we create the driver initializer function, which is what the 
driver
+/// manager needs to provide implementations for the ``Adbc**()`` functions 
that
+/// comprise the ADBC C API. The name of this function matters: this file will
+/// be built into a shared library named ``libdriver_example.(so|dll|dylib)``,
+/// so the driver manager will look for the symbol ``AdbcDriverExampleInit()``
+/// as the default entry point when asked to load the driver 
``"driver_example"``.
+
+extern "C" AdbcStatusCode AdbcDriverExampleInit(int version, void* raw_driver,
+                                                AdbcError* error) {
+  using ExampleDriver =
+      adbc::driver::Driver<DriverExampleDatabase, DriverExampleConnection,
+                           DriverExampleStatement>;
+  return ExampleDriver::Init(version, raw_driver, error);
+}
diff --git a/docs/source/cpp/recipe_driver/driver_example.h 
b/docs/source/cpp/recipe_driver/driver_example.h
new file mode 100644
index 000000000..68277041d
--- /dev/null
+++ b/docs/source/cpp/recipe_driver/driver_example.h
@@ -0,0 +1,21 @@
+// 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 <arrow-adbc/adbc.h>
+
+extern "C" AdbcStatusCode AdbcDriverExampleInit(int version, void* raw_driver,
+                                                AdbcError* error);
diff --git a/docs/source/cpp/recipe_driver/driver_example.py 
b/docs/source/cpp/recipe_driver/driver_example.py
new file mode 100644
index 000000000..86743449a
--- /dev/null
+++ b/docs/source/cpp/recipe_driver/driver_example.py
@@ -0,0 +1,67 @@
+# 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.
+
+# RECIPE STARTS HERE
+#: After verifying the basic driver functionality, we can use the
+#: ``adbc_driver_manager`` Python package's built-in dbapi implementation
+#: to expose a ready-to-go Pythonic database API. This is also useful for
+#: high-level testing!
+#:
+#: First, we'll import pathlib for a few path calculations and the
+#: ``adbc_driver_manager``'s ``dbapi`` module:
+from pathlib import Path
+
+from adbc_driver_manager import dbapi
+
+
+#: Next, we'll define a ``connect()`` function that wraps ``dbapi.connect()``
+#: with the location of the shared library we built using ``cmake`` in the 
previous
+#: section. For the purposes of our tutorial, this will be in the CMake 
``build/``
+#: directory.
+def connect(uri: str):
+    build_dir = Path(__file__).parent / "build"
+    for lib in [
+        "libdriver_example.dylib",
+        "libdriver_example.so",
+        "driver_example.dll",
+    ]:
+        driver_lib = build_dir / lib
+        if driver_lib.exists():
+            return dbapi.connect(
+                driver=str(driver_lib.resolve()), db_kwargs={"uri": uri}
+            )
+
+    raise RuntimeError("Can't find driver shared object")
+
+
+#: Next, we can give our driver a go! The two pieces we implemented in the 
driver
+#: were the "bulk ingest" feature and "select all from", so let's see if it 
works!
+if __name__ == "__main__":
+    import os
+
+    import pyarrow
+
+    with connect(uri=Path(__file__).parent.as_uri()) as con:
+        data = pyarrow.table({"col": [1, 2, 3]})
+        with con.cursor() as cur:
+            cur.adbc_ingest("example.arrows", data, mode="create")
+
+        with con.cursor() as cur:
+            cur.execute("SELECT * FROM example.arrows")
+            print(cur.fetchall())
+
+        os.unlink(Path(__file__).parent / "example.arrows")
diff --git a/docs/source/cpp/recipe_driver/driver_example_test.cc 
b/docs/source/cpp/recipe_driver/driver_example_test.cc
new file mode 100644
index 000000000..5aac55c98
--- /dev/null
+++ b/docs/source/cpp/recipe_driver/driver_example_test.cc
@@ -0,0 +1,64 @@
+// 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.
+
+// RECIPE STARTS HERE
+
+/// After we've written a sketch of the driver, the next step is to
+/// ensure that it can be loaded by the driver manager and that the
+/// database, connection, and statement instances can be initialized and
+/// released.
+///
+/// First, we'll include the driver manager and googletest_.
+///
+/// .. _googletest: https://github.com/google/googletest
+
+#include "driver_example.h"
+
+#include "arrow-adbc/adbc_driver_manager.h"
+#include "gtest/gtest.h"
+
+/// Next we'll declare a test case for the basic lifecycle:
+
+TEST(DriverExample, TestLifecycle) {
+  struct AdbcError error = ADBC_ERROR_INIT;
+
+  struct AdbcDatabase database;
+  ASSERT_EQ(AdbcDatabaseNew(&database, &error), ADBC_STATUS_OK);
+  AdbcDriverManagerDatabaseSetInitFunc(&database, &AdbcDriverExampleInit, 
&error);
+  ASSERT_EQ(AdbcDatabaseSetOption(&database, "uri", "file://foofy", &error),
+            ADBC_STATUS_OK);
+  ASSERT_EQ(AdbcDatabaseInit(&database, &error), ADBC_STATUS_OK);
+
+  struct AdbcConnection connection;
+  ASSERT_EQ(AdbcConnectionNew(&connection, &error), ADBC_STATUS_OK);
+  ASSERT_EQ(AdbcConnectionInit(&connection, &database, &error), 
ADBC_STATUS_OK);
+
+  struct AdbcStatement statement;
+  ASSERT_EQ(AdbcStatementNew(&connection, &statement, &error), ADBC_STATUS_OK);
+
+  ASSERT_EQ(AdbcStatementRelease(&statement, &error), ADBC_STATUS_OK);
+  ASSERT_EQ(AdbcConnectionRelease(&connection, &error), ADBC_STATUS_OK);
+  ASSERT_EQ(AdbcDatabaseRelease(&database, &error), ADBC_STATUS_OK);
+
+  if (error.release) {
+    error.release(&error);
+  }
+}
+
+/// Drivers that live in the apache/arrow-adbc repository can use the built-in
+/// validation library that implements a generic test suite against a 
fully-featured
+/// SQL database and provides utilities to test a range of inputs and outputs.
diff --git a/docs/source/ext/doxygen_inventory.py 
b/docs/source/ext/doxygen_inventory.py
index f918766e4..3e969cadb 100644
--- a/docs/source/ext/doxygen_inventory.py
+++ b/docs/source/ext/doxygen_inventory.py
@@ -55,16 +55,19 @@ def scrape_links(item_id_to_url, root):
         if kind == "dir":
             # Ignore, this is generated for a directory
             continue
-        elif kind in ("file", "group", "struct"):
+        elif kind in ("class", "file", "group", "struct"):
+            outer_domain = "c"
             if kind == "file":
                 name = compounddef.find("compoundname").text
                 file_id = compounddef.attrib["id"]
                 yield ("std", name, "doc", "", f"{file_id}.html")
-            elif kind == "struct":
+            elif kind in {"class", "struct"}:
                 name = compounddef.find("compoundname").text
                 anchor = compounddef.attrib["id"]
                 url = item_id_to_url[anchor]
-                yield ("c", name, "struct", anchor, url)
+                if kind == "class" or "::" in name:
+                    outer_domain = "cpp"
+                yield (outer_domain, name, kind, anchor, url)
 
             for memberdef in compounddef.findall(".//memberdef"):
                 member_kind = memberdef.attrib.get("kind")
@@ -73,17 +76,27 @@ def scrape_links(item_id_to_url, root):
                     name = memberdef.find("name").text
                     typ = "macro"
                 elif member_kind == "function":
-                    domain = "c"
-                    name = memberdef.find("name").text
+                    domain = outer_domain
+                    qualified = memberdef.find("qualifiedname")
+                    if qualified is not None:
+                        name = qualified.text
+                    else:
+                        name = memberdef.find("name").text
                     typ = "function"
                 elif member_kind == "typedef":
                     domain = "c"
                     name = memberdef.find("name").text
                     typ = "type"
                 elif member_kind == "variable":
-                    domain = "c"
+                    domain = outer_domain
                     name = memberdef.find("qualifiedname").text
                     typ = "member"
+                elif member_kind == "enum":
+                    domain = "c"
+                    name = memberdef.find("name").text
+                    typ = "enum"
+                elif member_kind == "friend":
+                    continue
                 else:
                     raise NotImplementedError(
                         f"<memberdef kind=\"{memberdef.attrib['kind']}\"> not 
supported"
@@ -120,6 +133,7 @@ def make_fake_domains(
     item_id_to_url = {}
     html_name = re.compile(r'name="([^\"]+)"')
     for index in html_root.rglob("*.html"):
+        item_id_to_url[index.stem] = str(index.relative_to(html_root))
         with index.open() as source:
             matches = html_name.findall(source.read())
             for m in matches:
diff --git a/docs/source/python/recipe/postgresql_get_table_schema.py 
b/docs/source/python/recipe/postgresql_get_table_schema.py
index aacbc1c25..59034c0c1 100644
--- a/docs/source/python/recipe/postgresql_get_table_schema.py
+++ b/docs/source/python/recipe/postgresql_get_table_schema.py
@@ -35,7 +35,7 @@ with conn.cursor() as cur:
 
     cur.execute("CREATE SCHEMA IF NOT EXISTS other_schema")
     cur.execute("DROP TABLE IF EXISTS other_schema.example")
-    cur.execute("CREATE TABLE other_schema.example (strings TEXT, values 
NUMERIC)")
+    cur.execute("CREATE TABLE other_schema.example (strings TEXT, values INT)")
 
 conn.commit()
 
@@ -62,7 +62,7 @@ assert conn.adbc_get_table_schema(
 ) == pyarrow.schema(
     [
         ("strings", "string"),
-        ("values", "string"),
+        ("values", "int32"),
     ]
 )
 
diff --git a/docs/source/r/index.rst b/docs/source/r/index.rst
index 52fa4f131..1d445ecd2 100644
--- a/docs/source/r/index.rst
+++ b/docs/source/r/index.rst
@@ -57,6 +57,7 @@ Package documentation
 ---------------------
 
 - `adbcdrivermanager <adbcdrivermanager/index.html>`_
+- `adbcbigquery <adbcbigquery/index.html>`_
 - `adbcflightsql <adbcflightsql/index.html>`_
 - `adbcpostgresql <adbcpostgresql/index.html>`_
 - `adbcsnowflake <adbcsnowflake/index.html>`_


Reply via email to