use adbc_core::options::{OptionDatabase, OptionValue};
use adbc_core::{Connection, Database, Driver, Statement};
use adbc_spanner::SpannerDriver;
use arrow_array::{BooleanArray, Float64Array, Int64Array, RecordBatchReader, StringArray};
use arrow_schema::DataType;
use google_cloud_lro::Poller;
use google_cloud_spanner::client::Spanner;
use google_cloud_spanner_admin_instance_v1::model::Instance;
const PROJECT: &str = "test-project";
const INSTANCE: &str = "test-instance";
const DATABASE: &str = "adbc-test";
fn database_path() -> String {
format!("projects/{PROJECT}/instances/{INSTANCE}/databases/{DATABASE}")
}
fn emulator_configured() -> bool {
std::env::var("SPANNER_EMULATOR_HOST")
.ok()
.filter(|s| !s.is_empty())
.is_some()
}
async fn ensure_database() {
let spanner = Spanner::builder()
.build()
.await
.expect("failed to build Spanner client for setup");
let instance_admin = spanner
.instance_admin_builder()
.build()
.await
.expect("failed to build instance admin client");
let _ = instance_admin
.create_instance()
.set_parent(format!("projects/{PROJECT}"))
.set_instance_id(INSTANCE)
.set_instance(
Instance::new()
.set_config(format!(
"projects/{PROJECT}/instanceConfigs/emulator-config"
))
.set_display_name("ADBC test instance")
.set_node_count(1),
)
.poller()
.until_done()
.await;
let database_admin = spanner
.database_admin_builder()
.build()
.await
.expect("failed to build database admin client");
let _ = database_admin
.create_database()
.set_parent(format!("projects/{PROJECT}/instances/{INSTANCE}"))
.set_create_statement(format!("CREATE DATABASE `{DATABASE}`"))
.set_extra_statements(vec!["CREATE TABLE Singers (\
SingerId INT64 NOT NULL, \
Name STRING(MAX), \
Active BOOL, \
Score FLOAT64\
) PRIMARY KEY (SingerId)"
.to_string()])
.poller()
.until_done()
.await;
}
#[test]
fn query_and_dml_round_trip() {
if !emulator_configured() {
eprintln!("SPANNER_EMULATOR_HOST not set — skipping Spanner emulator integration test");
return;
}
tokio::runtime::Runtime::new()
.expect("failed to build setup runtime")
.block_on(ensure_database());
let mut driver = SpannerDriver::try_new().expect("create driver");
let database = driver
.new_database_with_opts([(OptionDatabase::Uri, OptionValue::String(database_path()))])
.expect("create database");
let mut connection = database.new_connection().expect("create connection");
let table_types = connection.get_table_types().expect("get_table_types");
assert_eq!(table_types.schema().field(0).name(), "table_type");
let mut delete = connection.new_statement().expect("new statement");
delete
.set_sql_query("DELETE FROM Singers WHERE true")
.unwrap();
delete.execute_update().expect("delete");
let mut insert = connection.new_statement().expect("new statement");
insert
.set_sql_query(
"INSERT INTO Singers (SingerId, Name, Active, Score) \
VALUES (1, 'Alice', true, 4.5), (2, 'Bob', false, 3.25)",
)
.unwrap();
assert_eq!(insert.execute_update().expect("insert"), Some(2));
let mut query = connection.new_statement().expect("new statement");
query
.set_sql_query("SELECT SingerId, Name, Active, Score FROM Singers ORDER BY SingerId")
.unwrap();
let reader = query.execute().expect("query");
let schema = reader.schema();
assert_eq!(schema.field(0).name(), "SingerId");
assert_eq!(schema.field(0).data_type(), &DataType::Int64);
assert_eq!(schema.field(1).data_type(), &DataType::Utf8);
assert_eq!(schema.field(2).data_type(), &DataType::Boolean);
assert_eq!(schema.field(3).data_type(), &DataType::Float64);
let batches = reader
.collect::<Result<Vec<_>, _>>()
.expect("collect batches");
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
assert_eq!(total_rows, 2, "expected two rows back");
let batch = &batches[0];
let ids = batch
.column(0)
.as_any()
.downcast_ref::<Int64Array>()
.unwrap();
let names = batch
.column(1)
.as_any()
.downcast_ref::<StringArray>()
.unwrap();
let active = batch
.column(2)
.as_any()
.downcast_ref::<BooleanArray>()
.unwrap();
let score = batch
.column(3)
.as_any()
.downcast_ref::<Float64Array>()
.unwrap();
assert_eq!(
(
ids.value(0),
names.value(0),
active.value(0),
score.value(0)
),
(1, "Alice", true, 4.5)
);
assert_eq!(
(
ids.value(1),
names.value(1),
active.value(1),
score.value(1)
),
(2, "Bob", false, 3.25)
);
}