diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 81c91c7a..1308d75e 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -31,7 +31,7 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields.into_iter().map(|Field { ty, .. }| ty).collect(); Ok(quote!( - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Default)] pub struct ActiveModel { #(pub #field: sea_orm::ActiveValue<#ty>),* } diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 8bd53c4d..def06622 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -10,6 +10,8 @@ where state: ActiveValueState, } +pub type Val = ActiveValue; + #[derive(Clone, Debug)] enum ActiveValueState { Set, @@ -23,6 +25,18 @@ impl Default for ActiveValueState { } } +pub trait OneOrManyActiveModel +where + A: ActiveModelTrait, +{ + fn is_one() -> bool; + fn get_one(self) -> A; + + fn is_many() -> bool; + fn get_many(self) -> Vec; +} + +#[doc(hidden)] pub fn unchanged_active_value_not_intended_for_public_use(value: V) -> ActiveValue where V: Default, @@ -30,6 +44,24 @@ where ActiveValue::unchanged(value) } +pub trait ActiveModelOf +where + E: EntityTrait, +{ +} + +pub trait ActiveModelTrait: Clone + Debug + Default { + type Column: ColumnTrait; + + fn take(&mut self, c: Self::Column) -> ActiveValue; + + fn get(&self, c: Self::Column) -> ActiveValue; + + fn set(&mut self, c: Self::Column, v: Value); + + fn unset(&mut self, c: Self::Column); +} + impl ActiveValue where V: Default, @@ -99,20 +131,40 @@ where } } -pub trait ActiveModelOf +impl OneOrManyActiveModel for A where - E: EntityTrait, + A: ActiveModelTrait, { + fn is_one() -> bool { + true + } + fn get_one(self) -> A { + self + } + + fn is_many() -> bool { + false + } + fn get_many(self) -> Vec { + panic!("not many") + } } -pub trait ActiveModelTrait: Clone + Debug { - type Column: ColumnTrait; +impl OneOrManyActiveModel for Vec +where + A: ActiveModelTrait, +{ + fn is_one() -> bool { + false + } + fn get_one(self) -> A { + panic!("not one") + } - fn take(&mut self, c: Self::Column) -> ActiveValue; - - fn get(&self, c: Self::Column) -> ActiveValue; - - fn set(&mut self, c: Self::Column, v: Value); - - fn unset(&mut self, c: Self::Column); + fn is_many() -> bool { + true + } + fn get_many(self) -> Vec { + self + } } diff --git a/src/entity/base.rs b/src/entity/base.rs index b0058194..efea8713 100644 --- a/src/entity/base.rs +++ b/src/entity/base.rs @@ -1,6 +1,7 @@ use crate::{ - ColumnTrait, ModelTrait, PrimaryKeyOfModel, PrimaryKeyTrait, QueryHelper, RelationBuilder, - RelationTrait, RelationType, Select, + ActiveModelOf, ActiveModelTrait, ColumnTrait, Insert, ModelTrait, OneOrManyActiveModel, + PrimaryKeyOfModel, PrimaryKeyTrait, QueryHelper, RelationBuilder, RelationTrait, RelationType, + Select, }; use sea_query::{Iden, IntoValueTuple}; use std::fmt::Debug; @@ -46,7 +47,7 @@ pub trait EntityTrait: EntityName { /// ); /// ``` fn find() -> Select { - Select::::new() + Select::new() } /// Find a model by primary key @@ -94,4 +95,57 @@ pub trait EntityTrait: EntityName { } select } + + fn insert(models: C) -> Insert + where + A: ActiveModelTrait + ActiveModelOf, + C: OneOrManyActiveModel, + { + if C::is_one() { + Insert::new().one(models.get_one()) + } else if C::is_many() { + Insert::new().many(models.get_many()) + } else { + unreachable!() + } + } +} + +#[cfg(test)] +mod tests { + use crate::tests_cfg::cake; + use crate::{EntityTrait, Val}; + use sea_query::PostgresQueryBuilder; + + #[test] + fn insert_one() { + let apple = cake::ActiveModel { + name: Val::set("Apple Pie".to_owned()), + ..Default::default() + }; + assert_eq!( + cake::Entity::insert(apple) + .build(PostgresQueryBuilder) + .to_string(), + r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, + ); + } + + #[test] + fn insert_many() { + let apple = cake::ActiveModel { + name: Val::set("Apple Pie".to_owned()), + ..Default::default() + }; + let orange = cake::ActiveModel { + name: Val::set("Orange Scone".to_owned()), + ..Default::default() + }; + assert_eq!( + cake::Entity::insert(vec![apple, orange]) + .build(PostgresQueryBuilder) + .to_string(), + r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie'), ('Orange Scone')"#, + ); + } } diff --git a/src/operation/insert.rs b/src/operation/insert.rs index 2d317c3f..9242422e 100644 --- a/src/operation/insert.rs +++ b/src/operation/insert.rs @@ -8,6 +8,7 @@ where A: ActiveModelTrait, { pub(crate) query: InsertStatement, + pub(crate) columns: Vec, pub(crate) model: PhantomData, } @@ -24,6 +25,7 @@ where query: InsertStatement::new() .into_table(E::default().into_iden()) .to_owned(), + columns: Vec::new(), model: PhantomData, } } @@ -35,8 +37,14 @@ where let mut am: A = m.into(); let mut columns = Vec::new(); let mut values = Vec::new(); - for col in A::Column::iter() { + let columns_empty = self.columns.is_empty(); + for (idx, col) in A::Column::iter().enumerate() { let av = am.take(col); + if columns_empty { + self.columns.push(av.is_set()); + } else if self.columns[idx] != av.is_set() { + panic!("columns mismatch"); + } if !av.is_unset() { columns.push(col); values.push(av.into_value()); @@ -47,6 +55,17 @@ where self } + pub fn many(mut self, models: I) -> Self + where + M: Into, + I: IntoIterator, + { + for model in models.into_iter() { + self = self.one(model); + } + self + } + /// Get a mutable ref to the query builder pub fn query(&mut self) -> &mut InsertStatement { &mut self.query @@ -74,7 +93,7 @@ where #[cfg(test)] mod tests { use crate::tests_cfg::cake; - use crate::{ActiveValue, Insert}; + use crate::{Insert, Val}; use sea_query::PostgresQueryBuilder; #[test] @@ -82,8 +101,8 @@ mod tests { assert_eq!( Insert::::new() .one(cake::ActiveModel { - id: ActiveValue::unset(), - name: ActiveValue::set("Apple Pie".to_owned()), + id: Val::unset(), + name: Val::set("Apple Pie".to_owned()), }) .build(PostgresQueryBuilder) .to_string(), @@ -96,8 +115,8 @@ mod tests { assert_eq!( Insert::::new() .one(cake::ActiveModel { - id: ActiveValue::set(1), - name: ActiveValue::set("Apple Pie".to_owned()), + id: Val::set(1), + name: Val::set("Apple Pie".to_owned()), }) .build(PostgresQueryBuilder) .to_string(), @@ -118,4 +137,44 @@ mod tests { r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, ); } + + #[test] + fn insert_4() { + assert_eq!( + Insert::::new() + .many(vec![ + cake::Model { + id: 1, + name: "Apple Pie".to_owned(), + }, + cake::Model { + id: 2, + name: "Orange Scone".to_owned(), + } + ]) + .build(PostgresQueryBuilder) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#, + ); + } + + #[test] + #[should_panic(expected = "columns mismatch")] + fn insert_5() { + let apple = cake::ActiveModel { + name: Val::set("Apple".to_owned()), + ..Default::default() + }; + let orange = cake::ActiveModel { + id: Val::set(2), + name: Val::set("Orange".to_owned()), + }; + assert_eq!( + Insert::::new() + .many(vec![apple, orange]) + .build(PostgresQueryBuilder) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (NULL, 'Apple'), (2, 'Orange')"#, + ); + } }