use async_trait::async_trait;
use turso_orm_driver::ConnectionTrait;
use turso_sql::Value;
use super::base_entity::EntityTrait;
use crate::query::{DeleteResult, Insert, UpdateOne};
use crate::{DbErr, Result};
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum ActiveValue<T> {
Set(T),
Unchanged(T),
#[default]
NotSet,
}
#[allow(
non_snake_case,
reason = "mirrors the variant name for struct literals"
)]
pub fn Set<T>(value: T) -> ActiveValue<T> {
ActiveValue::Set(value)
}
#[allow(
non_snake_case,
reason = "mirrors the variant name for struct literals"
)]
pub fn Unchanged<T>(value: T) -> ActiveValue<T> {
ActiveValue::Unchanged(value)
}
#[allow(
non_upper_case_globals,
reason = "mirrors the variant name for struct literals"
)]
pub const NotSet: ActiveValue<()> = ActiveValue::NotSet;
impl<T> ActiveValue<T> {
pub fn is_set(&self) -> bool {
matches!(self, ActiveValue::Set(_))
}
pub fn is_unchanged(&self) -> bool {
matches!(self, ActiveValue::Unchanged(_))
}
pub fn is_not_set(&self) -> bool {
matches!(self, ActiveValue::NotSet)
}
pub fn as_ref(&self) -> Option<&T> {
match self {
ActiveValue::Set(v) | ActiveValue::Unchanged(v) => Some(v),
ActiveValue::NotSet => None,
}
}
pub fn take(&mut self) -> Option<T> {
match std::mem::replace(self, ActiveValue::NotSet) {
ActiveValue::Set(v) | ActiveValue::Unchanged(v) => Some(v),
ActiveValue::NotSet => None,
}
}
pub fn into_value(self) -> Option<T> {
match self {
ActiveValue::Set(v) | ActiveValue::Unchanged(v) => Some(v),
ActiveValue::NotSet => None,
}
}
pub fn reset(&mut self) {
if let ActiveValue::Set(v) = std::mem::replace(self, ActiveValue::NotSet) {
*self = ActiveValue::Unchanged(v);
} else if let ActiveValue::Unchanged(v) = std::mem::replace(self, ActiveValue::NotSet) {
*self = ActiveValue::Unchanged(v);
}
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ActiveValue<U> {
match self {
ActiveValue::Set(v) => ActiveValue::Set(f(v)),
ActiveValue::Unchanged(v) => ActiveValue::Unchanged(f(v)),
ActiveValue::NotSet => ActiveValue::NotSet,
}
}
}
impl<T: PartialEq> ActiveValue<T> {
pub fn set_if_not_equals(&mut self, value: T) {
match self {
ActiveValue::Unchanged(current) if *current == value => {}
_ => *self = ActiveValue::Set(value),
}
}
}
impl<T> From<T> for ActiveValue<T> {
fn from(value: T) -> Self {
ActiveValue::Set(value)
}
}
pub trait IntoActiveModel<A: ActiveModelTrait> {
fn into_active_model(self) -> A;
}
impl<A: ActiveModelTrait> IntoActiveModel<A> for A {
fn into_active_model(self) -> A {
self
}
}
pub trait IntoActiveValue<T> {
fn into_active_value(self) -> ActiveValue<T>;
}
impl<T: crate::types::TursoType> IntoActiveValue<T> for T {
fn into_active_value(self) -> ActiveValue<T> {
ActiveValue::Set(self)
}
}
impl<T: crate::types::TursoType> IntoActiveValue<T> for Option<T> {
fn into_active_value(self) -> ActiveValue<T> {
match self {
Some(v) => ActiveValue::Set(v),
None => ActiveValue::NotSet,
}
}
}
pub trait TryIntoModel<M> {
fn try_into_model(self) -> Result<M>;
}
#[async_trait]
pub trait ActiveModelTrait: Clone + Send + Sync + std::fmt::Debug + Default {
type Entity: EntityTrait<ActiveModel = Self>;
fn get(&self, column: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
fn set(&mut self, column: <Self::Entity as EntityTrait>::Column, value: Value) -> Result<()>;
fn not_set(&mut self, column: <Self::Entity as EntityTrait>::Column);
fn is_not_set(&self, column: <Self::Entity as EntityTrait>::Column) -> bool;
fn reset(&mut self, column: <Self::Entity as EntityTrait>::Column);
#[must_use]
fn reset_all(mut self) -> Self {
for c in <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter() {
self.reset(c);
}
self
}
fn is_changed(&self) -> bool {
<<Self::Entity as EntityTrait>::Column as super::Iterable>::iter()
.any(|c| self.get(c).is_set())
}
#[cfg(feature = "with-json")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
fn set_from_json(&mut self, json: serde_json::Value) -> Result<()> {
let serde_json::Value::Object(map) = json else {
return Err(DbErr::Json("expected a JSON object".into()));
};
for c in <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter() {
if let Some(v) =
map.get(<<Self::Entity as EntityTrait>::Column as super::IdenStatic>::as_str(&c))
{
self.set(c, json_to_value(v))?;
}
}
Ok(())
}
#[cfg(feature = "with-json")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
fn from_json(json: serde_json::Value) -> Result<Self> {
let mut am = Self::default();
am.set_from_json(json)?;
Ok(am)
}
fn get_primary_key_value(&self) -> Option<Vec<Value>> {
use super::primary_key::PrimaryKeyToColumn;
let mut values = Vec::new();
for pk in <<Self::Entity as EntityTrait>::PrimaryKey as super::Iterable>::iter() {
let value = self.get(pk.into_column()).into_value()?;
values.push(value);
}
Some(values)
}
async fn insert<C: ConnectionTrait>(
self,
db: &C,
) -> Result<<Self::Entity as EntityTrait>::Model>
where
Self: ActiveModelBehavior,
{
let am = <Self as ActiveModelBehavior>::before_save(self, db, true).await?;
let model = Insert::<Self>::one(am).exec_with_returning(db).await?;
<Self as ActiveModelBehavior>::after_save(model, db, true).await
}
async fn update<C: ConnectionTrait>(
self,
db: &C,
) -> Result<<Self::Entity as EntityTrait>::Model>
where
Self: ActiveModelBehavior,
{
let am = <Self as ActiveModelBehavior>::before_save(self, db, false).await?;
let model = UpdateOne::new(am).exec(db).await?;
<Self as ActiveModelBehavior>::after_save(model, db, false).await
}
async fn save<C: ConnectionTrait>(self, db: &C) -> Result<Self>
where
Self: ActiveModelBehavior,
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
{
let model = if self.is_update() {
self.update(db).await?
} else {
self.insert(db).await?
};
Ok(model.into_active_model())
}
fn is_update(&self) -> bool {
use super::primary_key::PrimaryKeyToColumn;
let mut keys = <<Self::Entity as EntityTrait>::PrimaryKey as super::Iterable>::iter();
let Some(first) = keys.next() else {
return false;
};
std::iter::once(first)
.chain(keys)
.all(|pk| self.get(pk.into_column()).is_unchanged())
}
async fn delete<C: ConnectionTrait>(self, db: &C) -> Result<DeleteResult>
where
Self: ActiveModelBehavior,
{
let am = <Self as ActiveModelBehavior>::before_delete(self, db).await?;
let result = crate::query::DeleteOne::new(am.clone()).exec(db).await?;
<Self as ActiveModelBehavior>::after_delete(am, db).await?;
Ok(result)
}
}
#[async_trait]
pub trait ActiveModelBehavior: ActiveModelTrait {
async fn before_save<C: ConnectionTrait>(self, _db: &C, _insert: bool) -> Result<Self> {
Ok(self)
}
async fn after_save<C: ConnectionTrait>(
model: <Self::Entity as EntityTrait>::Model,
_db: &C,
_insert: bool,
) -> Result<<Self::Entity as EntityTrait>::Model> {
Ok(model)
}
async fn before_delete<C: ConnectionTrait>(self, _db: &C) -> Result<Self> {
Ok(self)
}
async fn after_delete<C: ConnectionTrait>(self, _db: &C) -> Result<Self> {
Ok(self)
}
}
#[cfg(feature = "with-json")]
fn json_to_value(json: &serde_json::Value) -> Value {
match json {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Integer(i64::from(*b)),
serde_json::Value::Number(n) => n
.as_i64()
.map(Value::Integer)
.or_else(|| n.as_f64().map(Value::Real))
.unwrap_or(Value::Null),
serde_json::Value::String(s) => Value::Text(s.clone()),
compound => Value::Text(compound.to_string()),
}
}
pub fn decode_field<T: turso_orm_driver::FromValue>(column: &str, value: Value) -> Result<T> {
T::from_value(value, column).map_err(|e| DbErr::Type(e.to_string()))
}