Save ActiveModel
This commit is contained in:
parent
f433872c0a
commit
cf0127d89f
@ -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() {
|
||||
@ -25,4 +27,8 @@ async fn main() {
|
||||
println!("===== =====\n");
|
||||
|
||||
all_about_select(&db).await.unwrap();
|
||||
|
||||
println!("===== =====\n");
|
||||
|
||||
all_about_operation(&db).await.unwrap();
|
||||
}
|
||||
|
22
examples/sqlx-mysql/src/operation.rs
Normal file
22
examples/sqlx-mysql/src/operation.rs
Normal 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(())
|
||||
}
|
@ -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()
|
||||
@ -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()),*
|
||||
|
@ -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 {
|
||||
@ -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 }
|
||||
@ -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(),
|
||||
})
|
||||
}
|
||||
|
@ -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;
|
||||
|
61
src/connector/update.rs
Normal file
61
src/connector/update.rs
Normal 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(),
|
||||
})
|
||||
}
|
@ -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)]
|
||||
@ -44,6 +45,7 @@ where
|
||||
ActiveValue::unchanged(value)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ActiveModelTrait: Clone + Debug {
|
||||
type Entity: EntityTrait;
|
||||
|
||||
@ -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>
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -19,3 +19,5 @@ pub use result::*;
|
||||
pub use select::*;
|
||||
pub use traits::*;
|
||||
pub use update::*;
|
||||
|
||||
pub use crate::connector::{QueryErr, ExecErr};
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user