use super::common::common_helpers;
use crate::abstract_layer::DbType;
use crate::abstract_layer::common::{SingleSqlStatement, SqlExecutor, SqlStatement};
use crate::hooks::{HookContext, HookOperation};
use crate::migration::{SchemaColumn, schema_column};
use crate::model::{DbBackendTypeMapper, Model, Row, Value};
use crate::query::builder::{
FourTableSelect, GroupedSelect, InnerJoinedSelect, LeftJoinedSelect, MappedSelect,
MultiTableSelect, RelatedSelect, RightJoinedSelect, Select, WhereExpr,
};
use crate::query::filter::FilterExpr;
use crate::query::insert::{
InsertAssignment, InsertConflict, IntoInsertAssignment, IntoInsertDefaultColumn,
};
use crate::query::update::UpdateAssignment;
use crate::raw_sql::IntoRawSql;
use crate::utils::{FutureTraceExt, ResultTraceExt};
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
type ModelUpdateBatch = Vec<(Vec<(String, Value)>, Vec<FilterExpr>)>;
fn is_constraint_error(e: &crate::OrmerError) -> bool {
let msg = e.to_string();
msg.contains("UNIQUE constraint failed") || msg.contains("constraint")
}
fn table_name_for<T: Model>() -> &'static str {
T::table_name_for_db(DbType::Sqlite)
}
use crate::impl_backend_executor_methods;
use crate::impl_backend_join_executor_methods;
use crate::impl_backend_related_executor_methods;
use crate::impl_insert_conflict_methods;
pub struct SqliteTypeMapper;
impl DbBackendTypeMapper for SqliteTypeMapper {
fn sql_type(
rust_type: &str,
is_primary: bool,
is_auto_increment: bool,
is_nullable: bool,
enum_variants: Option<&[&str]>,
) -> String {
if enum_variants.is_some() {
return common_helpers::sql_type_with_nullability("TEXT", is_nullable || is_primary);
}
if is_primary {
if is_auto_increment {
return "INTEGER PRIMARY KEY AUTOINCREMENT".to_string();
} else {
return "INTEGER PRIMARY KEY".to_string();
}
}
let base_type = match rust_type {
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => "INTEGER",
"f32" | "f64" => "REAL",
"Duration" | "std::time::Duration" => "INTEGER",
"String" => "TEXT",
"bool" => "INTEGER",
"Vec<u8>" | "&[u8]" => "BLOB",
"DateTime"
| "chrono::DateTime"
| "chrono::DateTime<chrono::Utc>"
| "NaiveDateTime"
| "chrono::NaiveDateTime" => "TEXT",
"NaiveDate" | "chrono::NaiveDate" => "TEXT",
"NaiveTime" | "chrono::NaiveTime" => "TEXT",
"JsonValue" | "serde_json::Value" => "TEXT",
_ => "TEXT",
};
common_helpers::sql_type_with_nullability(base_type, is_nullable)
}
}
pub struct Database {
conn: Arc<turso::Connection>,
}
unsafe impl Send for Database {}
unsafe impl Sync for Database {}
#[allow(dead_code)]
struct SendableConnection(turso::Connection);
unsafe impl Send for SendableConnection {}
pub struct CreateTableExecutor<'a, T: Model> {
db: &'a Database,
table_name: Option<String>,
_marker: std::marker::PhantomData<T>,
}
impl<'a, T: Model> CreateTableExecutor<'a, T> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let create_sql = crate::generate_create_table_sql_with_name::<T>(
crate::abstract_layer::DbType::Sqlite,
self.table_name.as_deref(),
)?;
Ok(SqlStatement::single(DbType::Sqlite, create_sql, Vec::new()))
}
pub async fn execute(self) -> crate::Result<()> {
<Self as SqlExecutor>::execute(self).await
}
}
impl<'a, T: Model> SqlExecutor for CreateTableExecutor<'a, T> {
type Output = ();
fn to_sql(&self) -> crate::Result<SqlStatement> {
CreateTableExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
for statement in sql.statements {
self.db.conn.execute(&statement.sql, ()).trace().await?;
}
Ok(())
}
}
pub struct DropTableExecutor<'a, T: Model> {
db: &'a Database,
_marker: std::marker::PhantomData<T>,
}
impl<'a, T: Model> DropTableExecutor<'a, T> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
Ok(SqlStatement::single(
DbType::Sqlite,
format!(
"DROP TABLE IF EXISTS {}",
common_helpers::quote_table_name::<T>(DbType::Sqlite)
),
Vec::new(),
))
}
pub async fn execute(self) -> crate::Result<()> {
<Self as SqlExecutor>::execute(self).await
}
}
impl<'a, T: Model> SqlExecutor for DropTableExecutor<'a, T> {
type Output = ();
fn to_sql(&self) -> crate::Result<SqlStatement> {
DropTableExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
for statement in sql.statements {
self.db.conn.execute(&statement.sql, ()).trace().await?;
}
Ok(())
}
}
pub struct InsertExecutor<'a, I: crate::model::Insertable> {
db: &'a Database,
models: I,
conflict: Option<InsertConflict>,
_marker: std::marker::PhantomData<I::Model>,
}
impl_insert_conflict_methods!(InsertExecutor, with_conflict);
impl<'a, I: crate::model::Insertable + Send + Sync> InsertExecutor<'a, I> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let refs = self.models.as_refs();
if refs.is_empty() {
return Ok(SqlStatement::batch(DbType::Sqlite, Vec::new()));
}
let (sql, all_values) = common_helpers::build_insert_statement_with_conflict::<I::Model>(
DbType::Sqlite,
&refs,
self.conflict.as_ref(),
)?;
Ok(SqlStatement::single(DbType::Sqlite, sql, all_values))
}
pub async fn execute(self) -> crate::Result<<I::Model as Model>::AutoIncrementKeyType> {
<Self as SqlExecutor>::execute(self).await
}
pub async fn returning(mut self) -> crate::Result<Vec<I::Model>> {
if self.models.as_refs().is_empty() {
return Ok(Vec::new());
}
let hook_ctx = HookContext::new(HookOperation::Insert);
self.models.run_before_insert(hook_ctx).await?;
let refs = self.models.as_refs();
let (sql, all_values) = common_helpers::build_insert_statement_with_conflict::<I::Model>(
DbType::Sqlite,
&refs,
self.conflict.as_ref(),
)?;
let all_params = values_into_params(all_values)?;
let sql_with_returning = format!("{} RETURNING *", sql);
let mut rows = self
.db
.conn
.query(&sql_with_returning, all_params)
.trace()
.await?;
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let model = common_helpers::decode_model_from_indexed_values::<I::Model, _>(0, |i| {
let value = row.get_value(i)?;
convert_turso_value(&value)
})?;
results.push(model);
}
self.models.run_after_insert(hook_ctx).await?;
Ok(results)
}
}
impl<'a, I: crate::model::Insertable + Send + Sync> SqlExecutor for InsertExecutor<'a, I> {
type Output = <I::Model as Model>::AutoIncrementKeyType;
fn to_sql(&self) -> crate::Result<SqlStatement> {
InsertExecutor::to_sql(self)
}
async fn execute_with_sql(mut self, sql: SqlStatement) -> crate::Result<Self::Output> {
if sql.statements.is_empty() {
return Ok(<I::Model as Model>::AutoIncrementKeyType::default());
}
let hook_ctx = HookContext::new(HookOperation::Insert);
self.models.run_before_insert(hook_ctx).await?;
let statement = &sql.statements[0];
let params = values_to_params(&statement.params)?;
let rows_affected = self.db.conn.execute(&statement.sql, params).trace().await?;
self.models.run_after_insert(hook_ctx).await?;
let has_auto_increment = I::Model::COLUMN_SCHEMA.iter().any(|c| c.is_auto_increment);
if has_auto_increment {
if rows_affected == 0 {
return Ok(<I::Model as Model>::AutoIncrementKeyType::default());
}
let last_id = self.db.conn.last_insert_rowid();
let result = common_helpers::convert_auto_increment_key::<Self::Output>(last_id)?;
return Ok(result);
}
Ok(<I::Model as Model>::AutoIncrementKeyType::default())
}
}
pub struct InsertPartialExecutor<'a, T: Model> {
db: &'a Database,
assignments: Vec<InsertAssignment>,
source_table: Option<&'static str>,
_marker: PhantomData<T>,
}
impl<'a, T: Model> InsertPartialExecutor<'a, T> {
fn with_assignments(mut self, assignments: Vec<InsertAssignment>) -> Self {
self.assignments.extend(assignments);
self
}
fn with_source_table(mut self, source_table: &'static str) -> Self {
self.source_table = Some(source_table);
self
}
pub fn set<F, A>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> A,
A: IntoInsertAssignment<T>,
{
self.assignments
.push(f(T::Where::default()).into_insert_assignment());
self
}
pub fn default<F, C>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> C,
C: IntoInsertDefaultColumn<T>,
{
self.assignments.push(InsertAssignment::default(
f(T::Where::default()).into_insert_default_column(),
));
self
}
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
common_helpers::validate_insert_model_table::<T>(DbType::Sqlite, self.source_table)?;
let statement =
common_helpers::build_partial_insert_statement::<T>(DbType::Sqlite, &self.assignments)?;
Ok(SqlStatement::batch(
DbType::Sqlite,
vec![SingleSqlStatement::new(statement.sql, statement.params)],
))
}
pub async fn execute(self) -> crate::Result<<T as Model>::AutoIncrementKeyType>
where
T: Send + Sync,
{
<Self as SqlExecutor>::execute(self).await
}
}
impl<'a, T: Model + Send + Sync> SqlExecutor for InsertPartialExecutor<'a, T> {
type Output = <T as Model>::AutoIncrementKeyType;
fn to_sql(&self) -> crate::Result<SqlStatement> {
InsertPartialExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
if sql.statements.is_empty() {
return Ok(<T as Model>::AutoIncrementKeyType::default());
}
let statement = &sql.statements[0];
let params = values_to_params(&statement.params)?;
let rows_affected = self.db.conn.execute(&statement.sql, params).trace().await?;
if rows_affected == 0 {
return Ok(<T as Model>::AutoIncrementKeyType::default());
}
let has_auto_increment = T::COLUMN_SCHEMA.iter().any(|c| c.is_auto_increment);
if has_auto_increment {
let last_id = self.db.conn.last_insert_rowid();
return common_helpers::convert_auto_increment_key::<Self::Output>(last_id);
}
Ok(<T as Model>::AutoIncrementKeyType::default())
}
}
pub struct InsertOrUpdateExecutor<'a, I: crate::model::Insertable> {
db: &'a Database,
models: I,
_marker: std::marker::PhantomData<I::Model>,
}
impl<'a, I: crate::model::Insertable + Send + Sync> InsertOrUpdateExecutor<'a, I> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let refs = self.models.as_refs();
if refs.is_empty() {
return Ok(SqlStatement::batch(DbType::Sqlite, Vec::new()));
}
let (sql, all_values) = common_helpers::build_batch_insert_statement::<I::Model>(
DbType::Sqlite,
"INSERT INTO",
<I::Model as Model>::table_name_for_db(DbType::Sqlite),
I::Model::COLUMNS,
&refs,
common_helpers::BatchInsertValuesMode::All,
);
Ok(SqlStatement::single(DbType::Sqlite, sql, all_values))
}
pub async fn execute(mut self) -> crate::Result<()> {
if self.models.as_refs().is_empty() {
return Ok(());
}
let hook_ctx = HookContext::new(HookOperation::Insert);
self.models.run_before_insert(hook_ctx).await?;
let refs = self.models.as_refs();
let columns = I::Model::COLUMNS;
let col_count = columns.len();
let table_name = common_helpers::quote_table_name::<I::Model>(DbType::Sqlite);
let pk_columns = I::Model::primary_key_columns();
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, columns);
let insert_placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({insert_placeholders})");
let where_clauses: Vec<String> = pk_columns
.iter()
.enumerate()
.map(|(idx, c)| {
common_helpers::quote_assignment(
DbType::Sqlite,
c,
&common_helpers::placeholder(DbType::Sqlite, idx + 1),
)
})
.collect();
let delete_sql = format!(
"DELETE FROM {table_name} WHERE {}",
where_clauses.join(" AND ")
);
for model in refs.iter() {
let pk_values = model.primary_key_values();
let delete_params = values_into_params(pk_values)?;
self.db
.conn
.execute(&delete_sql, delete_params)
.trace()
.await?;
let all_values = model.field_values();
let insert_params = values_into_params(all_values)?;
self.db
.conn
.execute(&insert_sql, insert_params)
.trace()
.await?;
}
self.models.run_after_insert(hook_ctx).await?;
Ok(())
}
}
impl<'a, I: crate::model::Insertable + Send + Sync> SqlExecutor for InsertOrUpdateExecutor<'a, I> {
type Output = ();
fn to_sql(&self) -> crate::Result<SqlStatement> {
InsertOrUpdateExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
if sql.statements.is_empty() {
return Ok(());
}
let statement = &sql.statements[0];
let params = values_to_params(&statement.params)?;
self.db.conn.execute(&statement.sql, params).trace().await?;
Ok(())
}
}
pub struct InsertOrIgnoreExecutor<'a, I: crate::model::Insertable> {
db: &'a Database,
models: I,
_marker: std::marker::PhantomData<I::Model>,
}
impl<'a, I: crate::model::Insertable + Send + Sync> InsertOrIgnoreExecutor<'a, I> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let refs = self.models.as_refs();
if refs.is_empty() {
return Ok(SqlStatement::batch(DbType::Sqlite, Vec::new()));
}
let columns = I::Model::insert_columns();
let (sql, all_values) = common_helpers::build_batch_insert_statement::<I::Model>(
DbType::Sqlite,
"INSERT INTO",
<I::Model as Model>::table_name_for_db(DbType::Sqlite),
&columns,
&refs,
common_helpers::BatchInsertValuesMode::WithoutAutoIncrement,
);
Ok(SqlStatement::single(DbType::Sqlite, sql, all_values))
}
pub async fn execute(mut self) -> crate::Result<()> {
if self.models.as_refs().is_empty() {
return Ok(());
}
let hook_ctx = HookContext::new(HookOperation::Insert);
self.models.run_before_insert(hook_ctx).await?;
let refs = self.models.as_refs();
let columns = I::Model::insert_columns();
let col_count = columns.len();
let table_name = common_helpers::quote_table_name::<I::Model>(DbType::Sqlite);
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, &columns);
let placeholders_str = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let sql = format!("INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders_str})");
for model in refs.iter() {
let values = model.insert_values();
let params = values_into_params(values)?;
match self.db.conn.execute(&sql, params).trace().await {
Ok(_) => {}
Err(e) if is_constraint_error(&e) => {
}
Err(e) => return Err(e),
}
}
self.models.run_after_insert(hook_ctx).await?;
Ok(())
}
}
impl<'a, I: crate::model::Insertable + Send + Sync> SqlExecutor for InsertOrIgnoreExecutor<'a, I> {
type Output = ();
fn to_sql(&self) -> crate::Result<SqlStatement> {
InsertOrIgnoreExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
if sql.statements.is_empty() {
return Ok(());
}
let statement = &sql.statements[0];
let params = values_to_params(&statement.params)?;
match self.db.conn.execute(&statement.sql, params).trace().await {
Ok(_) => {}
Err(e) if is_constraint_error(&e) => {
}
Err(e) => return Err(e),
}
Ok(())
}
}
impl Database {
pub async fn connect(_db_type: super::DbType, path: &str) -> crate::Result<Self> {
let db = turso::Builder::new_local(path).build().trace().await?;
let conn = Arc::new(db.connect().trace_for("turso::Database::connect")?);
Ok(Self { conn })
}
pub fn create_table<T: Model>(&self) -> CreateTableExecutor<'_, T> {
CreateTableExecutor {
db: self,
table_name: None,
_marker: std::marker::PhantomData,
}
}
pub async fn validate_table<T: Model>(&self) -> crate::Result<()> {
let table_exists = self.check_table_exists::<T>().trace().await?;
if !table_exists {
return Err(crate::ormer_error!(
"Schema mismatch: table {} does not exist",
T::TABLE_NAME
));
}
self.validate_table_schema::<T>().await
}
async fn check_table_exists<T: Model>(&self) -> crate::Result<bool> {
let sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?";
let mut rows = self
.conn
.query(sql, [table_name_for::<T>()])
.trace()
.await?;
if let Some(row) = rows.next().trace().await? {
let count = row.get_value(0).trace_for("turso::Row::get_value")?;
match count {
turso::Value::Integer(c) => Ok(c > 0),
_ => Ok(false),
}
} else {
Ok(false)
}
}
async fn validate_table_schema<T: Model>(&self) -> crate::Result<()> {
let sql = format!("PRAGMA table_info({})", table_name_for::<T>());
let mut rows = self.conn.query(&sql, ()).trace().await?;
let mut actual_columns: Vec<(String, String, bool, bool)> = Vec::new();
while let Some(row) = rows.next().trace().await? {
let name = row.get_value(1).trace_for("turso::Row::get_value")?;
let col_type = row.get_value(2).trace_for("turso::Row::get_value")?;
let notnull = row.get_value(3).trace_for("turso::Row::get_value")?;
let pk = row.get_value(5).trace_for("turso::Row::get_value")?;
if let (
turso::Value::Text(name),
turso::Value::Text(col_type),
turso::Value::Integer(notnull),
turso::Value::Integer(pk),
) = (name, col_type, notnull, pk)
{
actual_columns.push((name, col_type, notnull != 0, pk != 0));
}
}
if actual_columns.len() != T::COLUMNS.len() {
return Err(crate::ormer_error!(
"Schema mismatch: table {}, reason: Column count mismatch: expected {}, but actual is {}",
T::TABLE_NAME,
T::COLUMNS.len(),
actual_columns.len()
));
}
for (i, expected_col) in T::COLUMN_SCHEMA.iter().enumerate() {
if i >= actual_columns.len() {
return Err(crate::ormer_error!(
"Schema mismatch: table {}, reason: Missing column: {}",
T::TABLE_NAME,
expected_col.name
));
}
let (actual_name, actual_type, actual_notnull, actual_pk) = &actual_columns[i];
if actual_name != expected_col.name {
return Err(crate::ormer_error!(
"Schema mismatch: table {}, reason: Column name mismatch at position {}: expected '{}', but actual is '{}'",
T::TABLE_NAME,
i,
expected_col.name,
actual_name
));
}
if expected_col.is_primary != *actual_pk {
return Err(crate::ormer_error!(
"Schema mismatch: table {}, reason: Primary key mismatch for '{}': expected {}primary key, but actual is {}primary key",
T::TABLE_NAME,
expected_col.name,
if expected_col.is_primary { "" } else { "not " },
if *actual_pk { "" } else { "not " }
));
}
let expected_type = crate::abstract_layer::DbType::Sqlite.sql_type(
expected_col.rust_type,
expected_col.is_primary,
expected_col.is_auto_increment,
expected_col.is_nullable,
expected_col.enum_variants,
);
let type_to_compare = if expected_col.is_primary {
match expected_col.rust_type {
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => {
"INTEGER".to_string()
}
"f32" | "f64" => "REAL".to_string(),
"String" => "TEXT".to_string(),
"bool" => "INTEGER".to_string(),
"Vec<u8>" | "&[u8]" => "BLOB".to_string(),
_ => "TEXT".to_string(),
}
} else {
let full_type = crate::abstract_layer::DbType::Sqlite.sql_type(
expected_col.rust_type,
false,
expected_col.is_auto_increment,
expected_col.is_nullable,
expected_col.enum_variants,
);
full_type.replace(" NOT NULL", "")
};
if !self.types_compatible(actual_type, &type_to_compare) {
return Err(crate::ormer_error!(
"Schema mismatch: table {}, reason: Column type mismatch for '{}': expected '{expected_type}', but actual is '{actual_type}'",
T::TABLE_NAME,
expected_col.name
));
}
if !expected_col.is_primary {
let expected_notnull = !expected_col.is_nullable;
if *actual_notnull != expected_notnull {
return Err(crate::ormer_error!(
"Schema mismatch: table {}, reason: Column nullability mismatch for '{}': expected {}NULL, but actual is {}NULL",
T::TABLE_NAME,
expected_col.name,
if expected_notnull { "NOT " } else { "" },
if *actual_notnull { "NOT " } else { "" }
));
}
}
}
Ok(())
}
fn types_compatible(&self, actual: &str, expected: &str) -> bool {
fn normalize(s: &str) -> String {
match s.to_uppercase().as_str() {
"INT" | "INTEGER" | "MEDIUMINT" | "BIGINT" | "INT64" => "INTEGER".to_string(),
"VARCHAR" | "CHARACTER" | "NCHAR" | "NVARCHAR" | "TEXT" | "CLOB" => {
"TEXT".to_string()
}
"BLOB" => "BLOB".to_string(),
"REAL" | "FLOAT" | "DOUBLE" | "DECIMAL" | "NUMERIC" => "REAL".to_string(),
_ => s.to_string(),
}
}
normalize(actual) == normalize(expected)
}
pub fn insert<I: crate::model::Insertable>(&self, models: I) -> InsertExecutor<'_, I> {
InsertExecutor {
db: self,
models,
conflict: None,
_marker: std::marker::PhantomData,
}
}
pub fn insert_partial<T: Model>(&self) -> InsertPartialExecutor<'_, T> {
InsertPartialExecutor {
db: self,
assignments: Vec::new(),
source_table: None,
_marker: std::marker::PhantomData,
}
}
pub fn insert_model<T>(
&self,
model: impl crate::model::InsertModel<T>,
) -> InsertPartialExecutor<'_, T>
where
T: Model,
{
self.insert_partial::<T>()
.with_source_table(model.insert_table_name())
.with_assignments(model.insert_assignments())
}
pub fn insert_or_update<I: crate::model::Insertable>(
&self,
models: I,
) -> InsertOrUpdateExecutor<'_, I> {
InsertOrUpdateExecutor {
db: self,
models,
_marker: std::marker::PhantomData,
}
}
pub fn insert_or_ignore<I: crate::model::Insertable>(
&self,
models: I,
) -> InsertOrIgnoreExecutor<'_, I> {
InsertOrIgnoreExecutor {
db: self,
models,
_marker: std::marker::PhantomData,
}
}
pub(crate) async fn insert_impl<T: Model>(
&self,
models: &[&T],
) -> crate::Result<T::AutoIncrementKeyType> {
if models.is_empty() {
return Ok(T::AutoIncrementKeyType::default());
}
let (sql, all_values) = common_helpers::build_insert_statement::<T>(DbType::Sqlite, models);
let all_params = values_into_params(all_values)?;
self.conn.execute(&sql, all_params).trace().await?;
let has_auto_increment = T::COLUMN_SCHEMA.iter().any(|c| c.is_auto_increment);
if has_auto_increment {
let last_id = self.conn.last_insert_rowid();
let result =
common_helpers::convert_auto_increment_key::<T::AutoIncrementKeyType>(last_id)?;
Ok(result)
} else {
Ok(T::AutoIncrementKeyType::default())
}
}
pub async fn insert_or_update_batch<T: Model>(&self, models: &[&T]) -> crate::Result<()> {
if models.is_empty() {
return Ok(());
}
let columns = T::insert_columns();
let col_count = columns.len();
let pk_columns = T::primary_key_columns();
let table_name = common_helpers::quote_table_name::<T>(DbType::Sqlite);
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, &columns);
let insert_placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({insert_placeholders})");
let where_clauses: Vec<String> = pk_columns
.iter()
.enumerate()
.map(|(idx, c)| {
common_helpers::quote_assignment(
DbType::Sqlite,
c,
&common_helpers::placeholder(DbType::Sqlite, idx + 1),
)
})
.collect();
let delete_sql = format!(
"DELETE FROM {table_name} WHERE {}",
where_clauses.join(" AND ")
);
for model in models.iter() {
let pk_values = model.primary_key_values();
let delete_params = values_into_params(pk_values)?;
self.conn
.execute(&delete_sql, delete_params)
.trace()
.await?;
let all_values = model.insert_values();
let insert_params = values_into_params(all_values)?;
self.conn
.execute(&insert_sql, insert_params)
.trace()
.await?;
}
Ok(())
}
pub async fn insert_or_ignore_batch<T: Model>(&self, models: &[&T]) -> crate::Result<()> {
if models.is_empty() {
return Ok(());
}
let columns = T::insert_columns();
let col_count = columns.len();
let table_name = common_helpers::quote_table_name::<T>(DbType::Sqlite);
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, &columns);
let placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders})");
for model in models.iter() {
let values = model.insert_values();
let params = values_into_params(values)?;
match self.conn.execute(&insert_sql, params).trace().await {
Ok(_) => {}
Err(e) if is_constraint_error(&e) => {
}
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn select<T: Model>(&self) -> SelectExecutor<'_, T> {
SelectExecutor {
select: Select::<T>::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn select_column<T: Model, V>(&self) -> GroupedSelectExecutor<'_, T, V> {
GroupedSelectExecutor {
select: GroupedSelect::<T, V>::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn delete<T: Model>(&self) -> DeleteExecutor<T> {
DeleteExecutor {
filters: Vec::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn update<T: Model>(&self) -> UpdateExecutor<T> {
UpdateExecutor {
sets: Vec::new(),
filters: Vec::new(),
model_updates: Vec::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn related<T: Model + 'static, R: Model>(&self) -> RelatedSelectExecutor<T, R> {
RelatedSelectExecutor {
select: Select::<T>::new().from::<T, R>(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub async fn begin(&self) -> crate::Result<Transaction> {
self.conn.execute("BEGIN", ()).trace().await?;
Ok(Transaction {
conn: self.conn.clone(),
committed: false,
rolled_back: false,
})
}
pub fn drop_table<T: Model>(&self) -> DropTableExecutor<'_, T> {
DropTableExecutor {
db: self,
_marker: std::marker::PhantomData,
}
}
pub async fn execute_sql(&self, sql: impl IntoRawSql) -> crate::Result<u64> {
let sql = sql.into_raw_sql();
let (sql, params) = sql.render(DbType::Sqlite)?;
self.exec_raw(&sql, params).await
}
pub(crate) async fn select_raw<V, C>(&self, sql: &str, params: Vec<Value>) -> crate::Result<C>
where
V: crate::model::FromRowValues,
C: FromIterator<V>,
{
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(sql, ()).trace().await?
} else {
self.conn.query(sql, turso_params).trace().await?
};
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let mut values = Vec::new();
for i in 0..row.column_count() {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
values.push(convert_turso_value(&value)?);
}
results.push(V::from_row_values(&values)?);
}
Ok(results.into_iter().collect())
}
pub(crate) async fn exec_raw(&self, sql: &str, params: Vec<Value>) -> crate::Result<u64> {
let turso_params = values_into_params(params)?;
if turso_params.is_empty() {
Ok(self.conn.execute(sql, ()).trace().await?)
} else {
Ok(self.conn.execute(sql, turso_params).trace().await?)
}
}
pub(crate) async fn migration_history(&self) -> crate::Result<Vec<(u64, String, u64)>> {
let mut rows = self
.conn
.query(
"SELECT version, name, checksum FROM __ormer_migrations ORDER BY version",
(),
)
.trace()
.await?;
let mut versions = Vec::new();
while let Some(row) = rows.next().trace().await? {
let version = match row.get_value(0).trace_for("turso::Row::get_value")? {
turso::Value::Integer(version) if version >= 0 => version as u64,
_ => continue,
};
let name = match row.get_value(1).trace_for("turso::Row::get_value")? {
turso::Value::Text(name) => name,
_ => String::new(),
};
let checksum = match row.get_value(2).trace_for("turso::Row::get_value")? {
turso::Value::Integer(checksum) if checksum >= 0 => checksum as u64,
turso::Value::Text(checksum) => checksum.parse::<u64>().unwrap_or(0),
_ => 0,
};
versions.push((version, name, checksum));
}
Ok(versions)
}
pub(crate) async fn schema_columns(
&self,
table_name: &str,
) -> crate::Result<Option<Vec<SchemaColumn>>> {
let mut exists = self
.conn
.query(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?",
[table_name],
)
.trace()
.await?;
let exists = match exists.next().trace().await? {
Some(row) => matches!(
row.get_value(0).trace_for("turso::Row::get_value")?,
turso::Value::Integer(count) if count > 0
),
None => false,
};
if !exists {
return Ok(None);
}
let escaped = table_name.replace('\'', "''");
let mut rows = self
.conn
.query(&format!("PRAGMA table_info('{escaped}')"), ())
.trace()
.await?;
let mut columns = Vec::new();
while let Some(row) = rows.next().trace().await? {
let name = match row.get_value(1).trace_for("turso::Row::get_value")? {
turso::Value::Text(value) => value,
_ => continue,
};
let type_name = match row.get_value(2).trace_for("turso::Row::get_value")? {
turso::Value::Text(value) => value,
_ => String::new(),
};
let nullable = !matches!(
row.get_value(3).trace_for("turso::Row::get_value")?,
turso::Value::Integer(value) if value != 0
);
let primary_key = matches!(
row.get_value(5).trace_for("turso::Row::get_value")?,
turso::Value::Integer(value) if value != 0
);
columns.push(schema_column(name, type_name, nullable, primary_key));
}
Ok(Some(columns))
}
pub async fn is_valid(&self) -> bool {
self.conn.execute("SELECT 1", ()).trace().await.is_ok()
}
}
pub struct Transaction {
conn: Arc<turso::Connection>,
committed: bool,
rolled_back: bool,
}
pub struct TransactionInsertExecutor<'a, I: crate::model::Insertable> {
txn: &'a mut Transaction,
models: I,
conflict: Option<InsertConflict>,
_marker: std::marker::PhantomData<I::Model>,
}
impl_insert_conflict_methods!(TransactionInsertExecutor);
impl<'a, I: crate::model::Insertable + Send + Sync> TransactionInsertExecutor<'a, I> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let refs = self.models.as_refs();
if refs.is_empty() {
return Ok(SqlStatement::batch(DbType::Sqlite, Vec::new()));
}
let (sql, all_values) = common_helpers::build_insert_statement_with_conflict::<I::Model>(
DbType::Sqlite,
&refs,
self.conflict.as_ref(),
)?;
Ok(SqlStatement::single(DbType::Sqlite, sql, all_values))
}
pub async fn execute(mut self) -> crate::Result<<I::Model as Model>::AutoIncrementKeyType> {
let sql = self.to_sql()?;
if sql.statements.is_empty() {
return Ok(<I::Model as Model>::AutoIncrementKeyType::default());
}
let hook_ctx = HookContext::new(HookOperation::Insert).transaction();
self.models.run_before_insert(hook_ctx).await?;
let statement = &sql.statements[0];
let all_params = values_to_params(&statement.params)?;
let rows_affected = self
.txn
.conn
.execute(&statement.sql, all_params)
.trace()
.await?;
self.models.run_after_insert(hook_ctx).await?;
let has_auto_increment = I::Model::COLUMN_SCHEMA.iter().any(|c| c.is_auto_increment);
if has_auto_increment {
if rows_affected == 0 {
return Ok(<I::Model as Model>::AutoIncrementKeyType::default());
}
let last_id = self.txn.conn.last_insert_rowid();
let result = common_helpers::convert_auto_increment_key::<
<I::Model as Model>::AutoIncrementKeyType,
>(last_id)?;
Ok(result)
} else {
Ok(<I::Model as Model>::AutoIncrementKeyType::default())
}
}
}
pub struct TransactionInsertOrUpdateExecutor<'a, I: crate::model::Insertable> {
txn: &'a mut Transaction,
models: I,
_marker: std::marker::PhantomData<I::Model>,
}
impl<'a, I: crate::model::Insertable + Send + Sync> TransactionInsertOrUpdateExecutor<'a, I> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let refs = self.models.as_refs();
if refs.is_empty() {
return Ok(SqlStatement::batch(DbType::Sqlite, Vec::new()));
}
let (sql, all_values) = common_helpers::build_batch_insert_statement::<I::Model>(
DbType::Sqlite,
"INSERT INTO",
<I::Model as Model>::table_name_for_db(DbType::Sqlite),
I::Model::COLUMNS,
&refs,
common_helpers::BatchInsertValuesMode::All,
);
Ok(SqlStatement::single(DbType::Sqlite, sql, all_values))
}
pub async fn execute(mut self) -> crate::Result<()> {
if self.models.as_refs().is_empty() {
return Ok(());
}
let hook_ctx = HookContext::new(HookOperation::Insert).transaction();
self.models.run_before_insert(hook_ctx).await?;
let refs = self.models.as_refs();
let columns = I::Model::COLUMNS;
let col_count = columns.len();
let table_name = common_helpers::quote_table_name::<I::Model>(DbType::Sqlite);
let pk_columns = I::Model::primary_key_columns();
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, columns);
let insert_placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({insert_placeholders})");
let where_clauses: Vec<String> = pk_columns
.iter()
.enumerate()
.map(|(idx, c)| {
common_helpers::quote_assignment(
DbType::Sqlite,
c,
&common_helpers::placeholder(DbType::Sqlite, idx + 1),
)
})
.collect();
let delete_sql = format!(
"DELETE FROM {table_name} WHERE {}",
where_clauses.join(" AND ")
);
for model in refs.iter() {
let pk_values = model.primary_key_values();
let delete_params = values_into_params(pk_values)?;
self.txn
.conn
.execute(&delete_sql, delete_params)
.trace()
.await?;
let all_values = model.field_values();
let insert_params = values_into_params(all_values)?;
self.txn
.conn
.execute(&insert_sql, insert_params)
.trace()
.await?;
}
self.models.run_after_insert(hook_ctx).await?;
Ok(())
}
}
pub struct TransactionInsertOrIgnoreExecutor<'a, I: crate::model::Insertable> {
txn: &'a mut Transaction,
models: I,
_marker: std::marker::PhantomData<I::Model>,
}
impl<'a, I: crate::model::Insertable + Send + Sync> TransactionInsertOrIgnoreExecutor<'a, I> {
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let refs = self.models.as_refs();
if refs.is_empty() {
return Ok(SqlStatement::batch(DbType::Sqlite, Vec::new()));
}
let columns = I::Model::insert_columns();
let (sql, all_values) = common_helpers::build_batch_insert_statement::<I::Model>(
DbType::Sqlite,
"INSERT INTO",
<I::Model as Model>::table_name_for_db(DbType::Sqlite),
&columns,
&refs,
common_helpers::BatchInsertValuesMode::WithoutAutoIncrement,
);
Ok(SqlStatement::single(DbType::Sqlite, sql, all_values))
}
pub async fn execute(mut self) -> crate::Result<()> {
if self.models.as_refs().is_empty() {
return Ok(());
}
let hook_ctx = HookContext::new(HookOperation::Insert).transaction();
self.models.run_before_insert(hook_ctx).await?;
let refs = self.models.as_refs();
let columns = I::Model::insert_columns();
let col_count = columns.len();
let table_name = common_helpers::quote_table_name::<I::Model>(DbType::Sqlite);
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, &columns);
let placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders})");
for model in refs.iter() {
let values = model.insert_values();
let params = values_into_params(values)?;
match self.txn.conn.execute(&insert_sql, params).trace().await {
Ok(_) => {}
Err(e) if is_constraint_error(&e) => {
}
Err(e) => return Err(e),
}
}
self.models.run_after_insert(hook_ctx).await?;
Ok(())
}
}
impl Transaction {
pub(crate) async fn exec_raw(&mut self, sql: &str, params: Vec<Value>) -> crate::Result<u64> {
let turso_params = values_into_params(params)?;
if turso_params.is_empty() {
Ok(self.conn.execute(sql, ()).trace().await?)
} else {
Ok(self.conn.execute(sql, turso_params).trace().await?)
}
}
pub(crate) async fn select_raw<V, C>(&self, sql: &str, params: Vec<Value>) -> crate::Result<C>
where
V: crate::model::FromRowValues,
C: FromIterator<V>,
{
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(sql, ()).trace().await?
} else {
self.conn.query(sql, turso_params).trace().await?
};
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let mut values = Vec::new();
for i in 0..row.column_count() {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
values.push(convert_turso_value(&value)?);
}
results.push(V::from_row_values(&values)?);
}
Ok(results.into_iter().collect())
}
pub async fn commit(mut self) -> crate::Result<()> {
if self.committed || self.rolled_back {
return Err(crate::ormer_error!(
"Transaction already committed or rolled back"
));
}
self.conn.execute("COMMIT", ()).trace().await?;
self.committed = true;
Ok(())
}
pub async fn rollback(mut self) -> crate::Result<()> {
if self.committed || self.rolled_back {
return Err(crate::ormer_error!(
"Transaction already committed or rolled back"
));
}
self.conn.execute("ROLLBACK", ()).trace().await?;
self.rolled_back = true;
Ok(())
}
pub fn select<T: Model>(&self) -> SelectExecutor<'_, T> {
SelectExecutor {
select: Select::<T>::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn select_column<T: Model, V>(&self) -> GroupedSelectExecutor<'_, T, V> {
GroupedSelectExecutor {
select: GroupedSelect::<T, V>::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn delete<T: Model>(&self) -> DeleteExecutor<T> {
DeleteExecutor {
filters: Vec::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn update<T: Model>(&self) -> UpdateExecutor<T> {
UpdateExecutor {
sets: Vec::new(),
filters: Vec::new(),
model_updates: Vec::new(),
conn: self.conn.clone(),
_marker: PhantomData,
}
}
pub fn insert<I: crate::model::Insertable>(
&mut self,
models: I,
) -> TransactionInsertExecutor<'_, I> {
TransactionInsertExecutor {
txn: self,
models,
conflict: None,
_marker: std::marker::PhantomData,
}
}
pub fn insert_or_update<I: crate::model::Insertable>(
&mut self,
models: I,
) -> TransactionInsertOrUpdateExecutor<'_, I> {
TransactionInsertOrUpdateExecutor {
txn: self,
models,
_marker: std::marker::PhantomData,
}
}
pub fn insert_or_ignore<I: crate::model::Insertable>(
&mut self,
models: I,
) -> TransactionInsertOrIgnoreExecutor<'_, I> {
TransactionInsertOrIgnoreExecutor {
txn: self,
models,
_marker: std::marker::PhantomData,
}
}
#[allow(dead_code)]
async fn insert_impl<T: Model>(
&mut self,
models: &[&T],
) -> crate::Result<T::AutoIncrementKeyType> {
if models.is_empty() {
return Ok(T::AutoIncrementKeyType::default());
}
let (sql, all_values) = common_helpers::build_insert_statement::<T>(DbType::Sqlite, models);
let all_params = values_into_params(all_values)?;
self.conn.execute(&sql, all_params).trace().await?;
let has_auto_increment = T::COLUMN_SCHEMA.iter().any(|c| c.is_auto_increment);
if has_auto_increment {
let last_id = self.conn.last_insert_rowid();
let result =
common_helpers::convert_auto_increment_key::<T::AutoIncrementKeyType>(last_id)?;
Ok(result)
} else {
Ok(T::AutoIncrementKeyType::default())
}
}
#[allow(dead_code)]
async fn insert_or_update_impl<T: Model>(&mut self, models: &[&T]) -> crate::Result<()> {
if models.is_empty() {
return Ok(());
}
let columns = T::insert_columns();
let col_count = columns.len();
let pk_columns = T::primary_key_columns();
let table_name = common_helpers::quote_table_name::<T>(DbType::Sqlite);
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, &columns);
let insert_placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({insert_placeholders})");
let where_clauses: Vec<String> = pk_columns
.iter()
.enumerate()
.map(|(idx, c)| {
common_helpers::quote_assignment(
DbType::Sqlite,
c,
&common_helpers::placeholder(DbType::Sqlite, idx + 1),
)
})
.collect();
let delete_sql = format!(
"DELETE FROM {table_name} WHERE {}",
where_clauses.join(" AND ")
);
for model in models.iter() {
let pk_values = model.primary_key_values();
let delete_params = values_into_params(pk_values)?;
self.conn
.execute(&delete_sql, delete_params)
.trace()
.await?;
let all_values = model.insert_values();
let insert_params = values_into_params(all_values)?;
self.conn
.execute(&insert_sql, insert_params)
.trace()
.await?;
}
Ok(())
}
#[allow(dead_code)]
async fn insert_or_ignore_impl<T: Model>(&mut self, models: &[&T]) -> crate::Result<()> {
if models.is_empty() {
return Ok(());
}
let columns = T::insert_columns();
let col_count = columns.len();
let table_name = common_helpers::quote_table_name::<T>(DbType::Sqlite);
let columns_str = common_helpers::quote_column_list(DbType::Sqlite, &columns);
let placeholders = common_helpers::placeholder_list(DbType::Sqlite, 1, col_count);
let insert_sql =
format!("INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders})");
for model in models.iter() {
let values = model.insert_values();
let params = values_into_params(values)?;
match self.conn.execute(&insert_sql, params).trace().await {
Ok(_) => {}
Err(e) if is_constraint_error(&e) => {
}
Err(e) => return Err(e),
}
}
Ok(())
}
}
pub struct SelectExecutor<'a, T: Model> {
select: Select<T>,
conn: Arc<turso::Connection>,
_marker: std::marker::PhantomData<&'a T>,
}
impl<'a, T: Model> Clone for SelectExecutor<'a, T> {
fn clone(&self) -> Self {
Self {
select: self.select.clone(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
}
pub struct LeftJoinedSelectExecutor<T: Model, J: Model> {
select: LeftJoinedSelect<T, J>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, J)>,
}
impl<T: Model, J: Model> Clone for LeftJoinedSelectExecutor<T, J> {
fn clone(&self) -> Self {
Self {
select: self.select.clone(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
}
pub struct InnerJoinedSelectExecutor<T: Model, J: Model> {
select: InnerJoinedSelect<T, J>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, J)>,
}
impl<T: Model, J: Model> Clone for InnerJoinedSelectExecutor<T, J> {
fn clone(&self) -> Self {
Self {
select: self.select.clone(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
}
pub struct RightJoinedSelectExecutor<T: Model, J: Model> {
select: RightJoinedSelect<T, J>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, J)>,
}
impl<T: Model, J: Model> Clone for RightJoinedSelectExecutor<T, J> {
fn clone(&self) -> Self {
Self {
select: self.select.clone(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
}
pub struct RelatedSelectExecutor<T: Model, R: Model> {
select: RelatedSelect<T, R>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, R)>,
}
#[allow(dead_code)]
pub struct MultiTableSelectExecutor<T: Model, R1: Model, R2: Model> {
select: MultiTableSelect<T, R1, R2>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, R1, R2)>,
}
#[allow(dead_code)]
pub struct FourTableSelectExecutor<T: Model, R1: Model, R2: Model, R3: Model> {
select: FourTableSelect<T, R1, R2, R3>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, R1, R2, R3)>,
}
pub struct MappedSelectExecutor<'a, T: Model, V> {
select: MappedSelect<T, V>,
conn: Arc<turso::Connection>,
_marker: PhantomData<&'a (T, V)>,
}
pub struct GroupedSelectExecutor<'a, T: Model, V> {
select: GroupedSelect<T, V>,
conn: Arc<turso::Connection>,
_marker: PhantomData<&'a (T, V)>,
}
impl<'a, T: Model, V> Clone for MappedSelectExecutor<'a, T, V> {
fn clone(&self) -> Self {
Self {
select: self.select.clone(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
}
impl<'a, T: Model, V> Clone for GroupedSelectExecutor<'a, T, V> {
fn clone(&self) -> Self {
Self {
select: self.select.clone(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
}
impl<'a, T: Model> SelectExecutor<'a, T> {
pub(crate) fn select_model<R: Model>(&self) -> SelectExecutor<'a, R> {
SelectExecutor {
select: Select::new(),
conn: Arc::clone(&self.conn),
_marker: PhantomData,
}
}
pub fn left_join<J: Model>(
self,
f: impl FnOnce(T::Where, J::Where) -> WhereExpr,
) -> LeftJoinedSelectExecutor<T, J> {
LeftJoinedSelectExecutor {
select: self.select.left_join::<J>(f),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn inner_join<J: Model>(
self,
f: impl FnOnce(T::Where, J::Where) -> WhereExpr,
) -> InnerJoinedSelectExecutor<T, J> {
InnerJoinedSelectExecutor {
select: self.select.inner_join::<J>(f),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn right_join<J: Model>(
self,
f: impl FnOnce(T::Where, J::Where) -> WhereExpr,
) -> RightJoinedSelectExecutor<T, J> {
RightJoinedSelectExecutor {
select: self.select.right_join::<J>(f),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn map_to<F, M>(self, f: F) -> MappedSelectExecutor<'a, T, M::Output>
where
F: FnOnce(<T as Model>::Where) -> M,
M: crate::query::builder::MapToResult,
{
let mapped_select = self.select.map_to(f);
MappedSelectExecutor {
select: mapped_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn ignore<F, M>(self, f: F) -> Self
where
F: FnOnce(<T as Model>::Where) -> M,
M: crate::query::builder::MapToResult,
{
Self {
select: self.select.ignore(f),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn select_column<F, V>(self, f: F) -> GroupedSelectExecutor<'a, T, V>
where
F: FnOnce(<T as Model>::Where) -> V,
V: crate::query::builder::SelectColumnResult,
{
let grouped_select = self.select.select_column(f);
GroupedSelectExecutor {
select: grouped_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn collect<C: FromIterator<T> + 'static>(self) -> CollectFuture<'a, T, C> {
CollectFuture {
executor: self,
_marker: PhantomData,
}
}
pub fn first(self) -> FirstFuture<'a, T> {
FirstFuture { executor: self }
}
pub fn count<F, C>(self, f: F) -> AggregateFuture<T, usize>
where
F: FnOnce(<T as Model>::Where) -> crate::query::builder::TypedColumn<C, T>,
{
let aggregate_select = self.select.count(f);
AggregateFuture {
aggregate_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn sum<F, C>(self, f: F) -> AggregateFuture<T, C::Output>
where
F: FnOnce(<T as Model>::Where) -> crate::query::builder::TypedColumn<C, T>,
C: crate::query::builder::AggregateResultType + 'static,
{
let aggregate_select = self.select.sum(f);
AggregateFuture {
aggregate_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn avg<F, C>(self, f: F) -> AggregateFuture<T, Option<f64>>
where
F: FnOnce(<T as Model>::Where) -> crate::query::builder::TypedColumn<C, T>,
C: crate::query::builder::AggregateResultType + 'static,
{
let aggregate_select = self.select.avg(f);
AggregateFuture {
aggregate_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn max<F, C>(self, f: F) -> AggregateFuture<T, C::Output>
where
F: FnOnce(<T as Model>::Where) -> crate::query::builder::TypedColumn<C, T>,
C: crate::query::builder::AggregateResultType + 'static,
{
let aggregate_select = self.select.max(f);
AggregateFuture {
aggregate_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn min<F, C>(self, f: F) -> AggregateFuture<T, C::Output>
where
F: FnOnce(<T as Model>::Where) -> crate::query::builder::TypedColumn<C, T>,
C: crate::query::builder::AggregateResultType + 'static,
{
let aggregate_select = self.select.min(f);
AggregateFuture {
aggregate_select,
conn: self.conn,
_marker: PhantomData,
}
}
pub fn from<T2, R: Model>(self) -> RelatedSelectExecutor<T, R>
where
T2: Model + 'static,
{
RelatedSelectExecutor {
select: self.select.from::<T2, R>(),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn from3<T2, R1: Model, R2: Model>(self) -> MultiTableSelectExecutor<T, R1, R2>
where
T2: Model + 'static,
{
MultiTableSelectExecutor {
select: self.select.from3::<T2, R1, R2>(),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn from4<T2, R1: Model, R2: Model, R3: Model>(
self,
) -> FourTableSelectExecutor<T, R1, R2, R3>
where
T2: Model + 'static,
{
FourTableSelectExecutor {
select: self.select.from4::<T2, R1, R2, R3>(),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn stream(self) -> SelectStream<'a, T> {
SelectStream {
select: self.select,
conn: super::common::StreamConnection::Sqlite(self.conn),
_marker: std::marker::PhantomData,
}
}
}
impl_backend_executor_methods!(SelectExecutor, conn, Arc<turso::Connection>, Select);
impl_backend_join_executor_methods!(
LeftJoinedSelectExecutor,
conn,
Arc<turso::Connection>,
LeftJoinedSelect
);
impl<T: Model, J: Model> LeftJoinedSelectExecutor<T, J> {
pub fn to_sql(&self) -> String {
self.select.to_sql_with_params(DbType::Sqlite).0
}
pub fn collect<C: FromIterator<(T, Option<J>)> + 'static>(
&self,
) -> LeftJoinCollectFuture<T, J> {
LeftJoinCollectFuture {
executor: self.clone(),
}
}
async fn collect_inner<C: FromIterator<(T, Option<J>)>>(self) -> crate::Result<C> {
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
let t_col_count = T::COLUMNS.len();
while let Some(row) = rows.next().trace().await? {
let mut t_data = HashMap::new();
for (i, col_name) in T::COLUMNS.iter().enumerate() {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
t_data.insert(col_name.to_string(), convert_turso_value(&value)?);
}
let t_model = T::from_row(&Row::new(t_data))?;
let mut j_data = HashMap::new();
let mut j_is_null = true;
for (i, col_name) in J::COLUMNS.iter().enumerate() {
let idx = t_col_count + i;
if let Ok(value) = row.get_value(idx) {
let ormer_value = convert_turso_value(&value)?;
if !matches!(ormer_value, Value::Null) {
j_is_null = false;
}
j_data.insert(col_name.to_string(), ormer_value);
}
}
let j_model = if j_is_null {
None
} else {
Some(J::from_row(&Row::new(j_data))?)
};
results.push((t_model, j_model));
}
Ok(results.into_iter().collect())
}
}
impl_backend_join_executor_methods!(
InnerJoinedSelectExecutor,
conn,
Arc<turso::Connection>,
InnerJoinedSelect
);
impl<T: Model, J: Model> InnerJoinedSelectExecutor<T, J> {
pub fn collect<C: FromIterator<(T, J)> + 'static>(&self) -> InnerJoinCollectFuture<T, J>
where
T: 'static,
J: 'static,
{
InnerJoinCollectFuture {
executor: self.clone(),
}
}
async fn collect_inner<C: FromIterator<(T, J)>>(self) -> crate::Result<C> {
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
let t_col_count = T::COLUMNS.len();
while let Some(row) = rows.next().trace().await? {
let mut t_data = HashMap::new();
for (i, col_name) in T::COLUMNS.iter().enumerate() {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
t_data.insert(col_name.to_string(), convert_turso_value(&value)?);
}
let t_model = T::from_row(&Row::new(t_data))?;
let mut j_data = HashMap::new();
for (i, col_name) in J::COLUMNS.iter().enumerate() {
let idx = t_col_count + i;
let value = row.get_value(idx).trace_for("turso::Row::get_value")?;
j_data.insert(col_name.to_string(), convert_turso_value(&value)?);
}
let j_model = J::from_row(&Row::new(j_data))?;
results.push((t_model, j_model));
}
Ok(results.into_iter().collect())
}
}
impl_backend_join_executor_methods!(
RightJoinedSelectExecutor,
conn,
Arc<turso::Connection>,
RightJoinedSelect
);
impl<T: Model, J: Model> RightJoinedSelectExecutor<T, J> {
pub fn collect<C: FromIterator<(Option<T>, J)> + 'static>(&self) -> RightJoinCollectFuture<T, J>
where
T: 'static,
J: 'static,
{
RightJoinCollectFuture {
executor: self.clone(),
}
}
async fn collect_inner<C: FromIterator<(Option<T>, J)>>(self) -> crate::Result<C> {
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
let t_col_count = T::COLUMNS.len();
while let Some(row) = rows.next().trace().await? {
let mut t_data = HashMap::new();
let mut t_is_null = true;
for (i, col_name) in T::COLUMNS.iter().enumerate() {
if let Ok(value) = row.get_value(i) {
t_data.insert(col_name.to_string(), convert_turso_value(&value)?);
t_is_null = false;
}
}
let t_model = if t_is_null {
None
} else {
Some(T::from_row(&Row::new(t_data))?)
};
let mut j_data = HashMap::new();
for (i, col_name) in J::COLUMNS.iter().enumerate() {
let idx = t_col_count + i;
let value = row.get_value(idx).trace_for("turso::Row::get_value")?;
j_data.insert(col_name.to_string(), convert_turso_value(&value)?);
}
let j_model = J::from_row(&Row::new(j_data))?;
results.push((t_model, j_model));
}
Ok(results.into_iter().collect())
}
}
pub struct CollectFuture<'a, T: Model, C: FromIterator<T>> {
executor: SelectExecutor<'a, T>,
_marker: std::marker::PhantomData<C>,
}
unsafe impl<'a, T: Model + Send, C: FromIterator<T> + Send> Send for CollectFuture<'a, T, C> {}
pub struct FirstFuture<'a, T: Model> {
executor: SelectExecutor<'a, T>,
}
unsafe impl<'a, T: Model + Send> Send for FirstFuture<'a, T> {}
pub struct AggregateFuture<T: Model, R> {
aggregate_select: crate::query::builder::AggregateSelect<T, R>,
conn: Arc<turso::Connection>,
_marker: PhantomData<(T, R)>,
}
impl<
T: Model + 'static + std::marker::Send,
R: crate::model::FromValue + 'static + std::marker::Send,
> std::future::IntoFuture for AggregateFuture<T, R>
{
type Output = crate::Result<R>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let (sql, params) = self.aggregate_select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
if let Some(row) = rows.next().trace().await? {
let value = row.get_value(0).trace_for("turso::Row::get_value")?;
let ormer_value = match value {
turso::Value::Integer(i) => crate::model::Value::Integer(i),
turso::Value::Real(r) => crate::model::Value::Real(r),
turso::Value::Text(t) => crate::model::Value::Text(t),
turso::Value::Blob(b) => {
crate::model::Value::Text(String::from_utf8_lossy(&b).to_string())
}
turso::Value::Null => crate::model::Value::Null,
};
R::from_value(&ormer_value)
} else {
R::from_value(&crate::model::Value::Null)
}
})
}
}
pub struct LeftJoinCollectFuture<T: Model, J: Model> {
executor: LeftJoinedSelectExecutor<T, J>,
}
unsafe impl<T: Model + Send, J: Model + Send> Send for LeftJoinCollectFuture<T, J> {}
pub struct InnerJoinCollectFuture<T: Model, J: Model> {
executor: InnerJoinedSelectExecutor<T, J>,
}
unsafe impl<T: Model + Send, J: Model + Send> Send for InnerJoinCollectFuture<T, J> {}
pub struct RightJoinCollectFuture<T: Model, J: Model> {
executor: RightJoinedSelectExecutor<T, J>,
}
unsafe impl<T: Model + Send, J: Model + Send> Send for RightJoinCollectFuture<T, J> {}
pub struct GroupedCollectFuture<'a, T: Model, V, C> {
executor: GroupedSelectExecutor<'a, T, V>,
_marker: PhantomData<(T, V, C)>,
}
unsafe impl<'a, T: Model + Send, V: Send, C: Send> Send for GroupedCollectFuture<'a, T, V, C> {}
impl<'a, T: Model + 'static + std::marker::Send + std::marker::Sync, C: FromIterator<T> + 'static>
std::future::IntoFuture for CollectFuture<'a, T, C>
{
type Output = crate::Result<C>;
type IntoFuture =
std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'a>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.executor.collect_inner().await })
}
}
impl<'a, T: Model + 'static + std::marker::Send + std::marker::Sync> std::future::IntoFuture
for FirstFuture<'a, T>
{
type Output = crate::Result<Option<T>>;
type IntoFuture =
std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'a>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let results: Vec<T> = self.executor.collect_inner().await?;
Ok(results.into_iter().next())
})
}
}
impl<T: Model + 'static + std::marker::Send, J: Model + 'static + std::marker::Send>
std::future::IntoFuture for LeftJoinCollectFuture<T, J>
{
type Output = crate::Result<Vec<(T, Option<J>)>>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.executor.collect_inner().await })
}
}
impl<T: Model + 'static + std::marker::Send, J: Model + 'static + std::marker::Send>
std::future::IntoFuture for InnerJoinCollectFuture<T, J>
{
type Output = crate::Result<Vec<(T, J)>>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.executor.collect_inner().await })
}
}
impl<T: Model + 'static + std::marker::Send, J: Model + 'static + std::marker::Send>
std::future::IntoFuture for RightJoinCollectFuture<T, J>
{
type Output = crate::Result<Vec<(Option<T>, J)>>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.executor.collect_inner().await })
}
}
impl_backend_related_executor_methods!(
RelatedSelectExecutor,
conn,
Arc<turso::Connection>,
RelatedSelect
);
impl<T: Model, R: Model> RelatedSelectExecutor<T, R> {
pub fn collect<C: FromIterator<T> + 'static>(self) -> RelatedCollectFuture<T, R> {
RelatedCollectFuture { executor: self }
}
pub(crate) fn into_collect_future(self) -> RelatedCollectFuture<T, R> {
RelatedCollectFuture { executor: self }
}
async fn collect_inner<C: FromIterator<T>>(self) -> crate::Result<C> {
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let model = common_helpers::decode_model_from_indexed_values::<T, _>(0, |i| {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
convert_turso_value(&value)
})?;
results.push(model);
}
Ok(results.into_iter().collect())
}
}
pub struct RelatedCollectFuture<T: Model, R: Model> {
executor: RelatedSelectExecutor<T, R>,
}
unsafe impl<T: Model + Send, R: Model + Send> Send for RelatedCollectFuture<T, R> {}
impl<T: Model + 'static + std::marker::Send, R: Model + 'static + std::marker::Send>
std::future::IntoFuture for RelatedCollectFuture<T, R>
{
type Output = crate::Result<Vec<T>>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.executor.collect_inner().await })
}
}
impl<'a, T: Model> SelectExecutor<'a, T> {
async fn collect_inner<C: FromIterator<T>>(self) -> crate::Result<C> {
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let model = common_helpers::decode_model_from_indexed_values::<T, _>(0, |i| {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
convert_turso_value(&value)
})?;
results.push(model);
}
Ok(results.into_iter().collect())
}
}
pub struct DeleteExecutor<T: Model> {
filters: Vec<FilterExpr>,
conn: Arc<turso::Connection>,
_marker: PhantomData<T>,
}
impl<T: Model> DeleteExecutor<T> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let (sql, params) = self.build_ormer_sql();
Ok(SqlStatement::single(DbType::Sqlite, sql, params))
}
pub async fn execute(self) -> crate::Result<u64> {
<Self as SqlExecutor>::execute(self).await
}
pub async fn returning(self) -> crate::Result<Vec<T>> {
let sql = self.to_sql()?;
let statement = &sql.statements[0];
let params = values_to_params(&statement.params)?;
let sql_with_returning = format!("{} RETURNING *", statement.sql);
let mut rows = self.conn.query(&sql_with_returning, params).trace().await?;
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let model = common_helpers::decode_model_from_indexed_values::<T, _>(0, |i| {
let value = row.get_value(i)?;
convert_turso_value(&value)
})?;
results.push(model);
}
Ok(results)
}
pub async fn exec(self) -> crate::Result<u64> {
self.execute().await
}
fn build_ormer_sql(&self) -> (String, Vec<Value>) {
let mut sql = format!(
"DELETE FROM {}",
common_helpers::quote_table_name::<T>(DbType::Sqlite)
);
let mut ormer_params = Vec::new();
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let mut param_idx = 1;
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let _ = common_helpers::format_filter_with_params(
filter,
&mut sql,
&mut param_idx,
&mut ormer_params,
DbType::Sqlite,
);
}
}
(sql, ormer_params)
}
#[allow(dead_code)]
fn build_sql(&self) -> (String, Vec<turso::Value>) {
let (sql, ormer_params) = self.build_ormer_sql();
let turso_params = values_into_params(ormer_params).unwrap_or_default();
(sql, turso_params)
}
}
impl<T: Model> SqlExecutor for DeleteExecutor<T> {
type Output = u64;
fn to_sql(&self) -> crate::Result<SqlStatement> {
DeleteExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
if sql.statements.is_empty() {
return Ok(0);
}
let statement = &sql.statements[0];
let params = values_to_params(&statement.params)?;
let result = self.conn.execute(&statement.sql, params).trace().await?;
Ok(result)
}
}
impl<T: Model + 'static + std::marker::Send> std::future::IntoFuture for DeleteExecutor<T> {
type Output = crate::Result<u64>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.execute().await })
}
}
pub struct UpdateExecutor<T: Model> {
sets: Vec<UpdateAssignment>,
filters: Vec<FilterExpr>,
model_updates: ModelUpdateBatch,
conn: Arc<turso::Connection>,
_marker: PhantomData<T>,
}
impl<T: Model> UpdateExecutor<T> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn set<F>(mut self, f: F) -> Self
where
F: FnOnce(&mut T::Update),
{
let mut update = T::Update::default();
f(&mut update);
self.sets
.extend(<T::Update as crate::query::update::UpdateFields>::assignments(&update));
self
}
pub fn set_model(mut self, model: &T) -> Self {
let mut model_sets = Vec::new();
for (col_name, value) in model.non_pk_field_values() {
model_sets.push((col_name.to_string(), value));
}
let pk_columns = T::primary_key_columns();
let pk_values = model.primary_key_values();
let mut model_filters = Vec::new();
for (col, val) in pk_columns.iter().zip(pk_values) {
let filter_val = common_helpers::value_to_filter_value(&val);
model_filters.push(crate::query::filter::FilterExpr::Comparison {
column: col.to_string(),
operator: "=".to_string(),
value: filter_val,
});
}
self.model_updates.push((model_sets, model_filters));
self
}
pub fn set_model_fields(mut self, model: &T, fields: &[String]) -> Self {
let model_sets = model
.non_pk_field_values_for_columns(fields)
.into_iter()
.map(|(col_name, value)| (col_name.to_string(), value))
.collect::<Vec<_>>();
let pk_columns = T::primary_key_columns();
let pk_values = model.primary_key_values();
let model_filters = pk_columns
.iter()
.zip(pk_values)
.map(|(col, val)| crate::query::filter::FilterExpr::Comparison {
column: col.to_string(),
operator: "=".to_string(),
value: common_helpers::value_to_filter_value(&val),
})
.collect();
if !model_sets.is_empty() {
self.model_updates.push((model_sets, model_filters));
}
self
}
pub fn to_sql(&self) -> crate::Result<SqlStatement> {
let statements = self.build_all_ormer_sql()?;
Ok(SqlStatement::batch(
DbType::Sqlite,
statements
.into_iter()
.map(|(sql, params)| SingleSqlStatement::new(sql, params))
.collect(),
))
}
pub async fn execute(self) -> crate::Result<u64> {
<Self as SqlExecutor>::execute(self).await
}
pub async fn returning(self) -> crate::Result<Vec<T>> {
let statements = self.to_sql()?;
let mut results = Vec::new();
for statement in &statements.statements {
let params = values_to_params(&statement.params)?;
let sql_with_returning = format!("{} RETURNING *", statement.sql);
let mut rows = self.conn.query(&sql_with_returning, params).trace().await?;
while let Some(row) = rows.next().trace().await? {
let model = common_helpers::decode_model_from_indexed_values::<T, _>(0, |i| {
let value = row.get_value(i)?;
convert_turso_value(&value)
})?;
results.push(model);
}
}
Ok(results)
}
pub async fn exec(self) -> crate::Result<u64> {
self.execute().await
}
fn build_all_ormer_sql(&self) -> crate::Result<Vec<(String, Vec<Value>)>> {
let mut statements = Vec::new();
if !self.sets.is_empty() || (self.model_updates.is_empty() && !self.filters.is_empty()) {
let mut sql = format!(
"UPDATE {} SET ",
common_helpers::quote_table_name::<T>(DbType::Sqlite)
);
let mut ormer_params = Vec::new();
let mut first = true;
for assignment in &self.sets {
if !first {
sql.push_str(", ");
}
sql.push_str(&common_helpers::format_update_assignment(
DbType::Sqlite,
assignment,
&mut ormer_params,
));
first = false;
}
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let mut param_idx = ormer_params.len() + 1;
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let _ = common_helpers::format_filter_with_params(
filter,
&mut sql,
&mut param_idx,
&mut ormer_params,
DbType::Sqlite,
);
}
}
statements.push((sql, ormer_params));
}
for (model_sets, model_filters) in &self.model_updates {
let mut sql = format!(
"UPDATE {} SET ",
common_helpers::quote_table_name::<T>(DbType::Sqlite)
);
let mut ormer_params = Vec::new();
let mut first = true;
for (col_name, value) in model_sets {
if !first {
sql.push_str(", ");
}
sql.push_str(&common_helpers::quote_assignment(
DbType::Sqlite,
col_name,
"?",
));
ormer_params.push(value.clone());
first = false;
}
if !model_filters.is_empty() {
sql.push_str(" WHERE ");
let mut param_idx = ormer_params.len() + 1;
for (i, filter) in model_filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let _ = common_helpers::format_filter_with_params(
filter,
&mut sql,
&mut param_idx,
&mut ormer_params,
DbType::Sqlite,
);
}
}
statements.push((sql, ormer_params));
}
Ok(statements)
}
#[allow(dead_code)]
fn build_all_sql(&self) -> crate::Result<Vec<(String, Vec<turso::Value>)>> {
self.build_all_ormer_sql()?
.into_iter()
.map(|(sql, ormer_params)| Ok((sql, values_into_params(ormer_params)?)))
.collect()
}
}
impl<T: Model> SqlExecutor for UpdateExecutor<T> {
type Output = u64;
fn to_sql(&self) -> crate::Result<SqlStatement> {
UpdateExecutor::to_sql(self)
}
async fn execute_with_sql(self, sql: SqlStatement) -> crate::Result<Self::Output> {
let mut total = 0;
for statement in &sql.statements {
let params = values_to_params(&statement.params)?;
total += self.conn.execute(&statement.sql, params).trace().await?;
}
Ok(total)
}
}
impl<T: Model + 'static + std::marker::Send> std::future::IntoFuture for UpdateExecutor<T> {
type Output = crate::Result<u64>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.execute().await })
}
}
fn value_to_turso_value(value: Value) -> turso::Value {
match value {
Value::Integer(v) => turso::Value::Integer(v),
Value::Text(v) => turso::Value::Text(v),
Value::TextArray(v) => turso::Value::Text(crate::model::stringify_string_vec(&v)),
Value::Real(v) => turso::Value::Real(v),
Value::Boolean(v) => turso::Value::Integer(if v { 1 } else { 0 }),
Value::Bytes(v) => turso::Value::Blob(v),
Value::Duration(v) => turso::Value::Integer(v.as_micros().min(i64::MAX as u128) as i64),
Value::DateTime(v) => turso::Value::Text(v.to_rfc3339()),
Value::Date(date) => turso::Value::Text(date.to_string()),
Value::Time(time) => turso::Value::Text(time.to_string()),
Value::Json(v) => turso::Value::Text(v.to_string()),
Value::Uuid(v) => turso::Value::Text(v.to_string()),
Value::BigInt(v) => turso::Value::Integer(v as i64),
Value::IntegerArray(_) | Value::BigIntArray(_) | Value::NullableBigIntArray(_) => {
panic!("SQLite backend does not support PostgreSQL array values")
}
Value::Null => turso::Value::Null,
}
}
fn values_to_params(values: &[Value]) -> crate::Result<Vec<turso::Value>> {
Ok(values.iter().cloned().map(value_to_turso_value).collect())
}
fn values_into_params(values: Vec<Value>) -> crate::Result<Vec<turso::Value>> {
Ok(values.into_iter().map(value_to_turso_value).collect())
}
fn convert_turso_value(value: &turso::Value) -> crate::Result<Value> {
match value {
turso::Value::Integer(v) => Ok(Value::Integer(*v)),
turso::Value::Text(v) => {
if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(v) {
return Ok(Value::DateTime(dt.with_timezone(&chrono::Utc)));
}
Ok(Value::Text(v.clone()))
}
turso::Value::Real(v) => Ok(Value::Real(*v)),
turso::Value::Null => Ok(Value::Null),
turso::Value::Blob(v) => Ok(Value::Bytes(v.clone())),
}
}
pub struct MappedCollectFuture<'a, T: Model + 'static, V: 'static, C: FromIterator<V> + 'static> {
executor: MappedSelectExecutor<'a, T, V>,
_marker: PhantomData<C>,
}
unsafe impl<'a, T: Model + Send, V: Send, C: FromIterator<V> + Send> Send
for MappedCollectFuture<'a, T, V, C>
{
}
impl<
'a,
T: Model + 'static + std::marker::Send + std::marker::Sync,
V: crate::model::FromRowValues + 'static + std::marker::Send + std::marker::Sync,
C: FromIterator<V> + 'static,
> std::future::IntoFuture for MappedCollectFuture<'a, T, V, C>
{
type Output = crate::Result<C>;
type IntoFuture =
std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'a>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.executor.collect_inner().await })
}
}
pub struct ModelCollectWithFuture<'a, T: Model, V, C, M, F> {
executor: MappedSelectExecutor<'a, T, V>,
transform: F,
_marker: PhantomData<(C, M)>,
}
unsafe impl<'a, T: Model + Send, V: Send, C: Send, M: Send, F: Send> Send
for ModelCollectWithFuture<'a, T, V, C, M, F>
{
}
impl<'a, T, V, C, M, F> std::future::IntoFuture for ModelCollectWithFuture<'a, T, V, C, M, F>
where
T: Model + 'static + std::marker::Send + std::marker::Sync,
V: crate::model::FromRowValues + 'static + std::marker::Send + std::marker::Sync,
C: FromIterator<M> + 'static,
M: 'static + std::marker::Send,
F: Fn(V) -> M + Clone + Send + 'static,
{
type Output = crate::Result<C>;
type IntoFuture =
std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'a>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let results: Vec<V> = self.executor.collect_inner().trace().await?;
Ok(results.into_iter().map(|v| (self.transform)(v)).collect())
})
}
}
impl<'a, T: Model, V> MappedSelectExecutor<'a, T, V> {
pub fn to_subquery_sql(&self) -> (String, Vec<crate::model::Value>) {
self.select.to_sql_with_params(DbType::Sqlite)
}
pub fn collect<C: FromIterator<V> + 'static>(self) -> MappedCollectFuture<'a, T, V, C> {
MappedCollectFuture {
executor: self,
_marker: PhantomData,
}
}
pub fn collect_with<C, F, M>(self, f: F) -> ModelCollectWithFuture<'a, T, V, C, M, F>
where
C: FromIterator<M> + 'static,
F: Fn(V) -> M + Clone + 'static,
M: 'static,
{
ModelCollectWithFuture {
executor: self.clone(),
transform: f,
_marker: PhantomData,
}
}
async fn collect_inner<C: FromIterator<V>>(self) -> crate::Result<C>
where
V: crate::model::FromRowValues,
{
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let column_count = self.select.column_names().len();
let typed_value =
common_helpers::decode_row_values_from_indexed_values(column_count, |i| {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
convert_turso_value(&value)
})?;
results.push(typed_value);
}
Ok(results.into_iter().collect())
}
}
impl<'a, T: Model, V> GroupedSelectExecutor<'a, T, V> {
pub fn collect<C: FromIterator<V> + 'static>(&self) -> GroupedCollectFuture<'a, T, V, C>
where
T: 'static,
V: crate::model::FromRowValues + 'static,
{
GroupedCollectFuture {
executor: self.clone(),
_marker: PhantomData,
}
}
pub fn group_by<F, G>(self, f: F) -> Self
where
F: FnOnce(<T as Model>::Where) -> G,
G: crate::query::builder::GroupByColumns,
{
Self {
select: self.select.group_by(f),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn having<F>(self, f: F) -> Self
where
F: FnOnce(<T as Model>::Where) -> crate::query::builder::WhereExpr,
{
Self {
select: self.select.having(f),
conn: self.conn,
_marker: PhantomData,
}
}
pub fn filter<F>(self, f: F) -> Self
where
F: FnOnce(T::Where) -> crate::query::builder::WhereExpr,
{
Self {
select: self.select.filter(f),
conn: self.conn,
_marker: PhantomData,
}
}
}
impl<
'a,
T: Model + 'static + std::marker::Send + std::marker::Sync,
V: crate::model::FromRowValues + 'static + std::marker::Send + std::marker::Sync,
C: FromIterator<V> + 'static,
> std::future::IntoFuture for GroupedCollectFuture<'a, T, V, C>
{
type Output = crate::Result<C>;
type IntoFuture =
std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'a>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let results: Vec<V> = self.executor.collect_inner().trace().await?;
Ok(results.into_iter().collect())
})
}
}
impl<'a, T: Model, V> GroupedSelectExecutor<'a, T, V> {
async fn collect_inner<C: FromIterator<V>>(self) -> crate::Result<C>
where
V: crate::model::FromRowValues,
{
let (sql, params) = self.select.build_sql(DbType::Sqlite);
let turso_params = values_into_params(params)?;
let mut rows = if turso_params.is_empty() {
self.conn.query(&sql, ()).trace().await?
} else {
self.conn.query(&sql, turso_params).trace().await?
};
let mut results = Vec::new();
while let Some(row) = rows.next().trace().await? {
let column_count = self.select.column_count();
let typed_value =
common_helpers::decode_row_values_from_indexed_values(column_count, |i| {
let value = row.get_value(i).trace_for("turso::Row::get_value")?;
convert_turso_value(&value)
})?;
results.push(typed_value);
}
Ok(results.into_iter().collect())
}
}
pub struct SelectStream<'a, T: Model> {
select: Select<T>,
conn: super::common::StreamConnection<'a>,
_marker: std::marker::PhantomData<&'a T>,
}
impl<'a, T: Model + 'static> SelectStream<'a, T> {
pub async fn into_iter(self) -> crate::Result<SelectStreamIterator<'a, T>> {
let (sql, params) = self.select.to_sql_with_params(DbType::Sqlite);
let conn = self.conn.expect_sqlite().clone();
let turso_params = values_into_params(params)?;
let rows = if turso_params.is_empty() {
conn.query(&sql, ()).trace().await?
} else {
conn.query(&sql, turso_params).trace().await?
};
Ok(SelectStreamIterator {
conn: super::common::StreamConnection::Sqlite(conn),
rows,
polluted: false,
_marker: std::marker::PhantomData,
})
}
}
pub struct SelectStreamIterator<'a, T: Model> {
#[allow(dead_code)]
conn: super::common::StreamConnection<'a>,
rows: turso::Rows,
polluted: bool, _marker: std::marker::PhantomData<&'a T>,
}
impl<'a, T: Model> Drop for SelectStreamIterator<'a, T> {
fn drop(&mut self) {
}
}
impl<'a, T: Model + 'static> SelectStreamIterator<'a, T> {
pub async fn next(&mut self) -> Option<crate::Result<T>> {
if self.polluted {
return None;
}
match self.rows.next().trace_for("turso::Rows::next").await {
Ok(Some(row)) => {
let mut data = HashMap::new();
for (i, col_name) in T::COLUMNS.iter().enumerate() {
match row.get_value(i) {
Ok(value) => match convert_turso_value(&value) {
Ok(ormer_value) => {
data.insert(col_name.to_string(), ormer_value);
}
Err(e) => {
self.polluted = true;
return Some(Err(e));
}
},
Err(e) => {
self.polluted = true;
return Some(Err(crate::ormer_error!(
"turso::Row::get_value failed: {e}"
)));
}
}
}
let ormer_row = Row::new(data);
Some(T::from_row(&ormer_row))
}
Ok(None) => None,
Err(e) => {
self.polluted = true;
Some(Err(e))
}
}
}
}