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 <arrow-adbc/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:

170  ArrowSchema schema = {};
171  CHECK_STREAM(stream, stream.get_schema(&stream, &schema));

Then we can use Nanoarrow to print it:

174  char buf[1024] = {};
175  ArrowSchemaToString(&schema, buf, sizeof(buf), /*recursive=*/1);
176  std::cout << "Result schema: " << buf << std::endl;

Now we can read the data. The data comes as a stream of Arrow record batches.

182  while (true) {
183    ArrowArray batch = {};
184    CHECK_STREAM(stream, stream.get_next(&stream, &batch));
185
186    if (batch.release == nullptr) {
187      // Stream has ended
188      break;
189    }

We can use Nanoarrow to print out the data, too.

192    ArrowArrayView view = {};
193    CHECK_NANOARROW(ArrowArrayViewInitFromSchema(&view, &schema, nullptr));
194    CHECK_NANOARROW(ArrowArrayViewSetArray(&view, &batch, nullptr));
195    std::cout << "Got a batch with " << batch.length << " rows" << std::endl;
196    for (int64_t i = 0; i < batch.length; i++) {
197      std::cout << "THEANSWER[" << i
198                << "] = " << view.children[0]->buffer_views[1].data.as_int64[i]
199                << std::endl;
200    }
201    ArrowArrayViewReset(&view);
202  }
203  // Output:
204  // Got a batch with 1 rows
205  // THEANSWER[0] = 42
206
207  stream.release(&stream);

Cleanup

At the end, we must release all our resources.

213  CHECK_ADBC(AdbcStatementRelease(&statement, &error));
214  CHECK_ADBC(AdbcConnectionRelease(&connection, &error));
215  CHECK_ADBC(AdbcDatabaseRelease(&database, &error));
216  return EXIT_SUCCESS;
217}
stdout
Got -1 rows
Result schema: struct<THEANSWER: int64>
Got a batch with 1 rows
THEANSWER[0] = 42