blob: 1f726ee69508b9f2cf723155d19472fb75c1af6b [file]
// 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 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:
///
/// .. code-block:: shell
///
/// mamba install cmake compilers libadbc-driver-manager libadbc-driver-sqlite
///
/// .. _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
/// cmake --build . --target recipe-quickstart
/// ./recipe-quickstart
///
/// .. _CMake: https://cmake.org/
/// Using ADBC
/// ==========
///
/// Let's start with some includes:
// For EXIT_SUCCESS
#include <cstdlib>
// For strerror
#include <cstring>
#include <iostream>
#include <arrow-adbc/adbc.h>
#include <nanoarrow.h>
/// Then we'll add some (very basic) error checking helpers.
// Error-checking helper for ADBC calls.
// Assumes that there is an AdbcError named `error` in scope.
#define CHECK_ADBC(EXPR) \
if (AdbcStatusCode status = (EXPR); status != ADBC_STATUS_OK) { \
if (error.message != nullptr) { \
std::cerr << error.message << std::endl; \
} \
return EXIT_FAILURE; \
}
// Error-checking helper for ArrowArrayStream.
#define CHECK_STREAM(STREAM, EXPR) \
if (int status = (EXPR); status != 0) { \
std::cerr << "(" << std::strerror(status) << "): "; \
const char* message = (STREAM).get_last_error(&(STREAM)); \
if (message != nullptr) { \
std::cerr << message << std::endl; \
} else { \
std::cerr << "(no error message)" << std::endl; \
} \
return EXIT_FAILURE; \
}
// Error-checking helper for Nanoarrow.
#define CHECK_NANOARROW(EXPR) \
if (int status = (EXPR); status != 0) { \
std::cerr << "(" << std::strerror(status) << "): failed" << std::endl; \
return EXIT_FAILURE; \
}
int 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.
AdbcError error = {};
AdbcDatabase database = {};
CHECK_ADBC(AdbcDatabaseNew(&database, &error));
/// The way the driver manager knows what driver we want is via the
/// ``driver`` option.
CHECK_ADBC(AdbcDatabaseSetOption(&database, "driver", "adbc_driver_sqlite", &error));
CHECK_ADBC(AdbcDatabaseInit(&database, &error));
/// Creating a Connection
/// ---------------------
///
/// ADBC distinguishes between ":term:`databases <database>`",
/// ":term:`connections <connection>`", and ":term:`statements
/// <statement>`". 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.
AdbcConnection connection = {};
CHECK_ADBC(AdbcConnectionNew(&connection, &error));
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.
AdbcStatement statement = {};
CHECK_ADBC(AdbcStatementNew(&connection, &statement, &error));
/// Executing a Query
/// -----------------
///
/// We execute a query by setting the query on the statement, then
/// calling :c:func:`AdbcStatementExecuteQuery`. The results come
/// back through the `Arrow C Data Interface`_.
///
/// .. _Arrow C Data Interface: https://arrow.apache.org/docs/format/CDataInterface.html
struct ArrowArrayStream stream = {};
int64_t rows_affected = -1;
CHECK_ADBC(AdbcStatementSetSqlQuery(&statement, "SELECT 42 AS THEANSWER", &error));
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.
std::cout << "Got " << rows_affected << " rows" << std::endl;
// Output: Got -1 rows
/// 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.)
///
/// .. _Arrow C++: https://arrow.apache.org/docs/cpp/index.html
/// .. _Nanoarrow: https://github.com/apache/arrow-nanoarrow
/// First we'll get the schema of the data:
ArrowSchema schema = {};
CHECK_STREAM(stream, stream.get_schema(&stream, &schema));
/// Then we can use Nanoarrow to print it:
char buf[1024] = {};
ArrowSchemaToString(&schema, buf, sizeof(buf), /*recursive=*/1);
std::cout << "Result schema: " << buf << std::endl;
// Output:
// Result schema: struct<THEANSWER: int64>
/// Now we can read the data. The data comes as a stream of Arrow
/// record batches.
while (true) {
ArrowArray batch = {};
CHECK_STREAM(stream, stream.get_next(&stream, &batch));
if (batch.release == nullptr) {
// Stream has ended
break;
}
/// We can use Nanoarrow to print out the data, too.
ArrowArrayView view = {};
CHECK_NANOARROW(ArrowArrayViewInitFromSchema(&view, &schema, nullptr));
CHECK_NANOARROW(ArrowArrayViewSetArray(&view, &batch, nullptr));
std::cout << "Got a batch with " << batch.length << " rows" << std::endl;
for (int64_t i = 0; i < batch.length; i++) {
std::cout << "THEANSWER[" << i
<< "] = " << view.children[0]->buffer_views[1].data.as_int64[i]
<< std::endl;
}
ArrowArrayViewReset(&view);
}
// Output:
// Got a batch with 1 rows
// THEANSWER[0] = 42
stream.release(&stream);
/// Cleanup
/// -------
/// At the end, we must release all our resources.
CHECK_ADBC(AdbcStatementRelease(&statement, &error));
CHECK_ADBC(AdbcConnectionRelease(&connection, &error));
CHECK_ADBC(AdbcDatabaseRelease(&database, &error));
return EXIT_SUCCESS;
}