use std::collections::HashMap;
use std::fmt;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use futures_util::Stream;
use turso_sql::{Statement, Value};
use crate::decode::FromValue;
use crate::error::{Error, Result};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ExecResult {
pub last_insert_id: i64,
pub rows_affected: u64,
}
#[derive(Clone)]
pub struct Row {
columns: Arc<Columns>,
values: Vec<Value>,
}
struct Columns {
names: Vec<String>,
index: HashMap<String, usize>,
}
impl Columns {
fn new(names: Vec<String>) -> Arc<Self> {
let index = names
.iter()
.enumerate()
.map(|(i, n)| (n.to_ascii_lowercase(), i))
.collect();
Arc::new(Self { names, index })
}
}
impl fmt::Debug for Row {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut m = f.debug_map();
for (name, value) in self.columns.names.iter().zip(&self.values) {
m.entry(name, value);
}
m.finish()
}
}
pub trait ColumnIndex: fmt::Display + Copy {
fn resolve(self, row: &Row) -> Option<usize>;
}
impl ColumnIndex for usize {
fn resolve(self, row: &Row) -> Option<usize> {
(self < row.values.len()).then_some(self)
}
}
impl ColumnIndex for &str {
fn resolve(self, row: &Row) -> Option<usize> {
row.columns
.index
.get(self)
.or_else(|| row.columns.index.get(&self.to_ascii_lowercase()))
.copied()
}
}
impl Row {
pub fn columns(&self) -> &[String] {
&self.columns.names
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn has(&self, column: &str) -> bool {
column.resolve(self).is_some()
}
pub fn get<T: FromValue>(&self, column: impl ColumnIndex) -> Result<T> {
let idx = column
.resolve(self)
.ok_or_else(|| Error::decode(column, T::TYPE_NAME, "no such column"))?;
T::from_value(self.values[idx].clone(), &self.columns.names[idx])
}
pub fn try_get<T: FromValue>(&self, column: impl ColumnIndex) -> Result<Option<T>> {
match column.resolve(self) {
None => Ok(None),
Some(idx) => {
T::from_value(self.values[idx].clone(), &self.columns.names[idx]).map(Some)
}
}
}
pub fn raw(&self, column: impl ColumnIndex) -> Option<&Value> {
column.resolve(self).map(|i| &self.values[i])
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
self.columns
.names
.iter()
.map(String::as_str)
.zip(self.values.iter())
}
}
pub type RowStream<'a> = Pin<Box<dyn Stream<Item = Result<Row>> + Send + 'a>>;
macro_rules! engine_module {
($(#[$meta:meta])* $name:ident, $engine:ident) => {
$(#[$meta])*
mod $name {
use std::sync::Arc;
use $engine::params_from_iter;
use turso_sql::{Statement, Value};
use super::{Columns, ExecResult, Row, RowStream};
use crate::error::Result;
fn from_engine(value: $engine::Value) -> Value {
match value {
$engine::Value::Null => Value::Null,
$engine::Value::Integer(n) => Value::Integer(n),
$engine::Value::Real(f) => Value::Real(f),
$engine::Value::Text(s) => Value::Text(s),
$engine::Value::Blob(b) => Value::Blob(b),
}
}
fn to_engine(value: Value) -> $engine::Value {
match value {
Value::Null => $engine::Value::Null,
Value::Integer(n) => $engine::Value::Integer(n),
Value::Real(f) => $engine::Value::Real(f),
Value::Text(s) => $engine::Value::Text(s),
Value::Blob(b) => $engine::Value::Blob(b),
}
}
fn params(statement: &Statement) -> Vec<$engine::Value> {
statement.values.iter().cloned().map(to_engine).collect()
}
fn row_from(columns: &Arc<Columns>, row: &$engine::Row) -> Result<Row> {
let mut values = Vec::with_capacity(columns.names.len());
for i in 0..columns.names.len() {
values.push(from_engine(row.get_value(i)?));
}
Ok(Row {
columns: Arc::clone(columns),
values,
})
}
async fn prepare(conn: &$engine::Connection, sql: &str) -> Result<$engine::Statement> {
Ok(conn.prepare_cached(sql).await?)
}
pub(super) async fn query_all(
conn: &$engine::Connection,
statement: &Statement,
) -> Result<Vec<Row>> {
let mut stmt = prepare(conn, &statement.sql).await?;
let mut rows = stmt.query(params_from_iter(params(statement))).await?;
let columns = Columns::new(rows.column_names());
let mut out = Vec::new();
while let Some(row) = rows.next().await? {
out.push(row_from(&columns, &row)?);
}
Ok(out)
}
pub(super) async fn query_one(
conn: &$engine::Connection,
statement: &Statement,
) -> Result<Option<Row>> {
let mut stmt = prepare(conn, &statement.sql).await?;
let mut rows = stmt.query(params_from_iter(params(statement))).await?;
let columns = Columns::new(rows.column_names());
let first = rows.next().await?;
while rows.next().await?.is_some() {}
first.map(|row| row_from(&columns, &row)).transpose()
}
pub(super) async fn execute(
conn: &$engine::Connection,
statement: &Statement,
) -> Result<ExecResult> {
let mut stmt = prepare(conn, &statement.sql).await?;
let rows_affected = stmt.execute(params_from_iter(params(statement))).await?;
Ok(ExecResult {
last_insert_id: conn.last_insert_rowid(),
rows_affected,
})
}
pub(super) async fn execute_unprepared(
conn: &$engine::Connection,
sql: &str,
) -> Result<ExecResult> {
conn.execute_batch(sql).await?;
Ok(ExecResult {
last_insert_id: conn.last_insert_rowid(),
rows_affected: 0,
})
}
pub(super) async fn execute_raw(conn: &$engine::Connection, sql: &str) -> Result<()> {
conn.execute(sql, ()).await?;
Ok(())
}
pub(super) async fn pragma_update(
conn: &$engine::Connection,
name: &str,
value: &str,
) -> Result<()> {
conn.pragma_update(name, value).await?;
Ok(())
}
pub(super) async fn stream<'a, H: Send + 'a>(
conn: &$engine::Connection,
statement: &Statement,
holder: H,
) -> Result<RowStream<'a>> {
let mut stmt = prepare(conn, &statement.sql).await?;
let rows = stmt.query(params_from_iter(params(statement))).await?;
let columns = Columns::new(rows.column_names());
Ok(Box::pin(futures_util::stream::unfold(
(rows, columns, holder),
|(mut rows, columns, holder)| async move {
match rows.next().await {
Ok(Some(row)) => {
Some((row_from(&columns, &row), (rows, columns, holder)))
}
Ok(None) => None,
Err(e) => Some((Err(e.into()), (rows, columns, holder))),
}
},
)))
}
}
};
}
engine_module!(embedded, turso);
engine_module!(
#[cfg(feature = "serverless")]
remote,
turso_serverless
);
#[derive(Clone)]
pub(crate) enum Conn {
Embedded(turso::Connection),
#[cfg(feature = "serverless")]
Remote(turso_serverless::Connection),
}
macro_rules! dispatch {
($conn:expr, |$c:ident| $call:expr) => {
match $conn {
Conn::Embedded($c) => {
use embedded as engine;
$call
}
#[cfg(feature = "serverless")]
Conn::Remote($c) => {
use remote as engine;
$call
}
}
};
}
impl Conn {
pub(crate) fn is_autocommit(&self) -> Result<bool> {
match self {
Conn::Embedded(c) => Ok(c.is_autocommit()?),
#[cfg(feature = "serverless")]
Conn::Remote(c) => Ok(c.is_autocommit()?),
}
}
pub(crate) fn busy_timeout(&self, timeout: Duration) -> Result<()> {
match self {
Conn::Embedded(c) => Ok(c.busy_timeout(timeout)?),
#[cfg(feature = "serverless")]
Conn::Remote(_) => Ok(()),
}
}
pub(crate) async fn pragma_update(&self, name: &str, value: &str) -> Result<()> {
dispatch!(self, |c| engine::pragma_update(c, name, value).await)
}
pub(crate) async fn execute_raw(&self, sql: &str) -> Result<()> {
dispatch!(self, |c| engine::execute_raw(c, sql).await)
}
}
pub(crate) async fn query_all(conn: &Conn, statement: &Statement) -> Result<Vec<Row>> {
tracing::debug!(sql = %statement.sql, "query_all");
dispatch!(conn, |c| engine::query_all(c, statement).await)
}
pub(crate) async fn query_one(conn: &Conn, statement: &Statement) -> Result<Option<Row>> {
tracing::debug!(sql = %statement.sql, "query_one");
dispatch!(conn, |c| engine::query_one(c, statement).await)
}
pub(crate) async fn execute(conn: &Conn, statement: &Statement) -> Result<ExecResult> {
tracing::debug!(sql = %statement.sql, "execute");
dispatch!(conn, |c| engine::execute(c, statement).await)
}
pub(crate) async fn execute_unprepared(conn: &Conn, sql: &str) -> Result<ExecResult> {
tracing::debug!(sql, "execute_unprepared");
dispatch!(conn, |c| engine::execute_unprepared(c, sql).await)
}
pub(crate) async fn stream<'a, H: Send + 'a>(
conn: &Conn,
statement: &Statement,
holder: H,
) -> Result<RowStream<'a>> {
tracing::debug!(sql = %statement.sql, "stream");
dispatch!(conn, |c| engine::stream(c, statement, holder).await)
}