Quickstart¶
Here we’ll briefly tour basic features of ADBC with the SQLite driver for Node.js.
Installation¶
npm install @apache-arrow/adbc-driver-manager apache-arrow
Note
The @apache-arrow/adbc-driver-manager package includes TypeScript
types so you don’t need a separate types package to get types.
Note
Node.js 22+, Deno 2.0+, or Bun 1.1+ are required.
To use any driver with the JavaScript driver manager, you’ll need install the appropriate driver shared library for your platform separately and pass the absolute path to the driver manager.
Some examples for the SQLite driver are provided below for convenience:
conda create -n adbc-sqlite -c conda-forge adbc-driver-sqlite
conda run -n adbc-sqlite python -c "import adbc_driver_sqlite; print(adbc_driver_sqlite._driver_path())"
PyPI:
python -m venv .venv
source .venv/bin/activate
pip install adbc-driver-sqlite
python -c "import adbc_driver_sqlite; print(adbc_driver_sqlite._driver_path())"
For apt and dnf packages, see Installation.
Creating a Database and Connection¶
The entry point is AdbcDatabase. Pass a driver
option to identify the backend — either a short name (resolved via
ADBC Driver Manager and Manifests search paths) or absolute path to a driver
shared library:
import { AdbcDatabase } from '@apache-arrow/adbc-driver-manager';
// Open a connection to an in-memory SQLite database.
const db = new AdbcDatabase({ driver: 'sqlite' });
const conn = await db.connect();
Executing a Query¶
To run a query, use AdbcConnection.query, which
returns an Apache Arrow Table containing the full result:
const table = await conn.query("SELECT 1 AS id, 'hello' AS greeting");
console.log(table.getChild('greeting')?.get(0)); // "hello"
For large result sets, use
AdbcConnection.queryStream instead. It returns a
RecordBatchReader that yields results one batch at a time without loading
the entire result into memory:
const reader = await conn.queryStream('SELECT * FROM large_table');
for await (const batch of reader) {
console.log(`Processing batch of ${batch.numRows} rows`);
}
Executing Updates¶
Use AdbcConnection.execute to execute statements
that do not return rows (INSERT, UPDATE, DELETE, DDL). It returns the number of
rows affected:
await conn.execute('CREATE TABLE users (id INTEGER, name TEXT)');
const affected = await conn.execute(
"INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob')"
);
console.log(affected); // 2
Parameterized Queries¶
Parameters are passed as an Apache Arrow Table whose columns correspond to
the bind parameters (? placeholders) in the SQL statement:
import { tableFromArrays } from 'apache-arrow';
// Bind a single row of parameters.
const params = tableFromArrays({ id: [1] });
const result = await conn.query('SELECT name FROM users WHERE id = ?', params);
console.log(result.getChild('name')?.get(0)); // "Alice"
The same approach works with AdbcConnection.execute
for DML:
const params = tableFromArrays({ id: [2], name: ['Charlie'] });
await conn.execute('INSERT INTO users VALUES (?, ?)', params);
Ingesting Bulk Data¶
AdbcConnection.ingest inserts an Arrow Table
into a database table in a single call, avoiding per-row overhead. Pass a
mode option using IngestMode to control whether
to create a new table or append to an existing one:
import { tableFromArrays } from 'apache-arrow';
import { IngestMode } from '@apache-arrow/adbc-driver-manager';
const data = tableFromArrays({
id: [1, 2, 3],
name: ['alice', 'bob', 'carol'],
});
// Create a new table and insert the data.
await conn.ingest('sample', data);
// Append more rows to the existing table.
const more = tableFromArrays({ id: [4], name: ['dave'] });
await conn.ingest('sample', more, { mode: IngestMode.Append });
For datasets that do not fit in memory use
AdbcConnection.ingestStream with a
RecordBatchReader:
import { RecordBatchReader, tableToIPC, tableFromArrays } from 'apache-arrow';
const data = tableFromArrays({ id: [1, 2, 3] });
const reader = RecordBatchReader.from(tableToIPC(data, 'stream'));
await conn.ingestStream('streaming_table', reader);
Getting Database Metadata¶
AdbcConnection.getObjects returns a nested Arrow
structure describing all catalogs, schemas, and tables in the database:
const objects = await conn.getObjects({ depth: 3, tableName: 'sample' });
const dbSchemas = objects.getChild('catalog_db_schemas')?.get(0);
const tables = dbSchemas?.get(0)?.db_schema_tables;
console.log(tables?.get(0)?.table_name); // "sample"
AdbcConnection.getTableSchema returns the Arrow
schema for a specific table:
const schema = await conn.getTableSchema({ tableName: 'sample' });
console.log(schema.fields.map(f => f.name)); // ["id", "name"]
AdbcConnection.getTableTypes lists the types of
table objects supported by the database:
const types = await conn.getTableTypes();
const typeNames = Array.from(
{ length: types.numRows },
(_, i) => types.getChild('table_type')?.get(i)
);
console.log(typeNames); // ["table", "view"]
Transactions¶
By default, connections operate in autocommit mode. Call
AdbcConnection.setAutoCommit with false to
manage transactions manually, then use
AdbcConnection.commit or
AdbcConnection.rollback:
conn.setAutoCommit(false);
await conn.execute("INSERT INTO users VALUES (99, 'temp')");
await conn.rollback(); // row is not persisted
await conn.execute("INSERT INTO users VALUES (100, 'permanent')");
await conn.commit(); // row is persisted
Low-Level Statement API¶
AdbcStatement gives direct access to ADBC’s
statement lifecycle for use cases that require more control, such as binding
parameters separately from execution or reusing a statement across multiple
queries:
const stmt = await conn.createStatement();
await stmt.setSqlQuery('SELECT id, name FROM users WHERE id = ?');
const params = tableFromArrays({ id: [1] });
await stmt.bind(params);
const reader = await stmt.executeQuery();
for await (const batch of reader) {
console.log(batch.numRows);
}
await stmt.close();