diff --git a/src/entity/identity.rs b/src/entity/identity.rs index b9316f9f..cfa95aa0 100644 --- a/src/entity/identity.rs +++ b/src/entity/identity.rs @@ -2,15 +2,31 @@ use crate::{ColumnTrait, EntityTrait, IdenStatic}; use sea_query::{Alias, DynIden, Iden, IntoIden, SeaRc}; use std::fmt; -/// Defines an operation for an Entity +/// List of column identifier #[derive(Debug, Clone)] pub enum Identity { - /// Performs one operation + /// Column identifier consists of 1 column Unary(DynIden), - /// Performs two operations + /// Column identifier consists of 2 columns Binary(DynIden, DynIden), - /// Performs three operations + /// Column identifier consists of 3 columns Ternary(DynIden, DynIden, DynIden), + /// Column identifier consists of more than 3 columns + Many(Vec), +} + +impl IntoIterator for Identity { + type Item = DynIden; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + Identity::Unary(ident1) => vec![ident1].into_iter(), + Identity::Binary(ident1, ident2) => vec![ident1, ident2].into_iter(), + Identity::Ternary(ident1, ident2, ident3) => vec![ident1, ident2, ident3].into_iter(), + Identity::Many(vec) => vec.into_iter(), + } + } } impl Iden for Identity { @@ -28,6 +44,11 @@ impl Iden for Identity { write!(s, "{}", iden2.to_string()).unwrap(); write!(s, "{}", iden3.to_string()).unwrap(); } + Identity::Many(vec) => { + for iden in vec.iter() { + write!(s, "{}", iden.to_string()).unwrap(); + } + } } } } @@ -47,6 +68,12 @@ where fn identity_of(self) -> Identity; } +impl IntoIdentity for Identity { + fn into_identity(self) -> Identity { + self + } +} + impl IntoIdentity for String { fn into_identity(self) -> Identity { self.as_str().into_identity() @@ -89,6 +116,36 @@ where } } +macro_rules! impl_into_identity { + ( $($T:ident : $N:tt),+ $(,)? ) => { + impl< $($T),+ > IntoIdentity for ( $($T),+ ) + where + $($T: IdenStatic),+ + { + fn into_identity(self) -> Identity { + Identity::Many(vec![ + $(self.$N.into_iden()),+ + ]) + } + } + }; +} + +#[rustfmt::skip] +mod impl_into_identity { + use super::*; + + impl_into_identity!(T0:0, T1:1, T2:2, T3:3); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10); + impl_into_identity!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10, T11:11); +} + impl IdentityOf for C where E: EntityTrait, @@ -99,22 +156,33 @@ where } } -impl IdentityOf for (C, C) -where - E: EntityTrait, - C: ColumnTrait, -{ - fn identity_of(self) -> Identity { - self.into_identity() - } +macro_rules! impl_identity_of { + ( $($T:ident),+ $(,)? ) => { + impl IdentityOf for ( $($T),+ ) + where + E: EntityTrait, + C: ColumnTrait, + { + fn identity_of(self) -> Identity { + self.into_identity() + } + } + }; } -impl IdentityOf for (C, C, C) -where - E: EntityTrait, - C: ColumnTrait, -{ - fn identity_of(self) -> Identity { - self.into_identity() - } +#[rustfmt::skip] +mod impl_identity_of { + use super::*; + + impl_identity_of!(C, C); + impl_identity_of!(C, C, C); + impl_identity_of!(C, C, C, C); + impl_identity_of!(C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C, C, C, C, C, C); + impl_identity_of!(C, C, C, C, C, C, C, C, C, C, C, C); } diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index 2c596dde..95f44331 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -65,3 +65,197 @@ pub trait PrimaryKeyToColumn { where Self: Sized; } + +#[cfg(test)] +mod tests { + #[test] + #[cfg(feature = "macros")] + fn test_composite_primary_key() { + mod primary_key_of_1 { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "primary_key_of_1")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub owner: String, + pub name: String, + pub description: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + mod primary_key_of_2 { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "primary_key_of_2")] + pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id_1: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub id_2: String, + pub owner: String, + pub name: String, + pub description: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + mod primary_key_of_3 { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "primary_key_of_3")] + pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id_1: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub id_2: String, + #[sea_orm(primary_key, auto_increment = false)] + pub id_3: Uuid, + pub owner: String, + pub name: String, + pub description: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + mod primary_key_of_4 { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "primary_key_of_4")] + pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id_1: TimeDateTimeWithTimeZone, + #[sea_orm(primary_key, auto_increment = false)] + pub id_2: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub id_3: Json, + #[sea_orm(primary_key, auto_increment = false)] + pub id_4: Decimal, + pub owner: String, + pub name: String, + pub description: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + mod primary_key_of_11 { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] + #[sea_orm( + rs_type = "String", + db_type = "String(Some(1))", + enum_name = "category" + )] + pub enum DeriveCategory { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, + } + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "primary_key_of_11")] + pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id_1: Vec, + #[sea_orm(primary_key, auto_increment = false)] + pub id_2: DeriveCategory, + #[sea_orm(primary_key, auto_increment = false)] + pub id_3: Date, + #[sea_orm(primary_key, auto_increment = false)] + pub id_4: DateTime, + #[sea_orm(primary_key, auto_increment = false)] + pub id_5: Time, + #[sea_orm(primary_key, auto_increment = false)] + pub id_6: TimeTime, + #[sea_orm(primary_key, auto_increment = false)] + pub id_7: DateTime, + #[sea_orm(primary_key, auto_increment = false)] + pub id_8: TimeDateTime, + #[sea_orm(primary_key, auto_increment = false)] + pub id_9: DateTimeLocal, + #[sea_orm(primary_key, auto_increment = false)] + pub id_10: DateTimeUtc, + #[sea_orm(primary_key, auto_increment = false)] + pub id_11: DateTimeWithTimeZone, + pub owner: String, + pub name: String, + pub description: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + mod primary_key_of_12 { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "primary_key_of_12")] + pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id_1: String, + #[sea_orm(primary_key, auto_increment = false)] + pub id_2: i8, + #[sea_orm(primary_key, auto_increment = false)] + pub id_3: u8, + #[sea_orm(primary_key, auto_increment = false)] + pub id_4: i16, + #[sea_orm(primary_key, auto_increment = false)] + pub id_5: u16, + #[sea_orm(primary_key, auto_increment = false)] + pub id_6: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub id_7: u32, + #[sea_orm(primary_key, auto_increment = false)] + pub id_8: i64, + #[sea_orm(primary_key, auto_increment = false)] + pub id_9: u64, + #[sea_orm(primary_key, auto_increment = false)] + pub id_10: f32, + #[sea_orm(primary_key, auto_increment = false)] + pub id_11: f64, + #[sea_orm(primary_key, auto_increment = false)] + pub id_12: bool, + pub owner: String, + pub name: String, + pub description: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + } +} diff --git a/src/entity/relation.rs b/src/entity/relation.rs index cb8c81f8..6f0d105f 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -365,31 +365,17 @@ where macro_rules! set_foreign_key_stmt { ( $relation: ident, $foreign_key: ident ) => { - let from_cols: Vec = match $relation.from_col { - Identity::Unary(o1) => vec![o1], - Identity::Binary(o1, o2) => vec![o1, o2], - Identity::Ternary(o1, o2, o3) => vec![o1, o2, o3], - } - .into_iter() - .map(|col| { - let col_name = col.to_string(); - $foreign_key.from_col(col); - col_name - }) - .collect(); - match $relation.to_col { - Identity::Unary(o1) => { - $foreign_key.to_col(o1); - } - Identity::Binary(o1, o2) => { - $foreign_key.to_col(o1); - $foreign_key.to_col(o2); - } - Identity::Ternary(o1, o2, o3) => { - $foreign_key.to_col(o1); - $foreign_key.to_col(o2); - $foreign_key.to_col(o3); - } + let from_cols: Vec = $relation + .from_col + .into_iter() + .map(|col| { + let col_name = col.to_string(); + $foreign_key.from_col(col); + col_name + }) + .collect(); + for col in $relation.to_col.into_iter() { + $foreign_key.to_col(col); } if let Some(action) = $relation.on_delete { $foreign_key.on_delete(action); diff --git a/src/executor/cursor.rs b/src/executor/cursor.rs index bb88326c..b1c3bc26 100644 --- a/src/executor/cursor.rs +++ b/src/executor/cursor.rs @@ -101,6 +101,56 @@ where .add(f(c2, v2)), ) .add(f(c1, v1)), + (Identity::Many(col_vec), ValueTuple::Many(val_vec)) + if col_vec.len() == val_vec.len() => + { + // The length of `col_vec` and `val_vec` should be equal and is denoted by "n". + // + // The elements of `col_vec` and `val_vec` are denoted by: + // - `col_vec`: "col_1", "col_2", ..., "col_n-1", "col_n" + // - `val_vec`: "val_1", "val_2", ..., "val_n-1", "val_n" + // + // The general form of the where condition should have "n" number of inner-AND-condition chained by an outer-OR-condition. + // The "n"-th inner-AND-condition should have exactly "n" number of column value expressions, + // to construct the expression we take the first "n" number of column and value from the respected vector. + // - if it's not the last element, then we construct a "col_1 = val_1" equal expression + // - otherwise, for the last element, we should construct a "col_n > val_n" greater than or "col_n < val_n" less than expression. + // i.e. + // WHERE + // (col_1 = val_1 AND col_2 = val_2 AND ... AND col_n > val_n) + // OR (col_1 = val_1 AND col_2 = val_2 AND ... AND col_n-1 > val_n-1) + // OR (col_1 = val_1 AND col_2 = val_2 AND ... AND col_n-2 > val_n-2) + // OR ... + // OR (col_1 = val_1 AND col_2 > val_2) + // OR (col_1 > val_1) + + // Counting from 1 to "n" (inclusive) but in reverse, i.e. n, n-1, ..., 2, 1 + (1..=col_vec.len()) + .rev() + .fold(Condition::any(), |cond_any, n| { + // Construct the inner-AND-condition + let inner_cond_all = + // Take the first "n" elements from the column and value vector respectively + col_vec.iter().zip(val_vec.iter()).enumerate().take(n).fold( + Condition::all(), + |inner_cond_all, (i, (col, val))| { + let val = val.clone(); + // Construct a equal expression, + // except for the last one being greater than or less than expression + let expr = if i != (n - 1) { + Expr::col((SeaRc::clone(&self.table), SeaRc::clone(col))) + .eq(val) + } else { + f(col, val) + }; + // Chain it with AND operator + inner_cond_all.add(expr) + }, + ); + // Chain inner-AND-condition with OR operator + cond_any.add(inner_cond_all) + }) + } _ => panic!("column arity mismatch"), } } @@ -145,6 +195,11 @@ where f(query, c2); f(query, c3); } + Identity::Many(vec) => { + for col in vec.iter() { + f(query, col); + } + } } } @@ -665,4 +720,160 @@ mod tests { Ok(()) } + + mod composite_entity { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] + #[sea_orm(table_name = "t")] + pub struct Model { + #[sea_orm(primary_key)] + pub col_1: String, + #[sea_orm(primary_key)] + pub col_2: String, + #[sea_orm(primary_key)] + pub col_3: String, + #[sea_orm(primary_key)] + pub col_4: String, + #[sea_orm(primary_key)] + pub col_5: String, + #[sea_orm(primary_key)] + pub col_6: String, + #[sea_orm(primary_key)] + pub col_7: String, + #[sea_orm(primary_key)] + pub col_8: String, + #[sea_orm(primary_key)] + pub col_9: String, + #[sea_orm(primary_key)] + pub col_10: String, + #[sea_orm(primary_key)] + pub col_11: String, + #[sea_orm(primary_key)] + pub col_12: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + #[smol_potat::test] + async fn cursor_by_many() -> Result<(), DbErr> { + use composite_entity::*; + + let base_sql = [ + r#"SELECT "t"."col_1", "t"."col_2", "t"."col_3", "t"."col_4", "t"."col_5", "t"."col_6", "t"."col_7", "t"."col_8", "t"."col_9", "t"."col_10", "t"."col_11", "t"."col_12""#, + r#"FROM "t" WHERE"#, + ].join(" "); + + assert_eq!( + DbBackend::Postgres.build(& + Entity::find() + .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4)) + .after(("val_1", "val_2", "val_3", "val_4")) + .query + ).to_string(), + format!("{base_sql} {}", [ + r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#, + r#"OR "t"."col_1" > 'val_1'"#, + ].join(" ")) + ); + + assert_eq!( + DbBackend::Postgres.build(& + Entity::find() + .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5)) + .after(("val_1", "val_2", "val_3", "val_4", "val_5")) + .query + ).to_string(), + format!("{base_sql} {}", [ + r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" > 'val_5')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#, + r#"OR "t"."col_1" > 'val_1'"#, + ].join(" ")) + ); + + assert_eq!( + DbBackend::Postgres.build(& + Entity::find() + .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6)) + .after(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6")) + .query + ).to_string(), + format!("{base_sql} {}", [ + r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" > 'val_6')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" > 'val_5')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#, + r#"OR "t"."col_1" > 'val_1'"#, + ].join(" ")) + ); + + assert_eq!( + DbBackend::Postgres.build(& + Entity::find() + .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7)) + .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7")) + .query + ).to_string(), + format!("{base_sql} {}", [ + r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#, + r#"OR "t"."col_1" < 'val_1'"#, + ].join(" ")) + ); + + assert_eq!( + DbBackend::Postgres.build(& + Entity::find() + .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7, Column::Col8)) + .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7", "val_8")) + .query + ).to_string(), + format!("{base_sql} {}", [ + r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" < 'val_8')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#, + r#"OR "t"."col_1" < 'val_1'"#, + ].join(" ")) + ); + + assert_eq!( + DbBackend::Postgres.build(& + Entity::find() + .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7, Column::Col8, Column::Col9)) + .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7", "val_8", "val_9")) + .query + ).to_string(), + format!("{base_sql} {}", [ + r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" = 'val_8' AND "t"."col_9" < 'val_9')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" < 'val_8')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#, + r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#, + r#"OR "t"."col_1" < 'val_1'"#, + ].join(" ")) + ); + + Ok(()) + } } diff --git a/src/executor/query.rs b/src/executor/query.rs index d4a9108e..28093822 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -908,136 +908,43 @@ where } } -impl TryGetableMany for (A, B) -where - A: TryGetable, - B: TryGetable, -{ - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - try_get_many_with_slice_len_of(2, cols)?; - Ok(( - A::try_get(res, pre, &cols[0])?, - B::try_get(res, pre, &cols[1])?, - )) - } +macro_rules! impl_try_get_many { + ( $LEN:expr, $($T:ident : $N:expr),+ $(,)? ) => { + impl< $($T),+ > TryGetableMany for ( $($T),+ ) + where + $($T: TryGetable),+ + { + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + try_get_many_with_slice_len_of($LEN, cols)?; + Ok(( + $($T::try_get(res, pre, &cols[$N])?),+ + )) + } - fn try_get_many_by_index(res: &QueryResult) -> Result { - Ok((A::try_get_by_index(res, 0)?, B::try_get_by_index(res, 1)?)) - } + fn try_get_many_by_index(res: &QueryResult) -> Result { + Ok(( + $($T::try_get_by_index(res, $N)?),+ + )) + } + } + }; } -impl TryGetableMany for (A, B, C) -where - A: TryGetable, - B: TryGetable, - C: TryGetable, -{ - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - try_get_many_with_slice_len_of(3, cols)?; - Ok(( - A::try_get(res, pre, &cols[0])?, - B::try_get(res, pre, &cols[1])?, - C::try_get(res, pre, &cols[2])?, - )) - } +#[rustfmt::skip] +mod impl_try_get_many { + use super::*; - fn try_get_many_by_index(res: &QueryResult) -> Result { - Ok(( - A::try_get_by_index(res, 0)?, - B::try_get_by_index(res, 1)?, - C::try_get_by_index(res, 2)?, - )) - } -} - -impl TryGetableMany for (A, B, C, D) -where - A: TryGetable, - B: TryGetable, - C: TryGetable, - D: TryGetable, -{ - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - try_get_many_with_slice_len_of(4, cols)?; - Ok(( - A::try_get(res, pre, &cols[0])?, - B::try_get(res, pre, &cols[1])?, - C::try_get(res, pre, &cols[2])?, - D::try_get(res, pre, &cols[3])?, - )) - } - - fn try_get_many_by_index(res: &QueryResult) -> Result { - Ok(( - A::try_get_by_index(res, 0)?, - B::try_get_by_index(res, 1)?, - C::try_get_by_index(res, 2)?, - D::try_get_by_index(res, 3)?, - )) - } -} - -impl TryGetableMany for (A, B, C, D, E) -where - A: TryGetable, - B: TryGetable, - C: TryGetable, - D: TryGetable, - E: TryGetable, -{ - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - try_get_many_with_slice_len_of(5, cols)?; - Ok(( - A::try_get(res, pre, &cols[0])?, - B::try_get(res, pre, &cols[1])?, - C::try_get(res, pre, &cols[2])?, - D::try_get(res, pre, &cols[3])?, - E::try_get(res, pre, &cols[4])?, - )) - } - - fn try_get_many_by_index(res: &QueryResult) -> Result { - Ok(( - A::try_get_by_index(res, 0)?, - B::try_get_by_index(res, 1)?, - C::try_get_by_index(res, 2)?, - D::try_get_by_index(res, 3)?, - E::try_get_by_index(res, 4)?, - )) - } -} - -impl TryGetableMany for (A, B, C, D, E, F) -where - A: TryGetable, - B: TryGetable, - C: TryGetable, - D: TryGetable, - E: TryGetable, - F: TryGetable, -{ - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - try_get_many_with_slice_len_of(6, cols)?; - Ok(( - A::try_get(res, pre, &cols[0])?, - B::try_get(res, pre, &cols[1])?, - C::try_get(res, pre, &cols[2])?, - D::try_get(res, pre, &cols[3])?, - E::try_get(res, pre, &cols[4])?, - F::try_get(res, pre, &cols[5])?, - )) - } - - fn try_get_many_by_index(res: &QueryResult) -> Result { - Ok(( - A::try_get_by_index(res, 0)?, - B::try_get_by_index(res, 1)?, - C::try_get_by_index(res, 2)?, - D::try_get_by_index(res, 3)?, - E::try_get_by_index(res, 4)?, - F::try_get_by_index(res, 5)?, - )) - } + impl_try_get_many!( 2, T0:0, T1:1); + impl_try_get_many!( 3, T0:0, T1:1, T2:2); + impl_try_get_many!( 4, T0:0, T1:1, T2:2, T3:3); + impl_try_get_many!( 5, T0:0, T1:1, T2:2, T3:3, T4:4); + impl_try_get_many!( 6, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5); + impl_try_get_many!( 7, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6); + impl_try_get_many!( 8, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7); + impl_try_get_many!( 9, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8); + impl_try_get_many!(10, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9); + impl_try_get_many!(11, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10); + impl_try_get_many!(12, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10, T11:11); } fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> { @@ -1132,12 +1039,22 @@ macro_rules! try_from_u64_err { }; } -// impl TryFromU64 for tuples with generic types -try_from_u64_err!(A, B); -try_from_u64_err!(A, B, C); -try_from_u64_err!(A, B, C, D); -try_from_u64_err!(A, B, C, D, E); -try_from_u64_err!(A, B, C, D, E, F); +#[rustfmt::skip] +mod try_from_u64_err { + use super::*; + + try_from_u64_err!(T0, T1); + try_from_u64_err!(T0, T1, T2); + try_from_u64_err!(T0, T1, T2, T3); + try_from_u64_err!(T0, T1, T2, T3, T4); + try_from_u64_err!(T0, T1, T2, T3, T4, T5); + try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6); + try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7); + try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8); + try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9); + try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); +} macro_rules! try_from_u64_numeric { ( $type: ty ) => { diff --git a/src/query/helper.rs b/src/query/helper.rs index 8d8ab435..ef754dcf 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -4,7 +4,7 @@ use crate::{ }; use sea_query::{ Alias, ConditionType, Expr, Iden, IntoCondition, IntoIden, LockType, SeaRc, SelectExpr, - SelectStatement, SimpleExpr, TableRef, + SelectStatement, TableRef, }; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; @@ -713,24 +713,15 @@ pub(crate) fn join_tbl_on_condition( to_tbl: SeaRc, owner_keys: Identity, foreign_keys: Identity, -) -> SimpleExpr { - match (owner_keys, foreign_keys) { - (Identity::Unary(o1), Identity::Unary(f1)) => { - Expr::col((SeaRc::clone(&from_tbl), o1)).equals((SeaRc::clone(&to_tbl), f1)) - } - (Identity::Binary(o1, o2), Identity::Binary(f1, f2)) => { - Expr::col((SeaRc::clone(&from_tbl), o1)) - .equals((SeaRc::clone(&to_tbl), f1)) - .and(Expr::col((SeaRc::clone(&from_tbl), o2)).equals((SeaRc::clone(&to_tbl), f2))) - } - (Identity::Ternary(o1, o2, o3), Identity::Ternary(f1, f2, f3)) => { - Expr::col((SeaRc::clone(&from_tbl), o1)) - .equals((SeaRc::clone(&to_tbl), f1)) - .and(Expr::col((SeaRc::clone(&from_tbl), o2)).equals((SeaRc::clone(&to_tbl), f2))) - .and(Expr::col((SeaRc::clone(&from_tbl), o3)).equals((SeaRc::clone(&to_tbl), f3))) - } - _ => panic!("Owner key and foreign key mismatch"), +) -> Condition { + let mut cond = Condition::all(); + for (owner_key, foreign_key) in owner_keys.into_iter().zip(foreign_keys.into_iter()) { + cond = cond.add( + Expr::col((SeaRc::clone(&from_tbl), owner_key)) + .equals((SeaRc::clone(&to_tbl), foreign_key)), + ); } + cond } pub(crate) fn unpack_table_ref(table_ref: &TableRef) -> DynIden { diff --git a/src/query/join.rs b/src/query/join.rs index 968b760b..09f2e385 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -250,8 +250,8 @@ mod tests { r#"SELECT "cake_filling_price"."cake_id", "cake_filling_price"."filling_id", "cake_filling_price"."price""#, r#"FROM "public"."cake_filling_price""#, r#"INNER JOIN "cake_filling" ON"#, - r#"("cake_filling"."cake_id" = "cake_filling_price"."cake_id") AND"#, - r#"("cake_filling"."filling_id" = "cake_filling_price"."filling_id")"#, + r#""cake_filling"."cake_id" = "cake_filling_price"."cake_id" AND"#, + r#""cake_filling"."filling_id" = "cake_filling_price"."filling_id""#, ] .join(" ") ); @@ -269,8 +269,8 @@ mod tests { r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id""#, r#"FROM "cake_filling""#, r#"INNER JOIN "public"."cake_filling_price" ON"#, - r#"("cake_filling_price"."cake_id" = "cake_filling"."cake_id") AND"#, - r#"("cake_filling_price"."filling_id" = "cake_filling"."filling_id")"#, + r#""cake_filling_price"."cake_id" = "cake_filling"."cake_id" AND"#, + r#""cake_filling_price"."filling_id" = "cake_filling"."filling_id""#, ] .join(" ") ); diff --git a/src/query/loader.rs b/src/query/loader.rs index 53420b01..28663898 100644 --- a/src/query/loader.rs +++ b/src/query/loader.rs @@ -383,6 +383,18 @@ where model.get(column_c), ) } + Identity::Many(cols) => { + let values = cols.iter().map(|col| { + let col_name = col.to_string(); + let column = <<::Entity as EntityTrait>::Column as FromStr>::from_str( + &col_name, + ) + .unwrap_or_else(|_| panic!("Failed at mapping '{}' to column", col_name)); + model.get(column) + }) + .collect(); + ValueTuple::Many(values) + } } } @@ -409,6 +421,12 @@ fn prepare_condition(table: &TableRef, col: &Identity, keys: &[ValueTuple]) -> C ]) .in_tuples(keys), ), + Identity::Many(cols) => { + let columns = cols + .iter() + .map(|col| SimpleExpr::Column(table_column(table, col))); + Condition::all().add(Expr::tuple(columns).in_tuples(keys)) + } } }