Rework ActiveValue (#340)

* WIP

* Fixup

* Fixup

* Update docs & rename `unset`

* Deprecate `Unset()` and reexport `ActiveValue::NotSet`

* Docs

Co-authored-by: Chris Tsang <chris.2y3@outlook.com>
This commit is contained in:
Billy Chan 2021-12-19 02:22:30 +08:00 committed by GitHub
parent 5104cd3573
commit adfb9ead54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 120 deletions

View File

@ -136,12 +136,12 @@ Fruit::update_many()
### Save ### Save
```rust ```rust
let banana = fruit::ActiveModel { let banana = fruit::ActiveModel {
id: Unset(None), id: NotSet,
name: Set("Banana".to_owned()), name: Set("Banana".to_owned()),
..Default::default() ..Default::default()
}; };
// create, because primary key `id` is `Unset` // create, because primary key `id` is `NotSet`
let mut banana = banana.save(db).await?.into_active_model(); let mut banana = banana.save(db).await?.into_active_model();
banana.name = Set("Banana Mongo".to_owned()); banana.name = Set("Banana Mongo".to_owned());

View File

@ -81,7 +81,7 @@ mod form {
async fn save_custom_active_model(db: &DbConn) -> Result<(), DbErr> { async fn save_custom_active_model(db: &DbConn) -> Result<(), DbErr> {
let pineapple = form::ActiveModel { let pineapple = form::ActiveModel {
id: Unset(None), id: NotSet,
name: Set("Pineapple".to_owned()), name: Set("Pineapple".to_owned()),
}; };

View File

@ -80,7 +80,7 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
impl std::convert::From<<Entity as EntityTrait>::Model> for ActiveModel { impl std::convert::From<<Entity as EntityTrait>::Model> for ActiveModel {
fn from(m: <Entity as EntityTrait>::Model) -> Self { fn from(m: <Entity as EntityTrait>::Model) -> Self {
Self { Self {
#(#field: sea_orm::unchanged_active_value_not_intended_for_public_use(m.#field)),* #(#field: sea_orm::ActiveValue::unchanged(m.#field)),*
} }
} }
} }
@ -99,18 +99,18 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
fn take(&mut self, c: <Self::Entity as EntityTrait>::Column) -> sea_orm::ActiveValue<sea_orm::Value> { fn take(&mut self, c: <Self::Entity as EntityTrait>::Column) -> sea_orm::ActiveValue<sea_orm::Value> {
match c { match c {
#(<Self::Entity as EntityTrait>::Column::#name => { #(<Self::Entity as EntityTrait>::Column::#name => {
let mut value = sea_orm::ActiveValue::unset(); let mut value = sea_orm::ActiveValue::not_set();
std::mem::swap(&mut value, &mut self.#field); std::mem::swap(&mut value, &mut self.#field);
value.into_wrapped_value() value.into_wrapped_value()
},)* },)*
_ => sea_orm::ActiveValue::unset(), _ => sea_orm::ActiveValue::not_set(),
} }
} }
fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> sea_orm::ActiveValue<sea_orm::Value> { fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> sea_orm::ActiveValue<sea_orm::Value> {
match c { match c {
#(<Self::Entity as EntityTrait>::Column::#name => self.#field.clone().into_wrapped_value(),)* #(<Self::Entity as EntityTrait>::Column::#name => self.#field.clone().into_wrapped_value(),)*
_ => sea_orm::ActiveValue::unset(), _ => sea_orm::ActiveValue::not_set(),
} }
} }
@ -121,23 +121,23 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
} }
} }
fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column) { fn not_set(&mut self, c: <Self::Entity as EntityTrait>::Column) {
match c { match c {
#(<Self::Entity as EntityTrait>::Column::#name => self.#field = sea_orm::ActiveValue::unset(),)* #(<Self::Entity as EntityTrait>::Column::#name => self.#field = sea_orm::ActiveValue::not_set(),)*
_ => {}, _ => {},
} }
} }
fn is_unset(&self, c: <Self::Entity as EntityTrait>::Column) -> bool { fn is_not_set(&self, c: <Self::Entity as EntityTrait>::Column) -> bool {
match c { match c {
#(<Self::Entity as EntityTrait>::Column::#name => self.#field.is_unset(),)* #(<Self::Entity as EntityTrait>::Column::#name => self.#field.is_not_set(),)*
_ => panic!("This ActiveModel does not have this field"), _ => panic!("This ActiveModel does not have this field"),
} }
} }
fn default() -> Self { fn default() -> Self {
Self { Self {
#(#field: sea_orm::ActiveValue::unset()),* #(#field: sea_orm::ActiveValue::not_set()),*
} }
} }
} }

View File

@ -5,35 +5,47 @@ use async_trait::async_trait;
use sea_query::{Nullable, ValueTuple}; use sea_query::{Nullable, ValueTuple};
use std::fmt::Debug; use std::fmt::Debug;
/// Defines a value from an ActiveModel and its state. pub use ActiveValue::NotSet;
/// The field `value` takes in an [Option] type where `Option::Some(V)` , with `V` holding
/// the value that operations like `UPDATE` are being performed on and /// Defines a stateful value used in ActiveModel.
/// the `state` field is either `ActiveValueState::Set` or `ActiveValueState::Unchanged`.
/// [Option::None] in the `value` field indicates no value being performed by an operation
/// and that the `state` field of the [ActiveValue] is set to `ActiveValueState::Unset` .
/// #### Example snippet
/// ```no_run
/// // The code snipped below does an UPDATE operation on a [ActiveValue]
/// // yielding the the SQL statement ` r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"# `
/// ///
/// There are three possible state represented by three enum variants.
/// - [ActiveValue::Set]: A [Value] was set
/// - [ActiveValue::Unchanged]: A [Value] remain unchanged
/// - [ActiveValue::NotSet]: A NULL value similar to [Option::None]
///
/// The stateful value is useful when constructing UPDATE SQL statement,
/// see an example below.
///
/// # Examples
///
/// ```
/// use sea_orm::tests_cfg::{cake, fruit}; /// use sea_orm::tests_cfg::{cake, fruit};
/// use sea_orm::{entity::*, query::*, DbBackend}; /// use sea_orm::{entity::*, query::*, DbBackend};
/// ///
/// Update::one(fruit::ActiveModel { /// // The code snipped below does an UPDATE operation on a `ActiveValue`
/// id: ActiveValue::set(1), /// assert_eq!(
/// name: ActiveValue::set("Orange".to_owned()), /// Update::one(fruit::ActiveModel {
/// cake_id: ActiveValue::unset(), /// id: ActiveValue::set(1),
/// }) /// name: ActiveValue::set("Orange".to_owned()),
/// .build(DbBackend::Postgres) /// cake_id: ActiveValue::not_set(),
/// .to_string(); /// })
/// .build(DbBackend::Postgres)
/// .to_string(),
/// r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"#
/// );
/// ``` /// ```
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
pub struct ActiveValue<V> pub enum ActiveValue<V>
where where
V: Into<Value>, V: Into<Value>,
{ {
value: Option<V>, /// A [Value] was set
state: ActiveValueState, Set(V),
/// A [Value] remain unchanged
Unchanged(V),
/// A NULL value similar to [Option::None]
NotSet,
} }
/// Defines a set operation on an [ActiveValue] /// Defines a set operation on an [ActiveValue]
@ -45,31 +57,22 @@ where
ActiveValue::set(v) ActiveValue::set(v)
} }
/// Defines an unset operation on an [ActiveValue] /// Defines an not set operation on an [ActiveValue]
#[deprecated(
since = "0.5.0",
note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]"
)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn Unset<V>(_: Option<bool>) -> ActiveValue<V> pub fn Unset<V>(_: Option<bool>) -> ActiveValue<V>
where where
V: Into<Value>, V: Into<Value>,
{ {
ActiveValue::unset() ActiveValue::not_set()
} }
// Defines the state of an [ActiveValue] /// Defines an unchanged operation on an [ActiveValue]
#[derive(Clone, Debug)] #[allow(non_snake_case)]
enum ActiveValueState { pub fn Unchanged<V>(value: V) -> ActiveValue<V>
Set,
Unchanged,
Unset,
}
impl Default for ActiveValueState {
fn default() -> Self {
Self::Unset
}
}
#[doc(hidden)]
pub fn unchanged_active_value_not_intended_for_public_use<V>(value: V) -> ActiveValue<V>
where where
V: Into<Value>, V: Into<Value>,
{ {
@ -93,11 +96,11 @@ pub trait ActiveModelTrait: Clone + Debug {
/// Set the Value into an ActiveModel /// Set the Value into an ActiveModel
fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value); fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value);
/// Set the state of an [ActiveValue] to the Unset state /// Set the state of an [ActiveValue] to the not set state
fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column); fn not_set(&mut self, c: <Self::Entity as EntityTrait>::Column);
/// Check the state of a [ActiveValue] /// Check the state of a [ActiveValue]
fn is_unset(&self, c: <Self::Entity as EntityTrait>::Column) -> bool; fn is_not_set(&self, c: <Self::Entity as EntityTrait>::Column) -> bool;
/// The default implementation of the ActiveModel /// The default implementation of the ActiveModel
fn default() -> Self; fn default() -> Self;
@ -378,7 +381,7 @@ pub trait ActiveModelTrait: Clone + Debug {
Self::after_save(model, false) Self::after_save(model, false)
} }
/// Insert the model if primary key is unset, update otherwise. /// Insert the model if primary key is not_set, update otherwise.
/// Only works if the entity has auto increment primary key. /// Only works if the entity has auto increment primary key.
async fn save<'a, C>(self, db: &'a C) -> Result<<Self::Entity as EntityTrait>::Model, DbErr> async fn save<'a, C>(self, db: &'a C) -> Result<<Self::Entity as EntityTrait>::Model, DbErr>
where where
@ -390,7 +393,7 @@ pub trait ActiveModelTrait: Clone + Debug {
let mut is_update = true; let mut is_update = true;
for key in <Self::Entity as EntityTrait>::PrimaryKey::iter() { for key in <Self::Entity as EntityTrait>::PrimaryKey::iter() {
let col = key.into_column(); let col = key.into_column();
if am.is_unset(col) { if am.is_not_set(col) {
is_update = false; is_update = false;
break; break;
} }
@ -555,7 +558,7 @@ macro_rules! impl_into_active_value {
fn into_active_value(self) -> ActiveValue<Option<$ty>> { fn into_active_value(self) -> ActiveValue<Option<$ty>> {
match self { match self {
Some(value) => Set(Some(value)), Some(value) => Set(Some(value)),
None => Unset(None), None => NotSet,
} }
} }
} }
@ -564,7 +567,7 @@ macro_rules! impl_into_active_value {
fn into_active_value(self) -> ActiveValue<Option<$ty>> { fn into_active_value(self) -> ActiveValue<Option<$ty>> {
match self { match self {
Some(value) => Set(value), Some(value) => Set(value),
None => Unset(None), None => NotSet,
} }
} }
} }
@ -613,74 +616,80 @@ impl_into_active_value!(crate::prelude::Decimal, Set);
#[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))]
impl_into_active_value!(crate::prelude::Uuid, Set); impl_into_active_value!(crate::prelude::Uuid, Set);
impl<V> Default for ActiveValue<V>
where
V: Into<Value>,
{
fn default() -> Self {
Self::NotSet
}
}
impl<V> ActiveValue<V> impl<V> ActiveValue<V>
where where
V: Into<Value>, V: Into<Value>,
{ {
/// Set the value of an [ActiveValue] and also set its state to `ActiveValueState::Set` /// Create an [ActiveValue::Set]
pub fn set(value: V) -> Self { pub fn set(value: V) -> Self {
Self { Self::Set(value)
value: Some(value),
state: ActiveValueState::Set,
}
} }
/// Check if the state of an [ActiveValue] is `ActiveValueState::Set` which returns true /// Check if the [ActiveValue] is [ActiveValue::Set]
pub fn is_set(&self) -> bool { pub fn is_set(&self) -> bool {
matches!(self.state, ActiveValueState::Set) matches!(self, Self::Set(_))
} }
pub(crate) fn unchanged(value: V) -> Self { /// Create an [ActiveValue::Unchanged]
Self { pub fn unchanged(value: V) -> Self {
value: Some(value), Self::Unchanged(value)
state: ActiveValueState::Unchanged,
}
} }
/// Check if the status of the [ActiveValue] is `ActiveValueState::Unchanged` /// Check if the [ActiveValue] is [ActiveValue::Unchanged]
/// which returns `true` if it is
pub fn is_unchanged(&self) -> bool { pub fn is_unchanged(&self) -> bool {
matches!(self.state, ActiveValueState::Unchanged) matches!(self, Self::Unchanged(_))
} }
/// Set the `value` field of the ActiveModel to [Option::None] and the /// Create an [ActiveValue::NotSet]
/// `state` field to `ActiveValueState::Unset` pub fn not_set() -> Self {
pub fn unset() -> Self { Self::default()
Self { }
value: None,
state: ActiveValueState::Unset, /// Check if the [ActiveValue] is [ActiveValue::NotSet]
pub fn is_not_set(&self) -> bool {
matches!(self, Self::NotSet)
}
/// Get the mutable value an [ActiveValue]
/// also setting itself to [ActiveValue::NotSet]
pub fn take(&mut self) -> Option<V> {
match std::mem::take(self) {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => Some(value),
ActiveValue::NotSet => None,
} }
} }
/// Check if the state of an [ActiveValue] is `ActiveValueState::Unset` /// Get an owned value of the [ActiveValue]
/// which returns true if it is
pub fn is_unset(&self) -> bool {
matches!(self.state, ActiveValueState::Unset)
}
/// Get the mutable value of the `value` field of an [ActiveValue]
/// also setting it's state to `ActiveValueState::Unset`
pub fn take(&mut self) -> Option<V> {
self.state = ActiveValueState::Unset;
self.value.take()
}
/// Get an owned value of the `value` field of the [ActiveValue]
pub fn unwrap(self) -> V { pub fn unwrap(self) -> V {
self.value.unwrap() match self {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => value,
ActiveValue::NotSet => panic!("Cannot unwrap ActiveValue::NotSet"),
}
} }
/// Check is a [Value] exists or not /// Check is a [Value] exists or not
pub fn into_value(self) -> Option<Value> { pub fn into_value(self) -> Option<Value> {
self.value.map(Into::into) match self {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => Some(value.into()),
ActiveValue::NotSet => None,
}
} }
/// Wrap the [Value] into a `ActiveValue<Value>` /// Wrap the [Value] into a `ActiveValue<Value>`
pub fn into_wrapped_value(self) -> ActiveValue<Value> { pub fn into_wrapped_value(self) -> ActiveValue<Value> {
match self.state { match self {
ActiveValueState::Set => ActiveValue::set(self.into_value().unwrap()), Self::Set(value) => ActiveValue::set(value.into()),
ActiveValueState::Unchanged => ActiveValue::unchanged(self.into_value().unwrap()), Self::Unchanged(value) => ActiveValue::unchanged(value.into()),
ActiveValueState::Unset => ActiveValue::unset(), Self::NotSet => ActiveValue::not_set(),
} }
} }
} }
@ -690,7 +699,10 @@ where
V: Into<Value>, V: Into<Value>,
{ {
fn as_ref(&self) -> &V { fn as_ref(&self) -> &V {
self.value.as_ref().unwrap() match self {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => value,
ActiveValue::NotSet => panic!("Cannot borrow ActiveValue::NotSet"),
}
} }
} }
@ -699,7 +711,12 @@ where
V: Into<Value> + std::cmp::PartialEq, V: Into<Value> + std::cmp::PartialEq,
{ {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.value.as_ref() == other.value.as_ref() 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,
}
} }
} }
@ -708,10 +725,10 @@ where
V: Into<Value> + Nullable, V: Into<Value> + Nullable,
{ {
fn from(value: ActiveValue<V>) -> Self { fn from(value: ActiveValue<V>) -> Self {
match value.state { match value {
ActiveValueState::Set => Set(value.value), ActiveValue::Set(value) => ActiveValue::set(Some(value)),
ActiveValueState::Unset => Unset(None), ActiveValue::Unchanged(value) => ActiveValue::unchanged(Some(value)),
ActiveValueState::Unchanged => ActiveValue::unchanged(value.value), ActiveValue::NotSet => ActiveValue::not_set(),
} }
} }
} }
@ -746,7 +763,7 @@ mod tests {
} }
.into_active_model(), .into_active_model(),
fruit::ActiveModel { fruit::ActiveModel {
id: Unset(None), id: NotSet,
name: Set("Apple".to_owned()), name: Set("Apple".to_owned()),
cake_id: Set(Some(1)), cake_id: Set(Some(1)),
} }
@ -775,8 +792,8 @@ mod tests {
} }
.into_active_model(), .into_active_model(),
fruit::ActiveModel { fruit::ActiveModel {
id: Unset(None), id: NotSet,
name: Unset(None), name: NotSet,
cake_id: Set(Some(1)), cake_id: Set(Some(1)),
} }
); );
@ -787,8 +804,8 @@ mod tests {
} }
.into_active_model(), .into_active_model(),
fruit::ActiveModel { fruit::ActiveModel {
id: Unset(None), id: NotSet,
name: Unset(None), name: NotSet,
cake_id: Set(None), cake_id: Set(None),
} }
); );
@ -796,9 +813,9 @@ mod tests {
assert_eq!( assert_eq!(
my_fruit::UpdateFruit { cake_id: None }.into_active_model(), my_fruit::UpdateFruit { cake_id: None }.into_active_model(),
fruit::ActiveModel { fruit::ActiveModel {
id: Unset(None), id: NotSet,
name: Unset(None), name: NotSet,
cake_id: Unset(None), cake_id: NotSet,
} }
); );
} }

View File

@ -201,12 +201,12 @@
//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; //! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*};
//! # async fn function(db: &DbConn) -> Result<(), DbErr> { //! # async fn function(db: &DbConn) -> Result<(), DbErr> {
//! let banana = fruit::ActiveModel { //! let banana = fruit::ActiveModel {
//! id: Unset(None), //! id: NotSet,
//! name: Set("Banana".to_owned()), //! name: Set("Banana".to_owned()),
//! ..Default::default() //! ..Default::default()
//! }; //! };
//! //!
//! // create, because primary key `id` is `Unset` //! // create, because primary key `id` is `NotSet`
//! let mut banana = banana.save(db).await?.into_active_model(); //! let mut banana = banana.save(db).await?.into_active_model();
//! //!
//! banana.name = Set("Banana Mongo".to_owned()); //! banana.name = Set("Banana Mongo".to_owned());

View File

@ -63,7 +63,7 @@ where
/// ///
/// assert_eq!( /// assert_eq!(
/// Insert::one(cake::ActiveModel { /// Insert::one(cake::ActiveModel {
/// id: Unset(None), /// id: NotSet,
/// name: Set("Apple Pie".to_owned()), /// name: Set("Apple Pie".to_owned()),
/// }) /// })
/// .build(DbBackend::Postgres) /// .build(DbBackend::Postgres)
@ -190,7 +190,7 @@ mod tests {
assert_eq!( assert_eq!(
Insert::<cake::ActiveModel>::new() Insert::<cake::ActiveModel>::new()
.add(cake::ActiveModel { .add(cake::ActiveModel {
id: ActiveValue::unset(), id: ActiveValue::not_set(),
name: ActiveValue::set("Apple Pie".to_owned()), name: ActiveValue::set("Apple Pie".to_owned()),
}) })
.build(DbBackend::Postgres) .build(DbBackend::Postgres)

View File

@ -236,7 +236,7 @@ mod tests {
Update::one(fruit::ActiveModel { Update::one(fruit::ActiveModel {
id: ActiveValue::set(1), id: ActiveValue::set(1),
name: ActiveValue::set("Orange".to_owned()), name: ActiveValue::set("Orange".to_owned()),
cake_id: ActiveValue::unset(), cake_id: ActiveValue::not_set(),
}) })
.build(DbBackend::Postgres) .build(DbBackend::Postgres)
.to_string(), .to_string(),

View File

@ -53,8 +53,8 @@ async fn crud_cake(db: &DbConn) -> Result<(), DbErr> {
assert_eq!( assert_eq!(
apple, apple,
cake::ActiveModel { cake::ActiveModel {
id: Set(1), id: Unchanged(1),
name: Set("Apple Pie".to_owned()), name: Unchanged("Apple Pie".to_owned()),
} }
); );