Save ActiveModel

This commit is contained in:
Chris Tsang 2021-06-05 20:24:32 +08:00
parent f433872c0a
commit cf0127d89f
10 changed files with 198 additions and 11 deletions

View File

@ -5,12 +5,14 @@ mod example_cake_filling;
mod example_filling; mod example_filling;
mod example_fruit; mod example_fruit;
mod select; mod select;
mod operation;
use example_cake as cake; use example_cake as cake;
use example_cake_filling as cake_filling; use example_cake_filling as cake_filling;
use example_filling as filling; use example_filling as filling;
use example_fruit as fruit; use example_fruit as fruit;
use select::*; use select::*;
use operation::*;
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
@ -25,4 +27,8 @@ async fn main() {
println!("===== =====\n"); println!("===== =====\n");
all_about_select(&db).await.unwrap(); all_about_select(&db).await.unwrap();
println!("===== =====\n");
all_about_operation(&db).await.unwrap();
} }

View File

@ -0,0 +1,22 @@
use crate::*;
use sea_orm::{entity::*, query::*, Database};
pub async fn all_about_operation(db: &Database) -> Result<(), ExecErr> {
let banana = fruit::ActiveModel {
name: Val::set("banana".to_owned()),
..Default::default()
};
let mut banana = banana.save(db).await?;
println!();
println!("Inserted: {:?}\n", banana);
banana.name = Val::set("banana banana".to_owned());
let banana = banana.save(db).await?;
println!();
println!("Updated: {:?}\n", banana);
Ok(())
}

View File

@ -36,6 +36,12 @@ 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::Database) -> Result<Self, sea_orm::ExecErr> {
sea_orm::save_active_model::<Self, Entity>(self, db).await
}
}
impl Default for ActiveModel { impl Default for ActiveModel {
fn default() -> Self { fn default() -> Self {
<Self as sea_orm::ActiveModelBehavior>::new() <Self as sea_orm::ActiveModelBehavior>::new()
@ -77,6 +83,12 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
} }
} }
fn is_unset(&self, c: <Self::Entity as EntityTrait>::Column) -> bool {
match c {
#(<Self::Entity as EntityTrait>::Column::#name => self.#field.is_unset()),*
}
}
fn default() -> Self { fn default() -> Self {
Self { Self {
#(#field: sea_orm::ActiveValue::unset()),* #(#field: sea_orm::ActiveValue::unset()),*

View File

@ -1,5 +1,6 @@
use crate::{Connection, Database, ExecErr, Statement}; use crate::{ActiveModelTrait, Connection, Database, ExecErr, Insert, QueryTrait, Statement};
use sea_query::{InsertStatement, QueryBuilder}; use sea_query::{InsertStatement, QueryBuilder};
use std::future::Future;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Inserter { pub struct Inserter {
@ -11,6 +12,16 @@ pub struct InsertResult {
pub last_insert_id: u64, pub last_insert_id: u64,
} }
impl<A> Insert<A>
where
A: ActiveModelTrait,
{
pub fn exec(self, db: &Database) -> impl Future<Output = Result<InsertResult, ExecErr>> + '_ {
// so that self is dropped before entering await
Inserter::new(self.into_query()).exec(db)
}
}
impl Inserter { impl Inserter {
pub fn new(query: InsertStatement) -> Self { pub fn new(query: InsertStatement) -> Self {
Self { query } Self { query }
@ -23,12 +34,17 @@ impl Inserter {
self.query.build(builder).into() self.query.build(builder).into()
} }
pub async fn exec(self, db: &Database) -> Result<InsertResult, ExecErr> { pub fn exec(self, db: &Database) -> impl Future<Output = Result<InsertResult, ExecErr>> + '_ {
let builder = db.get_query_builder_backend(); let builder = db.get_query_builder_backend();
let result = db.get_connection().execute(self.build(builder)).await?; exec_insert(self.build(builder), db)
// TODO: Postgres instead use query_one + returning clause
Ok(InsertResult {
last_insert_id: result.last_insert_id(),
})
} }
} }
// Only Statement impl Send
async fn exec_insert(statement: Statement, db: &Database) -> Result<InsertResult, ExecErr> {
let result = db.get_connection().execute(statement).await?;
// TODO: Postgres instead use query_one + returning clause
Ok(InsertResult {
last_insert_id: result.last_insert_id(),
})
}

View File

@ -1,10 +1,12 @@
mod executor; mod executor;
mod insert; mod insert;
mod select; mod select;
mod update;
pub use executor::*; pub use executor::*;
pub use insert::*; pub use insert::*;
pub use select::*; pub use select::*;
pub use update::*;
use crate::{DatabaseConnection, QueryResult, Statement, TypeErr}; use crate::{DatabaseConnection, QueryResult, Statement, TypeErr};
use async_trait::async_trait; use async_trait::async_trait;

61
src/connector/update.rs Normal file
View File

@ -0,0 +1,61 @@
use crate::{ActiveModelTrait, Connection, Database, ExecErr, Statement, Update};
use sea_query::{QueryBuilder, UpdateStatement};
use std::future::Future;
#[derive(Clone, Debug)]
pub struct Updater {
query: UpdateStatement,
}
#[derive(Clone, Debug)]
pub struct UpdateResult {
pub rows_affected: u64,
}
impl<'a, A: 'a> Update<A>
where
A: ActiveModelTrait,
{
pub fn exec(self, db: &'a Database) -> impl Future<Output = Result<A, ExecErr>> + 'a {
// so that self is dropped before entering await
exec_update_and_return_original(self.query, self.model, db)
}
}
impl Updater {
pub fn new(query: UpdateStatement) -> Self {
Self { query }
}
pub fn build<B>(&self, builder: B) -> Statement
where
B: QueryBuilder,
{
self.query.build(builder).into()
}
pub fn exec(self, db: &Database) -> impl Future<Output = Result<UpdateResult, ExecErr>> + '_ {
let builder = db.get_query_builder_backend();
exec_update(self.build(builder), db)
}
}
async fn exec_update_and_return_original<A>(
query: UpdateStatement,
model: A,
db: &Database,
) -> Result<A, ExecErr>
where
A: ActiveModelTrait,
{
Updater::new(query).exec(db).await?;
Ok(model)
}
// Only Statement impl Send
async fn exec_update(statement: Statement, db: &Database) -> Result<UpdateResult, ExecErr> {
let result = db.get_connection().execute(statement).await?;
Ok(UpdateResult {
rows_affected: result.rows_affected(),
})
}

View File

@ -1,4 +1,5 @@
use crate::{EntityTrait, Value}; use crate::{Database, EntityTrait, ExecErr, Iterable, PrimaryKeyToColumn, Value};
use async_trait::async_trait;
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -44,6 +45,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;
@ -55,15 +57,29 @@ pub trait ActiveModelTrait: Clone + Debug {
fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column); fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column);
fn is_unset(&self, c: <Self::Entity as EntityTrait>::Column) -> bool;
fn default() -> Self; fn default() -> Self;
} }
/// Behaviors for users to override
pub trait ActiveModelBehavior: ActiveModelTrait { pub trait ActiveModelBehavior: ActiveModelTrait {
type Entity: EntityTrait; type Entity: EntityTrait;
/// Create a new ActiveModel with default values. Also used by `Default::default()`.
fn new() -> Self { fn new() -> Self {
<Self as ActiveModelTrait>::default() <Self as ActiveModelTrait>::default()
} }
/// Will be called before saving to database
fn before_save(self) -> Self {
self
}
/// Will be called after saving to database
fn after_save(self) -> Self {
self
}
} }
impl<V> ActiveValue<V> impl<V> ActiveValue<V>
@ -119,7 +135,7 @@ where
pub fn into_wrapped_value(self) -> ActiveValue<Value> { pub fn into_wrapped_value(self) -> ActiveValue<Value> {
match self.state { match self.state {
ActiveValueState::Set => ActiveValue::set(self.into_value()), ActiveValueState::Set => ActiveValue::set(self.into_value()),
ActiveValueState::Unchanged => ActiveValue::set(self.into_value()), ActiveValueState::Unchanged => ActiveValue::unchanged(self.into_value()),
ActiveValueState::Unset => ActiveValue::unset(), ActiveValueState::Unset => ActiveValue::unset(),
} }
} }
@ -171,3 +187,53 @@ where
self self
} }
} }
/// Insert the model if primary key is unset, update otherwise
pub async fn save_active_model<A, E>(mut am: A, db: &Database) -> Result<A, ExecErr>
where
A: ActiveModelBehavior + ActiveModelTrait<Entity = E> + From<E::Model>,
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: &Database) -> Result<A, ExecErr>
where
A: ActiveModelTrait<Entity = E> + From<E::Model>,
E: EntityTrait,
{
let exec = E::insert(am).exec(db);
let res = exec.await?;
if res.last_insert_id != 0 {
let find = E::find_by(res.last_insert_id).one(db);
let res = find.await;
let model: E::Model = res.map_err(|_| ExecErr)?;
Ok(model.into())
} else {
Ok(A::default())
}
}
async fn update_active_model<A, E>(am: A, db: &Database) -> Result<A, ExecErr>
where
A: ActiveModelTrait<Entity = E>,
E: EntityTrait,
{
let exec = E::update(am).exec(db);
exec.await
}

View File

@ -50,7 +50,7 @@ where
} else if self.columns[idx] != av.is_set() { } else if self.columns[idx] != av.is_set() {
panic!("columns mismatch"); panic!("columns mismatch");
} }
if av.is_set() { if av.is_set() || av.is_unchanged() {
columns.push(col); columns.push(col);
values.push(av.into_value()); values.push(av.into_value());
} }

View File

@ -19,3 +19,5 @@ pub use result::*;
pub use select::*; pub use select::*;
pub use traits::*; pub use traits::*;
pub use update::*; pub use update::*;
pub use crate::connector::{QueryErr, ExecErr};

View File

@ -121,7 +121,7 @@ mod tests {
assert_eq!( assert_eq!(
Update::<fruit::ActiveModel>::new(fruit::ActiveModel { Update::<fruit::ActiveModel>::new(fruit::ActiveModel {
id: Val::set(2), id: Val::set(2),
name: Val::unset(), name: Val::unchanged("Apple".to_owned()),
cake_id: Val::set(Some(3)), cake_id: Val::set(Some(3)),
}) })
.build(PostgresQueryBuilder) .build(PostgresQueryBuilder)