diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4e1259c3..9a8e4c29 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,6 +15,86 @@ jobs: test: name: Unit Test runs-on: ubuntu-20.04 + strategy: + matrix: + # runtime: [async-std-native-tls, async-std-rustls, actix-native-tls, actix-rustls, tokio-native-tls, tokio-rustls] + runtime: [async-std-native-tls] + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-test-${{ matrix.runtime }}-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --all + --features default,runtime-${{ matrix.runtime }} + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --all + --features default,runtime-${{ matrix.runtime }} + + sqlite: + name: SQLite + runs-on: ubuntu-20.04 + strategy: + matrix: + # runtime: [async-std-native-tls, async-std-rustls, actix-native-tls, actix-rustls, tokio-native-tls, tokio-rustls] + runtime: [async-std-native-tls] + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-sqlite-${{ matrix.runtime }}-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --all + --features default,runtime-${{ matrix.runtime }} + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --all + --features default,sqlx-sqlite,runtime-${{ matrix.runtime }} + + postgres: + name: Postgres + runs-on: ubuntu-20.04 + strategy: + matrix: + # runtime: [async-std-native-tls, async-std-rustls, actix-native-tls, actix-rustls, tokio-native-tls, tokio-rustls] + runtime: [async-std-native-tls] services: mysql: image: mysql:8.0 @@ -50,11 +130,24 @@ jobs: toolchain: stable override: true + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-postgres-${{ matrix.runtime }}-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/cargo@v1 with: command: build + args: > + --all + --features default,runtime-${{ matrix.runtime }} - uses: actions-rs/cargo@v1 with: command: test - args: --all + args: > + --all + --features default,sqlx-postgres,runtime-${{ matrix.runtime }} diff --git a/Cargo.toml b/Cargo.toml index bfcca7c4..2ca1129c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,9 +42,9 @@ chrono = { version = "^0", optional = true } futures = { version = "^0.3" } futures-util = { version = "^0.3" } rust_decimal = { version = "^1", optional = true } -# sea-query = { version = "^0.12" } +sea-query = { version = "^0.12" } # sea-query = { path = "../sea-query" } -sea-query = { version = "^0.12", git = "https://github.com/samsamai/sea-query.git", branch = "ss/uuid" } +# sea-query = { version = "^0.12", git = "https://github.com/samsamai/sea-query.git", branch = "ss/uuid" } sea-orm-macros = { path = "sea-orm-macros", optional = true } sea-orm-codegen = { path = "sea-orm-codegen", optional = true } serde = { version = "^1.0", features = ["derive"] } @@ -60,17 +60,15 @@ uuid = { version = "0.8", features = ["serde", "v4"], optional = true } [dev-dependencies] async-std = { version = "^1.9", features = ["attributes"] } +tokio = { version = "^1.6", features = ["full"] } +actix-rt = { version = "2.2.0" } maplit = { version = "^1" } rust_decimal_macros = { version = "^1" } - sea-orm = { path = ".", features = [ - "sqlx-postgres", - "sqlx-mysql", - "sqlx-sqlite", "sqlx-json", "sqlx-chrono", "sqlx-decimal", - "runtime-async-std-native-tls", + "debug-print", ] } [features] @@ -83,6 +81,10 @@ default = [ "with-rust_decimal", "mock", "with-uuid", + "sqlx-postgres", + "sqlx-mysql", + "sqlx-sqlite", + ] macros = ["sea-orm-macros"] codegen = ["sea-orm-codegen"] @@ -103,9 +105,28 @@ sqlx-decimal = ["sqlx/decimal", "with-rust_decimal"] sqlx-mysql = ["sqlx-dep", "sea-query/sqlx-mysql", "sqlx/mysql"] sqlx-postgres = ["sqlx-dep", "sea-query/sqlx-postgres", "sqlx/postgres"] sqlx-sqlite = ["sqlx-dep", "sea-query/sqlx-sqlite", "sqlx/sqlite"] -runtime-actix-native-tls = ["sqlx/runtime-actix-native-tls"] -runtime-async-std-native-tls = ["sqlx/runtime-async-std-native-tls"] -runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls"] -runtime-actix-rustls = ["sqlx/runtime-actix-rustls"] -runtime-async-std-rustls = ["sqlx/runtime-async-std-rustls"] -runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls"] +runtime-async-std = [] +runtime-async-std-native-tls = [ + "sqlx/runtime-async-std-native-tls", + "runtime-async-std", +] +runtime-async-std-rustls = [ + "sqlx/runtime-async-std-rustls", + "runtime-async-std", +] +runtime-actix = [] +runtime-actix-native-tls = ["sqlx/runtime-actix-native-tls", "runtime-actix"] +runtime-actix-rustls = ["sqlx/runtime-actix-rustls", "runtime-actix"] +runtime-tokio = [] +runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls", "runtime-tokio"] +runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls", "runtime-tokio"] + +[[test]] +name = "sqlite-basic" +path = "tests/basic.rs" +required-features = ["sqlx-sqlite"] + +[[test]] +name = "postgres" +path = "tests/pg_tests.rs" +required-features = ["sqlx-postgres"] diff --git a/examples/cli/Cargo.toml b/examples/cli/Cargo.toml index 979e847a..75185d3e 100644 --- a/examples/cli/Cargo.toml +++ b/examples/cli/Cargo.toml @@ -5,5 +5,5 @@ edition = "2018" publish = false [dependencies] -sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print" ] } +sea-orm = { path = "../../" } strum = { version = "^0.20", features = [ "derive" ] } diff --git a/examples/cli/src/entity/cake.rs b/examples/cli/src/entity/cake.rs index 12e7c4ab..29f55ac6 100644 --- a/examples/cli/src/entity/cake.rs +++ b/examples/cli/src/entity/cake.rs @@ -36,7 +36,6 @@ impl PrimaryKeyTrait for PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { - CakeFilling, Fruit, } @@ -53,22 +52,24 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::CakeFilling.def() - } -} - impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/cli/src/entity/filling.rs b/examples/cli/src/entity/filling.rs index 752317a4..bedb1ab4 100644 --- a/examples/cli/src/entity/filling.rs +++ b/examples/cli/src/entity/filling.rs @@ -35,9 +35,7 @@ impl PrimaryKeyTrait for PrimaryKey { } #[derive(Copy, Clone, Debug, EnumIter)] -pub enum Relation { - CakeFilling, -} +pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; @@ -52,14 +50,17 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + _ => panic!("No RelationDef"), } } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { - Relation::CakeFilling.def() + super::cake_filling::Relation::Cake.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Filling.def().rev()) } } diff --git a/examples/codegen/Cargo.toml b/examples/codegen/Cargo.toml index a058f0f0..9b78ad0e 100644 --- a/examples/codegen/Cargo.toml +++ b/examples/codegen/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } -sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print" ] } +sea-orm = { path = "../../" } sea-orm-codegen = { path = "../../sea-orm-codegen" } sea-query = { version = "^0.12" } strum = { version = "^0.20", features = [ "derive" ] } diff --git a/examples/codegen/src/entity/cake.rs b/examples/codegen/src/entity/cake.rs index 12e7c4ab..29f55ac6 100644 --- a/examples/codegen/src/entity/cake.rs +++ b/examples/codegen/src/entity/cake.rs @@ -36,7 +36,6 @@ impl PrimaryKeyTrait for PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { - CakeFilling, Fruit, } @@ -53,22 +52,24 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::CakeFilling.def() - } -} - impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/filling.rs b/examples/codegen/src/entity/filling.rs index 752317a4..bedb1ab4 100644 --- a/examples/codegen/src/entity/filling.rs +++ b/examples/codegen/src/entity/filling.rs @@ -35,9 +35,7 @@ impl PrimaryKeyTrait for PrimaryKey { } #[derive(Copy, Clone, Debug, EnumIter)] -pub enum Relation { - CakeFilling, -} +pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; @@ -52,14 +50,17 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + _ => panic!("No RelationDef"), } } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { - Relation::CakeFilling.def() + super::cake_filling::Relation::Cake.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Filling.def().rev()) } } diff --git a/examples/sqlx-mysql/Cargo.toml b/examples/sqlx-mysql/Cargo.toml index 4b39809c..7954fb69 100644 --- a/examples/sqlx-mysql/Cargo.toml +++ b/examples/sqlx-mysql/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } -sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print", "sqlx-json", "macros" ], default-features = false } +sea-orm = { path = "../../" } serde_json = { version = "^1" } futures = { version = "^0.3" } async-stream = { version = "^0.3" } diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index 100314c9..94433fc2 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -16,9 +16,9 @@ name = "sea_orm_codegen" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.2", default-features = false, features = [ "sqlx-mysql", "runtime-async-std-native-tls", "discovery", "writer" ] } +sea-schema = { version = "^0.2", default-features = false, features = [ "sqlx-mysql", "discovery", "writer" ] } sea-query = { version = "^0.12" } -sqlx = { version = "^0.5", features = [ "mysql", "runtime-async-std-native-tls" ] } +sqlx = { version = "^0.5", default-features = false, features = [ "mysql" ] } syn = { version = "^1", default-features = false, features = [ "derive", "parsing", "proc-macro", "printing" ] } quote = "^1" heck = "^0.3" @@ -26,4 +26,30 @@ proc-macro2 = "^1" [dev-dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } -sea-orm = { path = "../", features = ["mock", "sqlx-json", "sqlx-chrono", "runtime-async-std-native-tls"] } +sea-orm = { path = "../" } + +[features] +runtime-actix-native-tls = [ + "sqlx/runtime-actix-native-tls", + "sea-schema/runtime-actix-native-tls", +] +runtime-async-std-native-tls = [ + "sqlx/runtime-async-std-native-tls", + "sea-schema/runtime-async-std-native-tls", +] +runtime-tokio-native-tls = [ + "sqlx/runtime-tokio-native-tls", + "sea-schema/runtime-tokio-native-tls", +] +runtime-actix-rustls = [ + "sqlx/runtime-actix-rustls", + "sea-schema/runtime-actix-rustls", +] +runtime-async-std-rustls = [ + "sqlx/runtime-async-std-rustls", + "sea-schema/runtime-async-std-rustls", +] +runtime-tokio-rustls = [ + "sqlx/runtime-tokio-rustls", + "sea-schema/runtime-tokio-rustls", +] diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index aeaed68d..f1e03b21 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -1,4 +1,4 @@ -use crate::{Column, PrimaryKey, Relation}; +use crate::{Column, ConjunctRelation, PrimaryKey, Relation}; use heck::{CamelCase, SnakeCase}; use proc_macro2::{Ident, TokenStream}; use quote::format_ident; @@ -8,6 +8,7 @@ pub struct Entity { pub(crate) table_name: String, pub(crate) columns: Vec, pub(crate) relations: Vec, + pub(crate) conjunct_relations: Vec, pub(crate) primary_keys: Vec, } @@ -115,6 +116,27 @@ impl Entity { let auto_increment = self.columns.iter().any(|col| col.auto_increment); format_ident!("{}", auto_increment) } + + pub fn get_conjunct_relations_via_snake_case(&self) -> Vec { + self.conjunct_relations + .iter() + .map(|con_rel| con_rel.get_via_snake_case()) + .collect() + } + + pub fn get_conjunct_relations_to_snake_case(&self) -> Vec { + self.conjunct_relations + .iter() + .map(|con_rel| con_rel.get_to_snake_case()) + .collect() + } + + pub fn get_conjunct_relations_to_camel_case(&self) -> Vec { + self.conjunct_relations + .iter() + .map(|con_rel| con_rel.get_to_camel_case()) + .collect() + } } #[cfg(test)] @@ -156,6 +178,7 @@ mod tests { rel_type: RelationType::HasOne, }, ], + conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], @@ -349,4 +372,43 @@ mod tests { format_ident!("{}", true) ); } + + #[test] + fn test_get_conjunct_relations_via_snake_case() { + let entity = setup(); + + for (i, elem) in entity + .get_conjunct_relations_via_snake_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.conjunct_relations[i].get_via_snake_case()); + } + } + + #[test] + fn test_get_conjunct_relations_to_snake_case() { + let entity = setup(); + + for (i, elem) in entity + .get_conjunct_relations_to_snake_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.conjunct_relations[i].get_to_snake_case()); + } + } + + #[test] + fn test_get_conjunct_relations_to_camel_case() { + let entity = setup(); + + for (i, elem) in entity + .get_conjunct_relations_to_camel_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.conjunct_relations[i].get_to_camel_case()); + } + } } diff --git a/sea-orm-codegen/src/entity/conjunct_relation.rs b/sea-orm-codegen/src/entity/conjunct_relation.rs new file mode 100644 index 00000000..05e8010a --- /dev/null +++ b/sea-orm-codegen/src/entity/conjunct_relation.rs @@ -0,0 +1,68 @@ +use heck::{CamelCase, SnakeCase}; +use proc_macro2::Ident; +use quote::format_ident; + +#[derive(Clone, Debug)] +pub struct ConjunctRelation { + pub(crate) via: String, + pub(crate) to: String, +} + +impl ConjunctRelation { + pub fn get_via_snake_case(&self) -> Ident { + format_ident!("{}", self.via.to_snake_case()) + } + + pub fn get_to_snake_case(&self) -> Ident { + format_ident!("{}", self.to.to_snake_case()) + } + + pub fn get_to_camel_case(&self) -> Ident { + format_ident!("{}", self.to.to_camel_case()) + } +} + +#[cfg(test)] +mod tests { + use crate::ConjunctRelation; + + fn setup() -> Vec { + vec![ + ConjunctRelation { + via: "cake_filling".to_owned(), + to: "cake".to_owned(), + }, + ConjunctRelation { + via: "cake_filling".to_owned(), + to: "filling".to_owned(), + }, + ] + } + + #[test] + fn test_get_via_snake_case() { + let conjunct_relations = setup(); + let via_vec = vec!["cake_filling", "cake_filling"]; + for (con_rel, via) in conjunct_relations.into_iter().zip(via_vec) { + assert_eq!(con_rel.get_via_snake_case(), via); + } + } + + #[test] + fn test_get_to_snake_case() { + let conjunct_relations = setup(); + let to_vec = vec!["cake", "filling"]; + for (con_rel, to) in conjunct_relations.into_iter().zip(to_vec) { + assert_eq!(con_rel.get_to_snake_case(), to); + } + } + + #[test] + fn test_get_to_camel_case() { + let conjunct_relations = setup(); + let to_vec = vec!["Cake", "Filling"]; + for (con_rel, to) in conjunct_relations.into_iter().zip(to_vec) { + assert_eq!(con_rel.get_to_camel_case(), to); + } + } +} diff --git a/sea-orm-codegen/src/entity/mod.rs b/sea-orm-codegen/src/entity/mod.rs index c1f43126..6ae09c5e 100644 --- a/sea-orm-codegen/src/entity/mod.rs +++ b/sea-orm-codegen/src/entity/mod.rs @@ -1,5 +1,6 @@ mod base_entity; mod column; +mod conjunct_relation; mod generator; mod primary_key; mod relation; @@ -8,6 +9,7 @@ mod writer; pub use base_entity::*; pub use column::*; +pub use conjunct_relation::*; pub use generator::*; pub use primary_key::*; pub use relation::*; diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index ad48d518..1ae26d00 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -1,4 +1,6 @@ -use crate::{Column, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType}; +use crate::{ + Column, ConjunctRelation, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType, +}; use sea_query::TableStatement; use sea_schema::mysql::def::Schema; use std::collections::HashMap; @@ -11,7 +13,8 @@ pub struct EntityTransformer { impl EntityTransformer { pub fn transform(self) -> Result { let mut inverse_relations: HashMap> = HashMap::new(); - let mut entities = Vec::new(); + let mut conjunct_relations: HashMap> = HashMap::new(); + let mut entities = HashMap::new(); for table_ref in self.schema.tables.iter() { let table_stmt = table_ref.write(); let table_create = match table_stmt { @@ -63,42 +66,65 @@ impl EntityTransformer { table_name: table_name.clone(), columns, relations: relations.clone().collect(), + conjunct_relations: vec![], primary_keys, }; - entities.push(entity); - for mut rel in relations.into_iter() { - let ref_table = rel.ref_table; - let mut unique = true; - for col in rel.columns.iter() { - if !unique_columns.contains(col) { - unique = false; - break; + entities.insert(table_name.clone(), entity.clone()); + for (i, mut rel) in relations.into_iter().enumerate() { + let is_conjunct_relation = entity.primary_keys.len() == entity.columns.len() + && entity.primary_keys.len() == 2; + match is_conjunct_relation { + true => { + let another_rel = entity.relations.get((i == 0) as usize).unwrap(); + let conjunct_relation = ConjunctRelation { + via: table_name.clone(), + to: another_rel.ref_table.clone(), + }; + if let Some(vec) = conjunct_relations.get_mut(&rel.ref_table) { + vec.push(conjunct_relation); + } else { + conjunct_relations.insert(rel.ref_table, vec![conjunct_relation]); + } + } + false => { + let ref_table = rel.ref_table; + let mut unique = true; + for col in rel.columns.iter() { + if !unique_columns.contains(col) { + unique = false; + break; + } + } + let rel_type = if unique { + RelationType::HasOne + } else { + RelationType::HasMany + }; + rel.rel_type = rel_type; + rel.ref_table = table_name.clone(); + rel.columns = Vec::new(); + rel.ref_columns = Vec::new(); + if let Some(vec) = inverse_relations.get_mut(&ref_table) { + vec.push(rel); + } else { + inverse_relations.insert(ref_table, vec![rel]); + } } } - let rel_type = if unique { - RelationType::HasOne - } else { - RelationType::HasMany - }; - rel.rel_type = rel_type; - rel.ref_table = table_name.clone(); - rel.columns = Vec::new(); - rel.ref_columns = Vec::new(); - if let Some(vec) = inverse_relations.get_mut(&ref_table) { - vec.push(rel); - } else { - inverse_relations.insert(ref_table, vec![rel]); - } } } - for (tbl_name, relations) in inverse_relations.iter() { - for ent in entities.iter_mut() { - if ent.table_name.eq(tbl_name) { - ent.relations.append(relations.clone().as_mut()); - } + for (tbl_name, mut relations) in inverse_relations.into_iter() { + if let Some(entity) = entities.get_mut(&tbl_name) { + entity.relations.append(&mut relations); } } - println!("{:#?}", entities); - Ok(EntityWriter { entities }) + for (tbl_name, mut conjunct_relations) in conjunct_relations.into_iter() { + if let Some(entity) = entities.get_mut(&tbl_name) { + entity.conjunct_relations.append(&mut conjunct_relations); + } + } + Ok(EntityWriter { + entities: entities.into_iter().map(|(_, v)| v).collect(), + }) } } diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 267b00d6..d0a49dc9 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -116,6 +116,7 @@ impl EntityWriter { Self::gen_impl_relation_trait(entity), ]; code_blocks.extend(Self::gen_impl_related(entity)); + code_blocks.extend(Self::gen_impl_conjunct_related(entity)); code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]); code_blocks } @@ -239,7 +240,7 @@ impl EntityWriter { let camel = entity.get_relation_ref_tables_camel_case(); let snake = entity.get_relation_ref_tables_snake_case(); camel - .iter() + .into_iter() .zip(snake) .map(|(c, s)| { quote! { @@ -253,6 +254,31 @@ impl EntityWriter { .collect() } + pub fn gen_impl_conjunct_related(entity: &Entity) -> Vec { + let table_name_camel_case = entity.get_table_name_camel_case_ident(); + let via_snake_case = entity.get_conjunct_relations_via_snake_case(); + let to_snake_case = entity.get_conjunct_relations_to_snake_case(); + let to_camel_case = entity.get_conjunct_relations_to_camel_case(); + via_snake_case + .into_iter() + .zip(to_snake_case) + .zip(to_camel_case) + .map(|((via_snake_case, to_snake_case), to_camel_case)| { + quote! { + impl Related for Entity { + fn to() -> RelationDef { + super::#via_snake_case::Relation::#to_camel_case.def() + } + + fn via() -> Option { + Some(super::#via_snake_case::Relation::#table_name_camel_case.def().rev()) + } + } + } + }) + .collect() + } + pub fn gen_impl_active_model_behavior() -> TokenStream { quote! { impl ActiveModelBehavior for ActiveModel {} @@ -277,7 +303,9 @@ impl EntityWriter { #[cfg(test)] mod tests { - use crate::{Column, Entity, EntityWriter, PrimaryKey, Relation, RelationType}; + use crate::{ + Column, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType, + }; use proc_macro2::TokenStream; use sea_query::ColumnType; use std::io::{self, BufRead, BufReader}; @@ -310,20 +338,16 @@ mod tests { unique: false, }, ], - relations: vec![ - Relation { - ref_table: "cake_filling".to_owned(), - columns: vec![], - ref_columns: vec![], - rel_type: RelationType::HasMany, - }, - Relation { - ref_table: "fruit".to_owned(), - columns: vec![], - ref_columns: vec![], - rel_type: RelationType::HasMany, - }, - ], + relations: vec![Relation { + ref_table: "fruit".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasMany, + }], + conjunct_relations: vec![ConjunctRelation { + via: "cake_filling".to_owned(), + to: "filling".to_owned(), + }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], @@ -360,6 +384,7 @@ mod tests { rel_type: RelationType::BelongsTo, }, ], + conjunct_relations: vec![], primary_keys: vec![ PrimaryKey { name: "cake_id".to_owned(), @@ -387,11 +412,10 @@ mod tests { unique: false, }, ], - relations: vec![Relation { - ref_table: "cake_filling".to_owned(), - columns: vec![], - ref_columns: vec![], - rel_type: RelationType::HasMany, + relations: vec![], + conjunct_relations: vec![ConjunctRelation { + via: "cake_filling".to_owned(), + to: "cake".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), @@ -436,6 +460,7 @@ mod tests { rel_type: RelationType::HasMany, }, ], + conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], @@ -471,6 +496,7 @@ mod tests { ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, }], + conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], diff --git a/sea-orm-codegen/tests/entity/cake.rs b/sea-orm-codegen/tests/entity/cake.rs index 12e7c4ab..29f55ac6 100644 --- a/sea-orm-codegen/tests/entity/cake.rs +++ b/sea-orm-codegen/tests/entity/cake.rs @@ -36,7 +36,6 @@ impl PrimaryKeyTrait for PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { - CakeFilling, Fruit, } @@ -53,22 +52,24 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::CakeFilling.def() - } -} - impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/entity/filling.rs b/sea-orm-codegen/tests/entity/filling.rs index 752317a4..bedb1ab4 100644 --- a/sea-orm-codegen/tests/entity/filling.rs +++ b/sea-orm-codegen/tests/entity/filling.rs @@ -35,9 +35,7 @@ impl PrimaryKeyTrait for PrimaryKey { } #[derive(Copy, Clone, Debug, EnumIter)] -pub enum Relation { - CakeFilling, -} +pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; @@ -52,14 +50,17 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + _ => panic!("No RelationDef"), } } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { - Relation::CakeFilling.def() + super::cake_filling::Relation::Cake.def() + } + fn via() -> Option { + Some(super::cake_filling::Relation::Filling.def().rev()) } } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index f8d60137..9414fea6 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,4 @@ -use crate::{ - error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Insert, Iterable, Statement, -}; +use crate::{error::*, ActiveModelTrait, DatabaseConnection, Insert, Statement}; use sea_query::InsertStatement; use std::future::Future; @@ -18,6 +16,7 @@ impl Insert where A: ActiveModelTrait, { + #[allow(unused_mut)] pub fn exec( self, db: &DatabaseConnection, @@ -26,6 +25,7 @@ where let mut query = self.query; #[cfg(feature = "sqlx-postgres")] if let DatabaseConnection::SqlxPostgresPoolConnection(_) = db { + use crate::{EntityTrait, Iterable}; use sea_query::{Alias, Expr, Query}; for key in ::PrimaryKey::iter() { query.returning( diff --git a/src/executor/query.rs b/src/executor/query.rs index 06e8ec7b..a7aba51e 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -134,6 +134,7 @@ macro_rules! try_getable_unsigned { row.try_get(column.as_str()) .map_err(crate::sqlx_error_to_query_err) } + #[cfg(feature = "sqlx-postgres")] QueryResultRow::SqlxPostgres(_) => { panic!("{} unsupported by sqlx-postgres", stringify!($type)) } @@ -161,6 +162,7 @@ macro_rules! try_getable_unsigned { Err(_) => Ok(None), } } + #[cfg(feature = "sqlx-postgres")] QueryResultRow::SqlxPostgres(_) => { panic!("{} unsupported by sqlx-postgres", stringify!($type)) } diff --git a/tests/bakery_chain_tests.rs b/tests/bakery_chain_tests.rs index d564d0b2..bae125e5 100644 --- a/tests/bakery_chain_tests.rs +++ b/tests/bakery_chain_tests.rs @@ -5,8 +5,8 @@ pub use common::{bakery_chain::*, setup::*, TestContext}; mod crud; -#[async_std::test] // cargo test --test bakery_chain_tests -- --nocapture +#[async_std::test] async fn main() { let base_url = "mysql://root:@localhost"; let db_name = "bakery_chain_schema_crud_tests"; diff --git a/tests/basic.rs b/tests/basic.rs index faa02a63..20256ad4 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -2,8 +2,11 @@ use sea_orm::{entity::*, error::*, sea_query, tests_cfg::*, DbBackend, DbConn, S mod setup; -#[async_std::test] // cargo test --test basic -- --nocapture +#[cfg_attr(feature = "runtime-async-std", async_std::main)] +#[cfg_attr(feature = "runtime-actix", actix_rt::main)] +#[cfg_attr(feature = "runtime-tokio", tokio::main)] +#[cfg(feature = "sqlx-sqlite")] async fn main() { let db: DbConn = setup::setup().await; diff --git a/tests/pg_tests.rs b/tests/pg_tests.rs index 1bf0b49d..c370112d 100644 --- a/tests/pg_tests.rs +++ b/tests/pg_tests.rs @@ -9,7 +9,10 @@ pub use common::bakery_chain::*; use sea_query::{ColumnDef, TableCreateStatement}; // cargo test --test pg_tests -- --nocapture -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::main)] +#[cfg_attr(feature = "runtime-actix", actix_rt::main)] +#[cfg_attr(feature = "runtime-tokio", tokio::main)] +#[cfg(feature = "sqlx-postgres")] async fn main() { let base_url = "postgres://root:root@localhost"; let db_name = "bakery_chain_schema_crud_tests";