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
Installing Drivers¶
See Installing Drivers for instructions on installing ADBC drivers for the database you want to connect to. For the example below, you could install dbc and then use dbc to install the SQLite driver and its manifest in the user configuration directory where the driver manager library will search for it:
dbc install --level user sqlite
You can also build drivers from source or use other installation methods. See the driver documentation for more details.
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 recipe-quickstart
./recipe-quickstart
Basic Example¶
Let’s start with some includes:
78// For EXIT_SUCCESS
79#include <cstdlib>
80// For strerror
81#include <cstring>
82#include <iostream>
83
84#include <arrow-adbc/adbc.h>
85#include <arrow-adbc/adbc_driver_manager.h>
86#include <nanoarrow.h>
Then we’ll add some (very basic) error checking helpers.
90// Error-checking helper for ADBC calls.
91// Assumes that there is an AdbcError named `error` in scope.
92#define CHECK_ADBC(EXPR) \
93 if (AdbcStatusCode status = (EXPR); status != ADBC_STATUS_OK) { \
94 if (error.message != nullptr) { \
95 std::cerr << error.message << std::endl; \
96 } \
97 return EXIT_FAILURE; \
98 }
99
100// Error-checking helper for ArrowArrayStream.
101#define CHECK_STREAM(STREAM, EXPR) \
102 if (int status = (EXPR); status != 0) { \
103 std::cerr << "(" << std::strerror(status) << "): "; \
104 const char* message = (STREAM).get_last_error(&(STREAM)); \
105 if (message != nullptr) { \
106 std::cerr << message << std::endl; \
107 } else { \
108 std::cerr << "(no error message)" << std::endl; \
109 } \
110 return EXIT_FAILURE; \
111 }
112
113// Error-checking helper for Nanoarrow.
114#define CHECK_NANOARROW(EXPR) \
115 if (int status = (EXPR); status != 0) { \
116 std::cerr << "(" << std::strerror(status) << "): failed" << std::endl; \
117 return EXIT_FAILURE; \
118 }
119
120int 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.
127 AdbcError error = {};
128
129 AdbcDatabase database = {};
130 CHECK_ADBC(AdbcDatabaseNew(&database, &error));
The way the driver manager knows what driver we want is via the
driver option.
133 CHECK_ADBC(AdbcDatabaseSetOption(&database, "driver", "sqlite", &error));
We also need to enable searching for driver manifests.
135 CHECK_ADBC(
136 AdbcDriverManagerDatabaseSetLoadFlags(&database, ADBC_LOAD_FLAG_DEFAULT, &error));
137 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.
148 AdbcConnection connection = {};
149 CHECK_ADBC(AdbcConnectionNew(&connection, &error));
150 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.
158 AdbcStatement statement = {};
159 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.
170 struct ArrowArrayStream stream = {};
171 int64_t rows_affected = -1;
172
173 CHECK_ADBC(AdbcStatementSetSqlQuery(&statement, "SELECT 42 AS THEANSWER", &error));
174 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.
180 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:
193 ArrowSchema schema = {};
194 CHECK_STREAM(stream, stream.get_schema(&stream, &schema));
Then we can use Nanoarrow to print it:
197 char buf[1024] = {};
198 ArrowSchemaToString(&schema, buf, sizeof(buf), /*recursive=*/1);
199 std::cout << "Result schema: " << buf << std::endl;
Now we can read the data. The data comes as a stream of Arrow record batches.
205 while (true) {
206 ArrowArray batch = {};
207 CHECK_STREAM(stream, stream.get_next(&stream, &batch));
208
209 if (batch.release == nullptr) {
210 // Stream has ended
211 break;
212 }
We can use Nanoarrow to print out the data, too.
215 ArrowArrayView view = {};
216 CHECK_NANOARROW(ArrowArrayViewInitFromSchema(&view, &schema, nullptr));
217 CHECK_NANOARROW(ArrowArrayViewSetArray(&view, &batch, nullptr));
218 std::cout << "Got a batch with " << batch.length << " rows" << std::endl;
219 for (int64_t i = 0; i < batch.length; i++) {
220 std::cout << "THEANSWER[" << i
221 << "] = " << view.children[0]->buffer_views[1].data.as_int64[i]
222 << std::endl;
223 }
224 ArrowArrayViewReset(&view);
225 }
226 // Output:
227 // Got a batch with 1 rows
228 // THEANSWER[0] = 42
229
230 stream.release(&stream);
Cleanup¶
At the end, we must release all our resources.
236 CHECK_ADBC(AdbcStatementRelease(&statement, &error));
237 CHECK_ADBC(AdbcConnectionRelease(&connection, &error));
238 CHECK_ADBC(AdbcDatabaseRelease(&database, &error));
239 return EXIT_SUCCESS;
240}
Got -1 rows
Result schema: struct<THEANSWER: int64>
Got a batch with 1 rows
THEANSWER[0] = 42