Save ActiveModel
This commit is contained in:
parent
f433872c0a
commit
cf0127d89f
@ -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();
|
||||||
}
|
}
|
||||||
|
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>),*
|
#(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()),*
|
||||||
|
@ -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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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
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;
|
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
|
||||||
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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};
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user