Skip to content

Commit

Permalink
Save ActiveModel
Browse files Browse the repository at this point in the history
  • Loading branch information
tyt2y3 committed Jun 5, 2021
1 parent f433872 commit cf0127d
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 11 deletions.
6 changes: 6 additions & 0 deletions examples/sqlx-mysql/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ mod example_cake_filling;
mod example_filling;
mod example_fruit;
mod select;
mod operation;

use example_cake as cake;
use example_cake_filling as cake_filling;
use example_filling as filling;
use example_fruit as fruit;
use select::*;
use operation::*;

#[async_std::main]
async fn main() {
Expand All @@ -25,4 +27,8 @@ async fn main() {
println!("===== =====\n");

all_about_select(&db).await.unwrap();

println!("===== =====\n");

all_about_operation(&db).await.unwrap();
}
22 changes: 22 additions & 0 deletions examples/sqlx-mysql/src/operation.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
12 changes: 12 additions & 0 deletions sea-orm-macros/src/derives/active_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ 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::Database) -> Result<Self, sea_orm::ExecErr> {
sea_orm::save_active_model::<Self, Entity>(self, db).await
}
}

impl Default for ActiveModel {
fn default() -> Self {
<Self as sea_orm::ActiveModelBehavior>::new()
Expand Down Expand Up @@ -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 {
Self {
#(#field: sea_orm::ActiveValue::unset()),*
Expand Down
30 changes: 23 additions & 7 deletions src/connector/insert.rs
Original file line number Diff line number Diff line change
@@ -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 std::future::Future;

#[derive(Clone, Debug)]
pub struct Inserter {
Expand All @@ -11,6 +12,16 @@ pub struct InsertResult {
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 {
pub fn new(query: InsertStatement) -> Self {
Self { query }
Expand All @@ -23,12 +34,17 @@ impl Inserter {
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 result = db.get_connection().execute(self.build(builder)).await?;
// TODO: Postgres instead use query_one + returning clause
Ok(InsertResult {
last_insert_id: result.last_insert_id(),
})
exec_insert(self.build(builder), db)
}
}

// 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(),
})
}
2 changes: 2 additions & 0 deletions src/connector/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod executor;
mod insert;
mod select;
mod update;

pub use executor::*;
pub use insert::*;
pub use select::*;
pub use update::*;

use crate::{DatabaseConnection, QueryResult, Statement, TypeErr};
use async_trait::async_trait;
Expand Down
61 changes: 61 additions & 0 deletions src/connector/update.rs
Original file line number Diff line number Diff line change
@@ -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(),
})
}
70 changes: 68 additions & 2 deletions src/entity/active_model.rs
Original file line number Diff line number Diff line change
@@ -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;

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

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

Expand All @@ -55,15 +57,29 @@ pub trait ActiveModelTrait: Clone + Debug {

fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column);

fn is_unset(&self, c: <Self::Entity as EntityTrait>::Column) -> bool;

fn default() -> Self;
}

/// Behaviors for users to override
pub trait ActiveModelBehavior: ActiveModelTrait {
type Entity: EntityTrait;

/// Create a new ActiveModel with default values. Also used by `Default::default()`.
fn new() -> Self {
<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>
Expand Down Expand Up @@ -119,7 +135,7 @@ where
pub fn into_wrapped_value(self) -> ActiveValue<Value> {
match self.state {
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(),
}
}
Expand Down Expand Up @@ -171,3 +187,53 @@ where
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
}
2 changes: 1 addition & 1 deletion src/query/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ where
} else if self.columns[idx] != av.is_set() {
panic!("columns mismatch");
}
if av.is_set() {
if av.is_set() || av.is_unchanged() {
columns.push(col);
values.push(av.into_value());
}
Expand Down
2 changes: 2 additions & 0 deletions src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ pub use result::*;
pub use select::*;
pub use traits::*;
pub use update::*;

pub use crate::connector::{QueryErr, ExecErr};
2 changes: 1 addition & 1 deletion src/query/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ mod tests {
assert_eq!(
Update::<fruit::ActiveModel>::new(fruit::ActiveModel {
id: Val::set(2),
name: Val::unset(),
name: Val::unchanged("Apple".to_owned()),
cake_id: Val::set(Some(3)),
})
.build(PostgresQueryBuilder)
Expand Down

0 comments on commit cf0127d

Please sign in to comment.