use async_trait::async_trait;
use turso_orm_driver::{ConnectionTrait, FromValue, Row};
use turso_sql::Value;
use super::active_model::{ActiveModelBehavior, ActiveModelTrait, IntoActiveModel};
use super::base_entity::EntityTrait;
use super::relation::{Linked, Related};
use crate::query::{DeleteResult, Select};
use crate::{DbErr, Result};
#[async_trait]
pub trait ModelTrait: Clone + Send + Sync + std::fmt::Debug {
type Entity: EntityTrait<Model = Self>;
fn get(&self, column: <Self::Entity as EntityTrait>::Column) -> Value;
fn set(&mut self, column: <Self::Entity as EntityTrait>::Column, value: Value) -> Result<()>;
fn find_related<R>(&self, _: R) -> Select<R>
where
R: EntityTrait,
Self::Entity: Related<R>,
{
Select::<R>::find_related_to::<Self::Entity>(self)
}
fn find_linked<L>(&self, link: L) -> Select<L::ToEntity>
where
L: Linked<FromEntity = Self::Entity>,
{
Select::<L::ToEntity>::find_linked_to(&link, self)
}
async fn delete<C>(self, db: &C) -> Result<DeleteResult>
where
C: ConnectionTrait,
Self: IntoActiveModel<<Self::Entity as EntityTrait>::ActiveModel>,
<Self::Entity as EntityTrait>::ActiveModel: ActiveModelBehavior,
{
self.into_active_model().delete(db).await
}
}
pub trait FromQueryResult: Sized + Send + Sync {
fn from_query_result(row: &Row, prefix: &str) -> Result<Self>;
fn from_query_result_optional(row: &Row, prefix: &str) -> Result<Option<Self>> {
let all_null = row
.iter()
.filter(|(name, _)| name.starts_with(prefix))
.all(|(_, v)| matches!(v, Value::Null));
if all_null {
Ok(None)
} else {
Self::from_query_result(row, prefix).map(Some)
}
}
}
impl FromQueryResult for Row {
fn from_query_result(row: &Row, _prefix: &str) -> Result<Self> {
Ok(row.clone())
}
}
macro_rules! tuple_from_query_result {
($(($($t:ident $i:tt),+));* $(;)?) => {$(
impl<$($t: FromValue + Send + Sync),+> FromQueryResult for ($($t,)+) {
fn from_query_result(row: &Row, _prefix: &str) -> Result<Self> {
Ok(($(row.get::<$t>($i)?,)+))
}
}
)*};
}
tuple_from_query_result! {
(A 0);
(A 0, B 1);
(A 0, B 1, C 2);
(A 0, B 1, C 2, D 3);
(A 0, B 1, C 2, D 3, E 4);
(A 0, B 1, C 2, D 3, E 4, F 5);
}
#[cfg(feature = "with-json")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
impl FromQueryResult for serde_json::Value {
fn from_query_result(row: &Row, prefix: &str) -> Result<Self> {
let mut object = serde_json::Map::new();
for (name, value) in row.iter() {
let Some(name) = name.strip_prefix(prefix) else {
continue;
};
let json = match value {
Value::Null => serde_json::Value::Null,
Value::Integer(n) => serde_json::Value::from(*n),
Value::Real(f) => serde_json::Value::from(*f),
Value::Text(s) => serde_json::Value::String(s.clone()),
Value::Blob(b) => serde_json::Value::Array(
b.iter()
.map(|byte| serde_json::Value::from(*byte))
.collect(),
),
};
object.insert(name.to_owned(), json);
}
Ok(serde_json::Value::Object(object))
}
}
pub fn get_field<T: FromValue>(row: &Row, prefix: &str, column: &str) -> Result<T> {
let name = if prefix.is_empty() {
column.to_owned()
} else {
format!("{prefix}{column}")
};
row.get::<T>(name.as_str()).map_err(DbErr::from)
}