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>`_