From 83e48597764a0efb477a61e3f9a751e20acb6ce8 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 22 Jun 2021 22:40:43 +0800 Subject: [PATCH 01/18] codegen ColumnDef null() and unique() --- examples/codegen/src/out/fruit.rs | 2 +- sea-orm-codegen/src/entity/column.rs | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/examples/codegen/src/out/fruit.rs b/examples/codegen/src/out/fruit.rs index 6e190749..17f653ed 100644 --- a/examples/codegen/src/out/fruit.rs +++ b/examples/codegen/src/out/fruit.rs @@ -47,7 +47,7 @@ impl ColumnTrait for Column { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(Some(255u32)).def(), - Self::CakeId => ColumnType::Integer.def(), + Self::CakeId => ColumnType::Integer.def().null(), } } } diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index b575057f..e8a0b543 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -9,6 +9,7 @@ pub struct Column { pub(crate) col_type: ColumnType, pub(crate) auto_increment: bool, pub(crate) not_null: bool, + pub(crate) unique: bool, } impl Column { @@ -50,7 +51,7 @@ impl Column { } pub fn get_def(&self) -> TokenStream { - match &self.col_type { + let mut col_def = match &self.col_type { ColumnType::Char(s) => match s { Some(s) => quote! { ColumnType::Char(Some(#s)).def() }, None => quote! { ColumnType::Char(None).def() }, @@ -86,7 +87,18 @@ impl Column { let s = s.to_string(); quote! { ColumnType::Custom(#s.to_owned()).def() } } + }; + if !self.not_null { + col_def.extend(quote! { + .null() + }); } + if self.unique { + col_def.extend(quote! { + .unique() + }); + } + col_def } } @@ -115,11 +127,21 @@ impl From<&ColumnDef> for Column { }) .collect(); let not_null = !not_nulls.is_empty(); + let uniques: Vec = col_def + .get_column_spec() + .iter() + .filter_map(|spec| match spec { + ColumnSpec::UniqueKey => Some(true), + _ => None, + }) + .collect(); + let unique = !uniques.is_empty(); Self { name, col_type, auto_increment, not_null, + unique, } } } From 9a25bb9c36d97e93a7c3c26b88fbe87c6dc02a6f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 22 Jun 2021 23:42:10 +0800 Subject: [PATCH 02/18] Use `belongs_to`, `has_one` and `has_many` --- examples/codegen/src/out/cake.rs | 10 ++---- examples/codegen/src/out/cake_filling.rs | 4 +-- examples/codegen/src/out/filling.rs | 5 +-- examples/codegen/src/out/fruit.rs | 2 +- sea-orm-codegen/src/entity/entity.rs | 4 +++ sea-orm-codegen/src/entity/relation.rs | 37 ++++++++++++++++++++--- sea-orm-codegen/src/entity/transformer.rs | 27 ++++++++++++++--- sea-orm-codegen/src/entity/writer.rs | 10 ++---- 8 files changed, 67 insertions(+), 32 deletions(-) diff --git a/examples/codegen/src/out/cake.rs b/examples/codegen/src/out/cake.rs index 87ee5f1a..9b786a5f 100644 --- a/examples/codegen/src/out/cake.rs +++ b/examples/codegen/src/out/cake.rs @@ -53,14 +53,8 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) - .from(Column::Id) - .to(super::cake_filling::Column::CakeId) - .into(), - Self::Fruit => Entity::has_many(super::fruit::Entity) - .from(Column::Id) - .to(super::fruit::Column::CakeId) - .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } diff --git a/examples/codegen/src/out/cake_filling.rs b/examples/codegen/src/out/cake_filling.rs index b35279d4..d5b4b8b6 100644 --- a/examples/codegen/src/out/cake_filling.rs +++ b/examples/codegen/src/out/cake_filling.rs @@ -54,11 +54,11 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Filling => Entity::has_one(super::filling::Entity) + Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), diff --git a/examples/codegen/src/out/filling.rs b/examples/codegen/src/out/filling.rs index 4134652f..e4563e55 100644 --- a/examples/codegen/src/out/filling.rs +++ b/examples/codegen/src/out/filling.rs @@ -52,10 +52,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) - .from(Column::Id) - .to(super::cake_filling::Column::FillingId) - .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), } } } diff --git a/examples/codegen/src/out/fruit.rs b/examples/codegen/src/out/fruit.rs index 17f653ed..f65d5b38 100644 --- a/examples/codegen/src/out/fruit.rs +++ b/examples/codegen/src/out/fruit.rs @@ -55,7 +55,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), diff --git a/sea-orm-codegen/src/entity/entity.rs b/sea-orm-codegen/src/entity/entity.rs index 788002ce..2abb4a7a 100644 --- a/sea-orm-codegen/src/entity/entity.rs +++ b/sea-orm-codegen/src/entity/entity.rs @@ -86,6 +86,10 @@ impl Entity { .collect() } + pub fn get_relation_defs(&self) -> Vec { + self.relations.iter().map(|rel| rel.get_def()).collect() + } + pub fn get_relation_rel_types(&self) -> Vec { self.relations .iter() diff --git a/sea-orm-codegen/src/entity/relation.rs b/sea-orm-codegen/src/entity/relation.rs index bbcfd155..8eab372d 100644 --- a/sea-orm-codegen/src/entity/relation.rs +++ b/sea-orm-codegen/src/entity/relation.rs @@ -1,9 +1,15 @@ use heck::{CamelCase, SnakeCase}; -use proc_macro2::Ident; -use quote::format_ident; -use sea_orm::RelationType; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; use sea_query::TableForeignKey; +#[derive(Clone, Debug)] +pub enum RelationType { + HasOne, + HasMany, + BelongsTo, +} + #[derive(Clone, Debug)] pub struct Relation { pub(crate) ref_table: String, @@ -21,10 +27,33 @@ impl Relation { format_ident!("{}", self.ref_table.to_camel_case()) } + pub fn get_def(&self) -> TokenStream { + let rel_type = self.get_rel_type(); + let ref_table_snake_case = self.get_ref_table_snake_case(); + match self.rel_type { + RelationType::HasOne | RelationType::HasMany => { + quote! { + Entity::#rel_type(super::#ref_table_snake_case::Entity).into() + } + } + RelationType::BelongsTo => { + let column_camel_case = self.get_column_camel_case(); + let ref_column_camel_case = self.get_ref_column_camel_case(); + quote! { + Entity::#rel_type(super::#ref_table_snake_case::Entity) + .from(Column::#column_camel_case) + .to(super::#ref_table_snake_case::Column::#ref_column_camel_case) + .into() + } + } + } + } + pub fn get_rel_type(&self) -> Ident { match self.rel_type { RelationType::HasOne => format_ident!("has_one"), RelationType::HasMany => format_ident!("has_many"), + RelationType::BelongsTo => format_ident!("belongs_to"), } } @@ -49,7 +78,7 @@ impl From<&TableForeignKey> for Relation { }; let columns = tbl_fk.get_columns(); let ref_columns = tbl_fk.get_ref_columns(); - let rel_type = RelationType::HasOne; + let rel_type = RelationType::BelongsTo; Self { ref_table, columns, diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index d437d0f3..02c7fb82 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -1,5 +1,4 @@ -use crate::{Entity, EntityWriter, Error, PrimaryKey, Relation}; -use sea_orm::RelationType; +use crate::{Column, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType}; use sea_query::TableStatement; use sea_schema::mysql::def::Schema; use std::{collections::HashMap, mem::swap}; @@ -31,11 +30,16 @@ impl EntityTransformer { )) } }; - let columns = table_create + let columns: Vec = table_create .get_columns() .iter() .map(|col_def| col_def.into()) .collect(); + let unique_columns: Vec = columns + .iter() + .filter(|col| col.unique) + .map(|col| col.name.clone()) + .collect(); let relations = table_create .get_foreign_key_create_stmts() .iter() @@ -64,9 +68,22 @@ impl EntityTransformer { entities.push(entity); for mut rel in relations.into_iter() { let ref_table = rel.ref_table; - swap(&mut rel.columns, &mut rel.ref_columns); - rel.rel_type = RelationType::HasMany; + 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 { diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 73e12d07..01dd4a4c 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -217,20 +217,14 @@ impl EntityWriter { pub fn gen_impl_relation_trait(entity: &Entity) -> TokenStream { let relation_ref_tables_camel_case = entity.get_relation_ref_tables_camel_case(); - let relation_rel_types = entity.get_relation_rel_types(); - let relation_ref_tables_snake_case = entity.get_relation_ref_tables_snake_case(); - let relation_columns_camel_case = entity.get_relation_columns_camel_case(); - let relation_ref_columns_camel_case = entity.get_relation_ref_columns_camel_case(); + let relation_defs = entity.get_relation_defs(); let quoted = if relation_ref_tables_camel_case.is_empty() { quote! { _ => panic!("No RelationDef"), } } else { quote! { - #(Self::#relation_ref_tables_camel_case => Entity::#relation_rel_types(super::#relation_ref_tables_snake_case::Entity) - .from(Column::#relation_columns_camel_case) - .to(super::#relation_ref_tables_snake_case::Column::#relation_ref_columns_camel_case) - .into()),* + #(Self::#relation_ref_tables_camel_case => #relation_defs),* } }; quote! { From 2cf8911a4b7d0e611c3a1bfe9db0beffe9e3867b Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 25 Jun 2021 12:30:18 +0800 Subject: [PATCH 03/18] Diesel --- Diesel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Diesel.md b/Diesel.md index d0680179..080c7154 100644 --- a/Diesel.md +++ b/Diesel.md @@ -20,7 +20,7 @@ SeaSchema is our schema discovery library, but it is not sealed inside SeaORM. S In addition to the sync vs async foundation, the biggest distinction between Diesel and SeaORM is static vs dynamic. Diesel has an everything-compile-time design which has its pros and cons. SeaORM is dynamic, in which things are established runtime. It offers more flexibility. While you loses some compile-time guarantee, SeaORM helps you to prove correctness by unit testing instead. -Both libraries make heavy use of traits and generics, but SeaORM generate less types. (Each column in Diesel is a struct, while each column in SeaORM is a enum variant). That probably means looser type/lifetime constraints and faster compilation. +Both libraries make heavy use of traits and generics, but SeaORM generate less types (each column in Diesel is a struct, while each column in SeaORM is a enum variant) and less depthness (there won't be `A>>`). That probably means looser type/lifetime constraints and faster compilation. You don't have to use macros when using SeaORM. We provide some derive macros for convenience, but they are entirely optional. From c6a1cfab2ea5668f32fb48caff73d1840624af41 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 23 Jun 2021 22:26:42 +0800 Subject: [PATCH 04/18] Update GitHub action --- .github/workflows/rust.yml | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a94a83e3..8853cca3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,22 +1,33 @@ -name: Rust +name: sea-orm on: push: - branches: [ master ] + branches: + - master pull_request: - branches: [ master ] + branches: + - master env: CARGO_TERM_COLOR: always jobs: - build: - + test: + name: Unit Test runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions-rs/cargo@v1 + with: + command: build + + - uses: actions-rs/cargo@v1 + with: + command: test From fa30519f999b73ce3cf688d65a694e20d08ab050 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 23 Jun 2021 18:38:11 +0800 Subject: [PATCH 05/18] sea-orm-cli read config from .env, update command, rename binary to sea-orm-cli --- Cargo.toml | 1 + examples/cli/.env | 2 + examples/cli/Cargo.toml | 9 ++ examples/cli/README.md | 18 ++++ .../src/out => cli/src/entity}/cake.rs | 0 .../out => cli/src/entity}/cake_filling.rs | 0 .../src/out => cli/src/entity}/filling.rs | 0 .../src/out => cli/src/entity}/fruit.rs | 14 +++ .../src/out => cli/src/entity}/mod.rs | 1 + .../src/out => cli/src/entity}/prelude.rs | 1 + examples/cli/src/entity/vendor.rs | 78 ++++++++++++++++ examples/cli/src/main.rs | 3 + examples/codegen/src/entity/cake.rs | 89 ++++++++++++++++++ examples/codegen/src/entity/cake_filling.rs | 90 ++++++++++++++++++ examples/codegen/src/entity/filling.rs | 75 +++++++++++++++ examples/codegen/src/entity/fruit.rs | 92 +++++++++++++++++++ examples/codegen/src/entity/mod.rs | 7 ++ examples/codegen/src/entity/prelude.rs | 7 ++ examples/codegen/src/entity/vendor.rs | 78 ++++++++++++++++ examples/codegen/src/main.rs | 4 +- sea-orm-cli/Cargo.toml | 3 +- sea-orm-cli/src/cli.rs | 12 ++- sea-orm-cli/src/main.rs | 9 +- 23 files changed, 582 insertions(+), 11 deletions(-) create mode 100644 examples/cli/.env create mode 100644 examples/cli/Cargo.toml create mode 100644 examples/cli/README.md rename examples/{codegen/src/out => cli/src/entity}/cake.rs (100%) rename examples/{codegen/src/out => cli/src/entity}/cake_filling.rs (100%) rename examples/{codegen/src/out => cli/src/entity}/filling.rs (100%) rename examples/{codegen/src/out => cli/src/entity}/fruit.rs (79%) rename examples/{codegen/src/out => cli/src/entity}/mod.rs (88%) rename examples/{codegen/src/out => cli/src/entity}/prelude.rs (84%) create mode 100644 examples/cli/src/entity/vendor.rs create mode 100644 examples/cli/src/main.rs create mode 100644 examples/codegen/src/entity/cake.rs create mode 100644 examples/codegen/src/entity/cake_filling.rs create mode 100644 examples/codegen/src/entity/filling.rs create mode 100644 examples/codegen/src/entity/fruit.rs create mode 100644 examples/codegen/src/entity/mod.rs create mode 100644 examples/codegen/src/entity/prelude.rs create mode 100644 examples/codegen/src/entity/vendor.rs diff --git a/Cargo.toml b/Cargo.toml index 43488456..02a11051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "sea-orm-cli", "examples/sqlx-mysql", "examples/codegen", + "examples/cli", ] [package] diff --git a/examples/cli/.env b/examples/cli/.env new file mode 100644 index 00000000..e08d4810 --- /dev/null +++ b/examples/cli/.env @@ -0,0 +1,2 @@ +DATABASE_URI=mysql://sea:sea@localhost/bakery +DATABASE_SCHEMA=bakery \ No newline at end of file diff --git a/examples/cli/Cargo.toml b/examples/cli/Cargo.toml new file mode 100644 index 00000000..979e847a --- /dev/null +++ b/examples/cli/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sea-orm-cli-example" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print" ] } +strum = { version = "^0.20", features = [ "derive" ] } diff --git a/examples/cli/README.md b/examples/cli/README.md new file mode 100644 index 00000000..1d34c06e --- /dev/null +++ b/examples/cli/README.md @@ -0,0 +1,18 @@ +# SeaORM CLI Example + +Prepare: + +Setup a test database and configure the connection string in `.env`. +Run `bakery.sql` to setup the test table and data. + +Building sea-orm-cli: + +```sh +(cd ../../sea-orm-cli ; cargo build) +``` + +Generating entity: + +```sh +../../target/debug/sea-orm-cli generate entity -o src/entity +``` diff --git a/examples/codegen/src/out/cake.rs b/examples/cli/src/entity/cake.rs similarity index 100% rename from examples/codegen/src/out/cake.rs rename to examples/cli/src/entity/cake.rs diff --git a/examples/codegen/src/out/cake_filling.rs b/examples/cli/src/entity/cake_filling.rs similarity index 100% rename from examples/codegen/src/out/cake_filling.rs rename to examples/cli/src/entity/cake_filling.rs diff --git a/examples/codegen/src/out/filling.rs b/examples/cli/src/entity/filling.rs similarity index 100% rename from examples/codegen/src/out/filling.rs rename to examples/cli/src/entity/filling.rs diff --git a/examples/codegen/src/out/fruit.rs b/examples/cli/src/entity/fruit.rs similarity index 79% rename from examples/codegen/src/out/fruit.rs rename to examples/cli/src/entity/fruit.rs index 6e190749..d3f21bf9 100644 --- a/examples/codegen/src/out/fruit.rs +++ b/examples/cli/src/entity/fruit.rs @@ -39,6 +39,7 @@ impl PrimaryKeyTrait for PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, + Vendor, } impl ColumnTrait for Column { @@ -59,6 +60,10 @@ impl RelationTrait for Relation { .from(Column::CakeId) .to(super::cake::Column::Id) .into(), + Self::Vendor => Entity::has_many(super::vendor::Entity) + .from(Column::Id) + .to(super::vendor::Column::FruitId) + .into(), } } } @@ -69,10 +74,19 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Vendor.def() + } +} + impl Model { pub fn find_cake(&self) -> Select { Entity::find_related().belongs_to::(self) } + pub fn find_vendor(&self) -> Select { + Entity::find_related().belongs_to::(self) + } } impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/out/mod.rs b/examples/cli/src/entity/mod.rs similarity index 88% rename from examples/codegen/src/out/mod.rs rename to examples/cli/src/entity/mod.rs index 006f7e80..395d29f9 100644 --- a/examples/codegen/src/out/mod.rs +++ b/examples/cli/src/entity/mod.rs @@ -4,3 +4,4 @@ pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; +pub mod vendor; diff --git a/examples/codegen/src/out/prelude.rs b/examples/cli/src/entity/prelude.rs similarity index 84% rename from examples/codegen/src/out/prelude.rs rename to examples/cli/src/entity/prelude.rs index b4c1c94f..b4e85c78 100644 --- a/examples/codegen/src/out/prelude.rs +++ b/examples/cli/src/entity/prelude.rs @@ -4,3 +4,4 @@ pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; +pub use super::vendor::Entity as Vendor; diff --git a/examples/cli/src/entity/vendor.rs b/examples/cli/src/entity/vendor.rs new file mode 100644 index 00000000..9c4ca7dd --- /dev/null +++ b/examples/cli/src/entity/vendor.rs @@ -0,0 +1,78 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "vendor" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub fruit_id: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + FruitId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + Self::FruitId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_one(super::fruit::Entity) + .from(Column::FruitId) + .to(super::fruit::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Model { + pub fn find_fruit(&self) -> Select { + Entity::find_related().belongs_to::(self) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/cli/src/main.rs b/examples/cli/src/main.rs new file mode 100644 index 00000000..1a7baf89 --- /dev/null +++ b/examples/cli/src/main.rs @@ -0,0 +1,3 @@ +mod entity; + +fn main() {} diff --git a/examples/codegen/src/entity/cake.rs b/examples/codegen/src/entity/cake.rs new file mode 100644 index 00000000..87ee5f1a --- /dev/null +++ b/examples/codegen/src/entity/cake.rs @@ -0,0 +1,89 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + CakeFilling, + Fruit, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) + .from(Column::Id) + .to(super::cake_filling::Column::CakeId) + .into(), + Self::Fruit => Entity::has_many(super::fruit::Entity) + .from(Column::Id) + .to(super::fruit::Column::CakeId) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::CakeFilling.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Model { + pub fn find_cake_filling(&self) -> Select { + Entity::find_related().belongs_to::(self) + } + pub fn find_fruit(&self) -> Select { + Entity::find_related().belongs_to::(self) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/cake_filling.rs b/examples/codegen/src/entity/cake_filling.rs new file mode 100644 index 00000000..b35279d4 --- /dev/null +++ b/examples/codegen/src/entity/cake_filling.rs @@ -0,0 +1,90 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake_filling" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub cake_id: i32, + pub filling_id: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + CakeId, + FillingId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + CakeId, + FillingId, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Filling, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::CakeId => ColumnType::Integer.def(), + Self::FillingId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::has_one(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Filling => Entity::has_one(super::filling::Entity) + .from(Column::FillingId) + .to(super::filling::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Filling.def() + } +} + +impl Model { + pub fn find_cake(&self) -> Select { + Entity::find_related().belongs_to::(self) + } + pub fn find_filling(&self) -> Select { + Entity::find_related().belongs_to::(self) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/filling.rs b/examples/codegen/src/entity/filling.rs new file mode 100644 index 00000000..4134652f --- /dev/null +++ b/examples/codegen/src/entity/filling.rs @@ -0,0 +1,75 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "filling" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + CakeFilling, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) + .from(Column::Id) + .to(super::cake_filling::Column::FillingId) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::CakeFilling.def() + } +} + +impl Model { + pub fn find_cake_filling(&self) -> Select { + Entity::find_related().belongs_to::(self) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/fruit.rs b/examples/codegen/src/entity/fruit.rs new file mode 100644 index 00000000..d3f21bf9 --- /dev/null +++ b/examples/codegen/src/entity/fruit.rs @@ -0,0 +1,92 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "fruit" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub cake_id: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + CakeId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Vendor, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + Self::CakeId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::has_one(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Vendor => Entity::has_many(super::vendor::Entity) + .from(Column::Id) + .to(super::vendor::Column::FruitId) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Vendor.def() + } +} + +impl Model { + pub fn find_cake(&self) -> Select { + Entity::find_related().belongs_to::(self) + } + pub fn find_vendor(&self) -> Select { + Entity::find_related().belongs_to::(self) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/mod.rs b/examples/codegen/src/entity/mod.rs new file mode 100644 index 00000000..395d29f9 --- /dev/null +++ b/examples/codegen/src/entity/mod.rs @@ -0,0 +1,7 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +pub mod cake; +pub mod cake_filling; +pub mod filling; +pub mod fruit; +pub mod vendor; diff --git a/examples/codegen/src/entity/prelude.rs b/examples/codegen/src/entity/prelude.rs new file mode 100644 index 00000000..b4e85c78 --- /dev/null +++ b/examples/codegen/src/entity/prelude.rs @@ -0,0 +1,7 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +pub use super::cake::Entity as Cake; +pub use super::cake_filling::Entity as CakeFilling; +pub use super::filling::Entity as Filling; +pub use super::fruit::Entity as Fruit; +pub use super::vendor::Entity as Vendor; diff --git a/examples/codegen/src/entity/vendor.rs b/examples/codegen/src/entity/vendor.rs new file mode 100644 index 00000000..9c4ca7dd --- /dev/null +++ b/examples/codegen/src/entity/vendor.rs @@ -0,0 +1,78 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "vendor" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub fruit_id: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + FruitId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + Self::FruitId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_one(super::fruit::Entity) + .from(Column::FruitId) + .to(super::fruit::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Model { + pub fn find_fruit(&self) -> Select { + Entity::find_related().belongs_to::(self) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/main.rs b/examples/codegen/src/main.rs index dcc034b2..f15463dd 100644 --- a/examples/codegen/src/main.rs +++ b/examples/codegen/src/main.rs @@ -1,4 +1,4 @@ -mod out; +mod entity; use sea_orm_codegen::{EntityGenerator, Error}; @@ -10,7 +10,7 @@ async fn main() -> Result<(), Error> { let _generator = EntityGenerator::discover(uri, schema) .await? .transform()? - .generate("src/out")?; + .generate("src/entity")?; Ok(()) } diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 88a69588..677954f6 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -12,10 +12,11 @@ keywords = [ "orm", "database", "sql", "mysql", "postgres", "sqlite", "cli" ] publish = false [[bin]] -name = "sea-orm" +name = "sea-orm-cli" path = "src/main.rs" [dependencies] clap = { version = "^2.33.3" } +dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes" ] } sea-orm-codegen = { path = "../sea-orm-codegen" } diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 7bedbb90..474dec5f 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -1,11 +1,11 @@ use clap::{App, AppSettings, Arg, SubCommand}; pub fn build_cli() -> App<'static, 'static> { - let entity_subcommand = SubCommand::with_name("entity") - .about("Entity related commands") + let entity_subcommand = SubCommand::with_name("generate") + .about("Codegen related commands") .setting(AppSettings::VersionlessSubcommands) .subcommand( - SubCommand::with_name("generate") + SubCommand::with_name("entity") .about("Generate entity") .arg( Arg::with_name("DATABASE_URI") @@ -13,7 +13,8 @@ pub fn build_cli() -> App<'static, 'static> { .short("u") .help("Database URI") .takes_value(true) - .required(true), + .required(true) + .env("DATABASE_URI"), ) .arg( Arg::with_name("DATABASE_SCHEMA") @@ -21,7 +22,8 @@ pub fn build_cli() -> App<'static, 'static> { .short("s") .help("Database schema") .takes_value(true) - .required(true), + .required(true) + .env("DATABASE_SCHEMA"), ) .arg( Arg::with_name("OUTPUT_DIR") diff --git a/sea-orm-cli/src/main.rs b/sea-orm-cli/src/main.rs index ad0451ad..0726e17b 100644 --- a/sea-orm-cli/src/main.rs +++ b/sea-orm-cli/src/main.rs @@ -1,4 +1,5 @@ use clap::ArgMatches; +use dotenv::dotenv; use sea_orm_codegen::EntityGenerator; use std::{error::Error, fmt::Display}; @@ -6,19 +7,21 @@ mod cli; #[async_std::main] async fn main() { + dotenv().ok(); + let matches = cli::build_cli().get_matches(); match matches.subcommand() { - ("entity", Some(matches)) => run_entity_command(matches) + ("generate", Some(matches)) => run_generate_command(matches) .await .unwrap_or_else(handle_error), _ => unreachable!("You should never see this message"), } } -async fn run_entity_command(matches: &ArgMatches<'_>) -> Result<(), Box> { +async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box> { match matches.subcommand() { - ("generate", Some(args)) => { + ("entity", Some(args)) => { let uri = args.value_of("DATABASE_URI").unwrap(); let schema = args.value_of("DATABASE_SCHEMA").unwrap(); let output_dir = args.value_of("OUTPUT_DIR").unwrap(); From 1b2dc1ca43a802c2f2574bcf86c46675a5534b0b Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 26 Jun 2021 22:06:10 +0800 Subject: [PATCH 06/18] Revert examples --- examples/codegen/src/out/cake.rs | 10 ++++++++-- examples/codegen/src/out/cake_filling.rs | 4 ++-- examples/codegen/src/out/filling.rs | 5 ++++- examples/codegen/src/out/fruit.rs | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/codegen/src/out/cake.rs b/examples/codegen/src/out/cake.rs index 9b786a5f..87ee5f1a 100644 --- a/examples/codegen/src/out/cake.rs +++ b/examples/codegen/src/out/cake.rs @@ -53,8 +53,14 @@ 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(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) + .from(Column::Id) + .to(super::cake_filling::Column::CakeId) + .into(), + Self::Fruit => Entity::has_many(super::fruit::Entity) + .from(Column::Id) + .to(super::fruit::Column::CakeId) + .into(), } } } diff --git a/examples/codegen/src/out/cake_filling.rs b/examples/codegen/src/out/cake_filling.rs index d5b4b8b6..b35279d4 100644 --- a/examples/codegen/src/out/cake_filling.rs +++ b/examples/codegen/src/out/cake_filling.rs @@ -54,11 +54,11 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::belongs_to(super::cake::Entity) + Self::Cake => Entity::has_one(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Filling => Entity::belongs_to(super::filling::Entity) + Self::Filling => Entity::has_one(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), diff --git a/examples/codegen/src/out/filling.rs b/examples/codegen/src/out/filling.rs index e4563e55..4134652f 100644 --- a/examples/codegen/src/out/filling.rs +++ b/examples/codegen/src/out/filling.rs @@ -52,7 +52,10 @@ 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::CakeFilling => Entity::has_many(super::cake_filling::Entity) + .from(Column::Id) + .to(super::cake_filling::Column::FillingId) + .into(), } } } diff --git a/examples/codegen/src/out/fruit.rs b/examples/codegen/src/out/fruit.rs index f65d5b38..17f653ed 100644 --- a/examples/codegen/src/out/fruit.rs +++ b/examples/codegen/src/out/fruit.rs @@ -55,7 +55,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::belongs_to(super::cake::Entity) + Self::Cake => Entity::has_one(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), From 0afd226dbee970eccff90410a0a8712da8f688bd Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 26 Jun 2021 22:27:29 +0800 Subject: [PATCH 07/18] Update generated examples --- examples/cli/src/entity/cake.rs | 10 ++-------- examples/cli/src/entity/cake_filling.rs | 4 ++-- examples/cli/src/entity/filling.rs | 5 +---- examples/cli/src/entity/fruit.rs | 16 +--------------- examples/cli/src/entity/mod.rs | 1 - examples/cli/src/entity/prelude.rs | 1 - 6 files changed, 6 insertions(+), 31 deletions(-) diff --git a/examples/cli/src/entity/cake.rs b/examples/cli/src/entity/cake.rs index 87ee5f1a..9b786a5f 100644 --- a/examples/cli/src/entity/cake.rs +++ b/examples/cli/src/entity/cake.rs @@ -53,14 +53,8 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) - .from(Column::Id) - .to(super::cake_filling::Column::CakeId) - .into(), - Self::Fruit => Entity::has_many(super::fruit::Entity) - .from(Column::Id) - .to(super::fruit::Column::CakeId) - .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } diff --git a/examples/cli/src/entity/cake_filling.rs b/examples/cli/src/entity/cake_filling.rs index b35279d4..d5b4b8b6 100644 --- a/examples/cli/src/entity/cake_filling.rs +++ b/examples/cli/src/entity/cake_filling.rs @@ -54,11 +54,11 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Filling => Entity::has_one(super::filling::Entity) + Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), diff --git a/examples/cli/src/entity/filling.rs b/examples/cli/src/entity/filling.rs index 4134652f..e4563e55 100644 --- a/examples/cli/src/entity/filling.rs +++ b/examples/cli/src/entity/filling.rs @@ -52,10 +52,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) - .from(Column::Id) - .to(super::cake_filling::Column::FillingId) - .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), } } } diff --git a/examples/cli/src/entity/fruit.rs b/examples/cli/src/entity/fruit.rs index c9b6d27a..f65d5b38 100644 --- a/examples/cli/src/entity/fruit.rs +++ b/examples/cli/src/entity/fruit.rs @@ -39,7 +39,6 @@ impl PrimaryKeyTrait for PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, - Vendor, } impl ColumnTrait for Column { @@ -56,14 +55,10 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Vendor => Entity::has_many(super::vendor::Entity) - .from(Column::Id) - .to(super::vendor::Column::FruitId) - .into(), } } } @@ -74,19 +69,10 @@ impl Related for Entity { } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::Vendor.def() - } -} - impl Model { pub fn find_cake(&self) -> Select { Entity::find_related().belongs_to::(self) } - pub fn find_vendor(&self) -> Select { - Entity::find_related().belongs_to::(self) - } } impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/cli/src/entity/mod.rs b/examples/cli/src/entity/mod.rs index 395d29f9..006f7e80 100644 --- a/examples/cli/src/entity/mod.rs +++ b/examples/cli/src/entity/mod.rs @@ -4,4 +4,3 @@ pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; -pub mod vendor; diff --git a/examples/cli/src/entity/prelude.rs b/examples/cli/src/entity/prelude.rs index b4e85c78..b4c1c94f 100644 --- a/examples/cli/src/entity/prelude.rs +++ b/examples/cli/src/entity/prelude.rs @@ -4,4 +4,3 @@ pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; -pub use super::vendor::Entity as Vendor; From 19c0b0c35dadcc6d01e552345cb61d1c8dfcdf27 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 23 Jun 2021 15:00:20 +0800 Subject: [PATCH 08/18] Select to many --- src/executor/select.rs | 39 +++++++++++++++++++++++++++++++++++---- src/query/combine.rs | 18 ++++++++++++++---- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/executor/select.rs b/src/executor/select.rs index d4d5830f..b15c0c4b 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,6 +1,6 @@ use crate::{ - query::combine, DatabaseConnection, EntityTrait, FromQueryResult, JsonValue, Paginator, - QueryErr, QueryResult, Select, SelectTwo, TypeErr, + query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, JsonValue, + ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, TypeErr, }; use sea_query::SelectStatement; use std::marker::PhantomData; @@ -125,6 +125,33 @@ where } } + fn parse_query_result(rows: Vec<(E::Model, F::Model)>) -> Vec<(E::Model, Vec)> { + let mut acc = Vec::new(); + for (l_model, r_model) in rows { + if acc.is_empty() { + acc.push((l_model, vec![r_model])); + continue; + } + let (last_l, last_r) = acc.last_mut().unwrap(); + let mut l_equal = true; + for pk_col in ::iter() { + let col = pk_col.into_column(); + let curr_val = l_model.get(col); + let last_val = last_l.get(col); + if !curr_val.eq(&last_val) { + l_equal = false; + break; + } + } + if l_equal { + last_r.push(r_model); + } else { + acc.push((l_model, vec![r_model])); + } + } + acc + } + pub async fn one( self, db: &DatabaseConnection, @@ -132,8 +159,12 @@ where self.into_model::().one(db).await } - pub async fn all(self, db: &DatabaseConnection) -> Result, QueryErr> { - self.into_model::().all(db).await + pub async fn all( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { + let rows = self.into_model::().all(db).await?; + Ok(Self::parse_query_result(rows)) } } diff --git a/src/query/combine.rs b/src/query/combine.rs index 2cde583f..50f000d4 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -1,7 +1,7 @@ use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo}; use core::marker::PhantomData; pub use sea_query::JoinType; -use sea_query::{Alias, ColumnRef, Iden, SeaRc, SelectExpr, SelectStatement, SimpleExpr}; +use sea_query::{Alias, ColumnRef, Iden, Order, SeaRc, SelectExpr, SelectStatement, SimpleExpr}; pub const SELECT_A: &str = "A_"; pub const SELECT_B: &str = "B_"; @@ -48,11 +48,12 @@ where F: EntityTrait, { pub(crate) fn new(query: SelectStatement) -> Self { - let myself = Self { + Self { query, entity: PhantomData, - }; - myself.prepare_select() + } + .prepare_select() + .prepare_order_by() } fn prepare_select(mut self) -> Self { @@ -65,6 +66,13 @@ where } self } + + fn prepare_order_by(mut self) -> Self { + for col in ::iter() { + self.query.order_by((E::default(), col), Order::Asc); + } + self + } } #[cfg(test)] @@ -97,6 +105,7 @@ mod tests { "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", + "ORDER BY `cake`.`id` ASC", ].join(" ") ); } @@ -116,6 +125,7 @@ mod tests { "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", "WHERE `cake`.`id` = 1 AND `fruit`.`id` = 2", + "ORDER BY `cake`.`id` ASC", ].join(" ") ); } From a072b1f2f31c51c83480aface35e422e7101b0ad Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 24 Jun 2021 18:56:07 +0800 Subject: [PATCH 09/18] from_query_result_opt, FIXME: unit test will fail --- src/entity/model.rs | 7 +++++ src/executor/select.rs | 66 ++++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/entity/model.rs b/src/entity/model.rs index eab2e533..acf21c0d 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -14,4 +14,11 @@ pub trait FromQueryResult { fn from_query_result(res: &QueryResult, pre: &str) -> Result where Self: Sized; + + fn from_query_result_opt(res: &QueryResult, pre: &str) -> Result, TypeErr> + where + Self: Sized, + { + Ok(Self::from_query_result(res, pre).ok()) + } } diff --git a/src/executor/select.rs b/src/executor/select.rs index b15c0c4b..4876b457 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -52,12 +52,12 @@ where M: FromQueryResult + Sized, N: FromQueryResult + Sized, { - type Item = (M, N); + type Item = (M, Option); fn from_raw_query_result(res: QueryResult) -> Result { Ok(( M::from_query_result(&res, combine::SELECT_A)?, - N::from_query_result(&res, combine::SELECT_B)?, + N::from_query_result_opt(&res, combine::SELECT_B)?, )) } } @@ -125,37 +125,10 @@ where } } - fn parse_query_result(rows: Vec<(E::Model, F::Model)>) -> Vec<(E::Model, Vec)> { - let mut acc = Vec::new(); - for (l_model, r_model) in rows { - if acc.is_empty() { - acc.push((l_model, vec![r_model])); - continue; - } - let (last_l, last_r) = acc.last_mut().unwrap(); - let mut l_equal = true; - for pk_col in ::iter() { - let col = pk_col.into_column(); - let curr_val = l_model.get(col); - let last_val = last_l.get(col); - if !curr_val.eq(&last_val) { - l_equal = false; - break; - } - } - if l_equal { - last_r.push(r_model); - } else { - acc.push((l_model, vec![r_model])); - } - } - acc - } - pub async fn one( self, db: &DatabaseConnection, - ) -> Result, QueryErr> { + ) -> Result)>, QueryErr> { self.into_model::().one(db).await } @@ -164,7 +137,7 @@ where db: &DatabaseConnection, ) -> Result)>, QueryErr> { let rows = self.into_model::().all(db).await?; - Ok(Self::parse_query_result(rows)) + Ok(parse_query_result::(rows)) } } @@ -202,3 +175,34 @@ where } } } + +fn parse_query_result(rows: Vec<(L::Model, Option)>) -> Vec<(L::Model, Vec)> +where + L: EntityTrait, +{ + let mut acc: Vec<(L::Model, Vec)> = Vec::new(); + for (l, r) in rows { + if let Some((last_l, last_r)) = acc.last_mut() { + let mut same_l = true; + for pk_col in ::iter() { + let col = pk_col.into_column(); + let val = l.get(col); + let last_val = last_l.get(col); + if !val.eq(&last_val) { + same_l = false; + break; + } + } + if same_l && r.is_some() { + last_r.push(r.unwrap()); + continue; + } + } + if r.is_some() { + acc.push((l, vec![r.unwrap()])); + } else { + acc.push((l, vec![])); + } + } + acc +} \ No newline at end of file From 394dfc07ccaa6c23e21e4a3bfb0d6c59c5cbddd6 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 26 Jun 2021 23:02:38 +0800 Subject: [PATCH 10/18] Fix clippy warning --- src/executor/select.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/executor/select.rs b/src/executor/select.rs index 4876b457..33243c25 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -193,9 +193,11 @@ where break; } } - if same_l && r.is_some() { - last_r.push(r.unwrap()); - continue; + if same_l { + if let Some(r) = r { + last_r.push(r); + continue; + } } } if r.is_some() { From df7bb5c195c8478a18b5f51195adc566da4d9400 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sun, 27 Jun 2021 00:03:16 +0800 Subject: [PATCH 11/18] left_join_and_select_also --- examples/sqlx-mysql/src/example_cake.rs | 5 +---- examples/sqlx-mysql/src/example_cake_filling.rs | 4 ++-- examples/sqlx-mysql/src/example_fruit.rs | 17 +++++++++++++++-- examples/sqlx-mysql/src/select.rs | 6 +++--- src/executor/select.rs | 8 ++------ src/query/join.rs | 2 +- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/examples/sqlx-mysql/src/example_cake.rs b/examples/sqlx-mysql/src/example_cake.rs index 003d700c..4a32fa56 100644 --- a/examples/sqlx-mysql/src/example_cake.rs +++ b/examples/sqlx-mysql/src/example_cake.rs @@ -51,10 +51,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Fruit => Entity::has_many(super::fruit::Entity) - .from(Column::Id) - .to(super::fruit::Column::CakeId) - .into(), + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } diff --git a/examples/sqlx-mysql/src/example_cake_filling.rs b/examples/sqlx-mysql/src/example_cake_filling.rs index d1974a04..19de83e4 100644 --- a/examples/sqlx-mysql/src/example_cake_filling.rs +++ b/examples/sqlx-mysql/src/example_cake_filling.rs @@ -53,11 +53,11 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Filling => Entity::has_one(super::filling::Entity) + Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), diff --git a/examples/sqlx-mysql/src/example_fruit.rs b/examples/sqlx-mysql/src/example_fruit.rs index 48daac55..b875da24 100644 --- a/examples/sqlx-mysql/src/example_fruit.rs +++ b/examples/sqlx-mysql/src/example_fruit.rs @@ -35,7 +35,9 @@ impl PrimaryKeyTrait for PrimaryKey { } #[derive(Copy, Clone, Debug, EnumIter)] -pub enum Relation {} +pub enum Relation { + Cake, +} impl ColumnTrait for Column { type EntityName = Entity; @@ -51,7 +53,18 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { - panic!() + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() } } diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index d624476d..8224caf1 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -66,7 +66,7 @@ async fn find_all(db: &DbConn) -> Result<(), QueryErr> { async fn find_together(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); - let both = Cake::find().left_join_and_select(Fruit).all(db).await?; + let both = Cake::find().left_join_and_select_also(Fruit).all(db).await?; println!(); for bb in both.iter() { @@ -141,7 +141,7 @@ async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> { async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fillings: "); - let both = Cake::find().left_join_and_select(Filling).all(db).await?; + let both = Cake::find().left_join_and_select_also(Filling).all(db).await?; println!(); for bb in both.iter() { @@ -211,7 +211,7 @@ async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); let cakes_fruits = Cake::find() - .left_join_and_select(Fruit) + .left_join_and_select_also(Fruit) .into_json() .all(db) .await?; diff --git a/src/executor/select.rs b/src/executor/select.rs index 33243c25..9a65f4b1 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -132,12 +132,8 @@ where self.into_model::().one(db).await } - pub async fn all( - self, - db: &DatabaseConnection, - ) -> Result)>, QueryErr> { - let rows = self.into_model::().all(db).await?; - Ok(parse_query_result::(rows)) + pub async fn all(self, db: &DatabaseConnection) -> Result)>, QueryErr> { + self.into_model::().all(db).await } } diff --git a/src/query/join.rs b/src/query/join.rs index 4302b2e7..01947216 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -41,7 +41,7 @@ where } /// Left Join with a Related Entity and select both Entity. - pub fn left_join_and_select(self, r: R) -> SelectTwo + pub fn left_join_and_select_also(self, r: R) -> SelectTwo where R: EntityTrait, E: Related, From a0db19758b7ddd202380ce463eca26463e956645 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sun, 27 Jun 2021 03:39:09 +0800 Subject: [PATCH 12/18] SelectTwoMany --- examples/sqlx-mysql/Readme.md | 30 +++++++---- examples/sqlx-mysql/src/select.rs | 12 +++-- src/entity/model.rs | 2 +- src/executor/select.rs | 59 ++++++++++++++++++--- src/query/combine.rs | 86 ++++++++++++++++++++++++++++--- src/query/join.rs | 11 +++- src/query/select.rs | 59 +++++++++++++++------ 7 files changed, 214 insertions(+), 45 deletions(-) diff --git a/examples/sqlx-mysql/Readme.md b/examples/sqlx-mysql/Readme.md index f7e5d7bc..77a90bd6 100644 --- a/examples/sqlx-mysql/Readme.md +++ b/examples/sqlx-mysql/Readme.md @@ -32,15 +32,27 @@ Model { id: 2, name: "Rasberry", cake_id: Some(1) } Model { id: 3, name: "Strawberry", cake_id: Some(2) } +Model { id: 4, name: "Apple", cake_id: None } + +Model { id: 5, name: "Banana", cake_id: None } + +Model { id: 6, name: "Cherry", cake_id: None } + +Model { id: 7, name: "Lemon", cake_id: None } + +Model { id: 8, name: "Orange", cake_id: None } + +Model { id: 9, name: "Pineapple", cake_id: None } + ===== ===== find cakes and fruits: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` -(Model { id: 1, name: "New York Cheese" }, Model { id: 2, name: "Rasberry", cake_id: Some(1) }) +(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 1, name: "Blueberry", cake_id: Some(1) })) -(Model { id: 1, name: "New York Cheese" }, Model { id: 1, name: "Blueberry", cake_id: Some(1) }) +(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 2, name: "Rasberry", cake_id: Some(1) })) -(Model { id: 2, name: "Chocolate Forest" }, Model { id: 3, name: "Strawberry", cake_id: Some(2) }) +(Model { id: 2, name: "Chocolate Forest" }, Some(Model { id: 3, name: "Strawberry", cake_id: Some(2) })) ===== ===== @@ -48,7 +60,7 @@ find one by primary key: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `ca Model { id: 1, name: "New York Cheese" } -find one by like: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1 +find one by name: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1 Some(Model { id: 2, name: "Chocolate Forest" }) @@ -68,15 +80,11 @@ SelectResult { name: "Chocolate Forest", num_of_fruits: 1 } ===== ===== -find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` +find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` ORDER BY `cake`.`id` ASC -(Model { id: 1, name: "New York Cheese" }, Model { id: 1, name: "Vanilla" }) +(Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Vanilla" }, Model { id: 2, name: "Lemon" }]) -(Model { id: 1, name: "New York Cheese" }, Model { id: 2, name: "Lemon" }) - -(Model { id: 2, name: "Chocolate Forest" }, Model { id: 2, name: "Lemon" }) - -(Model { id: 2, name: "Chocolate Forest" }, Model { id: 3, name: "Mango" }) +(Model { id: 2, name: "Chocolate Forest" }, [Model { id: 2, name: "Lemon" }, Model { id: 3, name: "Mango" }]) find fillings for cheese cake: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 1 LIMIT 1 SELECT `filling`.`id`, `filling`.`name` FROM `filling` INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id` INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id` WHERE `cake`.`id` = 1 diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index 8224caf1..fc4f2d07 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -66,7 +66,10 @@ async fn find_all(db: &DbConn) -> Result<(), QueryErr> { async fn find_together(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); - let both = Cake::find().left_join_and_select_also(Fruit).all(db).await?; + let both = Cake::find() + .left_join_and_select_also(Fruit) + .all(db) + .await?; println!(); for bb in both.iter() { @@ -141,7 +144,10 @@ async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> { async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fillings: "); - let both = Cake::find().left_join_and_select_also(Filling).all(db).await?; + let both = Cake::find() + .left_join_and_select_with(Filling) + .all(db) + .await?; println!(); for bb in both.iter() { @@ -211,7 +217,7 @@ async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); let cakes_fruits = Cake::find() - .left_join_and_select_also(Fruit) + .left_join_and_select_with(Fruit) .into_json() .all(db) .await?; diff --git a/src/entity/model.rs b/src/entity/model.rs index acf21c0d..81821c68 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -15,7 +15,7 @@ pub trait FromQueryResult { where Self: Sized; - fn from_query_result_opt(res: &QueryResult, pre: &str) -> Result, TypeErr> + fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result, TypeErr> where Self: Sized, { diff --git a/src/executor/select.rs b/src/executor/select.rs index 9a65f4b1..6c74c8cb 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,6 +1,7 @@ use crate::{ query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, JsonValue, - ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, TypeErr, + ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, + SelectTwoMany, TypeErr, }; use sea_query::SelectStatement; use std::marker::PhantomData; @@ -57,7 +58,7 @@ where fn from_raw_query_result(res: QueryResult) -> Result { Ok(( M::from_query_result(&res, combine::SELECT_A)?, - N::from_query_result_opt(&res, combine::SELECT_B)?, + N::from_query_result_optional(&res, combine::SELECT_B)?, )) } } @@ -132,11 +133,54 @@ where self.into_model::().one(db).await } - pub async fn all(self, db: &DatabaseConnection) -> Result)>, QueryErr> { + pub async fn all( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { self.into_model::().all(db).await } } +impl SelectTwoMany +where + E: EntityTrait, + F: EntityTrait, +{ + fn into_model(self) -> Selector> + where + M: FromQueryResult, + N: FromQueryResult, + { + Selector { + query: self.query, + selector: SelectTwoModel { model: PhantomData }, + } + } + + #[cfg(feature = "with-json")] + pub fn into_json(self) -> Selector> { + Selector { + query: self.query, + selector: SelectTwoModel { model: PhantomData }, + } + } + + pub async fn one( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { + self.into_model::().one(db).await + } + + pub async fn all( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { + let rows = self.into_model::().all(db).await?; + Ok(consolidate_query_result::(rows)) + } +} + impl Selector where S: SelectorTrait, @@ -172,11 +216,14 @@ where } } -fn parse_query_result(rows: Vec<(L::Model, Option)>) -> Vec<(L::Model, Vec)> +fn consolidate_query_result( + rows: Vec<(L::Model, Option)>, +) -> Vec<(L::Model, Vec)> where L: EntityTrait, + R: EntityTrait, { - let mut acc: Vec<(L::Model, Vec)> = Vec::new(); + let mut acc: Vec<(L::Model, Vec)> = Vec::new(); for (l, r) in rows { if let Some((last_l, last_r)) = acc.last_mut() { let mut same_l = true; @@ -203,4 +250,4 @@ where } } acc -} \ No newline at end of file +} diff --git a/src/query/combine.rs b/src/query/combine.rs index 50f000d4..f52b7925 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo}; +use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo, SelectTwoMany}; use core::marker::PhantomData; pub use sea_query::JoinType; use sea_query::{Alias, ColumnRef, Iden, Order, SeaRc, SelectExpr, SelectStatement, SimpleExpr}; @@ -40,9 +40,36 @@ where self = self.apply_alias(SELECT_A); SelectTwo::new(self.into_query()) } + + pub fn select_with(mut self, _: F) -> SelectTwoMany + where + F: EntityTrait, + { + self = self.apply_alias(SELECT_A); + SelectTwoMany::new(self.into_query()) + } } impl SelectTwo +where + E: EntityTrait, + F: EntityTrait, +{ + pub(crate) fn new(query: SelectStatement) -> Self { + Self { + query, + entity: PhantomData, + } + .prepare_select() + } + + fn prepare_select(mut self) -> Self { + prepare_select_two::(&mut self); + self + } +} + +impl SelectTwoMany where E: EntityTrait, F: EntityTrait, @@ -57,13 +84,7 @@ where } fn prepare_select(mut self) -> Self { - for col in ::iter() { - let alias = format!("{}{}", SELECT_B, col.to_string().as_str()); - self.query.expr(SelectExpr { - expr: col.into_simple_expr(), - alias: Some(SeaRc::new(Alias::new(&alias))), - }); - } + prepare_select_two::(&mut self); self } @@ -75,6 +96,20 @@ where } } +fn prepare_select_two(selector: &mut S) +where + F: EntityTrait, + S: QueryTrait, +{ + for col in ::iter() { + let alias = format!("{}{}", SELECT_B, col.to_string().as_str()); + selector.query().expr(SelectExpr { + expr: col.into_simple_expr(), + alias: Some(SeaRc::new(Alias::new(&alias))), + }); + } +} + #[cfg(test)] mod tests { use crate::tests_cfg::{cake, fruit}; @@ -101,6 +136,22 @@ mod tests { .select_also(fruit::Entity) .build(MysqlQueryBuilder) .to_string(), + [ + "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", + "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", + "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", + ].join(" ") + ); + } + + #[test] + fn select_with_1() { + assert_eq!( + cake::Entity::find() + .left_join(fruit::Entity) + .select_with(fruit::Entity) + .build(MysqlQueryBuilder) + .to_string(), [ "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", @@ -120,6 +171,25 @@ mod tests { .filter(fruit::Column::Id.eq(2)) .build(MysqlQueryBuilder) .to_string(), + [ + "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", + "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", + "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", + "WHERE `cake`.`id` = 1 AND `fruit`.`id` = 2", + ].join(" ") + ); + } + + #[test] + fn select_with_2() { + assert_eq!( + cake::Entity::find() + .left_join(fruit::Entity) + .select_with(fruit::Entity) + .filter(cake::Column::Id.eq(1)) + .filter(fruit::Column::Id.eq(2)) + .build(MysqlQueryBuilder) + .to_string(), [ "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", diff --git a/src/query/join.rs b/src/query/join.rs index 01947216..12d7ef58 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo}; +use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo, SelectTwoMany}; pub use sea_query::JoinType; impl Select @@ -48,6 +48,15 @@ where { self.left_join(r).select_also(r) } + + /// Left Join with a Related Entity and select the related Entity as a `Vec` + pub fn left_join_and_select_with(self, r: R) -> SelectTwoMany + where + R: EntityTrait, + E: Related, + { + self.left_join(r).select_with(r) + } } #[cfg(test)] diff --git a/src/query/select.rs b/src/query/select.rs index af483908..91db2907 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -23,6 +23,16 @@ where pub(crate) entity: PhantomData<(E, F)>, } +#[derive(Clone, Debug)] +pub struct SelectTwoMany +where + E: EntityTrait, + F: EntityTrait, +{ + pub(crate) query: SelectStatement, + pub(crate) entity: PhantomData<(E, F)>, +} + pub trait IntoSimpleExpr { fn into_simple_expr(self) -> SimpleExpr; } @@ -51,6 +61,18 @@ macro_rules! impl_trait { &mut self.query } } + + impl $trait for SelectTwoMany + where + E: EntityTrait, + F: EntityTrait, + { + type QueryStatement = SelectStatement; + + fn query(&mut self) -> &mut SelectStatement { + &mut self.query + } + } }; } @@ -118,19 +140,26 @@ where } } -impl QueryTrait for SelectTwo -where - E: EntityTrait, - F: EntityTrait, -{ - type QueryStatement = SelectStatement; - fn query(&mut self) -> &mut SelectStatement { - &mut self.query - } - fn as_query(&self) -> &SelectStatement { - &self.query - } - fn into_query(self) -> SelectStatement { - self.query - } +macro_rules! select_two { + ( $selector: ident ) => { + impl QueryTrait for $selector + where + E: EntityTrait, + F: EntityTrait, + { + type QueryStatement = SelectStatement; + fn query(&mut self) -> &mut SelectStatement { + &mut self.query + } + fn as_query(&self) -> &SelectStatement { + &self.query + } + fn into_query(self) -> SelectStatement { + self.query + } + } + }; } + +select_two!(SelectTwo); +select_two!(SelectTwoMany); From 688891f70618982096e2a57ceaeb32788b7eba70 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sun, 27 Jun 2021 15:57:22 +0800 Subject: [PATCH 13/18] Use MockDatabase in doc test --- src/database/connection.rs | 7 +- src/database/transaction.rs | 11 ++ src/entity/active_model.rs | 49 -------- src/entity/base_entity.rs | 225 +++++++++++++++++++++--------------- src/executor/paginator.rs | 19 +-- src/query/mod.rs | 2 +- 6 files changed, 154 insertions(+), 159 deletions(-) diff --git a/src/database/connection.rs b/src/database/connection.rs index e2dfbb9b..96170099 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -1,4 +1,4 @@ -use crate::{ExecErr, ExecResult, QueryErr, QueryResult, Statement}; +use crate::{ExecErr, ExecResult, QueryErr, QueryResult, Statement, Transaction}; use sea_query::{ MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder, SqliteQueryBuilder, }; @@ -118,6 +118,11 @@ impl DatabaseConnection { pub fn as_mock_connection(&self) -> Option { None } + + pub fn into_transaction_log(self) -> Vec { + let mut mocker = self.as_mock_connection().get_mocker_mutex().lock().unwrap(); + mocker.drain_transaction_log() + } } impl QueryBuilderBackend { diff --git a/src/database/transaction.rs b/src/database/transaction.rs index d31a9303..ff9bba4f 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -1,4 +1,5 @@ use crate::Statement; +use sea_query::{Value, Values}; #[derive(Debug, Clone, PartialEq)] pub struct Transaction { @@ -6,6 +7,16 @@ pub struct Transaction { } impl Transaction { + pub fn from_sql_and_values(sql: &str, values: I) -> Self + where + I: IntoIterator, + { + Self::one(Statement { + sql: sql.to_owned(), + values: Some(Values(values.into_iter().collect())), + }) + } + /// Create a Transaction with one statement pub fn one(stmt: Statement) -> Self { Self { stmts: vec![stmt] } diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index f306cb6a..dd35a423 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -42,17 +42,6 @@ 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 @@ -197,44 +186,6 @@ where } } -impl OneOrManyActiveModel for A -where - 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") - } -} - -impl OneOrManyActiveModel for Vec -where - A: ActiveModelTrait, -{ - fn is_one() -> bool { - false - } - fn get_one(self) -> A { - panic!("not one") - } - - fn is_many() -> bool { - true - } - fn get_many(self) -> Vec { - self - } -} - /// Insert the model if primary key is unset, update otherwise. /// Only works if the entity has auto increment primary key. pub async fn save_active_model(mut am: A, db: &DatabaseConnection) -> Result diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 1553acd7..19fe6a52 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -1,7 +1,7 @@ use crate::{ ActiveModelTrait, ColumnTrait, Delete, DeleteOne, FromQueryResult, Insert, ModelTrait, - OneOrManyActiveModel, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, - RelationBuilder, RelationTrait, RelationType, Select, Update, UpdateOne, + PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, RelationTrait, + RelationType, Select, Update, UpdateMany, UpdateOne, }; use sea_query::{Iden, IntoValueTuple}; use std::fmt::Debug; @@ -50,14 +50,27 @@ pub trait EntityTrait: EntityName { } /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// # async_std::task::block_on(async { + /// cake::Entity::find().one(&db).await; + /// cake::Entity::find().all(&db).await; + /// # }); /// /// assert_eq!( - /// cake::Entity::find() - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""# - /// ); + /// db.into_transaction_log(), + /// vec![ + /// Transaction::from_sql_and_values( + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, vec![1u64.into()] + /// ), + /// Transaction::from_sql_and_values( + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] + /// ), + /// ]); /// ``` fn find() -> Select { Select::new() @@ -65,28 +78,42 @@ pub trait EntityTrait: EntityName { /// Find a model by primary key /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// # async_std::task::block_on(async { + /// cake::Entity::find_by_id(11).all(&db).await; + /// # }); /// /// assert_eq!( - /// cake::Entity::find_by_id(11) - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = 11"# - /// ); + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = $1"#, vec![11i32.into()] + /// )]); /// ``` /// Find by composite key /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling}; + /// + /// # async_std::task::block_on(async { + /// cake_filling::Entity::find_by_id((2, 3)).all(&db).await; + /// # }); /// /// assert_eq!( - /// cake_filling::Entity::find_by_id((2, 3)) - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// [ - /// r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id" FROM "cake_filling""#, - /// r#"WHERE "cake_filling"."cake_id" = 2 AND "cake_filling"."filling_id" = 3"#, - /// ].join(" ") - /// ); + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values([ + /// r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id" FROM "cake_filling""#, + /// r#"WHERE "cake_filling"."cake_id" = $1 AND "cake_filling"."filling_id" = $2"#, + /// ].join(" ").as_str(), + /// vec![2i32.into(), 3i32.into()] + /// )]); /// ``` fn find_by_id(values: V) -> Select where @@ -108,69 +135,29 @@ pub trait EntityTrait: EntityName { select } - /// Insert one /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// /// let apple = cake::ActiveModel { /// name: 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')"#, - /// ); - /// ``` - /// Insert many - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::PostgresQueryBuilder}; /// - /// let apple = cake::ActiveModel { - /// name: Set("Apple Pie".to_owned()), - /// ..Default::default() - /// }; - /// let orange = cake::ActiveModel { - /// name: 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')"#, - /// ); - /// ``` - fn insert(models: C) -> Insert - where - A: ActiveModelTrait, - C: OneOrManyActiveModel, - { - if C::is_one() { - Self::insert_one(models.get_one()) - } else if C::is_many() { - Self::insert_many(models.get_many()) - } else { - unreachable!() - } - } - - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::PostgresQueryBuilder}; + /// # async_std::task::block_on(async { + /// cake::Entity::insert(apple).exec(&db).await; + /// # }); /// - /// let apple = cake::ActiveModel { - /// name: Set("Apple Pie".to_owned()), - /// ..Default::default() - /// }; /// assert_eq!( - /// cake::Entity::insert_one(apple) - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, - /// ); + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"INSERT INTO "cake" ("name") VALUES ($1)"#, vec!["Apple Pie".into()] + /// )]); /// ``` - fn insert_one(model: A) -> Insert + fn insert(model: A) -> Insert where A: ActiveModelTrait, { @@ -178,7 +165,11 @@ pub trait EntityTrait: EntityName { } /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// /// let apple = cake::ActiveModel { /// name: Set("Apple Pie".to_owned()), @@ -188,12 +179,17 @@ pub trait EntityTrait: EntityName { /// name: Set("Orange Scone".to_owned()), /// ..Default::default() /// }; + /// + /// # async_std::task::block_on(async { + /// cake::Entity::insert_many(vec![apple, orange]).exec(&db).await; + /// # }); + /// /// assert_eq!( - /// cake::Entity::insert_many(vec![apple, orange]) - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie'), ('Orange Scone')"#, - /// ); + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"INSERT INTO "cake" ("name") VALUES ($1), ($2)"#, + /// vec!["Apple Pie".into(), "Orange Scone".into()] + /// )]); /// ``` fn insert_many(models: I) -> Insert where @@ -204,19 +200,27 @@ pub trait EntityTrait: EntityName { } /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; /// /// let orange = fruit::ActiveModel { /// id: Set(1), /// name: Set("Orange".to_owned()), /// ..Default::default() /// }; + /// + /// # async_std::task::block_on(async { + /// fruit::Entity::update(orange).exec(&db).await; + /// # }); + /// /// assert_eq!( - /// fruit::Entity::update(orange) - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"#, - /// ); + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2"#, vec!["Orange".into(), 1i32.into()] + /// )]); /// ``` fn update(model: A) -> UpdateOne where @@ -226,18 +230,51 @@ pub trait EntityTrait: EntityName { } /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::PostgresQueryBuilder}; + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::{cake, fruit}, sea_query::{Expr, Value}}; + /// + /// # async_std::task::block_on(async { + /// fruit::Entity::update_many() + /// .col_expr(fruit::Column::CakeId, Expr::value(Value::Null)) + /// .filter(fruit::Column::Name.contains("Apple")) + /// .exec(&db) + /// .await; + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#, vec![Value::Null, "%Apple%".into()] + /// )]); + /// ``` + fn update_many() -> UpdateMany { + Update::many(Self::default()) + } + + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; /// /// let orange = fruit::ActiveModel { /// id: Set(3), /// ..Default::default() /// }; + /// + /// # async_std::task::block_on(async { + /// fruit::Entity::delete(orange).exec(&db).await; + /// # }); + /// /// assert_eq!( - /// fruit::Entity::delete(orange) - /// .build(PostgresQueryBuilder) - /// .to_string(), - /// r#"DELETE FROM "fruit" WHERE "fruit"."id" = 3"#, - /// ); + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, vec![3i32.into()] + /// )]); /// ``` fn delete(model: A) -> DeleteOne where diff --git a/src/executor/paginator.rs b/src/executor/paginator.rs index e40c4dc4..ead5114b 100644 --- a/src/executor/paginator.rs +++ b/src/executor/paginator.rs @@ -175,9 +175,7 @@ mod tests { query_builder.build(select.offset(4).limit(2)), ]; - let mut mocker = db.as_mock_connection().get_mocker_mutex().lock().unwrap(); - - assert_eq!(mocker.drain_transaction_log(), Transaction::wrap(stmts)); + assert_eq!(db.into_transaction_log(), Transaction::wrap(stmts)); Ok(()) } @@ -211,9 +209,7 @@ mod tests { query_builder.build(select.offset(4).limit(2)), ]; - let mut mocker = db.as_mock_connection().get_mocker_mutex().lock().unwrap(); - - assert_eq!(mocker.drain_transaction_log(), Transaction::wrap(stmts)); + assert_eq!(db.into_transaction_log(), Transaction::wrap(stmts)); Ok(()) } @@ -244,9 +240,8 @@ mod tests { let query_builder = db.get_query_builder_backend(); let stmts = vec![query_builder.build(&select)]; - let mut mocker = db.as_mock_connection().get_mocker_mutex().lock().unwrap(); - assert_eq!(mocker.drain_transaction_log(), Transaction::wrap(stmts)); + assert_eq!(db.into_transaction_log(), Transaction::wrap(stmts)); Ok(()) } @@ -297,9 +292,7 @@ mod tests { query_builder.build(select.offset(4).limit(2)), ]; - let mut mocker = db.as_mock_connection().get_mocker_mutex().lock().unwrap(); - - assert_eq!(mocker.drain_transaction_log(), Transaction::wrap(stmts)); + assert_eq!(db.into_transaction_log(), Transaction::wrap(stmts)); Ok(()) } @@ -331,9 +324,7 @@ mod tests { query_builder.build(select.offset(4).limit(2)), ]; - let mut mocker = db.as_mock_connection().get_mocker_mutex().lock().unwrap(); - - assert_eq!(mocker.drain_transaction_log(), Transaction::wrap(stmts)); + assert_eq!(db.into_transaction_log(), Transaction::wrap(stmts)); Ok(()) } } diff --git a/src/query/mod.rs b/src/query/mod.rs index 7f32edc5..51060a1e 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -20,4 +20,4 @@ pub use select::*; pub use traits::*; pub use update::*; -pub use crate::executor::{ExecErr, QueryErr}; +pub use crate::executor::{ExecErr, InsertResult, QueryErr, UpdateResult}; From 9f93c7ed6f1a61536b62af3015ddeda13246b093 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 28 Jun 2021 01:34:49 +0800 Subject: [PATCH 14/18] Use find_related in place of find_cake --- examples/sqlx-mysql/src/example_cake.rs | 10 ---------- examples/sqlx-mysql/src/example_filling.rs | 6 ------ examples/sqlx-mysql/src/select.rs | 4 ++-- src/database/connection.rs | 1 + src/entity/model.rs | 9 ++++++++- src/query/helper.rs | 6 +++--- src/query/join.rs | 4 ++-- src/tests_cfg/cake.rs | 10 ---------- src/tests_cfg/filling.rs | 6 ------ 9 files changed, 16 insertions(+), 40 deletions(-) diff --git a/examples/sqlx-mysql/src/example_cake.rs b/examples/sqlx-mysql/src/example_cake.rs index 4a32fa56..475315e8 100644 --- a/examples/sqlx-mysql/src/example_cake.rs +++ b/examples/sqlx-mysql/src/example_cake.rs @@ -72,14 +72,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_fruit(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - - pub fn find_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/sqlx-mysql/src/example_filling.rs b/examples/sqlx-mysql/src/example_filling.rs index d2dece8a..925b92fc 100644 --- a/examples/sqlx-mysql/src/example_filling.rs +++ b/examples/sqlx-mysql/src/example_filling.rs @@ -62,10 +62,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index fc4f2d07..825efdab 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -159,7 +159,7 @@ async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { let cheese = Cake::find_by_id(1).one(db).await?; if let Some(cheese) = cheese { - let fillings: Vec = cheese.find_filling().all(db).await?; + let fillings: Vec = cheese.find_related(Filling).all(db).await?; println!(); for ff in fillings.iter() { @@ -172,7 +172,7 @@ async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { let lemon = Filling::find_by_id(2).one(db).await?; if let Some(lemon) = lemon { - let cakes: Vec = lemon.find_cake().all(db).await?; + let cakes: Vec = lemon.find_related(Cake).all(db).await?; println!(); for cc in cakes.iter() { diff --git a/src/database/connection.rs b/src/database/connection.rs index 96170099..95911c29 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -119,6 +119,7 @@ impl DatabaseConnection { None } + #[cfg(feature = "mock")] pub fn into_transaction_log(self) -> Vec { let mut mocker = self.as_mock_connection().get_mocker_mutex().lock().unwrap(); mocker.drain_transaction_log() diff --git a/src/entity/model.rs b/src/entity/model.rs index 81821c68..4419218a 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, QueryResult, TypeErr}; +use crate::{Select, EntityTrait, Related, QueryFilter, QueryResult, TypeErr}; pub use sea_query::Value; use std::fmt::Debug; @@ -8,6 +8,13 @@ pub trait ModelTrait: Clone + Debug { fn get(&self, c: ::Column) -> Value; fn set(&mut self, c: ::Column, v: Value); + + fn find_related(&self, _: R) -> Select + where + R: EntityTrait, + Self::Entity: Related { + >::find_related().belongs_to(self) + } } pub trait FromQueryResult { diff --git a/src/query/helper.rs b/src/query/helper.rs index da01fab4..7044c63d 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -258,11 +258,11 @@ pub trait QueryFilter: Sized { } /// Apply a where condition using the model's primary key - fn belongs_to(mut self, model: &E::Model) -> Self + fn belongs_to(mut self, model: &M) -> Self where - E: EntityTrait, + M: ModelTrait, { - for key in E::PrimaryKey::iter() { + for key in ::PrimaryKey::iter() { let col = key.into_column(); self = self.filter(col.eq(model.get(col))); } diff --git a/src/query/join.rs b/src/query/join.rs index 12d7ef58..f15dcfe6 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -62,7 +62,7 @@ where #[cfg(test)] mod tests { use crate::tests_cfg::{cake, filling, fruit}; - use crate::{ColumnTrait, EntityTrait, QueryFilter, QueryTrait}; + use crate::{ColumnTrait, EntityTrait, QueryFilter, QueryTrait, ModelTrait}; use sea_query::MysqlQueryBuilder; #[test] @@ -139,7 +139,7 @@ mod tests { }; assert_eq!( - cake_model.find_fruit().build(MysqlQueryBuilder).to_string(), + cake_model.find_related(fruit::Entity).build(MysqlQueryBuilder).to_string(), [ "SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit`", "INNER JOIN `cake` ON `cake`.`id` = `fruit`.`cake_id`", diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index c5ba7348..f8a35d6c 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -73,14 +73,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_fruit(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - - pub fn find_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/src/tests_cfg/filling.rs b/src/tests_cfg/filling.rs index 9dbe78f1..a246d146 100644 --- a/src/tests_cfg/filling.rs +++ b/src/tests_cfg/filling.rs @@ -63,10 +63,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} From 7e3bc1c7b3447b03cf779b2409ff725babea820a Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 28 Jun 2021 01:35:21 +0800 Subject: [PATCH 15/18] Delete many --- src/entity/base_entity.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 19fe6a52..1717ce19 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -1,7 +1,7 @@ use crate::{ - ActiveModelTrait, ColumnTrait, Delete, DeleteOne, FromQueryResult, Insert, ModelTrait, - PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, RelationTrait, - RelationType, Select, Update, UpdateMany, UpdateOne, + ActiveModelTrait, ColumnTrait, Delete, DeleteMany, DeleteOne, FromQueryResult, Insert, + ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, + RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, }; use sea_query::{Iden, IntoValueTuple}; use std::fmt::Debug; @@ -234,7 +234,7 @@ pub trait EntityTrait: EntityName { /// # use sea_orm::{MockDatabase, Transaction}; /// # let db = MockDatabase::new().into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::{cake, fruit}, sea_query::{Expr, Value}}; + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::{Expr, Value}}; /// /// # async_std::task::block_on(async { /// fruit::Entity::update_many() @@ -282,4 +282,28 @@ pub trait EntityTrait: EntityName { { Delete::one(model) } + + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{MockDatabase, Transaction}; + /// # let db = MockDatabase::new().into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; + /// + /// # async_std::task::block_on(async { + /// fruit::Entity::delete_many() + /// .filter(fruit::Column::Name.contains("Apple")) + /// .exec(&db) + /// .await; + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#, vec!["%Apple%".into()] + /// )]); + /// ``` + fn delete_many() -> DeleteMany { + Delete::many(Self::default()) + } } From 5d16222108af5cadc518d517a212eaf3aad742ad Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 28 Jun 2021 01:36:59 +0800 Subject: [PATCH 16/18] Update example --- examples/sqlx-mysql/src/operation.rs | 10 +++++----- examples/sqlx-mysql/src/select.rs | 6 +++--- src/lib.rs | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/sqlx-mysql/src/operation.rs b/examples/sqlx-mysql/src/operation.rs index 2eb95607..d7a5ed78 100644 --- a/examples/sqlx-mysql/src/operation.rs +++ b/examples/sqlx-mysql/src/operation.rs @@ -20,12 +20,12 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), ExecErr> { name: Set("pear".to_owned()), ..Default::default() }; - let res = Fruit::insert(pear).exec(db).await?; + let res: InsertResult = Fruit::insert(pear).exec(db).await?; println!(); - println!("Inserted: {:?}\n", res); + println!("Inserted: last_insert_id = {}\n", res.last_insert_id); - let pear = Fruit::find_by_id(res.last_insert_id) + let pear: Option = Fruit::find_by_id(res.last_insert_id) .one(db) .await .map_err(|_| ExecErr)?; @@ -36,10 +36,10 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), ExecErr> { let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); - let res = Fruit::update(pear).exec(db).await?; + let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; println!(); - println!("Updated: {:?}\n", res); + println!("Updated: {:?}\n", pear); Ok(()) } diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index 825efdab..6057671e 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -44,7 +44,7 @@ pub async fn all_about_select(db: &DbConn) -> Result<(), QueryErr> { async fn find_all(db: &DbConn) -> Result<(), QueryErr> { print!("find all cakes: "); - let cakes = Cake::find().all(db).await?; + let cakes: Vec = Cake::find().all(db).await?; println!(); for cc in cakes.iter() { @@ -105,7 +105,7 @@ async fn find_one(db: &DbConn) -> Result<(), QueryErr> { print!("find models belong to: "); - let fruits = cheese.find_fruit().all(db).await?; + let fruits = cheese.find_related(Fruit).all(db).await?; println!(); for ff in fruits.iter() { @@ -144,7 +144,7 @@ async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> { async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fillings: "); - let both = Cake::find() + let both: Vec<(cake::Model, Vec)> = Cake::find() .left_join_and_select_with(Filling) .all(db) .await?; diff --git a/src/lib.rs b/src/lib.rs index 89b83a4d..273479d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod driver; pub mod entity; mod executor; pub mod query; +#[doc(hidden)] pub mod tests_cfg; mod util; From 0a215f75ed075ae930da8faa700e3017a22527e8 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 28 Jun 2021 02:10:46 +0800 Subject: [PATCH 17/18] Docs --- src/entity/model.rs | 5 +- src/lib.rs | 125 +++++++++++++++++++++++++++++++++++++++++++ src/query/join.rs | 7 ++- src/tests_cfg/mod.rs | 5 ++ 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/src/entity/model.rs b/src/entity/model.rs index 4419218a..6161538b 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,4 @@ -use crate::{Select, EntityTrait, Related, QueryFilter, QueryResult, TypeErr}; +use crate::{EntityTrait, QueryFilter, QueryResult, Related, Select, TypeErr}; pub use sea_query::Value; use std::fmt::Debug; @@ -12,7 +12,8 @@ pub trait ModelTrait: Clone + Debug { fn find_related(&self, _: R) -> Select where R: EntityTrait, - Self::Entity: Related { + Self::Entity: Related, + { >::find_related().belongs_to(self) } } diff --git a/src/lib.rs b/src/lib.rs index 273479d1..dc7720ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,128 @@ +//! # Select +//! ``` +//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), QueryErr> { +//! # +//! // find all models +//! let cakes: Vec = Cake::find().all(db).await?; +//! +//! // find and filter +//! let chocolate: Vec = Cake::find() +//! .filter(cake::Column::Name.contains("chocolate")) +//! .all(db) +//! .await?; +//! +//! // find one model +//! let cheese: Option = Cake::find_by_id(1).one(db).await?; +//! let cheese: cake::Model = cheese.unwrap(); +//! +//! // find related models (lazy) +//! let fruits: Vec = cheese.find_related(Fruit).all(db).await?; +//! +//! // find related models (eager) +//! let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() +//! .left_join_and_select_with(Fruit) +//! .all(db) +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` +//! # Insert +//! ``` +//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), ExecErr> { +//! # +//! let apple = fruit::ActiveModel { +//! name: Set("Apple".to_owned()), +//! ..Default::default() // no need to set primary key +//! }; +//! +//! let pear = fruit::ActiveModel { +//! name: Set("Pear".to_owned()), +//! ..Default::default() +//! }; +//! +//! // insert one +//! let res: InsertResult = Fruit::insert(pear).exec(db).await?; +//! +//! println!("InsertResult: {}", res.last_insert_id); +//! # +//! # Ok(()) +//! # } +//! # +//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> { +//! # let apple = fruit::ActiveModel { +//! # name: Set("Apple".to_owned()), +//! # ..Default::default() // no need to set primary key +//! # }; +//! # +//! # let pear = fruit::ActiveModel { +//! # name: Set("Pear".to_owned()), +//! # ..Default::default() +//! # }; +//! +//! // insert many +//! Fruit::insert_many(vec![apple, pear]).exec(db).await?; +//! # +//! # Ok(()) +//! # } +//! ``` +//! # Update +//! ``` +//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; +//! # +//! use sea_orm::sea_query::{Expr, Value}; +//! +//! # async fn function(db: &DbConn) -> Result<(), QueryErr> { +//! let pear: Option = Fruit::find_by_id(1).one(db).await?; +//! # Ok(()) +//! # } +//! # +//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> { +//! # let pear: Option = Fruit::find_by_id(1).one(db).await.unwrap(); +//! +//! let mut pear: fruit::ActiveModel = pear.unwrap().into(); +//! pear.name = Set("Sweet pear".to_owned()); +//! +//! // update one +//! let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; +//! +//! // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' +//! Fruit::update_many() +//! .col_expr(fruit::Column::CakeId, Expr::value(Value::Null)) +//! .filter(fruit::Column::Name.contains("Apple")) +//! .exec(db) +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` +//! # Delete +//! ``` +//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; +//! # +//! # async fn function(db: &DbConn) -> Result<(), QueryErr> { +//! let orange: Option = Fruit::find_by_id(1).one(db).await?; +//! # Ok(()) +//! # } +//! # +//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> { +//! # let orange: Option = Fruit::find_by_id(1).one(db).await.unwrap(); +//! let orange: fruit::ActiveModel = orange.unwrap().into(); +//! +//! // delete one +//! fruit::Entity::delete(orange).exec(db).await?; +//! +//! // delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE 'Orange' +//! fruit::Entity::delete_many() +//! .filter(fruit::Column::Name.contains("Orange")) +//! .exec(db) +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` mod database; mod driver; pub mod entity; diff --git a/src/query/join.rs b/src/query/join.rs index f15dcfe6..e3dc6dfb 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -62,7 +62,7 @@ where #[cfg(test)] mod tests { use crate::tests_cfg::{cake, filling, fruit}; - use crate::{ColumnTrait, EntityTrait, QueryFilter, QueryTrait, ModelTrait}; + use crate::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QueryTrait}; use sea_query::MysqlQueryBuilder; #[test] @@ -139,7 +139,10 @@ mod tests { }; assert_eq!( - cake_model.find_related(fruit::Entity).build(MysqlQueryBuilder).to_string(), + cake_model + .find_related(fruit::Entity) + .build(MysqlQueryBuilder) + .to_string(), [ "SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit`", "INNER JOIN `cake` ON `cake`.`id` = `fruit`.`cake_id`", diff --git a/src/tests_cfg/mod.rs b/src/tests_cfg/mod.rs index e39a16f3..ca9b8a68 100644 --- a/src/tests_cfg/mod.rs +++ b/src/tests_cfg/mod.rs @@ -4,3 +4,8 @@ pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; + +pub use cake::Entity as Cake; +pub use cake_filling::Entity as CakeFilling; +pub use filling::Entity as Filling; +pub use fruit::Entity as Fruit; From a0d9b47b1fc4f90479624b7cea284ef075e3969f Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 28 Jun 2021 02:17:03 +0800 Subject: [PATCH 18/18] Rename left_join_and_select_* to find_*_related --- examples/sqlx-mysql/src/select.rs | 6 +++--- src/lib.rs | 2 +- src/query/join.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index 6057671e..0fe9c921 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -67,7 +67,7 @@ async fn find_together(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); let both = Cake::find() - .left_join_and_select_also(Fruit) + .find_also_related(Fruit) .all(db) .await?; @@ -145,7 +145,7 @@ async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fillings: "); let both: Vec<(cake::Model, Vec)> = Cake::find() - .left_join_and_select_with(Filling) + .find_with_related(Filling) .all(db) .await?; @@ -217,7 +217,7 @@ async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); let cakes_fruits = Cake::find() - .left_join_and_select_with(Fruit) + .find_with_related(Fruit) .into_json() .all(db) .await?; diff --git a/src/lib.rs b/src/lib.rs index dc7720ff..35571f42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! //! // find related models (eager) //! let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() -//! .left_join_and_select_with(Fruit) +//! .find_with_related(Fruit) //! .all(db) //! .await?; //! diff --git a/src/query/join.rs b/src/query/join.rs index e3dc6dfb..4481f5af 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -41,7 +41,7 @@ where } /// Left Join with a Related Entity and select both Entity. - pub fn left_join_and_select_also(self, r: R) -> SelectTwo + pub fn find_also_related(self, r: R) -> SelectTwo where R: EntityTrait, E: Related, @@ -50,7 +50,7 @@ where } /// Left Join with a Related Entity and select the related Entity as a `Vec` - pub fn left_join_and_select_with(self, r: R) -> SelectTwoMany + pub fn find_with_related(self, r: R) -> SelectTwoMany where R: EntityTrait, E: Related,