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
```rust
let banana = fruit::ActiveModel {
id: Unset(None),
id: NotSet,
name: Set("Banana".to_owned()),
..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();
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> {
let pineapple = form::ActiveModel {
id: Unset(None),
id: NotSet,
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 {
fn from(m: <Entity as EntityTrait>::Model) -> 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> {
match c {
#(<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);
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> {
match c {
#(<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 {
#(<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 {
#(<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"),
}
}
fn default() -> 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 std::fmt::Debug;
/// Defines a value from an ActiveModel and its state.
/// 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
/// 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"# `
pub use ActiveValue::NotSet;
/// Defines a stateful value used in ActiveModel.
///
/// 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::{entity::*, query::*, DbBackend};
///
/// Update::one(fruit::ActiveModel {
/// id: ActiveValue::set(1),
/// name: ActiveValue::set("Orange".to_owned()),
/// cake_id: ActiveValue::unset(),
/// })
/// .build(DbBackend::Postgres)
/// .to_string();
/// // The code snipped below does an UPDATE operation on a `ActiveValue`
/// assert_eq!(
/// Update::one(fruit::ActiveModel {
/// id: ActiveValue::set(1),
/// name: ActiveValue::set("Orange".to_owned()),
/// cake_id: ActiveValue::not_set(),
/// })
/// .build(DbBackend::Postgres)
/// .to_string(),
/// r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"#
/// );
/// ```
#[derive(Clone, Debug, Default)]
pub struct ActiveValue<V>
#[derive(Clone, Debug)]
pub enum ActiveValue<V>
where
V: Into<Value>,
{
value: Option<V>,
state: ActiveValueState,
/// A [Value] was set
Set(V),
/// A [Value] remain unchanged
Unchanged(V),
/// A NULL value similar to [Option::None]
NotSet,
}
/// Defines a set operation on an [ActiveValue]
@ -45,31 +57,22 @@ where
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)]
pub fn Unset<V>(_: Option<bool>) -> ActiveValue<V>
where
V: Into<Value>,
{
ActiveValue::unset()
ActiveValue::not_set()
}
// Defines the state of an [ActiveValue]
#[derive(Clone, Debug)]
enum ActiveValueState {
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>
/// Defines an unchanged operation on an [ActiveValue]
#[allow(non_snake_case)]
pub fn Unchanged<V>(value: V) -> ActiveValue<V>
where
V: Into<Value>,
{
@ -93,11 +96,11 @@ pub trait ActiveModelTrait: Clone + Debug {
/// Set the Value into an ActiveModel
fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value);
/// Set the state of an [ActiveValue] to the Unset state
fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column);
/// Set the state of an [ActiveValue] to the not set state
fn not_set(&mut self, c: <Self::Entity as EntityTrait>::Column);
/// 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
fn default() -> Self;
@ -378,7 +381,7 @@ pub trait ActiveModelTrait: Clone + Debug {
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.
async fn save<'a, C>(self, db: &'a C) -> Result<<Self::Entity as EntityTrait>::Model, DbErr>
where
@ -390,7 +393,7 @@ pub trait ActiveModelTrait: Clone + Debug {
let mut is_update = true;
for key in <Self::Entity as EntityTrait>::PrimaryKey::iter() {
let col = key.into_column();
if am.is_unset(col) {
if am.is_not_set(col) {
is_update = false;
break;
}
@ -555,7 +558,7 @@ macro_rules! impl_into_active_value {
fn into_active_value(self) -> ActiveValue<Option<$ty>> {
match self {
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>> {
match self {
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")))]
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>
where
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 {
Self {
value: Some(value),
state: ActiveValueState::Set,
}
Self::Set(value)
}
/// 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 {
matches!(self.state, ActiveValueState::Set)
matches!(self, Self::Set(_))
}
pub(crate) fn unchanged(value: V) -> Self {
Self {
value: Some(value),
state: ActiveValueState::Unchanged,
}
/// Create an [ActiveValue::Unchanged]
pub fn unchanged(value: V) -> Self {
Self::Unchanged(value)
}
/// Check if the status of the [ActiveValue] is `ActiveValueState::Unchanged`
/// which returns `true` if it is
/// Check if the [ActiveValue] is [ActiveValue::Unchanged]
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
/// `state` field to `ActiveValueState::Unset`
pub fn unset() -> Self {
Self {
value: None,
state: ActiveValueState::Unset,
/// Create an [ActiveValue::NotSet]
pub fn not_set() -> Self {
Self::default()
}
/// 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`
/// 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]
/// Get an owned value of the [ActiveValue]
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
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>`
pub fn into_wrapped_value(self) -> ActiveValue<Value> {
match self.state {
ActiveValueState::Set => ActiveValue::set(self.into_value().unwrap()),
ActiveValueState::Unchanged => ActiveValue::unchanged(self.into_value().unwrap()),
ActiveValueState::Unset => ActiveValue::unset(),
match self {
Self::Set(value) => ActiveValue::set(value.into()),
Self::Unchanged(value) => ActiveValue::unchanged(value.into()),
Self::NotSet => ActiveValue::not_set(),
}
}
}
@ -690,7 +699,10 @@ where
V: Into<Value>,
{
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,
{
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,
{
fn from(value: ActiveValue<V>) -> Self {
match value.state {
ActiveValueState::Set => Set(value.value),
ActiveValueState::Unset => Unset(None),
ActiveValueState::Unchanged => ActiveValue::unchanged(value.value),
match value {
ActiveValue::Set(value) => ActiveValue::set(Some(value)),
ActiveValue::Unchanged(value) => ActiveValue::unchanged(Some(value)),
ActiveValue::NotSet => ActiveValue::not_set(),
}
}
}
@ -746,7 +763,7 @@ mod tests {
}
.into_active_model(),
fruit::ActiveModel {
id: Unset(None),
id: NotSet,
name: Set("Apple".to_owned()),
cake_id: Set(Some(1)),
}
@ -775,8 +792,8 @@ mod tests {
}
.into_active_model(),
fruit::ActiveModel {
id: Unset(None),
name: Unset(None),
id: NotSet,
name: NotSet,
cake_id: Set(Some(1)),
}
);
@ -787,8 +804,8 @@ mod tests {
}
.into_active_model(),
fruit::ActiveModel {
id: Unset(None),
name: Unset(None),
id: NotSet,
name: NotSet,
cake_id: Set(None),
}
);
@ -796,9 +813,9 @@ mod tests {
assert_eq!(
my_fruit::UpdateFruit { cake_id: None }.into_active_model(),
fruit::ActiveModel {
id: Unset(None),
name: Unset(None),
cake_id: Unset(None),
id: NotSet,
name: NotSet,
cake_id: NotSet,
}
);
}

View File

@ -201,12 +201,12 @@
//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*};
//! # async fn function(db: &DbConn) -> Result<(), DbErr> {
//! let banana = fruit::ActiveModel {
//! id: Unset(None),
//! id: NotSet,
//! name: Set("Banana".to_owned()),
//! ..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();
//!
//! banana.name = Set("Banana Mongo".to_owned());

View File

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

View File

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

View File

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