Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActiveModel insert & update #132

Merged
merged 5 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ path = "src/lib.rs"

[dependencies]
async-stream = { version = "^0.3" }
async-trait = { version = "^0.1" }
chrono = { version = "^0", optional = true }
futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ let mut pear: fruit::ActiveModel = pear.unwrap().into();
pear.name = Set("Sweet pear".to_owned());

// 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%'
Fruit::update_many()
Expand Down
2 changes: 1 addition & 1 deletion examples/async-std/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> {
let mut pear: fruit::ActiveModel = pear.unwrap().into();
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!("Updated: {:?}\n", pear);
Expand Down
10 changes: 0 additions & 10 deletions sea-orm-macros/src/derives/active_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
#(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
}
}
tyt2y3 marked this conversation as resolved.
Show resolved Hide resolved

impl std::default::Default for ActiveModel {
fn default() -> Self {
<Self as sea_orm::ActiveModelBehavior>::new()
Expand Down
147 changes: 67 additions & 80 deletions src/entity/active_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
error::*, DatabaseConnection, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn,
PrimaryKeyTrait, Value,
};
use async_trait::async_trait;
use std::fmt::Debug;

#[derive(Clone, Debug, Default)]
Expand Down Expand Up @@ -50,6 +51,7 @@ where
ActiveValue::unchanged(value)
}

#[async_trait]
pub trait ActiveModelTrait: Clone + Debug {
type Entity: EntityTrait;

Expand All @@ -65,9 +67,71 @@ pub trait ActiveModelTrait: Clone + Debug {

fn default() -> Self;

// below is not yet possible. right now we define these methods in DeriveActiveModel
// fn save(self, db: &DatabaseConnection) -> impl Future<Output = Result<Self, DbErr>>;
// fn delete(self, db: &DatabaseConnection) -> impl Future<Output = Result<DeleteResult, DbErr>>;
async fn insert(self, db: &DatabaseConnection) -> Result<Self, DbErr>
where
<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?;
tyt2y3 marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -185,80 +249,3 @@ where
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
}
2 changes: 1 addition & 1 deletion src/entity/base_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ pub trait EntityTrait: EntityName {
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// 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
/// );
/// #
Expand Down
2 changes: 1 addition & 1 deletion src/entity/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Selec
pub use sea_query::Value;
use std::fmt::Debug;

pub trait ModelTrait: Clone + Debug {
tyt2y3 marked this conversation as resolved.
Show resolved Hide resolved
pub trait ModelTrait: Clone + Send + Debug {
type Entity: EntityTrait;

fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;
Expand Down
1 change: 1 addition & 0 deletions src/entity/primary_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::fmt::Debug;
//LINT: composite primary key cannot auto increment
pub trait PrimaryKeyTrait: IdenStatic + Iterable {
type ValueType: Sized
+ Send
+ Default
+ Debug
+ PartialEq
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
//! pear.name = Set("Sweet pear".to_owned());
//!
//! // 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%'
//! Fruit::update_many()
Expand Down
13 changes: 9 additions & 4 deletions src/query/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ impl Update {
E: EntityTrait,
A: ActiveModelTrait<Entity = E>,
{
let myself = UpdateOne {
UpdateOne {
query: UpdateStatement::new()
.table(A::Entity::default().table_ref())
.to_owned(),
model,
};
myself.prepare()
}
.prepare_filters()
.prepare_values()
}

/// Update many ActiveModel
Expand Down Expand Up @@ -85,7 +86,7 @@ impl<A> UpdateOne<A>
where
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() {
let col = key.into_column();
let av = self.model.get(col);
Expand All @@ -95,6 +96,10 @@ where
panic!("PrimaryKey is not set");
}
}
self
}

pub(crate) fn prepare_values(mut self) -> Self {
for col in <A::Entity as EntityTrait>::Column::iter() {
if <A::Entity as EntityTrait>::PrimaryKey::from_column(col).is_some() {
continue;
Expand Down
16 changes: 6 additions & 10 deletions tests/crud/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ pub async fn test_update_cake(db: &DbConn) {
cake_am.name = Set("Extra chocolate mud cake".to_owned());
cake_am.price = Set(dec!(20.00));

let _cake_update_res: cake::ActiveModel = Cake::update(cake_am)
.exec(db)
.await
.expect("could not update cake");
let _cake_update_res: cake::ActiveModel =
cake_am.update(db).await.expect("could not update cake");

let cake: Option<cake::Model> = Cake::find_by_id(cake_insert_res.last_insert_id)
.one(db)
Expand Down Expand Up @@ -81,10 +79,8 @@ pub async fn test_update_bakery(db: &DbConn) {
bakery_am.name = Set("SeaBreeze Bakery".to_owned());
bakery_am.profit_margin = Set(12.00);

let _bakery_update_res: bakery::ActiveModel = Bakery::update(bakery_am)
.exec(db)
.await
.expect("could not update bakery");
let _bakery_update_res: bakery::ActiveModel =
bakery_am.update(db).await.expect("could not update bakery");

let bakery: Option<bakery::Model> = Bakery::find_by_id(bakery_insert_res.last_insert_id)
.one(db)
Expand Down Expand Up @@ -123,8 +119,8 @@ pub async fn test_update_deleted_customer(db: &DbConn) {
..Default::default()
};

let _customer_update_res: customer::ActiveModel = Customer::update(customer)
.exec(db)
let _customer_update_res: customer::ActiveModel = customer
.update(db)
.await
.expect("could not update customer");

Expand Down