Support composite primary key of length up to 12 (#1508)

* feat: support composite primary key of length up to 12

* induction proof

* docs

* revert tests

* testing cursor by 4+ columns
This commit is contained in:
Billy Chan 2023-04-13 16:18:47 +08:00 committed by GitHub
parent dd261f2a2f
commit d45bb5b304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 584 additions and 199 deletions

View File

@ -2,15 +2,31 @@ use crate::{ColumnTrait, EntityTrait, IdenStatic};
use sea_query::{Alias, DynIden, Iden, IntoIden, SeaRc}; use sea_query::{Alias, DynIden, Iden, IntoIden, SeaRc};
use std::fmt; use std::fmt;
/// Defines an operation for an Entity /// List of column identifier
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Identity { pub enum Identity {
/// Performs one operation /// Column identifier consists of 1 column
Unary(DynIden), Unary(DynIden),
/// Performs two operations /// Column identifier consists of 2 columns
Binary(DynIden, DynIden), Binary(DynIden, DynIden),
/// Performs three operations /// Column identifier consists of 3 columns
Ternary(DynIden, DynIden, DynIden), Ternary(DynIden, DynIden, DynIden),
/// Column identifier consists of more than 3 columns
Many(Vec<DynIden>),
}
impl IntoIterator for Identity {
type Item = DynIden;
type IntoIter = std::vec::IntoIter<Self::Item>;
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 { impl Iden for Identity {
@ -28,6 +44,11 @@ impl Iden for Identity {
write!(s, "{}", iden2.to_string()).unwrap(); write!(s, "{}", iden2.to_string()).unwrap();
write!(s, "{}", iden3.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; fn identity_of(self) -> Identity;
} }
impl IntoIdentity for Identity {
fn into_identity(self) -> Identity {
self
}
}
impl IntoIdentity for String { impl IntoIdentity for String {
fn into_identity(self) -> Identity { fn into_identity(self) -> Identity {
self.as_str().into_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<E, C> IdentityOf<E> for C impl<E, C> IdentityOf<E> for C
where where
E: EntityTrait<Column = C>, E: EntityTrait<Column = C>,
@ -99,22 +156,33 @@ where
} }
} }
impl<E, C> IdentityOf<E> for (C, C) macro_rules! impl_identity_of {
where ( $($T:ident),+ $(,)? ) => {
impl<E, C> IdentityOf<E> for ( $($T),+ )
where
E: EntityTrait<Column = C>, E: EntityTrait<Column = C>,
C: ColumnTrait, C: ColumnTrait,
{ {
fn identity_of(self) -> Identity { fn identity_of(self) -> Identity {
self.into_identity() self.into_identity()
} }
}
};
} }
impl<E, C> IdentityOf<E> for (C, C, C) #[rustfmt::skip]
where mod impl_identity_of {
E: EntityTrait<Column = C>, use super::*;
C: ColumnTrait,
{ impl_identity_of!(C, C);
fn identity_of(self) -> Identity { impl_identity_of!(C, C, C);
self.into_identity() 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);
} }

View File

@ -65,3 +65,197 @@ pub trait PrimaryKeyToColumn {
where where
Self: Sized; 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<u8>,
#[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 {}
}
}
}

View File

@ -365,11 +365,8 @@ where
macro_rules! set_foreign_key_stmt { macro_rules! set_foreign_key_stmt {
( $relation: ident, $foreign_key: ident ) => { ( $relation: ident, $foreign_key: ident ) => {
let from_cols: Vec<String> = match $relation.from_col { let from_cols: Vec<String> = $relation
Identity::Unary(o1) => vec![o1], .from_col
Identity::Binary(o1, o2) => vec![o1, o2],
Identity::Ternary(o1, o2, o3) => vec![o1, o2, o3],
}
.into_iter() .into_iter()
.map(|col| { .map(|col| {
let col_name = col.to_string(); let col_name = col.to_string();
@ -377,19 +374,8 @@ macro_rules! set_foreign_key_stmt {
col_name col_name
}) })
.collect(); .collect();
match $relation.to_col { for col in $relation.to_col.into_iter() {
Identity::Unary(o1) => { $foreign_key.to_col(col);
$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);
}
} }
if let Some(action) = $relation.on_delete { if let Some(action) = $relation.on_delete {
$foreign_key.on_delete(action); $foreign_key.on_delete(action);

View File

@ -101,6 +101,56 @@ where
.add(f(c2, v2)), .add(f(c2, v2)),
) )
.add(f(c1, v1)), .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"), _ => panic!("column arity mismatch"),
} }
} }
@ -145,6 +195,11 @@ where
f(query, c2); f(query, c2);
f(query, c3); f(query, c3);
} }
Identity::Many(vec) => {
for col in vec.iter() {
f(query, col);
}
}
} }
} }
@ -665,4 +720,160 @@ mod tests {
Ok(()) 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(())
}
} }

View File

@ -908,136 +908,43 @@ where
} }
} }
impl<A, B> TryGetableMany for (A, B) macro_rules! impl_try_get_many {
where ( $LEN:expr, $($T:ident : $N:expr),+ $(,)? ) => {
A: TryGetable, impl< $($T),+ > TryGetableMany for ( $($T),+ )
B: TryGetable, where
{ $($T: TryGetable),+
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> { fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
try_get_many_with_slice_len_of(2, cols)?; try_get_many_with_slice_len_of($LEN, cols)?;
Ok(( Ok((
A::try_get(res, pre, &cols[0])?, $($T::try_get(res, pre, &cols[$N])?),+
B::try_get(res, pre, &cols[1])?,
)) ))
} }
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> { fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((A::try_get_by_index(res, 0)?, B::try_get_by_index(res, 1)?)) Ok((
$($T::try_get_by_index(res, $N)?),+
))
} }
}
};
} }
impl<A, B, C> TryGetableMany for (A, B, C) #[rustfmt::skip]
where mod impl_try_get_many {
A: TryGetable, use super::*;
B: TryGetable,
C: TryGetable,
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
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])?,
))
}
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> { impl_try_get_many!( 2, T0:0, T1:1);
Ok(( impl_try_get_many!( 3, T0:0, T1:1, T2:2);
A::try_get_by_index(res, 0)?, impl_try_get_many!( 4, T0:0, T1:1, T2:2, T3:3);
B::try_get_by_index(res, 1)?, impl_try_get_many!( 5, T0:0, T1:1, T2:2, T3:3, T4:4);
C::try_get_by_index(res, 2)?, 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<A, B, C, D> TryGetableMany for (A, B, C, D) 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);
where 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);
A: TryGetable,
B: TryGetable,
C: TryGetable,
D: TryGetable,
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
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<Self, TryGetError> {
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<A, B, C, D, E> 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<Self, TryGetError> {
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<Self, TryGetError> {
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<A, B, C, D, E, F> 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<Self, TryGetError> {
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<Self, TryGetError> {
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)?,
))
}
} }
fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> { 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 #[rustfmt::skip]
try_from_u64_err!(A, B); mod try_from_u64_err {
try_from_u64_err!(A, B, C); use super::*;
try_from_u64_err!(A, B, C, D);
try_from_u64_err!(A, B, C, D, E); try_from_u64_err!(T0, T1);
try_from_u64_err!(A, B, C, D, E, F); 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 { macro_rules! try_from_u64_numeric {
( $type: ty ) => { ( $type: ty ) => {

View File

@ -4,7 +4,7 @@ use crate::{
}; };
use sea_query::{ use sea_query::{
Alias, ConditionType, Expr, Iden, IntoCondition, IntoIden, LockType, SeaRc, SelectExpr, Alias, ConditionType, Expr, Iden, IntoCondition, IntoIden, LockType, SeaRc, SelectExpr,
SelectStatement, SimpleExpr, TableRef, SelectStatement, TableRef,
}; };
pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement};
@ -713,24 +713,15 @@ pub(crate) fn join_tbl_on_condition(
to_tbl: SeaRc<dyn Iden>, to_tbl: SeaRc<dyn Iden>,
owner_keys: Identity, owner_keys: Identity,
foreign_keys: Identity, foreign_keys: Identity,
) -> SimpleExpr { ) -> Condition {
match (owner_keys, foreign_keys) { let mut cond = Condition::all();
(Identity::Unary(o1), Identity::Unary(f1)) => { for (owner_key, foreign_key) in owner_keys.into_iter().zip(foreign_keys.into_iter()) {
Expr::col((SeaRc::clone(&from_tbl), o1)).equals((SeaRc::clone(&to_tbl), f1)) cond = cond.add(
} Expr::col((SeaRc::clone(&from_tbl), owner_key))
(Identity::Binary(o1, o2), Identity::Binary(f1, f2)) => { .equals((SeaRc::clone(&to_tbl), foreign_key)),
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"),
} }
cond
} }
pub(crate) fn unpack_table_ref(table_ref: &TableRef) -> DynIden { pub(crate) fn unpack_table_ref(table_ref: &TableRef) -> DynIden {

View File

@ -250,8 +250,8 @@ mod tests {
r#"SELECT "cake_filling_price"."cake_id", "cake_filling_price"."filling_id", "cake_filling_price"."price""#, r#"SELECT "cake_filling_price"."cake_id", "cake_filling_price"."filling_id", "cake_filling_price"."price""#,
r#"FROM "public"."cake_filling_price""#, r#"FROM "public"."cake_filling_price""#,
r#"INNER JOIN "cake_filling" ON"#, r#"INNER JOIN "cake_filling" ON"#,
r#"("cake_filling"."cake_id" = "cake_filling_price"."cake_id") AND"#, r#""cake_filling"."cake_id" = "cake_filling_price"."cake_id" AND"#,
r#"("cake_filling"."filling_id" = "cake_filling_price"."filling_id")"#, r#""cake_filling"."filling_id" = "cake_filling_price"."filling_id""#,
] ]
.join(" ") .join(" ")
); );
@ -269,8 +269,8 @@ mod tests {
r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id""#, r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id""#,
r#"FROM "cake_filling""#, r#"FROM "cake_filling""#,
r#"INNER JOIN "public"."cake_filling_price" ON"#, r#"INNER JOIN "public"."cake_filling_price" ON"#,
r#"("cake_filling_price"."cake_id" = "cake_filling"."cake_id") AND"#, 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"."filling_id" = "cake_filling"."filling_id""#,
] ]
.join(" ") .join(" ")
); );

View File

@ -383,6 +383,18 @@ where
model.get(column_c), model.get(column_c),
) )
} }
Identity::Many(cols) => {
let values = cols.iter().map(|col| {
let col_name = col.to_string();
let column = <<<Model as ModelTrait>::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), .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))
}
} }
} }