use adbc_core::error::{Error, Result, Status};
use adbc_core::options::{OptionStatement, OptionValue};
use adbc_core::{Optionable, PartitionedResult, Statement};
use arrow_array::{RecordBatch, RecordBatchIterator, RecordBatchReader};
use arrow_schema::Schema;
use google_cloud_spanner::client::DatabaseClient;
use google_cloud_spanner::statement::Statement as SpannerSql;
use google_cloud_spanner::transaction::ReadWriteTransaction;
use crate::conversion::result_set_to_batch;
use crate::error::{err, from_spanner, invalid_state, not_implemented};
use crate::runtime::SharedRuntime;
pub struct SpannerStatement {
runtime: SharedRuntime,
client: DatabaseClient,
read_only: bool,
sql: Option<String>,
}
impl SpannerStatement {
pub(crate) fn new(runtime: SharedRuntime, client: DatabaseClient, read_only: bool) -> Self {
Self {
runtime,
client,
read_only,
sql: None,
}
}
fn sql(&self) -> Result<String> {
self.sql
.clone()
.ok_or_else(|| invalid_state("no SQL query set on statement; call set_sql_query first"))
}
}
impl Optionable for SpannerStatement {
type Option = OptionStatement;
fn set_option(&mut self, key: Self::Option, _value: OptionValue) -> Result<()> {
Err(not_implemented(&format!(
"statement option {}",
key.as_ref()
)))
}
fn get_option_string(&self, key: Self::Option) -> Result<String> {
Err(err(
format!("option {} is not set", key.as_ref()),
Status::NotFound,
))
}
fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
Err(err(
format!("option {} is not set", key.as_ref()),
Status::NotFound,
))
}
fn get_option_int(&self, key: Self::Option) -> Result<i64> {
Err(err(
format!("option {} is not set", key.as_ref()),
Status::NotFound,
))
}
fn get_option_double(&self, key: Self::Option) -> Result<f64> {
Err(err(
format!("option {} is not set", key.as_ref()),
Status::NotFound,
))
}
}
impl Statement for SpannerStatement {
fn bind(&mut self, _batch: RecordBatch) -> Result<()> {
Err(not_implemented("Statement::bind (parameter binding)"))
}
fn bind_stream(&mut self, _reader: Box<dyn RecordBatchReader + Send>) -> Result<()> {
Err(not_implemented("Statement::bind_stream (bulk ingest)"))
}
fn execute(&mut self) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
let sql = self.sql()?;
let client = self.client.clone();
let (schema, batch) = self.runtime.block_on(async move {
let transaction = client.single_use().build();
let statement = SpannerSql::builder(sql).build();
let result_set = transaction
.execute_query(statement)
.await
.map_err(from_spanner)?;
result_set_to_batch(result_set).await
})?;
Ok(Box::new(RecordBatchIterator::new(vec![Ok(batch)], schema)))
}
fn execute_update(&mut self) -> Result<Option<i64>> {
if self.read_only {
return Err(invalid_state(
"cannot execute DML: the connection is read-only",
));
}
let sql = self.sql()?;
let client = self.client.clone();
let affected = self.runtime.block_on(async move {
let runner = client
.read_write_transaction()
.build()
.await
.map_err(from_spanner)?;
let outcome = runner
.run(move |transaction: ReadWriteTransaction| {
let sql = sql.clone();
async move {
let statement = SpannerSql::builder(sql).build();
transaction.execute_update(statement).await
}
})
.await
.map_err(from_spanner)?;
Ok::<i64, Error>(outcome.result)
})?;
Ok(Some(affected))
}
fn execute_schema(&mut self) -> Result<Schema> {
Err(not_implemented("Statement::execute_schema"))
}
fn execute_partitions(&mut self) -> Result<PartitionedResult> {
Err(not_implemented("Statement::execute_partitions"))
}
fn get_parameter_schema(&self) -> Result<Schema> {
Err(not_implemented("Statement::get_parameter_schema"))
}
fn prepare(&mut self) -> Result<()> {
Ok(())
}
fn set_sql_query(&mut self, query: impl AsRef<str>) -> Result<()> {
self.sql = Some(query.as_ref().to_string());
Ok(())
}
fn set_substrait_plan(&mut self, _plan: impl AsRef<[u8]>) -> Result<()> {
Err(not_implemented("Statement::set_substrait_plan"))
}
fn cancel(&mut self) -> Result<()> {
Err(not_implemented("Statement::cancel"))
}
}