Quickstart#
Recipe source: quickstart.cc
Here we’ll briefly tour basic features of ADBC with the SQLite driver in C++17.
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:
mamba install cmake compilers libadbc-driver-manager libadbc-driver-sqlite
Building#
We’ll use CMake here. From a source checkout of the ADBC repository:
mkdir build
cd build
cmake ../docs/source/cpp/recipe
cmake --build . --target quickstart
./quickstart
Using ADBC#
Let’s start with some includes:
59// For EXIT_SUCCESS
60#include <cstdlib>
61// For strerror
62#include <cstring>
63#include <iostream>
64
65#include <adbc.h>
66#include <nanoarrow.h>
Then we’ll add some (very basic) error checking helpers.
70// Error-checking helper for ADBC calls.
71// Assumes that there is an AdbcError named `error` in scope.
72#define CHECK_ADBC(EXPR) \
73 if (AdbcStatusCode status = (EXPR); status != ADBC_STATUS_OK) { \
74 if (error.message != nullptr) { \
75 std::cerr << error.message << std::endl; \
76 } \
77 return EXIT_FAILURE; \
78 }
79
80// Error-checking helper for ArrowArrayStream.
81#define CHECK_STREAM(STREAM, EXPR) \
82 if (int status = (EXPR); status != 0) { \
83 std::cerr << "(" << std::strerror(status) << "): "; \
84 const char* message = (STREAM).get_last_error(&(STREAM)); \
85 if (message != nullptr) { \
86 std::cerr << message << std::endl; \
87 } else { \
88 std::cerr << "(no error message)" << std::endl; \
89 } \
90 return EXIT_FAILURE; \
91 }
92
93// Error-checking helper for Nanoarrow.
94#define CHECK_NANOARROW(EXPR) \
95 if (int status = (EXPR); status != 0) { \
96 std::cerr << "(" << std::strerror(status) << "): failed" << std::endl; \
97 return EXIT_FAILURE; \
98 }
99
100int main() {
Loading the Driver#
We’ll load the SQLite driver using the driver manager. We don’t have to explicitly link to the driver this way.
107 AdbcError error = {};
108
109 AdbcDatabase database = {};
110 CHECK_ADBC(AdbcDatabaseNew(&database, &error));
The way the driver manager knows what driver we want is via the
driver
option.
113 CHECK_ADBC(AdbcDatabaseSetOption(&database, "driver", "adbc_driver_sqlite", &error));
114 CHECK_ADBC(AdbcDatabaseInit(&database, &error));
Creating a Connection#
ADBC distinguishes between “databases”, “connections”, and “statements”. A “database” holds shared state across multiple connections. For example, in the SQLite driver, it holds the actual instance of SQLite. A “connection” is one connection to the database.
125 AdbcConnection connection = {};
126 CHECK_ADBC(AdbcConnectionNew(&connection, &error));
127 CHECK_ADBC(AdbcConnectionInit(&connection, &database, &error));
Creating a Statement#
A statement lets us execute queries. They are used for both prepared and non-prepared (“ad-hoc”) queries.
135 AdbcStatement statement = {};
136 CHECK_ADBC(AdbcStatementNew(&connection, &statement, &error));
Executing a Query#
We execute a query by setting the query on the statement, then
calling AdbcStatementExecuteQuery()
. The results come
back through the Arrow C Data Interface.
147 struct ArrowArrayStream stream = {};
148 int64_t rows_affected = -1;
149
150 CHECK_ADBC(AdbcStatementSetSqlQuery(&statement, "SELECT 42 AS THEANSWER", &error));
151 CHECK_ADBC(AdbcStatementExecuteQuery(&statement, &stream, &rows_affected, &error));
While the API gives us the number of rows, the SQLite driver
can’t actually know how many rows there are in the result set
ahead of time, so this value will actually just be -1
to
indicate that the value is not known.
157 std::cout << "Got " << rows_affected << " rows" << std::endl;
We need an Arrow implementation to read the actual results. We can use Arrow C++ or Nanoarrow for that. For simplicity, we’ll use Nanoarrow here. (The CMake configuration for this example downloads and builds Nanoarrow from source as part of the build.)
First we’ll get the schema of the data:
169 ArrowSchema schema = {};
170 CHECK_STREAM(stream, stream.get_schema(&stream, &schema));
Then we can use Nanoarrow to print it:
173 char buf[1024] = {};
174 ArrowSchemaToString(&schema, buf, sizeof(buf), /*recursive=*/1);
175 std::cout << buf << std::endl;
Now we can read the data. The data comes as a stream of Arrow record batches.
179 while (true) {
180 ArrowArray batch = {};
181 CHECK_STREAM(stream, stream.get_next(&stream, &batch));
182
183 if (batch.release == nullptr) {
184 // Stream has ended
185 break;
186 }
We can use Nanoarrow to print out the data, too.
189 ArrowArrayView view = {};
190 CHECK_NANOARROW(ArrowArrayViewInitFromSchema(&view, &schema, nullptr));
191 CHECK_NANOARROW(ArrowArrayViewSetArray(&view, &batch, nullptr));
192 std::cout << "Got a batch with " << batch.length << " rows" << std::endl;
193 for (int64_t i = 0; i < batch.length; i++) {
194 std::cout << "THEANSWER[" << i
195 << "] = " << view.children[0]->buffer_views[1].data.as_int64[i]
196 << std::endl;
197 }
198 ArrowArrayViewReset(&view);
199 }
200
201 std::cout << "Finished reading result set" << std::endl;
202 stream.release(&stream);
Cleanup#
At the end, we must release all our resources.
208 CHECK_ADBC(AdbcStatementRelease(&statement, &error));
209 CHECK_ADBC(AdbcConnectionRelease(&connection, &error));
210 CHECK_ADBC(AdbcDatabaseRelease(&database, &error));
211 return EXIT_SUCCESS;
212}