adbc-spanner 0.1.0

ADBC (Arrow Database Connectivity) driver for Google Cloud Spanner
Documentation

adbc-spanner

Crates.io Docs.rs CI License

An ADBC (Arrow Database Connectivity) driver for Google Cloud Spanner, written in Rust.

It implements the native Rust adbc_core driver traits on top of the official google-cloud-spanner preview client from googleapis/google-cloud-rust. Query results come back as Apache Arrow record batches, so Spanner data flows into the Arrow ecosystem (DataFusion, Polars, Flight, …) without a row-by-row copy.

SpannerDriver ──▶ SpannerDatabase ──▶ SpannerConnection ──▶ SpannerStatement

Status

Early but working and tested end-to-end against the Spanner emulator. Supported today:

  • Connecting to production Spanner or a Spanner emulator.
  • SQL queries (execute), returned as typed Arrow RecordBatches.
  • DML (execute_update), returning the affected-row count, run inside an automatically-retried read/write transaction.
  • get_table_types() connection metadata.

Not yet supported (return NotImplemented): query parameter binding / bulk ingest, manual multi-statement transactions (the driver is autocommit-only), Substrait, partitioned execution, and the richer catalog-metadata calls (get_objects, get_table_schema, get_statistics, …).

Shared library (loadable driver)

Besides the Rust crate, this builds a C-ABI shared library that any ADBC driver manager can load (libadbc_spanner.so on Linux, libadbc_spanner.dylib on macOS, adbc_spanner.dll on Windows). It exports the standard AdbcSpannerInit entrypoint (plus an AdbcDriverInit fallback).

Prebuilt libraries for Linux (x86-64, aarch64), macOS (Apple Silicon) and Windows (x86-64) are attached to every CI run and to each tagged release. To build one yourself: cargo build --releasetarget/release/libadbc_spanner.so.

Example, loading it from the Python driver manager:

import adbc_driver_manager
db = adbc_driver_manager.AdbcDatabase(
    driver="/path/to/libadbc_spanner.so",
    entrypoint="AdbcSpannerInit",
    uri="projects/my-project/instances/my-instance/databases/my-db",
)

Usage

Add the dependency (this crate plus the Arrow crates you consume results with):

[dependencies]
adbc-spanner = "0.1"
adbc_core = "0.23"
arrow-array = "58"
use adbc_core::options::{OptionDatabase, OptionValue};
use adbc_core::{Connection, Database, Driver, Statement};
use arrow_array::cast::AsArray;
use arrow_array::types::Int64Type;
use adbc_spanner::SpannerDriver;

fn main() -> adbc_core::error::Result<()> {
    let mut driver = SpannerDriver::try_new()?;

    // The Spanner database path is supplied through the standard `uri` option.
    let database = driver.new_database_with_opts([(
        OptionDatabase::Uri,
        OptionValue::String("projects/my-project/instances/my-instance/databases/my-db".into()),
    )])?;

    let mut connection = database.new_connection()?;
    let mut statement = connection.new_statement()?;

    statement.set_sql_query("SELECT SingerId FROM Singers ORDER BY SingerId")?;
    let reader = statement.execute()?;

    for batch in reader {
        let batch = batch?;
        let ids = batch.column(0).as_primitive::<Int64Type>();
        for id in ids.values() {
            println!("singer {id}");
        }
    }
    Ok(())
}

Configuration options

Options are set on the database (via new_database_with_opts or set_option):

Option Meaning
OptionDatabase::Uri / adbc.spanner.database The Spanner database path projects/<p>/instances/<i>/databases/<d>. Required.
adbc.spanner.endpoint Explicit gRPC endpoint, e.g. http://localhost:9010 for an emulator.
adbc.spanner.emulator true to connect with anonymous credentials (emulator mode).

The driver also honours the SPANNER_EMULATOR_HOST environment variable: when set it is used as the endpoint and anonymous credentials are selected automatically. Against production Spanner, Application Default Credentials are used.

Type mapping

Spanner type Arrow type
BOOL Boolean
INT64 Int64
FLOAT64 Float64
FLOAT32 Float32
BYTES Binary
STRING / DATE / TIMESTAMP / NUMERIC / JSON / UUID / INTERVAL / ENUM Utf8
ARRAY / STRUCT Utf8 (JSON-encoded)

NULLs are represented as null slots in the corresponding Arrow array.

Testing

Unit tests run with no external dependencies:

cargo test

The end-to-end integration test in tests/emulator.rs runs the driver against the Cloud Spanner emulator. It is skipped automatically unless SPANNER_EMULATOR_HOST is set, so the command above stays green everywhere.

The helper script starts the emulator in Docker, points the tests at it, and tears it down again:

scripts/with-emulator.sh cargo test --test emulator -- --nocapture

CI (.github/workflows/ci.yml) runs cargo fmt --check, clippy, and the full test suite with the emulator as a service container, so the integration test runs on every push.

Releasing

Releases are cut with cargo-release, configured under [package.metadata.release] in Cargo.toml.

Prerequisites: cargo install cargo-release, a crates.io token (cargo login), and push access to main.

Preview a release (dry run — this is the default, nothing is changed):

cargo release patch      # or: minor / major

Perform it:

cargo release patch --execute

That single command:

  1. bumps the version in Cargo.toml and commits it (Release X.Y.Z),
  2. publishes the crate to crates.io,
  3. creates the annotated tag vX.Y.Z and pushes the commit and tag to origin.

Pushing the vX.Y.Z tag triggers the Shared libraries workflow, which builds the Linux (x86-64, aarch64), macOS (Apple Silicon) and Windows (x86-64) shared libraries and attaches them to the GitHub Release for that tag. So the flow is: cargo release … --execute → crates.io publish + tag → CI attaches the prebuilt libraries to the release.

License

Licensed under the Apache License, Version 2.0.