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}