Merge remote-tracking branch 'origin/master' into active-enum
This commit is contained in:
commit
d525c710ce
8
.github/workflows/rust.yml
vendored
8
.github/workflows/rust.yml
vendored
@ -145,7 +145,7 @@ jobs:
|
||||
with:
|
||||
command: test
|
||||
args: >
|
||||
--all
|
||||
--workspace
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@ -153,6 +153,12 @@ jobs:
|
||||
args: >
|
||||
--manifest-path sea-orm-rocket/Cargo.toml
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: >
|
||||
--manifest-path sea-orm-cli/Cargo.toml
|
||||
|
||||
cli:
|
||||
name: CLI
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
@ -36,6 +36,7 @@ assert_eq!(
|
||||
))
|
||||
);
|
||||
|
||||
// update many remains the same
|
||||
assert_eq!(
|
||||
Update::many(cake::Entity)
|
||||
.col_expr(cake::Column::Name, Expr::value("Cheese Cake".to_owned()))
|
||||
|
@ -37,6 +37,7 @@ serde_json = { version = "^1", optional = true }
|
||||
sqlx = { version = "^0.5", optional = true }
|
||||
uuid = { version = "0.8", features = ["serde", "v4"], optional = true }
|
||||
ouroboros = "0.11"
|
||||
url = "^2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
smol = { version = "^1.2" }
|
||||
|
@ -4,9 +4,7 @@
|
||||
|
||||
<h1>SeaORM</h1>
|
||||
|
||||
<p>
|
||||
<strong>🐚 An async & dynamic ORM for Rust</strong>
|
||||
</p>
|
||||
<h3>🐚 An async & dynamic ORM for Rust</h3>
|
||||
|
||||
[](https://crates.io/crates/sea-orm)
|
||||
[](https://docs.rs/sea-orm)
|
||||
@ -18,7 +16,7 @@
|
||||
|
||||
# SeaORM
|
||||
|
||||
SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust.
|
||||
#### SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust.
|
||||
|
||||
[](https://www.sea-ql.org/SeaORM/docs/index)
|
||||
[](https://github.com/SeaQL/sea-orm/tree/master/examples/basic)
|
||||
|
@ -1,11 +1,2 @@
|
||||
[workspace]
|
||||
# A separate workspace
|
||||
|
||||
[package]
|
||||
name = "sea-orm-issues-249"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
sea-orm = { path = "../../", default-features = false, features = ["mock"] }
|
||||
members = ["core", "app"]
|
||||
|
1
issues/249/README.md
Normal file
1
issues/249/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Demo of a pure logic crate depending on SeaORM with no enabled features
|
9
issues/249/app/Cargo.toml
Normal file
9
issues/249/app/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "sea-orm-issues-249-app"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
core = { path = "../core" }
|
||||
sea-orm = { path = "../../../", default-features = false, features = ["macros", "sqlx-sqlite", "runtime-async-std-native-tls"] }
|
14
issues/249/app/src/main.rs
Normal file
14
issues/249/app/src/main.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use core::clone_a_model;
|
||||
|
||||
use sea_orm::tests_cfg::cake;
|
||||
|
||||
fn main() {
|
||||
let c1 = cake::Model {
|
||||
id: 1,
|
||||
name: "Cheese".to_owned(),
|
||||
};
|
||||
|
||||
let c2 = clone_a_model(&c1);
|
||||
|
||||
println!("{:?}", c2);
|
||||
}
|
11
issues/249/core/Cargo.toml
Normal file
11
issues/249/core/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "core"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
sea-orm = { path = "../../../", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
sea-orm = { path = "../../../", features = ["mock"] }
|
17
issues/249/core/src/lib.rs
Normal file
17
issues/249/core/src/lib.rs
Normal file
@ -0,0 +1,17 @@
|
||||
pub use sea_orm::entity::*;
|
||||
|
||||
pub fn clone_a_model<M>(model: &M) -> M
|
||||
where
|
||||
M: ModelTrait {
|
||||
model.clone()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
println!("OK");
|
||||
}
|
||||
}
|
@ -32,6 +32,10 @@ sea-schema = { version = "^0.2.9", default-features = false, features = [
|
||||
sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ] }
|
||||
env_logger = { version = "^0.9" }
|
||||
log = { version = "^0.4" }
|
||||
url = "^2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
smol = "1.2.5"
|
||||
|
||||
[features]
|
||||
default = [ "runtime-async-std-native-tls" ]
|
||||
|
@ -3,6 +3,7 @@ use dotenv::dotenv;
|
||||
use log::LevelFilter;
|
||||
use sea_orm_codegen::{EntityTransformer, OutputFile, WithSerde};
|
||||
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command, str::FromStr};
|
||||
use url::Url;
|
||||
|
||||
mod cli;
|
||||
|
||||
@ -23,7 +24,6 @@ async fn main() {
|
||||
async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
||||
match matches.subcommand() {
|
||||
("entity", Some(args)) => {
|
||||
let url = args.value_of("DATABASE_URL").unwrap();
|
||||
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
||||
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
|
||||
let tables = args
|
||||
@ -32,8 +32,67 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
||||
.collect::<Vec<_>>();
|
||||
let expanded_format = args.is_present("EXPANDED_FORMAT");
|
||||
let with_serde = args.value_of("WITH_SERDE").unwrap();
|
||||
if args.is_present("VERBOSE") {
|
||||
let _ = ::env_logger::builder()
|
||||
.filter_level(LevelFilter::Debug)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
// The database should be a valid URL that can be parsed
|
||||
// protocol://username:password@host/database_name
|
||||
let url = Url::parse(
|
||||
args.value_of("DATABASE_URL")
|
||||
.expect("No database url could be found"),
|
||||
)?;
|
||||
|
||||
// Make sure we have all the required url components
|
||||
//
|
||||
// Missing scheme will have been caught by the Url::parse() call
|
||||
// above
|
||||
|
||||
let url_username = url.username();
|
||||
let url_password = url.password();
|
||||
let url_host = url.host_str();
|
||||
|
||||
// Panic on any that are missing
|
||||
if url_username.is_empty() {
|
||||
panic!("No username was found in the database url");
|
||||
}
|
||||
if url_password.is_none() {
|
||||
panic!("No password was found in the database url");
|
||||
}
|
||||
if url_host.is_none() {
|
||||
panic!("No host was found in the database url");
|
||||
}
|
||||
|
||||
// The database name should be the first element of the path string
|
||||
//
|
||||
// Throwing an error if there is no database name since it might be
|
||||
// accepted by the database without it, while we're looking to dump
|
||||
// information from a particular database
|
||||
let database_name = url
|
||||
.path_segments()
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"There is no database name as part of the url path: {}",
|
||||
url.as_str()
|
||||
)
|
||||
})
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
// An empty string as the database name is also an error
|
||||
if database_name.is_empty() {
|
||||
panic!(
|
||||
"There is no database name as part of the url path: {}",
|
||||
url.as_str()
|
||||
);
|
||||
}
|
||||
|
||||
// Closures for filtering tables
|
||||
let filter_tables = |table: &str| -> bool {
|
||||
if tables.len() > 0 {
|
||||
if !tables.is_empty() {
|
||||
return tables.contains(&table);
|
||||
}
|
||||
|
||||
@ -43,49 +102,43 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
||||
if include_hidden_tables {
|
||||
true
|
||||
} else {
|
||||
!table.starts_with("_")
|
||||
!table.starts_with('_')
|
||||
}
|
||||
};
|
||||
if args.is_present("VERBOSE") {
|
||||
let _ = ::env_logger::builder()
|
||||
.filter_level(LevelFilter::Debug)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
let table_stmts = if url.starts_with("mysql://") {
|
||||
use sea_schema::mysql::discovery::SchemaDiscovery;
|
||||
use sqlx::MySqlPool;
|
||||
let table_stmts = match url.scheme() {
|
||||
"mysql" => {
|
||||
use sea_schema::mysql::discovery::SchemaDiscovery;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
let url_parts: Vec<&str> = url.split("/").collect();
|
||||
let schema = url_parts.last().unwrap();
|
||||
let connection = MySqlPool::connect(url).await?;
|
||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
||||
let schema = schema_discovery.discover().await;
|
||||
schema
|
||||
.tables
|
||||
.into_iter()
|
||||
.filter(|schema| filter_tables(&schema.info.name))
|
||||
.filter(|schema| filter_hidden_tables(&schema.info.name))
|
||||
.map(|schema| schema.write())
|
||||
.collect()
|
||||
} else if url.starts_with("postgres://") || url.starts_with("postgresql://") {
|
||||
use sea_schema::postgres::discovery::SchemaDiscovery;
|
||||
use sqlx::PgPool;
|
||||
let connection = MySqlPool::connect(url.as_str()).await?;
|
||||
let schema_discovery = SchemaDiscovery::new(connection, database_name);
|
||||
let schema = schema_discovery.discover().await;
|
||||
schema
|
||||
.tables
|
||||
.into_iter()
|
||||
.filter(|schema| filter_tables(&schema.info.name))
|
||||
.filter(|schema| filter_hidden_tables(&schema.info.name))
|
||||
.map(|schema| schema.write())
|
||||
.collect()
|
||||
}
|
||||
"postgres" | "postgresql" => {
|
||||
use sea_schema::postgres::discovery::SchemaDiscovery;
|
||||
use sqlx::PgPool;
|
||||
|
||||
let schema = args.value_of("DATABASE_SCHEMA").unwrap_or("public");
|
||||
let connection = PgPool::connect(url).await?;
|
||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
||||
let schema = schema_discovery.discover().await;
|
||||
schema
|
||||
.tables
|
||||
.into_iter()
|
||||
.filter(|schema| filter_tables(&schema.info.name))
|
||||
.filter(|schema| filter_hidden_tables(&schema.info.name))
|
||||
.map(|schema| schema.write())
|
||||
.collect()
|
||||
} else {
|
||||
panic!("This database is not supported ({})", url)
|
||||
let schema = args.value_of("DATABASE_SCHEMA").unwrap_or("public");
|
||||
let connection = PgPool::connect(url.as_str()).await?;
|
||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
||||
let schema = schema_discovery.discover().await;
|
||||
schema
|
||||
.tables
|
||||
.into_iter()
|
||||
.filter(|schema| filter_tables(&schema.info.name))
|
||||
.filter(|schema| filter_hidden_tables(&schema.info.name))
|
||||
.map(|schema| schema.write())
|
||||
.collect()
|
||||
}
|
||||
_ => unimplemented!("{} is not supported", url.scheme()),
|
||||
};
|
||||
|
||||
let output = EntityTransformer::transform(table_stmts)?
|
||||
@ -99,6 +152,8 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
||||
let mut file = fs::File::create(file_path)?;
|
||||
file.write_all(content.as_bytes())?;
|
||||
}
|
||||
|
||||
// Format each of the files
|
||||
for OutputFile { name, .. } in output.files.iter() {
|
||||
Command::new("rustfmt")
|
||||
.arg(dir.join(name))
|
||||
@ -119,3 +174,125 @@ where
|
||||
eprintln!("{}", error);
|
||||
::std::process::exit(1);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::AppSettings;
|
||||
use url::ParseError;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_generate_entity_no_protocol() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"://root:root@localhost:3306/database",
|
||||
]);
|
||||
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap()))
|
||||
});
|
||||
|
||||
// Make sure result is a ParseError
|
||||
match result {
|
||||
Ok(Err(e)) => match e.downcast::<ParseError>() {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("Expected ParseError but got: {:?}", e),
|
||||
},
|
||||
_ => panic!("Should have panicked"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_generate_entity_no_database_section() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"postgresql://root:root@localhost:3306",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap()))
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_generate_entity_no_database_path() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://root:root@localhost:3306/",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap()))
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_generate_entity_no_username() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://:root@localhost:3306/database",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap()))
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_generate_entity_no_password() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://root:@localhost:3306/database",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap()))
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_generate_entity_no_host() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"postgres://root:root@/database",
|
||||
]);
|
||||
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap()))
|
||||
});
|
||||
|
||||
// Make sure result is a ParseError
|
||||
match result {
|
||||
Ok(Err(e)) => match e.downcast::<ParseError>() {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("Expected ParseError but got: {:?}", e),
|
||||
},
|
||||
_ => panic!("Should have panicked"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
};
|
||||
use sea_query::{MysqlQueryBuilder, PostgresQueryBuilder, QueryBuilder, SqliteQueryBuilder};
|
||||
use std::{future::Future, pin::Pin};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
use sqlx::pool::PoolConnection;
|
||||
@ -223,12 +224,13 @@ impl DatabaseConnection {
|
||||
|
||||
impl DbBackend {
|
||||
pub fn is_prefix_of(self, base_url: &str) -> bool {
|
||||
let base_url_parsed = Url::parse(base_url).unwrap();
|
||||
match self {
|
||||
Self::Postgres => {
|
||||
base_url.starts_with("postgres://") || base_url.starts_with("postgresql://")
|
||||
base_url_parsed.scheme() == "postgres" || base_url_parsed.scheme() == "postgresql"
|
||||
}
|
||||
Self::MySql => base_url.starts_with("mysql://"),
|
||||
Self::Sqlite => base_url.starts_with("sqlite:"),
|
||||
Self::MySql => base_url_parsed.scheme() == "mysql",
|
||||
Self::Sqlite => base_url_parsed.scheme() == "sqlite",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,10 +460,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
my_fruit::UpdateFruit {
|
||||
cake_id: None,
|
||||
}
|
||||
.into_active_model(),
|
||||
my_fruit::UpdateFruit { cake_id: None }.into_active_model(),
|
||||
fruit::ActiveModel {
|
||||
id: Unset(None),
|
||||
name: Unset(None),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable,
|
||||
ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo,
|
||||
SelectTwoMany, Statement, TryGetableMany,
|
||||
error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable, ModelTrait,
|
||||
Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany,
|
||||
Statement, TryGetableMany,
|
||||
};
|
||||
use futures::{Stream, TryStreamExt};
|
||||
use sea_query::SelectStatement;
|
||||
|
@ -11,9 +11,7 @@
|
||||
//!
|
||||
//! <h1>SeaORM</h1>
|
||||
//!
|
||||
//! <p>
|
||||
//! <strong>🐚 An async & dynamic ORM for Rust</strong>
|
||||
//! </p>
|
||||
//! <h3>🐚 An async & dynamic ORM for Rust</h3>
|
||||
//!
|
||||
//! [](https://crates.io/crates/sea-orm)
|
||||
//! [](https://docs.rs/sea-orm)
|
||||
@ -25,7 +23,7 @@
|
||||
//!
|
||||
//! # SeaORM
|
||||
//!
|
||||
//! SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust.
|
||||
//! #### SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust.
|
||||
//!
|
||||
//! [](https://www.sea-ql.org/SeaORM/docs/index)
|
||||
//! [](https://github.com/SeaQL/sea-orm/tree/master/examples/basic)
|
||||
|
@ -3,9 +3,11 @@ pub mod applog;
|
||||
pub mod metadata;
|
||||
pub mod repository;
|
||||
pub mod schema;
|
||||
pub mod self_join;
|
||||
|
||||
pub use active_enum::Entity as ActiveEnum;
|
||||
pub use applog::Entity as Applog;
|
||||
pub use metadata::Entity as Metadata;
|
||||
pub use repository::Entity as Repository;
|
||||
pub use schema::*;
|
||||
pub use self_join::Entity as SelfJoin;
|
||||
|
@ -2,13 +2,14 @@ pub use super::super::bakery_chain::*;
|
||||
|
||||
use super::*;
|
||||
use crate::common::setup::create_table;
|
||||
use sea_orm::{ConnectionTrait, DatabaseConnection, DbConn, ExecResult, Statement, error::*, sea_query};
|
||||
use sea_query::{Alias, ColumnDef};
|
||||
use sea_orm::{error::*, sea_query, DatabaseConnection, DbConn, ExecResult};
|
||||
use sea_query::{ColumnDef, ForeignKeyCreateStatement};
|
||||
|
||||
pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> {
|
||||
create_log_table(db).await?;
|
||||
create_metadata_table(db).await?;
|
||||
create_repository_table(db).await?;
|
||||
create_self_join_table(db).await?;
|
||||
create_active_enum_table(db).await?;
|
||||
|
||||
Ok(())
|
||||
@ -77,6 +78,30 @@ pub async fn create_repository_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
create_table(db, &stmt, Repository).await
|
||||
}
|
||||
|
||||
pub async fn create_self_join_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
let stmt = sea_query::Table::create()
|
||||
.table(self_join::Entity)
|
||||
.col(
|
||||
ColumnDef::new(self_join::Column::Uuid)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(self_join::Column::UuidRef).uuid())
|
||||
.col(ColumnDef::new(self_join::Column::Time).time())
|
||||
.foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.name("fk-self_join-self_join")
|
||||
.from_tbl(SelfJoin)
|
||||
.from_col(self_join::Column::UuidRef)
|
||||
.to_tbl(SelfJoin)
|
||||
.to_col(self_join::Column::Uuid),
|
||||
)
|
||||
.to_owned();
|
||||
|
||||
create_table(db, &stmt, SelfJoin).await
|
||||
}
|
||||
|
||||
pub async fn create_active_enum_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
let stmt = sea_query::Table::create()
|
||||
.table(active_enum::Entity)
|
||||
|
77
tests/common/features/self_join.rs
Normal file
77
tests/common/features/self_join.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "self_join")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub uuid: Uuid,
|
||||
pub uuid_ref: Option<Uuid>,
|
||||
pub time: Option<Time>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(belongs_to = "Entity", from = "Column::UuidRef", to = "Column::Uuid")]
|
||||
SelfReferencing,
|
||||
}
|
||||
|
||||
pub struct SelfReferencingLink;
|
||||
|
||||
impl Linked for SelfReferencingLink {
|
||||
type FromEntity = Entity;
|
||||
|
||||
type ToEntity = Entity;
|
||||
|
||||
fn link(&self) -> Vec<RelationDef> {
|
||||
vec![Relation::SelfReferencing.def()]
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use sea_orm::*;
|
||||
|
||||
#[test]
|
||||
fn find_linked_001() {
|
||||
let self_join_model = Model {
|
||||
uuid: Uuid::default(),
|
||||
uuid_ref: None,
|
||||
time: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
self_join_model
|
||||
.find_linked(SelfReferencingLink)
|
||||
.build(DbBackend::MySql)
|
||||
.to_string(),
|
||||
[
|
||||
r#"SELECT `self_join`.`uuid`, `self_join`.`uuid_ref`, `self_join`.`time`"#,
|
||||
r#"FROM `self_join`"#,
|
||||
r#"INNER JOIN `self_join` AS `r0` ON `r0`.`uuid_ref` = `self_join`.`uuid`"#,
|
||||
r#"WHERE `r0`.`uuid` = '00000000-0000-0000-0000-000000000000'"#,
|
||||
]
|
||||
.join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_also_linked_001() {
|
||||
assert_eq!(
|
||||
Entity::find()
|
||||
.find_also_linked(SelfReferencingLink)
|
||||
.build(DbBackend::MySql)
|
||||
.to_string(),
|
||||
[
|
||||
r#"SELECT `self_join`.`uuid` AS `A_uuid`, `self_join`.`uuid_ref` AS `A_uuid_ref`, `self_join`.`time` AS `A_time`,"#,
|
||||
r#"`r0`.`uuid` AS `B_uuid`, `r0`.`uuid_ref` AS `B_uuid_ref`, `r0`.`time` AS `B_time`"#,
|
||||
r#"FROM `self_join`"#,
|
||||
r#"LEFT JOIN `self_join` AS `r0` ON `self_join`.`uuid_ref` = `r0`.`uuid`"#,
|
||||
]
|
||||
.join(" ")
|
||||
);
|
||||
}
|
||||
}
|
89
tests/self_join_tests.rs
Normal file
89
tests/self_join_tests.rs
Normal file
@ -0,0 +1,89 @@
|
||||
pub mod common;
|
||||
|
||||
pub use common::{features::*, setup::*, TestContext};
|
||||
use pretty_assertions::assert_eq;
|
||||
use sea_orm::{entity::prelude::*, *};
|
||||
|
||||
#[sea_orm_macros::test]
|
||||
#[cfg(any(
|
||||
feature = "sqlx-mysql",
|
||||
feature = "sqlx-sqlite",
|
||||
feature = "sqlx-postgres"
|
||||
))]
|
||||
async fn main() -> Result<(), DbErr> {
|
||||
let ctx = TestContext::new("self_join_tests").await;
|
||||
create_tables(&ctx.db).await?;
|
||||
create_metadata(&ctx.db).await?;
|
||||
ctx.delete().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> {
|
||||
let model = self_join::Model {
|
||||
uuid: Uuid::new_v4(),
|
||||
uuid_ref: None,
|
||||
time: Some(Time::from_hms(1, 00, 00)),
|
||||
};
|
||||
|
||||
model.clone().into_active_model().insert(db).await?;
|
||||
|
||||
let linked_model = self_join::Model {
|
||||
uuid: Uuid::new_v4(),
|
||||
uuid_ref: Some(model.clone().uuid),
|
||||
time: Some(Time::from_hms(2, 00, 00)),
|
||||
};
|
||||
|
||||
linked_model.clone().into_active_model().insert(db).await?;
|
||||
|
||||
let not_linked_model = self_join::Model {
|
||||
uuid: Uuid::new_v4(),
|
||||
uuid_ref: None,
|
||||
time: Some(Time::from_hms(3, 00, 00)),
|
||||
};
|
||||
|
||||
not_linked_model
|
||||
.clone()
|
||||
.into_active_model()
|
||||
.insert(db)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
model
|
||||
.find_linked(self_join::SelfReferencingLink)
|
||||
.all(db)
|
||||
.await?,
|
||||
vec![]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
linked_model
|
||||
.find_linked(self_join::SelfReferencingLink)
|
||||
.all(db)
|
||||
.await?,
|
||||
vec![model.clone()]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
not_linked_model
|
||||
.find_linked(self_join::SelfReferencingLink)
|
||||
.all(db)
|
||||
.await?,
|
||||
vec![]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self_join::Entity::find()
|
||||
.find_also_linked(self_join::SelfReferencingLink)
|
||||
.order_by_asc(self_join::Column::Time)
|
||||
.all(db)
|
||||
.await?,
|
||||
vec![
|
||||
(model.clone(), None),
|
||||
(linked_model, Some(model)),
|
||||
(not_linked_model, None),
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user