use crate::{
error::*, ConnectionTrait, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, Value,
};
use async_trait::async_trait;
use sea_query::{Nullable, ValueTuple};
use std::fmt::Debug;
pub use ActiveValue::NotSet;
#[derive(Clone, Debug)]
pub enum ActiveValue<V>
where
V: Into<Value>,
{
Set(V),
Unchanged(V),
NotSet,
}
#[allow(non_snake_case)]
pub fn Set<V>(v: V) -> ActiveValue<V>
where
V: Into<Value>,
{
ActiveValue::set(v)
}
#[deprecated(
since = "0.5.0",
note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]"
)]
#[allow(non_snake_case)]
pub fn Unset<V>(_: Option<bool>) -> ActiveValue<V>
where
V: Into<Value>,
{
ActiveValue::not_set()
}
#[allow(non_snake_case)]
pub fn Unchanged<V>(value: V) -> ActiveValue<V>
where
V: Into<Value>,
{
ActiveValue::unchanged(value)
}
#[async_trait]
pub trait ActiveModelTrait: Clone + Debug {
type Entity: EntityTrait;
fn take(&mut self, c: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value);
fn not_set(&mut self, c: <Self::Entity as EntityTrait>::Column);
fn is_not_set(&self, c: <Self::Entity as EntityTrait>::Column) -> bool;
fn default() -> Self;
#[allow(clippy::question_mark)]
fn get_primary_key_value(&self) -> Option<ValueTuple> {
let mut cols = <Self::Entity as EntityTrait>::PrimaryKey::iter();
macro_rules! next {
() => {
if let Some(col) = cols.next() {
if let Some(val) = self.get(col.into_column()).into_value() {
val
} else {
return None;
}
} else {
return None;
}
};
}
match <Self::Entity as EntityTrait>::PrimaryKey::iter().count() {
1 => {
let s1 = next!();
Some(ValueTuple::One(s1))
}
2 => {
let s1 = next!();
let s2 = next!();
Some(ValueTuple::Two(s1, s2))
}
3 => {
let s1 = next!();
let s2 = next!();
let s3 = next!();
Some(ValueTuple::Three(s1, s2, s3))
}
4 => {
let s1 = next!();
let s2 = next!();
let s3 = next!();
let s4 = next!();
Some(ValueTuple::Four(s1, s2, s3, s4))
}
5 => {
let s1 = next!();
let s2 = next!();
let s3 = next!();
let s4 = next!();
let s5 = next!();
Some(ValueTuple::Five(s1, s2, s3, s4, s5))
}
6 => {
let s1 = next!();
let s2 = next!();
let s3 = next!();
let s4 = next!();
let s5 = next!();
let s6 = next!();
Some(ValueTuple::Six(s1, s2, s3, s4, s5, s6))
}
_ => panic!("The arity cannot be larger than 6"),
}
}
async fn insert<'a, C>(self, db: &'a C) -> Result<<Self::Entity as EntityTrait>::Model, DbErr>
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_save(self, true)?;
let model = <Self::Entity as EntityTrait>::insert(am)
.exec_with_returning(db)
.await?;
Self::after_save(model, true)
}
async fn update<'a, C>(self, db: &'a C) -> Result<<Self::Entity as EntityTrait>::Model, DbErr>
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_save(self, false)?;
let model: <Self::Entity as EntityTrait>::Model = Self::Entity::update(am).exec(db).await?;
Self::after_save(model, false)
}
async fn save<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait,
{
let mut is_update = true;
for key in <Self::Entity as EntityTrait>::PrimaryKey::iter() {
let col = key.into_column();
if self.is_not_set(col) {
is_update = false;
break;
}
}
let res = if !is_update {
self.insert(db).await
} else {
self.update(db).await
}?;
Ok(res.into_active_model())
}
async fn delete<'a, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
where
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_delete(self)?;
let am_clone = am.clone();
let delete_res = Self::Entity::delete(am).exec(db).await?;
ActiveModelBehavior::after_delete(am_clone)?;
Ok(delete_res)
}
#[cfg(feature = "with-json")]
fn set_from_json(&mut self, json: serde_json::Value) -> Result<(), DbErr>
where
<<Self as ActiveModelTrait>::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
for<'de> <<Self as ActiveModelTrait>::Entity as EntityTrait>::Model:
serde::de::Deserialize<'de>,
{
use crate::Iterable;
let primary_key_values: Vec<(<Self::Entity as EntityTrait>::Column, ActiveValue<Value>)> =
<<Self::Entity as EntityTrait>::PrimaryKey>::iter()
.map(|pk| (pk.into_column(), self.take(pk.into_column())))
.collect();
*self = Self::from_json(json)?;
for (col, active_value) in primary_key_values {
match active_value {
ActiveValue::Unchanged(v) | ActiveValue::Set(v) => self.set(col, v),
NotSet => self.not_set(col),
}
}
Ok(())
}
#[cfg(feature = "with-json")]
fn from_json(json: serde_json::Value) -> Result<Self, DbErr>
where
<<Self as ActiveModelTrait>::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
for<'de> <<Self as ActiveModelTrait>::Entity as EntityTrait>::Model:
serde::de::Deserialize<'de>,
{
use crate::{Iden, Iterable};
let json_keys: Vec<(<Self::Entity as EntityTrait>::Column, bool)> =
<<Self::Entity as EntityTrait>::Column>::iter()
.map(|col| (col, json.get(col.to_string()).is_some()))
.collect();
let model: <Self::Entity as EntityTrait>::Model =
serde_json::from_value(json).map_err(|e| DbErr::Json(e.to_string()))?;
let mut am = model.into_active_model();
for (col, json_key_exists) in json_keys {
if json_key_exists && !am.is_not_set(col) {
am.set(col, am.get(col).unwrap());
} else {
am.not_set(col);
}
}
Ok(am)
}
fn is_changed(&self) -> bool {
<Self::Entity as EntityTrait>::Column::iter()
.any(|col| self.get(col).is_set() && !self.get(col).is_unchanged())
}
}
#[allow(unused_variables)]
pub trait ActiveModelBehavior: ActiveModelTrait {
fn new() -> Self {
<Self as ActiveModelTrait>::default()
}
fn before_save(self, insert: bool) -> Result<Self, DbErr> {
Ok(self)
}
fn after_save(
model: <Self::Entity as EntityTrait>::Model,
insert: bool,
) -> Result<<Self::Entity as EntityTrait>::Model, DbErr> {
Ok(model)
}
fn before_delete(self) -> Result<Self, DbErr> {
Ok(self)
}
fn after_delete(self) -> Result<Self, DbErr> {
Ok(self)
}
}
pub trait IntoActiveModel<A>
where
A: ActiveModelTrait,
{
fn into_active_model(self) -> A;
}
impl<A> IntoActiveModel<A> for A
where
A: ActiveModelTrait,
{
fn into_active_model(self) -> A {
self
}
}
pub trait IntoActiveValue<V>
where
V: Into<Value>,
{
fn into_active_value(self) -> ActiveValue<V>;
}
macro_rules! impl_into_active_value {
($ty: ty) => {
impl IntoActiveValue<$ty> for $ty {
fn into_active_value(self) -> ActiveValue<$ty> {
Set(self)
}
}
impl IntoActiveValue<Option<$ty>> for Option<$ty> {
fn into_active_value(self) -> ActiveValue<Option<$ty>> {
match self {
Some(value) => Set(Some(value)),
None => NotSet,
}
}
}
impl IntoActiveValue<Option<$ty>> for Option<Option<$ty>> {
fn into_active_value(self) -> ActiveValue<Option<$ty>> {
match self {
Some(value) => Set(value),
None => NotSet,
}
}
}
};
}
impl_into_active_value!(bool);
impl_into_active_value!(i8);
impl_into_active_value!(i16);
impl_into_active_value!(i32);
impl_into_active_value!(i64);
impl_into_active_value!(u8);
impl_into_active_value!(u16);
impl_into_active_value!(u32);
impl_into_active_value!(u64);
impl_into_active_value!(f32);
impl_into_active_value!(f64);
impl_into_active_value!(&'static str);
impl_into_active_value!(String);
#[cfg(feature = "with-json")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
impl_into_active_value!(crate::prelude::Json);
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
impl_into_active_value!(crate::prelude::Date);
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
impl_into_active_value!(crate::prelude::Time);
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
impl_into_active_value!(crate::prelude::DateTime);
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
impl_into_active_value!(crate::prelude::DateTimeWithTimeZone);
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
impl_into_active_value!(crate::prelude::DateTimeUtc);
#[cfg(feature = "with-chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
impl_into_active_value!(crate::prelude::DateTimeLocal);
#[cfg(feature = "with-rust_decimal")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-rust_decimal")))]
impl_into_active_value!(crate::prelude::Decimal);
#[cfg(feature = "with-uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))]
impl_into_active_value!(crate::prelude::Uuid);
impl<V> Default for ActiveValue<V>
where
V: Into<Value>,
{
fn default() -> Self {
Self::NotSet
}
}
impl<V> ActiveValue<V>
where
V: Into<Value>,
{
pub fn set(value: V) -> Self {
Self::Set(value)
}
pub fn is_set(&self) -> bool {
matches!(self, Self::Set(_))
}
pub fn unchanged(value: V) -> Self {
Self::Unchanged(value)
}
pub fn is_unchanged(&self) -> bool {
matches!(self, Self::Unchanged(_))
}
pub fn not_set() -> Self {
Self::default()
}
pub fn is_not_set(&self) -> bool {
matches!(self, Self::NotSet)
}
pub fn take(&mut self) -> Option<V> {
match std::mem::take(self) {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => Some(value),
ActiveValue::NotSet => None,
}
}
pub fn unwrap(self) -> V {
match self {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => value,
ActiveValue::NotSet => panic!("Cannot unwrap ActiveValue::NotSet"),
}
}
pub fn into_value(self) -> Option<Value> {
match self {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => Some(value.into()),
ActiveValue::NotSet => None,
}
}
pub fn into_wrapped_value(self) -> ActiveValue<Value> {
match self {
Self::Set(value) => ActiveValue::set(value.into()),
Self::Unchanged(value) => ActiveValue::unchanged(value.into()),
Self::NotSet => ActiveValue::not_set(),
}
}
}
impl<V> std::convert::AsRef<V> for ActiveValue<V>
where
V: Into<Value>,
{
fn as_ref(&self) -> &V {
match self {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => value,
ActiveValue::NotSet => panic!("Cannot borrow ActiveValue::NotSet"),
}
}
}
impl<V> PartialEq for ActiveValue<V>
where
V: Into<Value> + std::cmp::PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ActiveValue::Set(l), ActiveValue::Set(r)) => l == r,
(ActiveValue::Unchanged(l), ActiveValue::Unchanged(r)) => l == r,
(ActiveValue::NotSet, ActiveValue::NotSet) => true,
_ => false,
}
}
}
impl<V> From<ActiveValue<V>> for ActiveValue<Option<V>>
where
V: Into<Value> + Nullable,
{
fn from(value: ActiveValue<V>) -> Self {
match value {
ActiveValue::Set(value) => ActiveValue::set(Some(value)),
ActiveValue::Unchanged(value) => ActiveValue::unchanged(Some(value)),
ActiveValue::NotSet => ActiveValue::not_set(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{entity::*, tests_cfg::*, DbErr};
use pretty_assertions::assert_eq;
#[cfg(feature = "with-json")]
use serde_json::json;
#[test]
#[cfg(feature = "macros")]
fn test_derive_into_active_model_1() {
mod my_fruit {
pub use super::fruit::*;
use crate as sea_orm;
use crate::entity::prelude::*;
#[derive(DeriveIntoActiveModel)]
pub struct NewFruit {
pub name: String,
pub cake_id: i32,
}
}
assert_eq!(
my_fruit::NewFruit {
name: "Apple".to_owned(),
cake_id: 1,
}
.into_active_model(),
fruit::ActiveModel {
id: NotSet,
name: Set("Apple".to_owned()),
cake_id: Set(Some(1)),
}
);
}
#[test]
#[cfg(feature = "macros")]
fn test_derive_into_active_model_2() {
mod my_fruit {
pub use super::fruit::*;
use crate as sea_orm;
use crate::entity::prelude::*;
#[derive(DeriveIntoActiveModel)]
pub struct UpdateFruit {
pub cake_id: Option<Option<i32>>,
}
}
assert_eq!(
my_fruit::UpdateFruit {
cake_id: Some(Some(1)),
}
.into_active_model(),
fruit::ActiveModel {
id: NotSet,
name: NotSet,
cake_id: Set(Some(1)),
}
);
assert_eq!(
my_fruit::UpdateFruit {
cake_id: Some(None),
}
.into_active_model(),
fruit::ActiveModel {
id: NotSet,
name: NotSet,
cake_id: Set(None),
}
);
assert_eq!(
my_fruit::UpdateFruit { cake_id: None }.into_active_model(),
fruit::ActiveModel {
id: NotSet,
name: NotSet,
cake_id: NotSet,
}
);
}
#[test]
#[cfg(feature = "with-json")]
#[should_panic(
expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"#
)]
fn test_active_model_set_from_json_1() {
let mut cake: cake::ActiveModel = Default::default();
cake.set_from_json(json!({
"name": "Apple Pie",
}))
.unwrap();
}
#[test]
#[cfg(feature = "with-json")]
fn test_active_model_set_from_json_2() -> Result<(), DbErr> {
let mut fruit: fruit::ActiveModel = Default::default();
fruit.set_from_json(json!({
"name": "Apple",
}))?;
assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::NotSet,
}
);
assert_eq!(
fruit::ActiveModel::from_json(json!({
"name": "Apple",
}))?,
fruit::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::NotSet,
}
);
fruit.set_from_json(json!({
"name": "Apple",
"cake_id": null,
}))?;
assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::Set(None),
}
);
fruit.set_from_json(json!({
"id": null,
"name": "Apple",
"cake_id": 1,
}))?;
assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::Set(Some(1)),
}
);
fruit.set_from_json(json!({
"id": 2,
"name": "Apple",
"cake_id": 1,
}))?;
assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::Set(Some(1)),
}
);
let mut fruit = fruit::ActiveModel {
id: ActiveValue::Set(1),
name: ActiveValue::NotSet,
cake_id: ActiveValue::NotSet,
};
fruit.set_from_json(json!({
"id": 8,
"name": "Apple",
"cake_id": 1,
}))?;
assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::Set(1),
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::Set(Some(1)),
}
);
Ok(())
}
#[smol_potat::test]
#[cfg(feature = "with-json")]
async fn test_active_model_set_from_json_3() -> Result<(), DbErr> {
use crate::*;
let db = MockDatabase::new(DbBackend::Postgres)
.append_exec_results(vec![
MockExecResult {
last_insert_id: 1,
rows_affected: 1,
},
MockExecResult {
last_insert_id: 1,
rows_affected: 1,
},
])
.append_query_results(vec![
vec![fruit::Model {
id: 1,
name: "Apple".to_owned(),
cake_id: None,
}],
vec![fruit::Model {
id: 2,
name: "Orange".to_owned(),
cake_id: Some(1),
}],
])
.into_connection();
let mut fruit: fruit::ActiveModel = Default::default();
fruit.set_from_json(json!({
"name": "Apple",
}))?;
fruit.save(&db).await?;
let mut fruit = fruit::ActiveModel {
id: Set(2),
..Default::default()
};
fruit.set_from_json(json!({
"id": 9,
"name": "Orange",
"cake_id": 1,
}))?;
fruit.save(&db).await?;
assert_eq!(
db.into_transaction_log(),
vec![
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"INSERT INTO "fruit" ("name") VALUES ($1) RETURNING "id", "name", "cake_id""#,
vec!["Apple".into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "fruit" SET "name" = $1, "cake_id" = $2 WHERE "fruit"."id" = $3 RETURNING "id", "name", "cake_id""#,
vec!["Orange".into(), 1i32.into(), 2i32.into()]
),
]
);
Ok(())
}
#[test]
fn test_active_model_is_changed() {
let mut fruit: fruit::ActiveModel = Default::default();
assert!(!fruit.is_changed());
fruit.set(fruit::Column::Name, "apple".into());
assert!(fruit.is_changed());
}
}