Merge pull request #132 from SeaQL/update-and-insert

Add ActiveModel insert & update
This commit is contained in:
Chris Tsang 2021-09-10 14:30:54 +08:00 committed by GitHub
commit 1775ab478c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 89 additions and 109 deletions

View File

@ -23,6 +23,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
async-stream = { version = "^0.3" } async-stream = { version = "^0.3" }
async-trait = { version = "^0.1" }
chrono = { version = "^0", optional = true } chrono = { version = "^0", optional = true }
futures = { version = "^0.3" } futures = { version = "^0.3" }
futures-util = { version = "^0.3" } futures-util = { version = "^0.3" }

View File

@ -98,7 +98,7 @@ let mut pear: fruit::ActiveModel = pear.unwrap().into();
pear.name = Set("Sweet pear".to_owned()); pear.name = Set("Sweet pear".to_owned());
// update one // update one
let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; let pear: fruit::ActiveModel = pear.update(db).await?;
// update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%'
Fruit::update_many() Fruit::update_many()

View File

@ -33,7 +33,7 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> {
let mut pear: fruit::ActiveModel = pear.unwrap().into(); let mut pear: fruit::ActiveModel = pear.unwrap().into();
pear.name = Set("Sweet pear".to_owned()); pear.name = Set("Sweet pear".to_owned());
let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; let pear: fruit::ActiveModel = pear.update(db).await?;
println!(); println!();
println!("Updated: {:?}\n", pear); println!("Updated: {:?}\n", pear);

View File

@ -36,16 +36,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
#(pub #field: sea_orm::ActiveValue<#ty>),* #(pub #field: sea_orm::ActiveValue<#ty>),*
} }
impl ActiveModel {
pub async fn save(self, db: &sea_orm::DatabaseConnection) -> Result<Self, sea_orm::DbErr> {
sea_orm::save_active_model::<Self, Entity>(self, db).await
}
pub async fn delete(self, db: &sea_orm::DatabaseConnection) -> Result<sea_orm::DeleteResult, sea_orm::DbErr> {
sea_orm::delete_active_model::<Self, Entity>(self, db).await
}
}
impl std::default::Default for ActiveModel { impl std::default::Default for ActiveModel {
fn default() -> Self { fn default() -> Self {
<Self as sea_orm::ActiveModelBehavior>::new() <Self as sea_orm::ActiveModelBehavior>::new()

View File

@ -2,6 +2,7 @@ use crate::{
error::*, DatabaseConnection, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, error::*, DatabaseConnection, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn,
PrimaryKeyTrait, Value, PrimaryKeyTrait, Value,
}; };
use async_trait::async_trait;
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -50,6 +51,7 @@ where
ActiveValue::unchanged(value) ActiveValue::unchanged(value)
} }
#[async_trait]
pub trait ActiveModelTrait: Clone + Debug { pub trait ActiveModelTrait: Clone + Debug {
type Entity: EntityTrait; type Entity: EntityTrait;
@ -65,9 +67,71 @@ pub trait ActiveModelTrait: Clone + Debug {
fn default() -> Self; fn default() -> Self;
// below is not yet possible. right now we define these methods in DeriveActiveModel async fn insert(self, db: &DatabaseConnection) -> Result<Self, DbErr>
// fn save(self, db: &DatabaseConnection) -> impl Future<Output = Result<Self, DbErr>>; where
// fn delete(self, db: &DatabaseConnection) -> impl Future<Output = Result<DeleteResult, DbErr>>; <Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
{
let am = self;
let exec = <Self::Entity as EntityTrait>::insert(am).exec(db);
let res = exec.await?;
// TODO: if the entity does not have auto increment primary key, then last_insert_id is a wrong value
// FIXME: Assumed valid last_insert_id is not equals to Default::default()
if <<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::auto_increment()
&& res.last_insert_id != <<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType::default()
{
let find = <Self::Entity as EntityTrait>::find_by_id(res.last_insert_id).one(db);
let found = find.await;
let model: Option<<Self::Entity as EntityTrait>::Model> = found?;
match model {
Some(model) => Ok(model.into_active_model()),
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
}
} else {
Ok(Self::default())
}
}
async fn update(self, db: &DatabaseConnection) -> Result<Self, DbErr> {
let exec = Self::Entity::update(self).exec(db);
exec.await
}
/// Insert the model if primary key is unset, update otherwise.
/// Only works if the entity has auto increment primary key.
async fn save(self, db: &DatabaseConnection) -> Result<Self, DbErr>
where
Self: ActiveModelBehavior,
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
{
let mut am = self;
am = ActiveModelBehavior::before_save(am);
let mut is_update = true;
for key in <Self::Entity as EntityTrait>::PrimaryKey::iter() {
let col = key.into_column();
if am.is_unset(col) {
is_update = false;
break;
}
}
if !is_update {
am = am.insert(db).await?;
} else {
am = am.update(db).await?;
}
am = ActiveModelBehavior::after_save(am);
Ok(am)
}
/// Delete an active model by its primary key
async fn delete(self, db: &DatabaseConnection) -> Result<DeleteResult, DbErr>
where
Self: ActiveModelBehavior,
{
let mut am = self;
am = ActiveModelBehavior::before_delete(am);
let exec = Self::Entity::delete(am).exec(db);
exec.await
}
} }
/// Behaviors for users to override /// Behaviors for users to override
@ -185,80 +249,3 @@ where
self.value.as_ref() == other.value.as_ref() self.value.as_ref() == other.value.as_ref()
} }
} }
/// Insert the model if primary key is unset, update otherwise.
/// Only works if the entity has auto increment primary key.
pub async fn save_active_model<A, E>(mut am: A, db: &DatabaseConnection) -> Result<A, DbErr>
where
A: ActiveModelBehavior + ActiveModelTrait<Entity = E>,
E::Model: IntoActiveModel<A>,
E: EntityTrait,
{
am = ActiveModelBehavior::before_save(am);
let mut is_update = true;
for key in E::PrimaryKey::iter() {
let col = key.into_column();
if am.is_unset(col) {
is_update = false;
break;
}
}
if !is_update {
am = insert_and_select_active_model::<A, E>(am, db).await?;
} else {
am = update_active_model::<A, E>(am, db).await?;
}
am = ActiveModelBehavior::after_save(am);
Ok(am)
}
async fn insert_and_select_active_model<A, E>(am: A, db: &DatabaseConnection) -> Result<A, DbErr>
where
A: ActiveModelTrait<Entity = E>,
E::Model: IntoActiveModel<A>,
E: EntityTrait,
{
let exec = E::insert(am).exec(db);
let res = exec.await?;
// TODO: if the entity does not have auto increment primary key, then last_insert_id is a wrong value
// FIXME: Assumed valid last_insert_id is not equals to Default::default()
if <E::PrimaryKey as PrimaryKeyTrait>::auto_increment()
&& res.last_insert_id != <E::PrimaryKey as PrimaryKeyTrait>::ValueType::default()
{
let find = E::find_by_id(res.last_insert_id).one(db);
let found = find.await;
let model: Option<E::Model> = found?;
match model {
Some(model) => Ok(model.into_active_model()),
None => Err(DbErr::Exec(format!(
"Failed to find inserted item: {}",
E::default().to_string(),
))),
}
} else {
Ok(A::default())
}
}
async fn update_active_model<A, E>(am: A, db: &DatabaseConnection) -> Result<A, DbErr>
where
A: ActiveModelTrait<Entity = E>,
E: EntityTrait,
{
let exec = E::update(am).exec(db);
exec.await
}
/// Delete an active model by its primary key
pub async fn delete_active_model<A, E>(
mut am: A,
db: &DatabaseConnection,
) -> Result<DeleteResult, DbErr>
where
A: ActiveModelBehavior + ActiveModelTrait<Entity = E>,
E: EntityTrait,
{
am = ActiveModelBehavior::before_delete(am);
let exec = E::delete(am).exec(db);
exec.await
}

View File

@ -386,7 +386,7 @@ pub trait EntityTrait: EntityName {
/// # let _: Result<(), DbErr> = smol::block_on(async { /// # let _: Result<(), DbErr> = smol::block_on(async {
/// # /// #
/// assert_eq!( /// assert_eq!(
/// fruit::Entity::update(orange.clone()).exec(&db).await?, // Clone here because we need to assert_eq /// orange.clone().update(&db).await?, // Clone here because we need to assert_eq
/// orange /// orange
/// ); /// );
/// # /// #

View File

@ -2,7 +2,7 @@ use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Selec
pub use sea_query::Value; pub use sea_query::Value;
use std::fmt::Debug; use std::fmt::Debug;
pub trait ModelTrait: Clone + Debug { pub trait ModelTrait: Clone + Send + Debug {
type Entity: EntityTrait; type Entity: EntityTrait;
fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value; fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;

View File

@ -6,6 +6,7 @@ use std::fmt::Debug;
//LINT: composite primary key cannot auto increment //LINT: composite primary key cannot auto increment
pub trait PrimaryKeyTrait: IdenStatic + Iterable { pub trait PrimaryKeyTrait: IdenStatic + Iterable {
type ValueType: Sized type ValueType: Sized
+ Send
+ Default + Default
+ Debug + Debug
+ PartialEq + PartialEq

View File

@ -126,7 +126,7 @@
//! pear.name = Set("Sweet pear".to_owned()); //! pear.name = Set("Sweet pear".to_owned());
//! //!
//! // update one //! // update one
//! let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; //! let pear: fruit::ActiveModel = pear.update(db).await?;
//! //!
//! // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' //! // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%'
//! Fruit::update_many() //! Fruit::update_many()

View File

@ -47,13 +47,14 @@ impl Update {
E: EntityTrait, E: EntityTrait,
A: ActiveModelTrait<Entity = E>, A: ActiveModelTrait<Entity = E>,
{ {
let myself = UpdateOne { UpdateOne {
query: UpdateStatement::new() query: UpdateStatement::new()
.table(A::Entity::default().table_ref()) .table(A::Entity::default().table_ref())
.to_owned(), .to_owned(),
model, model,
}; }
myself.prepare() .prepare_filters()
.prepare_values()
} }
/// Update many ActiveModel /// Update many ActiveModel
@ -85,7 +86,7 @@ impl<A> UpdateOne<A>
where where
A: ActiveModelTrait, A: ActiveModelTrait,
{ {
pub(crate) fn prepare(mut self) -> Self { pub(crate) fn prepare_filters(mut self) -> Self {
for key in <A::Entity as EntityTrait>::PrimaryKey::iter() { for key in <A::Entity as EntityTrait>::PrimaryKey::iter() {
let col = key.into_column(); let col = key.into_column();
let av = self.model.get(col); let av = self.model.get(col);
@ -95,6 +96,10 @@ where
panic!("PrimaryKey is not set"); panic!("PrimaryKey is not set");
} }
} }
self
}
pub(crate) fn prepare_values(mut self) -> Self {
for col in <A::Entity as EntityTrait>::Column::iter() { for col in <A::Entity as EntityTrait>::Column::iter() {
if <A::Entity as EntityTrait>::PrimaryKey::from_column(col).is_some() { if <A::Entity as EntityTrait>::PrimaryKey::from_column(col).is_some() {
continue; continue;

View File

@ -42,10 +42,8 @@ pub async fn test_update_cake(db: &DbConn) {
cake_am.name = Set("Extra chocolate mud cake".to_owned()); cake_am.name = Set("Extra chocolate mud cake".to_owned());
cake_am.price = Set(dec!(20.00)); cake_am.price = Set(dec!(20.00));
let _cake_update_res: cake::ActiveModel = Cake::update(cake_am) let _cake_update_res: cake::ActiveModel =
.exec(db) cake_am.update(db).await.expect("could not update cake");
.await
.expect("could not update cake");
let cake: Option<cake::Model> = Cake::find_by_id(cake_insert_res.last_insert_id) let cake: Option<cake::Model> = Cake::find_by_id(cake_insert_res.last_insert_id)
.one(db) .one(db)
@ -81,10 +79,8 @@ pub async fn test_update_bakery(db: &DbConn) {
bakery_am.name = Set("SeaBreeze Bakery".to_owned()); bakery_am.name = Set("SeaBreeze Bakery".to_owned());
bakery_am.profit_margin = Set(12.00); bakery_am.profit_margin = Set(12.00);
let _bakery_update_res: bakery::ActiveModel = Bakery::update(bakery_am) let _bakery_update_res: bakery::ActiveModel =
.exec(db) bakery_am.update(db).await.expect("could not update bakery");
.await
.expect("could not update bakery");
let bakery: Option<bakery::Model> = Bakery::find_by_id(bakery_insert_res.last_insert_id) let bakery: Option<bakery::Model> = Bakery::find_by_id(bakery_insert_res.last_insert_id)
.one(db) .one(db)
@ -123,8 +119,8 @@ pub async fn test_update_deleted_customer(db: &DbConn) {
..Default::default() ..Default::default()
}; };
let _customer_update_res: customer::ActiveModel = Customer::update(customer) let _customer_update_res: customer::ActiveModel = customer
.exec(db) .update(db)
.await .await
.expect("could not update customer"); .expect("could not update customer");