Merge pull request #132 from SeaQL/update-and-insert
Add ActiveModel insert & update
This commit is contained in:
commit
1775ab478c
@ -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" }
|
||||||
|
@ -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()
|
||||||
|
@ -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);
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
/// );
|
/// );
|
||||||
/// #
|
/// #
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user