Merge branch 'master' into ss/bakery
This commit is contained in:
commit
59322d37d8
33
.github/workflows/rust.yml
vendored
33
.github/workflows/rust.yml
vendored
@ -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
|
||||
|
@ -6,6 +6,7 @@ members = [
|
||||
"sea-orm-cli",
|
||||
"examples/sqlx-mysql",
|
||||
"examples/codegen",
|
||||
"examples/cli",
|
||||
]
|
||||
|
||||
[package]
|
||||
|
@ -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<B<C<D>>>`). 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.
|
||||
|
||||
|
2
examples/cli/.env
Normal file
2
examples/cli/.env
Normal file
@ -0,0 +1,2 @@
|
||||
DATABASE_URI=mysql://sea:sea@localhost/bakery
|
||||
DATABASE_SCHEMA=bakery
|
9
examples/cli/Cargo.toml
Normal file
9
examples/cli/Cargo.toml
Normal file
@ -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" ] }
|
18
examples/cli/README.md
Normal file
18
examples/cli/README.md
Normal file
@ -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
|
||||
```
|
83
examples/cli/src/entity/cake.rs
Normal file
83
examples/cli/src/entity/cake.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! 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).into(),
|
||||
Self::Fruit => Entity::has_many(super::fruit::Entity).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake_filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::CakeFilling.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake_filling(&self) -> Select<super::cake_filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
90
examples/cli/src/entity/cake_filling.rs
Normal file
90
examples/cli/src/entity/cake_filling.rs
Normal file
@ -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::belongs_to(super::cake::Entity)
|
||||
.from(Column::CakeId)
|
||||
.to(super::cake::Column::Id)
|
||||
.into(),
|
||||
Self::Filling => Entity::belongs_to(super::filling::Entity)
|
||||
.from(Column::FillingId)
|
||||
.to(super::filling::Column::Id)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Cake.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Filling.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake(&self) -> Select<super::cake::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
pub fn find_filling(&self) -> Select<super::filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
72
examples/cli/src/entity/filling.rs
Normal file
72
examples/cli/src/entity/filling.rs
Normal file
@ -0,0 +1,72 @@
|
||||
//! 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).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake_filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::CakeFilling.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake_filling(&self) -> Select<super::cake_filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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(),
|
78
examples/cli/src/entity/vendor.rs
Normal file
78
examples/cli/src/entity/vendor.rs
Normal file
@ -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<i32>,
|
||||
}
|
||||
|
||||
#[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<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
3
examples/cli/src/main.rs
Normal file
3
examples/cli/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod entity;
|
||||
|
||||
fn main() {}
|
92
examples/codegen/src/entity/fruit.rs
Normal file
92
examples/codegen/src/entity/fruit.rs
Normal file
@ -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<i32>,
|
||||
}
|
||||
|
||||
#[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<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Cake.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::vendor::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Vendor.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake(&self) -> Select<super::cake::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
pub fn find_vendor(&self) -> Select<super::vendor::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
7
examples/codegen/src/entity/mod.rs
Normal file
7
examples/codegen/src/entity/mod.rs
Normal file
@ -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;
|
7
examples/codegen/src/entity/prelude.rs
Normal file
7
examples/codegen/src/entity/prelude.rs
Normal file
@ -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;
|
78
examples/codegen/src/entity/vendor.rs
Normal file
78
examples/codegen/src/entity/vendor.rs
Normal file
@ -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<i32>,
|
||||
}
|
||||
|
||||
#[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<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -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(())
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,14 +72,4 @@ impl Related<super::filling::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
|
||||
pub fn find_filling(&self) -> Select<super::filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
@ -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(),
|
||||
|
@ -62,10 +62,4 @@ impl Related<super::cake::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake(&self) -> Select<super::cake::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
@ -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<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Cake.def()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::Model> = 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(())
|
||||
}
|
||||
|
@ -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::Model> = Cake::find().all(db).await?;
|
||||
|
||||
println!();
|
||||
for cc in cakes.iter() {
|
||||
@ -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(Fruit).all(db).await?;
|
||||
let both = Cake::find()
|
||||
.find_also_related(Fruit)
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
println!();
|
||||
for bb in both.iter() {
|
||||
@ -102,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() {
|
||||
@ -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(Filling).all(db).await?;
|
||||
let both: Vec<(cake::Model, Vec<filling::Model>)> = Cake::find()
|
||||
.find_with_related(Filling)
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
println!();
|
||||
for bb in both.iter() {
|
||||
@ -153,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<filling::Model> = cheese.find_filling().all(db).await?;
|
||||
let fillings: Vec<filling::Model> = cheese.find_related(Filling).all(db).await?;
|
||||
|
||||
println!();
|
||||
for ff in fillings.iter() {
|
||||
@ -166,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<cake::Model> = lemon.find_cake().all(db).await?;
|
||||
let cakes: Vec<cake::Model> = lemon.find_related(Cake).all(db).await?;
|
||||
|
||||
println!();
|
||||
for cc in cakes.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(Fruit)
|
||||
.find_with_related(Fruit)
|
||||
.into_json()
|
||||
.all(db)
|
||||
.await?;
|
||||
|
@ -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" }
|
||||
|
@ -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")
|
||||
|
@ -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<dyn Error>> {
|
||||
async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
||||
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();
|
||||
|
@ -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<bool> = 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,10 @@ impl Entity {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_defs(&self) -> Vec<TokenStream> {
|
||||
self.relations.iter().map(|rel| rel.get_def()).collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_rel_types(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
|
@ -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,
|
||||
|
@ -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<Column> = table_create
|
||||
.get_columns()
|
||||
.iter()
|
||||
.map(|col_def| col_def.into())
|
||||
.collect();
|
||||
let unique_columns: Vec<String> = 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 {
|
||||
|
@ -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! {
|
||||
|
@ -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,12 @@ impl DatabaseConnection {
|
||||
pub fn as_mock_connection(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock")]
|
||||
pub fn into_transaction_log(self) -> Vec<Transaction> {
|
||||
let mut mocker = self.as_mock_connection().get_mocker_mutex().lock().unwrap();
|
||||
mocker.drain_transaction_log()
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryBuilderBackend {
|
||||
|
@ -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<I>(sql: &str, values: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Value>,
|
||||
{
|
||||
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] }
|
||||
|
@ -42,17 +42,6 @@ impl Default for ActiveValueState {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OneOrManyActiveModel<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
{
|
||||
fn is_one() -> bool;
|
||||
fn get_one(self) -> A;
|
||||
|
||||
fn is_many() -> bool;
|
||||
fn get_many(self) -> Vec<A>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn unchanged_active_value_not_intended_for_public_use<V>(value: V) -> ActiveValue<V>
|
||||
where
|
||||
@ -197,44 +186,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> OneOrManyActiveModel<A> 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<A> {
|
||||
panic!("not many")
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> OneOrManyActiveModel<A> for Vec<A>
|
||||
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<A> {
|
||||
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<A, E>(mut am: A, db: &DatabaseConnection) -> Result<A, ExecErr>
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
ActiveModelTrait, ColumnTrait, Delete, DeleteOne, FromQueryResult, Insert, ModelTrait,
|
||||
OneOrManyActiveModel, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related,
|
||||
RelationBuilder, RelationTrait, RelationType, Select, Update, 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;
|
||||
@ -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<Self> {
|
||||
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<V>(values: V) -> Select<Self>
|
||||
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<A, C>(models: C) -> Insert<A>
|
||||
where
|
||||
A: ActiveModelTrait<Entity = Self>,
|
||||
C: OneOrManyActiveModel<A>,
|
||||
{
|
||||
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<A>(model: A) -> Insert<A>
|
||||
fn insert<A>(model: A) -> Insert<A>
|
||||
where
|
||||
A: ActiveModelTrait<Entity = Self>,
|
||||
{
|
||||
@ -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<A, I>(models: I) -> Insert<A>
|
||||
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<A>(model: A) -> UpdateOne<A>
|
||||
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::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<Self> {
|
||||
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<A>(model: A) -> DeleteOne<A>
|
||||
where
|
||||
@ -245,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<Self> {
|
||||
Delete::many(Self::default())
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{EntityTrait, QueryResult, TypeErr};
|
||||
use crate::{EntityTrait, QueryFilter, QueryResult, Related, Select, TypeErr};
|
||||
pub use sea_query::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@ -8,10 +8,25 @@ pub trait ModelTrait: Clone + Debug {
|
||||
fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;
|
||||
|
||||
fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value);
|
||||
|
||||
fn find_related<R>(&self, _: R) -> Select<R>
|
||||
where
|
||||
R: EntityTrait,
|
||||
Self::Entity: Related<R>,
|
||||
{
|
||||
<Self::Entity as Related<R>>::find_related().belongs_to(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromQueryResult {
|
||||
fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, TypeErr>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, TypeErr>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(Self::from_query_result(res, pre).ok())
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
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,
|
||||
SelectTwoMany, TypeErr,
|
||||
};
|
||||
use sea_query::SelectStatement;
|
||||
use std::marker::PhantomData;
|
||||
@ -52,12 +53,12 @@ where
|
||||
M: FromQueryResult + Sized,
|
||||
N: FromQueryResult + Sized,
|
||||
{
|
||||
type Item = (M, N);
|
||||
type Item = (M, Option<N>);
|
||||
|
||||
fn from_raw_query_result(res: QueryResult) -> Result<Self::Item, TypeErr> {
|
||||
Ok((
|
||||
M::from_query_result(&res, combine::SELECT_A)?,
|
||||
N::from_query_result(&res, combine::SELECT_B)?,
|
||||
N::from_query_result_optional(&res, combine::SELECT_B)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -128,15 +129,58 @@ where
|
||||
pub async fn one(
|
||||
self,
|
||||
db: &DatabaseConnection,
|
||||
) -> Result<Option<(E::Model, F::Model)>, QueryErr> {
|
||||
) -> Result<Option<(E::Model, Option<F::Model>)>, QueryErr> {
|
||||
self.into_model::<E::Model, F::Model>().one(db).await
|
||||
}
|
||||
|
||||
pub async fn all(self, db: &DatabaseConnection) -> Result<Vec<(E::Model, F::Model)>, QueryErr> {
|
||||
pub async fn all(
|
||||
self,
|
||||
db: &DatabaseConnection,
|
||||
) -> Result<Vec<(E::Model, Option<F::Model>)>, QueryErr> {
|
||||
self.into_model::<E::Model, F::Model>().all(db).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, F> SelectTwoMany<E, F>
|
||||
where
|
||||
E: EntityTrait,
|
||||
F: EntityTrait,
|
||||
{
|
||||
fn into_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
|
||||
where
|
||||
M: FromQueryResult,
|
||||
N: FromQueryResult,
|
||||
{
|
||||
Selector {
|
||||
query: self.query,
|
||||
selector: SelectTwoModel { model: PhantomData },
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "with-json")]
|
||||
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
|
||||
Selector {
|
||||
query: self.query,
|
||||
selector: SelectTwoModel { model: PhantomData },
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn one(
|
||||
self,
|
||||
db: &DatabaseConnection,
|
||||
) -> Result<Option<(E::Model, Option<F::Model>)>, QueryErr> {
|
||||
self.into_model::<E::Model, F::Model>().one(db).await
|
||||
}
|
||||
|
||||
pub async fn all(
|
||||
self,
|
||||
db: &DatabaseConnection,
|
||||
) -> Result<Vec<(E::Model, Vec<F::Model>)>, QueryErr> {
|
||||
let rows = self.into_model::<E::Model, F::Model>().all(db).await?;
|
||||
Ok(consolidate_query_result::<E, F>(rows))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Selector<S>
|
||||
where
|
||||
S: SelectorTrait,
|
||||
@ -171,3 +215,39 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn consolidate_query_result<L, R>(
|
||||
rows: Vec<(L::Model, Option<R::Model>)>,
|
||||
) -> Vec<(L::Model, Vec<R::Model>)>
|
||||
where
|
||||
L: EntityTrait,
|
||||
R: EntityTrait,
|
||||
{
|
||||
let mut acc: Vec<(L::Model, Vec<R::Model>)> = 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 <L::PrimaryKey as Iterable>::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 {
|
||||
if let Some(r) = r {
|
||||
last_r.push(r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.is_some() {
|
||||
acc.push((l, vec![r.unwrap()]));
|
||||
} else {
|
||||
acc.push((l, vec![]));
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
126
src/lib.rs
126
src/lib.rs
@ -1,8 +1,134 @@
|
||||
//! # Select
|
||||
//! ```
|
||||
//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*};
|
||||
//! # async fn function(db: &DbConn) -> Result<(), QueryErr> {
|
||||
//! #
|
||||
//! // find all models
|
||||
//! let cakes: Vec<cake::Model> = Cake::find().all(db).await?;
|
||||
//!
|
||||
//! // find and filter
|
||||
//! let chocolate: Vec<cake::Model> = Cake::find()
|
||||
//! .filter(cake::Column::Name.contains("chocolate"))
|
||||
//! .all(db)
|
||||
//! .await?;
|
||||
//!
|
||||
//! // find one model
|
||||
//! let cheese: Option<cake::Model> = Cake::find_by_id(1).one(db).await?;
|
||||
//! let cheese: cake::Model = cheese.unwrap();
|
||||
//!
|
||||
//! // find related models (lazy)
|
||||
//! let fruits: Vec<fruit::Model> = cheese.find_related(Fruit).all(db).await?;
|
||||
//!
|
||||
//! // find related models (eager)
|
||||
//! let cake_with_fruits: Vec<(cake::Model, Vec<fruit::Model>)> = Cake::find()
|
||||
//! .find_with_related(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::Model> = Fruit::find_by_id(1).one(db).await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! #
|
||||
//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> {
|
||||
//! # let pear: Option<fruit::Model> = 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::Model> = Fruit::find_by_id(1).one(db).await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! #
|
||||
//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> {
|
||||
//! # let orange: Option<fruit::Model> = 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;
|
||||
mod executor;
|
||||
pub mod query;
|
||||
#[doc(hidden)]
|
||||
pub mod tests_cfg;
|
||||
mod util;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
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, 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_";
|
||||
@ -40,6 +40,14 @@ where
|
||||
self = self.apply_alias(SELECT_A);
|
||||
SelectTwo::new(self.into_query())
|
||||
}
|
||||
|
||||
pub fn select_with<F>(mut self, _: F) -> SelectTwoMany<E, F>
|
||||
where
|
||||
F: EntityTrait,
|
||||
{
|
||||
self = self.apply_alias(SELECT_A);
|
||||
SelectTwoMany::new(self.into_query())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, F> SelectTwo<E, F>
|
||||
@ -48,25 +56,60 @@ where
|
||||
F: EntityTrait,
|
||||
{
|
||||
pub(crate) fn new(query: SelectStatement) -> Self {
|
||||
let myself = Self {
|
||||
Self {
|
||||
query,
|
||||
entity: PhantomData,
|
||||
};
|
||||
myself.prepare_select()
|
||||
}
|
||||
.prepare_select()
|
||||
}
|
||||
|
||||
fn prepare_select(mut self) -> Self {
|
||||
for col in <F::Column as Iterable>::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::<F, Self>(&mut self);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, F> SelectTwoMany<E, F>
|
||||
where
|
||||
E: EntityTrait,
|
||||
F: EntityTrait,
|
||||
{
|
||||
pub(crate) fn new(query: SelectStatement) -> Self {
|
||||
Self {
|
||||
query,
|
||||
entity: PhantomData,
|
||||
}
|
||||
.prepare_select()
|
||||
.prepare_order_by()
|
||||
}
|
||||
|
||||
fn prepare_select(mut self) -> Self {
|
||||
prepare_select_two::<F, Self>(&mut self);
|
||||
self
|
||||
}
|
||||
|
||||
fn prepare_order_by(mut self) -> Self {
|
||||
for col in <E::PrimaryKey as Iterable>::iter() {
|
||||
self.query.order_by((E::default(), col), Order::Asc);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_select_two<F, S>(selector: &mut S)
|
||||
where
|
||||
F: EntityTrait,
|
||||
S: QueryTrait<QueryStatement = SelectStatement>,
|
||||
{
|
||||
for col in <F::Column as Iterable>::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 +144,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[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`",
|
||||
"FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`",
|
||||
"ORDER BY `cake`.`id` ASC",
|
||||
].join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_also_2() {
|
||||
assert_eq!(
|
||||
@ -119,4 +179,24 @@ mod tests {
|
||||
].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`",
|
||||
"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(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -258,11 +258,11 @@ pub trait QueryFilter: Sized {
|
||||
}
|
||||
|
||||
/// Apply a where condition using the model's primary key
|
||||
fn belongs_to<E>(mut self, model: &E::Model) -> Self
|
||||
fn belongs_to<M>(mut self, model: &M) -> Self
|
||||
where
|
||||
E: EntityTrait,
|
||||
M: ModelTrait,
|
||||
{
|
||||
for key in E::PrimaryKey::iter() {
|
||||
for key in <M::Entity as EntityTrait>::PrimaryKey::iter() {
|
||||
let col = key.into_column();
|
||||
self = self.filter(col.eq(model.get(col)));
|
||||
}
|
||||
|
@ -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<E> Select<E>
|
||||
@ -41,19 +41,28 @@ where
|
||||
}
|
||||
|
||||
/// Left Join with a Related Entity and select both Entity.
|
||||
pub fn left_join_and_select<R>(self, r: R) -> SelectTwo<E, R>
|
||||
pub fn find_also_related<R>(self, r: R) -> SelectTwo<E, R>
|
||||
where
|
||||
R: EntityTrait,
|
||||
E: Related<R>,
|
||||
{
|
||||
self.left_join(r).select_also(r)
|
||||
}
|
||||
|
||||
/// Left Join with a Related Entity and select the related Entity as a `Vec`
|
||||
pub fn find_with_related<R>(self, r: R) -> SelectTwoMany<E, R>
|
||||
where
|
||||
R: EntityTrait,
|
||||
E: Related<R>,
|
||||
{
|
||||
self.left_join(r).select_with(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests_cfg::{cake, filling, fruit};
|
||||
use crate::{ColumnTrait, EntityTrait, QueryFilter, QueryTrait};
|
||||
use crate::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QueryTrait};
|
||||
use sea_query::MysqlQueryBuilder;
|
||||
|
||||
#[test]
|
||||
@ -130,7 +139,10 @@ 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`",
|
||||
|
@ -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};
|
||||
|
@ -23,6 +23,16 @@ where
|
||||
pub(crate) entity: PhantomData<(E, F)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SelectTwoMany<E, F>
|
||||
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<E, F> $trait for SelectTwoMany<E, F>
|
||||
where
|
||||
E: EntityTrait,
|
||||
F: EntityTrait,
|
||||
{
|
||||
type QueryStatement = SelectStatement;
|
||||
|
||||
fn query(&mut self) -> &mut SelectStatement {
|
||||
&mut self.query
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -118,19 +140,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, F> QueryTrait for SelectTwo<E, F>
|
||||
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<E, F> QueryTrait for $selector<E, F>
|
||||
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);
|
||||
|
@ -73,14 +73,4 @@ impl Related<super::filling::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
|
||||
pub fn find_filling(&self) -> Select<super::filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
@ -63,10 +63,4 @@ impl Related<super::cake::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake(&self) -> Select<super::cake::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user