Merge branch 'master' into ss/test_suite

# Conflicts:
#	Cargo.toml
This commit is contained in:
Sam Samai 2021-07-14 09:12:55 +10:00
commit 2e0f6df743
41 changed files with 1923 additions and 345 deletions

View File

@ -31,3 +31,4 @@ jobs:
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all

View File

@ -37,23 +37,27 @@ name = "sea_orm"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
async-stream = { version="^0.3" } async-stream = { version = "^0.3" }
chrono = { version="^0", optional=true } chrono = { version = "^0", optional = true }
futures = { version="^0.3" } futures = { version = "^0.3" }
futures-util = { version="^0.3" } futures-util = { version = "^0.3" }
rust_decimal = { version="^1", optional=true } rust_decimal = { version = "^1", optional = true }
sea-query = { version="^0.12" } sea-query = { version = "^0.12" }
sea-orm-macros = { path="sea-orm-macros", optional=true } sea-orm-macros = { path = "sea-orm-macros", optional = true }
serde = { version="^1.0", features=["derive"] } sea-orm-codegen = { path = "sea-orm-codegen", optional = true }
sqlx = { version="^0.5", optional=true } serde = { version = "^1.0", features = ["derive"] }
strum = { git="https://github.com/SeaQL/strum.git", branch="sea-orm", version="^0.21", features=["derive", "sea-orm"] } sqlx = { version = "^0.5", optional = true }
serde_json = { version="^1", optional=true } strum = { git = "https://github.com/SeaQL/strum.git", branch = "sea-orm", version = "^0.21", features = [
"derive",
"sea-orm",
] }
serde_json = { version = "^1", optional = true }
[dev-dependencies] [dev-dependencies]
async-std = { version="^1.9", features=["attributes"] } async-std = { version = "^1.9", features = ["attributes"] }
maplit = { version="^1" } maplit = { version = "^1" }
rust_decimal_macros = { version="^1" } rust_decimal_macros = { version = "^1" }
sea-orm = { path=".", features=[ sea-orm = { path = ".", features = [
"sqlx-sqlite", "sqlx-sqlite",
"sqlx-json", "sqlx-json",
"sqlx-chrono", "sqlx-chrono",
@ -64,8 +68,16 @@ sea-orm = { path=".", features=[
[features] [features]
debug-print = [] debug-print = []
default = ["macros", "with-json", "with-chrono", "with-rust_decimal", "mock"] default = [
"macros",
"codegen",
"with-json",
"with-chrono",
"with-rust_decimal",
"mock",
]
macros = ["sea-orm-macros"] macros = ["sea-orm-macros"]
codegen = ["sea-orm-codegen"]
mock = [] mock = []
with-json = ["serde_json", "sea-query/with-json"] with-json = ["serde_json", "sea-query/with-json"]
with-chrono = ["chrono", "sea-query/with-chrono"] with-chrono = ["chrono", "sea-query/with-chrono"]

View File

@ -23,3 +23,7 @@ syn = { version = "^1", default-features = false, features = [ "derive", "parsin
quote = "^1" quote = "^1"
heck = "^0.3" heck = "^0.3"
proc-macro2 = "^1" proc-macro2 = "^1"
[dev-dependencies]
async-std = { version = "^1.9", features = [ "attributes" ] }
sea-orm = { path = "../", features = ["mock", "sqlx-json", "sqlx-chrono", "runtime-async-std-native-tls"] }

View File

@ -22,7 +22,7 @@ impl Column {
} }
pub fn get_rs_type(&self) -> TokenStream { pub fn get_rs_type(&self) -> TokenStream {
let ident = match self.col_type { let ident: TokenStream = match self.col_type {
ColumnType::Char(_) ColumnType::Char(_)
| ColumnType::String(_) | ColumnType::String(_)
| ColumnType::Text | ColumnType::Text
@ -32,18 +32,18 @@ impl Column {
| ColumnType::Date | ColumnType::Date
| ColumnType::Json | ColumnType::Json
| ColumnType::JsonBinary | ColumnType::JsonBinary
| ColumnType::Custom(_) => format_ident!("String"), | ColumnType::Custom(_) => "String",
ColumnType::TinyInteger(_) => format_ident!("i8"), ColumnType::TinyInteger(_) => "i8",
ColumnType::SmallInteger(_) => format_ident!("i16"), ColumnType::SmallInteger(_) => "i16",
ColumnType::Integer(_) => format_ident!("i32"), ColumnType::Integer(_) => "i32",
ColumnType::BigInteger(_) => format_ident!("i64"), ColumnType::BigInteger(_) => "i64",
ColumnType::Float(_) | ColumnType::Decimal(_) | ColumnType::Money(_) => { ColumnType::Float(_) | ColumnType::Decimal(_) | ColumnType::Money(_) => "f32",
format_ident!("f32") ColumnType::Double(_) => "f64",
} ColumnType::Binary(_) => "Vec<u8>",
ColumnType::Double(_) => format_ident!("f64"), ColumnType::Boolean => "bool",
ColumnType::Binary(_) => format_ident!("Vec<u8>"), }
ColumnType::Boolean => format_ident!("bool"), .parse()
}; .unwrap();
match self.not_null { match self.not_null {
true => quote! { #ident }, true => quote! { #ident },
false => quote! { Option<#ident> }, false => quote! { Option<#ident> },
@ -102,6 +102,12 @@ impl Column {
} }
} }
impl From<ColumnDef> for Column {
fn from(col_def: ColumnDef) -> Self {
(&col_def).into()
}
}
impl From<&ColumnDef> for Column { impl From<&ColumnDef> for Column {
fn from(col_def: &ColumnDef) -> Self { fn from(col_def: &ColumnDef) -> Self {
let name = col_def.get_column_name(); let name = col_def.get_column_name();
@ -145,3 +151,164 @@ impl From<&ColumnDef> for Column {
} }
} }
} }
#[cfg(test)]
mod tests {
use crate::Column;
use proc_macro2::TokenStream;
use quote::quote;
use sea_query::{Alias, ColumnDef, ColumnType, SeaRc};
fn setup() -> Vec<Column> {
macro_rules! make_col {
($name:expr, $col_type:expr) => {
Column {
name: $name.to_owned(),
col_type: $col_type,
auto_increment: false,
not_null: false,
unique: false,
}
};
}
vec![
make_col!("id", ColumnType::String(Some(255))),
make_col!(
"cake_id",
ColumnType::Custom(SeaRc::new(Alias::new("cus_col")))
),
make_col!("CakeId", ColumnType::TinyInteger(None)),
make_col!("CakeId", ColumnType::SmallInteger(None)),
make_col!("CakeId", ColumnType::Integer(Some(11))),
make_col!("CakeFillingId", ColumnType::BigInteger(None)),
make_col!("cake-filling-id", ColumnType::Float(None)),
make_col!("CAKE_FILLING_ID", ColumnType::Double(None)),
make_col!("CAKE-FILLING-ID", ColumnType::Binary(None)),
make_col!("CAKE", ColumnType::Boolean),
]
}
#[test]
fn test_get_name_snake_case() {
let columns = setup();
let snack_cases = vec![
"id",
"cake_id",
"cake_id",
"cake_id",
"cake_id",
"cake_filling_id",
"cake_filling_id",
"cake_filling_id",
"cake_filling_id",
"cake",
];
for (col, snack_case) in columns.into_iter().zip(snack_cases) {
assert_eq!(col.get_name_snake_case().to_string(), snack_case);
}
}
#[test]
fn test_get_name_camel_case() {
let columns = setup();
let camel_cases = vec![
"Id",
"CakeId",
"CakeId",
"CakeId",
"CakeId",
"CakeFillingId",
"CakeFillingId",
"CakeFillingId",
"CakeFillingId",
"Cake",
];
for (col, camel_case) in columns.into_iter().zip(camel_cases) {
assert_eq!(col.get_name_camel_case().to_string(), camel_case);
}
}
#[test]
fn test_get_rs_type() {
let columns = setup();
let rs_types = vec![
"String", "String", "i8", "i16", "i32", "i64", "f32", "f64", "Vec<u8>", "bool",
];
for (mut col, rs_type) in columns.into_iter().zip(rs_types) {
let rs_type: TokenStream = rs_type.parse().unwrap();
col.not_null = true;
assert_eq!(col.get_rs_type().to_string(), quote!(#rs_type).to_string());
col.not_null = false;
assert_eq!(
col.get_rs_type().to_string(),
quote!(Option<#rs_type>).to_string()
);
}
}
#[test]
fn test_get_def() {
let columns = setup();
let col_defs = vec![
"ColumnType::String(Some(255u32)).def()",
"ColumnType::Custom(\"cus_col\".to_owned()).def()",
"ColumnType::TinyInteger.def()",
"ColumnType::SmallInteger.def()",
"ColumnType::Integer.def()",
"ColumnType::BigInteger.def()",
"ColumnType::Float.def()",
"ColumnType::Double.def()",
"ColumnType::Binary.def()",
"ColumnType::Boolean.def()",
];
for (mut col, col_def) in columns.into_iter().zip(col_defs) {
let mut col_def: TokenStream = col_def.parse().unwrap();
col.not_null = true;
assert_eq!(col.get_def().to_string(), col_def.to_string());
col.not_null = false;
col_def.extend(quote!(.null()));
assert_eq!(col.get_def().to_string(), col_def.to_string());
col.unique = true;
col_def.extend(quote!(.unique()));
assert_eq!(col.get_def().to_string(), col_def.to_string());
}
}
#[test]
fn test_from_column_def() {
let column: Column = ColumnDef::new(Alias::new("id")).string().into();
assert_eq!(
column.get_def().to_string(),
quote! {
ColumnType::String(None).def().null()
}
.to_string()
);
let column: Column = ColumnDef::new(Alias::new("id")).string().not_null().into();
assert!(column.not_null);
let column: Column = ColumnDef::new(Alias::new("id"))
.string()
.unique_key()
.not_null()
.into();
assert!(column.unique);
assert!(column.not_null);
let column: Column = ColumnDef::new(Alias::new("id"))
.string()
.auto_increment()
.unique_key()
.not_null()
.into();
assert!(column.auto_increment);
assert!(column.unique);
assert!(column.not_null);
}
}

View File

@ -111,15 +111,242 @@ impl Entity {
.collect() .collect()
} }
pub fn get_relation_rel_find_helpers(&self) -> Vec<Ident> {
self.relations
.iter()
.map(|rel| rel.get_rel_find_helper())
.collect()
}
pub fn get_primary_key_auto_increment(&self) -> Ident { pub fn get_primary_key_auto_increment(&self) -> Ident {
let auto_increment = self.columns.iter().any(|col| col.auto_increment); let auto_increment = self.columns.iter().any(|col| col.auto_increment);
format_ident!("{}", auto_increment) format_ident!("{}", auto_increment)
} }
} }
#[cfg(test)]
mod tests {
use crate::{Column, Entity, PrimaryKey, Relation, RelationType};
use quote::format_ident;
use sea_query::ColumnType;
fn setup() -> Entity {
Entity {
table_name: "special_cake".to_owned(),
columns: vec![
Column {
name: "id".to_owned(),
col_type: ColumnType::String(None),
auto_increment: false,
not_null: false,
unique: false,
},
Column {
name: "name".to_owned(),
col_type: ColumnType::String(None),
auto_increment: false,
not_null: false,
unique: false,
},
],
relations: vec![
Relation {
ref_table: "fruit".to_owned(),
columns: vec!["id".to_owned()],
ref_columns: vec!["cake_id".to_owned()],
rel_type: RelationType::HasOne,
},
Relation {
ref_table: "filling".to_owned(),
columns: vec!["id".to_owned()],
ref_columns: vec!["cake_id".to_owned()],
rel_type: RelationType::HasOne,
},
],
primary_keys: vec![PrimaryKey {
name: "id".to_owned(),
}],
}
}
#[test]
fn test_get_table_name_snake_case() {
let entity = setup();
assert_eq!(
entity.get_table_name_snake_case(),
"special_cake".to_owned()
);
}
#[test]
fn test_get_table_name_camel_case() {
let entity = setup();
assert_eq!(entity.get_table_name_camel_case(), "SpecialCake".to_owned());
}
#[test]
fn test_get_table_name_snake_case_ident() {
let entity = setup();
assert_eq!(
entity.get_table_name_snake_case_ident(),
format_ident!("{}", "special_cake")
);
}
#[test]
fn test_get_table_name_camel_case_ident() {
let entity = setup();
assert_eq!(
entity.get_table_name_camel_case_ident(),
format_ident!("{}", "SpecialCake")
);
}
#[test]
fn test_get_column_names_snake_case() {
let entity = setup();
for (i, elem) in entity.get_column_names_snake_case().into_iter().enumerate() {
assert_eq!(elem, entity.columns[i].get_name_snake_case());
}
}
#[test]
fn test_get_column_names_camel_case() {
let entity = setup();
for (i, elem) in entity.get_column_names_camel_case().into_iter().enumerate() {
assert_eq!(elem, entity.columns[i].get_name_camel_case());
}
}
#[test]
fn test_get_column_rs_types() {
let entity = setup();
for (i, elem) in entity.get_column_rs_types().into_iter().enumerate() {
assert_eq!(
elem.to_string(),
entity.columns[i].get_rs_type().to_string()
);
}
}
#[test]
fn test_get_column_defs() {
let entity = setup();
for (i, elem) in entity.get_column_defs().into_iter().enumerate() {
assert_eq!(elem.to_string(), entity.columns[i].get_def().to_string());
}
}
#[test]
fn test_get_primary_key_names_snake_case() {
let entity = setup();
for (i, elem) in entity
.get_primary_key_names_snake_case()
.into_iter()
.enumerate()
{
assert_eq!(elem, entity.primary_keys[i].get_name_snake_case());
}
}
#[test]
fn test_get_primary_key_names_camel_case() {
let entity = setup();
for (i, elem) in entity
.get_primary_key_names_camel_case()
.into_iter()
.enumerate()
{
assert_eq!(elem, entity.primary_keys[i].get_name_camel_case());
}
}
#[test]
fn test_get_relation_ref_tables_snake_case() {
let entity = setup();
for (i, elem) in entity
.get_relation_ref_tables_snake_case()
.into_iter()
.enumerate()
{
assert_eq!(elem, entity.relations[i].get_ref_table_snake_case());
}
}
#[test]
fn test_get_relation_ref_tables_camel_case() {
let entity = setup();
for (i, elem) in entity
.get_relation_ref_tables_camel_case()
.into_iter()
.enumerate()
{
assert_eq!(elem, entity.relations[i].get_ref_table_camel_case());
}
}
#[test]
fn test_get_relation_defs() {
let entity = setup();
for (i, elem) in entity.get_relation_defs().into_iter().enumerate() {
assert_eq!(elem.to_string(), entity.relations[i].get_def().to_string());
}
}
#[test]
fn test_get_relation_rel_types() {
let entity = setup();
for (i, elem) in entity.get_relation_rel_types().into_iter().enumerate() {
assert_eq!(elem, entity.relations[i].get_rel_type());
}
}
#[test]
fn test_get_relation_columns_camel_case() {
let entity = setup();
for (i, elem) in entity
.get_relation_columns_camel_case()
.into_iter()
.enumerate()
{
assert_eq!(elem, entity.relations[i].get_column_camel_case());
}
}
#[test]
fn test_get_relation_ref_columns_camel_case() {
let entity = setup();
for (i, elem) in entity
.get_relation_ref_columns_camel_case()
.into_iter()
.enumerate()
{
assert_eq!(elem, entity.relations[i].get_ref_column_camel_case());
}
}
#[test]
fn test_get_primary_key_auto_increment() {
let mut entity = setup();
assert_eq!(
entity.get_primary_key_auto_increment(),
format_ident!("{}", false)
);
entity.columns[0].auto_increment = true;
assert_eq!(
entity.get_primary_key_auto_increment(),
format_ident!("{}", true)
);
}
}

View File

@ -16,3 +16,28 @@ impl PrimaryKey {
format_ident!("{}", self.name.to_camel_case()) format_ident!("{}", self.name.to_camel_case())
} }
} }
#[cfg(test)]
mod tests {
use crate::PrimaryKey;
fn setup() -> PrimaryKey {
PrimaryKey {
name: "cake_id".to_owned(),
}
}
#[test]
fn test_get_name_snake_case() {
let primary_key = setup();
assert_eq!(primary_key.get_name_snake_case(), "cake_id".to_owned());
}
#[test]
fn test_get_name_camel_case() {
let primary_key = setup();
assert_eq!(primary_key.get_name_camel_case(), "CakeId".to_owned());
}
}

View File

@ -64,10 +64,6 @@ impl Relation {
pub fn get_ref_column_camel_case(&self) -> Ident { pub fn get_ref_column_camel_case(&self) -> Ident {
format_ident!("{}", self.ref_columns[0].to_camel_case()) format_ident!("{}", self.ref_columns[0].to_camel_case())
} }
pub fn get_rel_find_helper(&self) -> Ident {
format_ident!("find_{}", self.ref_table.to_snake_case())
}
} }
impl From<&TableForeignKey> for Relation { impl From<&TableForeignKey> for Relation {
@ -87,3 +83,95 @@ impl From<&TableForeignKey> for Relation {
} }
} }
} }
#[cfg(test)]
mod tests {
use crate::{Relation, RelationType};
use proc_macro2::TokenStream;
fn setup() -> Vec<Relation> {
vec![
Relation {
ref_table: "fruit".to_owned(),
columns: vec!["id".to_owned()],
ref_columns: vec!["cake_id".to_owned()],
rel_type: RelationType::HasOne,
},
Relation {
ref_table: "filling".to_owned(),
columns: vec!["filling_id".to_owned()],
ref_columns: vec!["id".to_owned()],
rel_type: RelationType::BelongsTo,
},
Relation {
ref_table: "filling".to_owned(),
columns: vec!["filling_id".to_owned()],
ref_columns: vec!["id".to_owned()],
rel_type: RelationType::HasMany,
},
]
}
#[test]
fn test_get_ref_table_snake_case() {
let relations = setup();
let snake_cases = vec!["fruit", "filling", "filling"];
for (rel, snake_case) in relations.into_iter().zip(snake_cases) {
assert_eq!(rel.get_ref_table_snake_case().to_string(), snake_case);
}
}
#[test]
fn test_get_ref_table_camel_case() {
let relations = setup();
let camel_cases = vec!["Fruit", "Filling", "Filling"];
for (rel, camel_case) in relations.into_iter().zip(camel_cases) {
assert_eq!(rel.get_ref_table_camel_case().to_string(), camel_case);
}
}
#[test]
fn test_get_def() {
let relations = setup();
let rel_defs = vec![
"Entity::has_one(super::fruit::Entity).into()",
"Entity::belongs_to(super::filling::Entity) \
.from(Column::FillingId) \
.to(super::filling::Column::Id) \
.into()",
"Entity::has_many(super::filling::Entity).into()",
];
for (rel, rel_def) in relations.into_iter().zip(rel_defs) {
let rel_def: TokenStream = rel_def.parse().unwrap();
assert_eq!(rel.get_def().to_string(), rel_def.to_string());
}
}
#[test]
fn test_get_rel_type() {
let relations = setup();
let rel_types = vec!["has_one", "belongs_to", "has_many"];
for (rel, rel_type) in relations.into_iter().zip(rel_types) {
assert_eq!(rel.get_rel_type(), rel_type);
}
}
#[test]
fn test_get_column_camel_case() {
let relations = setup();
let cols = vec!["Id", "FillingId", "FillingId"];
for (rel, col) in relations.into_iter().zip(cols) {
assert_eq!(rel.get_column_camel_case(), col);
}
}
#[test]
fn test_get_ref_column_camel_case() {
let relations = setup();
let ref_cols = vec!["CakeId", "Id", "Id"];
for (rel, ref_col) in relations.into_iter().zip(ref_cols) {
assert_eq!(rel.get_ref_column_camel_case(), ref_col);
}
}
}

View File

@ -98,6 +98,7 @@ impl EntityTransformer {
} }
} }
} }
println!("{:#?}", entities);
Ok(EntityWriter { entities }) Ok(EntityWriter { entities })
} }
} }

View File

@ -116,9 +116,7 @@ impl EntityWriter {
Self::gen_impl_relation_trait(entity), Self::gen_impl_relation_trait(entity),
]; ];
code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(Self::gen_impl_related(entity));
code_blocks.extend(vec![ code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]);
Self::gen_impl_active_model_behavior(),
]);
code_blocks code_blocks
} }
@ -152,7 +150,7 @@ impl EntityWriter {
quote! { quote! {
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model { pub struct Model {
#(pub #column_names_snake_case: #column_rs_types),* #(pub #column_names_snake_case: #column_rs_types,)*
} }
} }
} }
@ -162,7 +160,7 @@ impl EntityWriter {
quote! { quote! {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column { pub enum Column {
#(#column_names_camel_case),* #(#column_names_camel_case,)*
} }
} }
} }
@ -172,7 +170,7 @@ impl EntityWriter {
quote! { quote! {
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey { pub enum PrimaryKey {
#(#primary_key_names_camel_case),* #(#primary_key_names_camel_case,)*
} }
} }
} }
@ -193,7 +191,7 @@ impl EntityWriter {
quote! { quote! {
#[derive(Copy, Clone, Debug, EnumIter)] #[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation { pub enum Relation {
#(#relation_ref_tables_camel_case),* #(#relation_ref_tables_camel_case,)*
} }
} }
} }
@ -207,7 +205,7 @@ impl EntityWriter {
fn def(&self) -> ColumnDef { fn def(&self) -> ColumnDef {
match self { match self {
#(Self::#column_names_camel_case => #column_defs),* #(Self::#column_names_camel_case => #column_defs,)*
} }
} }
} }
@ -223,7 +221,7 @@ impl EntityWriter {
} }
} else { } else {
quote! { quote! {
#(Self::#relation_ref_tables_camel_case => #relation_defs),* #(Self::#relation_ref_tables_camel_case => #relation_defs,)*
} }
}; };
quote! { quote! {
@ -276,3 +274,239 @@ impl EntityWriter {
} }
} }
} }
#[cfg(test)]
mod tests {
use crate::{Column, Entity, EntityWriter, PrimaryKey, Relation, RelationType};
use proc_macro2::TokenStream;
use sea_query::ColumnType;
use std::io::{self, BufRead, BufReader};
const ENTITY_FILES: [&'static str; 5] = [
include_str!("../../tests/entity/cake.rs"),
include_str!("../../tests/entity/cake_filling.rs"),
include_str!("../../tests/entity/filling.rs"),
include_str!("../../tests/entity/fruit.rs"),
include_str!("../../tests/entity/vendor.rs"),
];
fn setup() -> Vec<Entity> {
vec![
Entity {
table_name: "cake".to_owned(),
columns: vec![
Column {
name: "id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: true,
not_null: true,
unique: false,
},
Column {
name: "name".to_owned(),
col_type: ColumnType::String(Some(255)),
auto_increment: false,
not_null: true,
unique: false,
},
],
relations: vec![
Relation {
ref_table: "cake_filling".to_owned(),
columns: vec![],
ref_columns: vec![],
rel_type: RelationType::HasMany,
},
Relation {
ref_table: "fruit".to_owned(),
columns: vec![],
ref_columns: vec![],
rel_type: RelationType::HasMany,
},
],
primary_keys: vec![PrimaryKey {
name: "id".to_owned(),
}],
},
Entity {
table_name: "cake_filling".to_owned(),
columns: vec![
Column {
name: "cake_id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "filling_id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
],
relations: vec![
Relation {
ref_table: "cake".to_owned(),
columns: vec!["cake_id".to_owned()],
ref_columns: vec!["id".to_owned()],
rel_type: RelationType::BelongsTo,
},
Relation {
ref_table: "filling".to_owned(),
columns: vec!["filling_id".to_owned()],
ref_columns: vec!["id".to_owned()],
rel_type: RelationType::BelongsTo,
},
],
primary_keys: vec![
PrimaryKey {
name: "cake_id".to_owned(),
},
PrimaryKey {
name: "filling_id".to_owned(),
},
],
},
Entity {
table_name: "filling".to_owned(),
columns: vec![
Column {
name: "id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: true,
not_null: true,
unique: false,
},
Column {
name: "name".to_owned(),
col_type: ColumnType::String(Some(255)),
auto_increment: false,
not_null: true,
unique: false,
},
],
relations: vec![Relation {
ref_table: "cake_filling".to_owned(),
columns: vec![],
ref_columns: vec![],
rel_type: RelationType::HasMany,
}],
primary_keys: vec![PrimaryKey {
name: "id".to_owned(),
}],
},
Entity {
table_name: "fruit".to_owned(),
columns: vec![
Column {
name: "id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: true,
not_null: true,
unique: false,
},
Column {
name: "name".to_owned(),
col_type: ColumnType::String(Some(255)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "cake_id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: false,
unique: false,
},
],
relations: vec![
Relation {
ref_table: "cake".to_owned(),
columns: vec!["cake_id".to_owned()],
ref_columns: vec!["id".to_owned()],
rel_type: RelationType::BelongsTo,
},
Relation {
ref_table: "vendor".to_owned(),
columns: vec![],
ref_columns: vec![],
rel_type: RelationType::HasMany,
},
],
primary_keys: vec![PrimaryKey {
name: "id".to_owned(),
}],
},
Entity {
table_name: "vendor".to_owned(),
columns: vec![
Column {
name: "id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: true,
not_null: true,
unique: false,
},
Column {
name: "name".to_owned(),
col_type: ColumnType::String(Some(255)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "fruit_id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: false,
unique: false,
},
],
relations: vec![Relation {
ref_table: "fruit".to_owned(),
columns: vec!["fruit_id".to_owned()],
ref_columns: vec!["id".to_owned()],
rel_type: RelationType::BelongsTo,
}],
primary_keys: vec![PrimaryKey {
name: "id".to_owned(),
}],
},
]
}
#[test]
fn test_gen_code_blocks() -> io::Result<()> {
let entities = setup();
assert_eq!(entities.len(), ENTITY_FILES.len());
for (i, entity) in entities.iter().enumerate() {
let mut reader = BufReader::new(ENTITY_FILES[i].as_bytes());
let mut lines: Vec<String> = Vec::new();
reader.read_until(b';', &mut Vec::new())?;
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
lines.push(line.to_owned());
line.clear();
}
let content = lines.join("");
let expected: TokenStream = content.parse().unwrap();
let generated = EntityWriter::gen_code_blocks(entity)
.into_iter()
.skip(1)
.fold(TokenStream::new(), |mut acc, tok| {
acc.extend(tok);
acc
});
assert_eq!(expected.to_string(), generated.to_string());
}
Ok(())
}
}

View File

@ -0,0 +1,74 @@
//! 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 ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,81 @@
//! 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 ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,66 @@
//! 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 ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,80 @@
//! 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().null(),
}
}
}
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::Vendor => Entity::has_many(super::vendor::Entity).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 ActiveModelBehavior for ActiveModel {}

View 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;

View 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;

View 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 {
"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().null(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Fruit => Entity::belongs_to(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 ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,127 @@
mod entity;
use entity::*;
use sea_orm::{entity::*, error::*, MockDatabase, MockExecResult, Transaction};
#[async_std::test]
async fn test_insert() -> Result<(), DbErr> {
let exec_result = MockExecResult {
last_insert_id: 1,
rows_affected: 1,
};
let db = MockDatabase::new()
.append_exec_results(vec![exec_result.clone()])
.into_connection();
let apple = cake::ActiveModel {
name: Set("Apple Pie".to_owned()),
..Default::default()
};
let insert_result = cake::Entity::insert(apple).exec(&db).await?;
assert_eq!(insert_result.last_insert_id, exec_result.last_insert_id);
assert_eq!(
db.into_transaction_log(),
vec![Transaction::from_sql_and_values(
r#"INSERT INTO "cake" ("name") VALUES ($1)"#,
vec!["Apple Pie".into()]
)]
);
Ok(())
}
#[async_std::test]
async fn test_select() -> Result<(), DbErr> {
let query_results = vec![cake_filling::Model {
cake_id: 2,
filling_id: 3,
}];
let db = MockDatabase::new()
.append_query_results(vec![query_results.clone()])
.into_connection();
let selected_models = cake_filling::Entity::find_by_id((2, 3)).all(&db).await?;
assert_eq!(selected_models, query_results);
assert_eq!(
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()]
)]
);
Ok(())
}
#[async_std::test]
async fn test_update() -> Result<(), DbErr> {
let exec_result = MockExecResult {
last_insert_id: 1,
rows_affected: 1,
};
let db = MockDatabase::new()
.append_exec_results(vec![exec_result.clone()])
.into_connection();
let orange = fruit::ActiveModel {
id: Set(1),
name: Set("Orange".to_owned()),
..Default::default()
};
let updated_model = fruit::Entity::update(orange.clone()).exec(&db).await?;
assert_eq!(updated_model, orange);
assert_eq!(
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()]
)]
);
Ok(())
}
#[async_std::test]
async fn test_delete() -> Result<(), DbErr> {
let exec_result = MockExecResult {
last_insert_id: 1,
rows_affected: 1,
};
let db = MockDatabase::new()
.append_exec_results(vec![exec_result.clone()])
.into_connection();
let orange = fruit::ActiveModel {
id: Set(3),
..Default::default()
};
let delete_result = fruit::Entity::delete(orange).exec(&db).await?;
assert_eq!(delete_result.rows_affected, exec_result.rows_affected);
assert_eq!(
db.into_transaction_log(),
vec![Transaction::from_sql_and_values(
r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#,
vec![3i32.into()]
)]
);
Ok(())
}

View File

@ -71,7 +71,11 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
fn take(&mut self, c: <Self::Entity as EntityTrait>::Column) -> sea_orm::ActiveValue<sea_orm::Value> { fn take(&mut self, c: <Self::Entity as EntityTrait>::Column) -> sea_orm::ActiveValue<sea_orm::Value> {
match c { match c {
#(<Self::Entity as EntityTrait>::Column::#name => std::mem::take(&mut self.#field).into_wrapped_value(),)* #(<Self::Entity as EntityTrait>::Column::#name => {
let mut value = sea_orm::ActiveValue::unset();
std::mem::swap(&mut value, &mut self.#field);
value.into_wrapped_value()
},)*
_ => sea_orm::ActiveValue::unset(), _ => sea_orm::ActiveValue::unset(),
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{error::*, ExecResult, QueryResult, Statement}; use crate::{error::*, ExecResult, QueryResult, Statement, Syntax};
use sea_query::{ use sea_query::{
MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder, SchemaStatementBuilder, MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder, SchemaStatementBuilder,
SqliteQueryBuilder, SqliteQueryBuilder,
@ -134,29 +134,49 @@ impl DatabaseConnection {
} }
impl QueryBuilderBackend { impl QueryBuilderBackend {
pub fn syntax(&self) -> Syntax {
match self {
Self::MySql => Syntax::MySql,
Self::Postgres => Syntax::Postgres,
Self::Sqlite => Syntax::Sqlite,
}
}
pub fn build<S>(&self, statement: &S) -> Statement pub fn build<S>(&self, statement: &S) -> Statement
where where
S: QueryStatementBuilder, S: QueryStatementBuilder,
{ {
match self { Statement::from_string_values_tuple(
Self::MySql => statement.build(MysqlQueryBuilder), self.syntax(),
Self::Postgres => statement.build(PostgresQueryBuilder), match self {
Self::Sqlite => statement.build(SqliteQueryBuilder), Self::MySql => statement.build(MysqlQueryBuilder),
} Self::Postgres => statement.build(PostgresQueryBuilder),
.into() Self::Sqlite => statement.build(SqliteQueryBuilder),
},
)
} }
} }
impl SchemaBuilderBackend { impl SchemaBuilderBackend {
pub fn syntax(&self) -> Syntax {
match self {
Self::MySql => Syntax::MySql,
Self::Postgres => Syntax::Postgres,
Self::Sqlite => Syntax::Sqlite,
}
}
pub fn build<S>(&self, statement: &S) -> Statement pub fn build<S>(&self, statement: &S) -> Statement
where where
S: SchemaStatementBuilder, S: SchemaStatementBuilder,
{ {
match self { Statement::from_string(
Self::MySql => statement.build(MysqlQueryBuilder), self.syntax(),
Self::Postgres => statement.build(PostgresQueryBuilder), match self {
Self::Sqlite => statement.build(SqliteQueryBuilder), Self::MySql => statement.build(MysqlQueryBuilder),
} Self::Postgres => statement.build(PostgresQueryBuilder),
.into() Self::Sqlite => statement.build(SqliteQueryBuilder),
},
)
} }
} }

View File

@ -79,11 +79,7 @@ impl MockDatabaseTrait for MockDatabase {
} }
} }
fn query( fn query(&mut self, counter: usize, statement: Statement) -> Result<Vec<QueryResult>, DbErr> {
&mut self,
counter: usize,
statement: Statement,
) -> Result<Vec<QueryResult>, DbErr> {
self.transaction_log.push(Transaction::one(statement)); self.transaction_log.push(Transaction::one(statement));
if counter < self.query_results.len() { if counter < self.query_results.len() {
Ok(std::mem::take(&mut self.query_results[counter]) Ok(std::mem::take(&mut self.query_results[counter])

View File

@ -29,6 +29,9 @@ impl Database {
if crate::MockDatabaseConnector::accepts(string) { if crate::MockDatabaseConnector::accepts(string) {
return crate::MockDatabaseConnector::connect(string).await; return crate::MockDatabaseConnector::connect(string).await;
} }
Err(DbErr::Conn(format!("The connection string '{}' has no supporting driver.", string))) Err(DbErr::Conn(format!(
"The connection string '{}' has no supporting driver.",
string
)))
} }
} }

View File

@ -1,26 +1,38 @@
use sea_query::{inject_parameters, MySqlQueryBuilder, Values}; use crate::QueryBuilderWithSyntax;
use sea_query::{
inject_parameters, MysqlQueryBuilder, PostgresQueryBuilder, QueryBuilder, SqliteQueryBuilder,
Values,
};
use std::fmt; use std::fmt;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Syntax {
MySql,
Postgres,
Sqlite,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Statement { pub struct Statement {
pub sql: String, pub sql: String,
pub values: Option<Values>, pub values: Option<Values>,
pub syntax: Syntax,
} }
impl From<String> for Statement { impl Statement {
fn from(stmt: String) -> Statement { pub fn from_string(syntax: Syntax, stmt: String) -> Statement {
Statement { Statement {
sql: stmt, sql: stmt,
values: None, values: None,
syntax,
} }
} }
}
impl From<(String, Values)> for Statement { pub fn from_string_values_tuple(syntax: Syntax, stmt: (String, Values)) -> Statement {
fn from(stmt: (String, Values)) -> Statement {
Statement { Statement {
sql: stmt.0, sql: stmt.0,
values: Some(stmt.1), values: Some(stmt.1),
syntax,
} }
} }
} }
@ -29,8 +41,11 @@ impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.values { match &self.values {
Some(values) => { Some(values) => {
let string = let string = inject_parameters(
inject_parameters(&self.sql, values.0.clone(), &MySqlQueryBuilder::default()); &self.sql,
values.0.clone(),
self.syntax.get_query_builder().as_ref(),
);
write!(f, "{}", &string) write!(f, "{}", &string)
} }
None => { None => {
@ -39,3 +54,31 @@ impl fmt::Display for Statement {
} }
} }
} }
impl Syntax {
pub fn get_query_builder(&self) -> Box<dyn QueryBuilder> {
match self {
Self::MySql => Box::new(MysqlQueryBuilder),
Self::Postgres => Box::new(PostgresQueryBuilder),
Self::Sqlite => Box::new(SqliteQueryBuilder),
}
}
}
impl QueryBuilderWithSyntax for MysqlQueryBuilder {
fn syntax(&self) -> Syntax {
Syntax::MySql
}
}
impl QueryBuilderWithSyntax for PostgresQueryBuilder {
fn syntax(&self) -> Syntax {
Syntax::Postgres
}
}
impl QueryBuilderWithSyntax for SqliteQueryBuilder {
fn syntax(&self) -> Syntax {
Syntax::Sqlite
}
}

View File

@ -1,4 +1,4 @@
use crate::Statement; use crate::{Statement, Syntax};
use sea_query::{Value, Values}; use sea_query::{Value, Values};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -11,10 +11,10 @@ impl Transaction {
where where
I: IntoIterator<Item = Value>, I: IntoIterator<Item = Value>,
{ {
Self::one(Statement { Self::one(Statement::from_string_values_tuple(
sql: sql.to_owned(), Syntax::Postgres,
values: Some(Values(values.into_iter().collect())), (sql.to_string(), Values(values.into_iter().collect())),
}) ))
} }
/// Create a Transaction with one statement /// Create a Transaction with one statement

View File

@ -49,7 +49,9 @@ impl SqlxMySqlPoolConnection {
Err(err) => Err(sqlx_error_to_exec_err(err)), Err(err) => Err(sqlx_error_to_exec_err(err)),
} }
} else { } else {
Err(DbErr::Exec("Failed to acquire connection from pool.".to_owned())) Err(DbErr::Exec(
"Failed to acquire connection from pool.".to_owned(),
))
} }
} }
@ -66,7 +68,9 @@ impl SqlxMySqlPoolConnection {
}, },
} }
} else { } else {
Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
} }
} }
@ -80,7 +84,9 @@ impl SqlxMySqlPoolConnection {
Err(err) => Err(sqlx_error_to_query_err(err)), Err(err) => Err(sqlx_error_to_query_err(err)),
} }
} else { } else {
Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
} }
} }
} }

View File

@ -49,7 +49,9 @@ impl SqlxSqlitePoolConnection {
Err(err) => Err(sqlx_error_to_exec_err(err)), Err(err) => Err(sqlx_error_to_exec_err(err)),
} }
} else { } else {
Err(DbErr::Exec("Failed to acquire connection from pool.".to_owned())) Err(DbErr::Exec(
"Failed to acquire connection from pool.".to_owned(),
))
} }
} }
@ -66,7 +68,9 @@ impl SqlxSqlitePoolConnection {
}, },
} }
} else { } else {
Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
} }
} }
@ -80,7 +84,9 @@ impl SqlxSqlitePoolConnection {
Err(err) => Err(sqlx_error_to_query_err(err)), Err(err) => Err(sqlx_error_to_query_err(err)),
} }
} else { } else {
Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
} }
} }
} }

View File

@ -227,7 +227,11 @@ where
let model: Option<E::Model> = found?; let model: Option<E::Model> = found?;
match model { match model {
Some(model) => Ok(model.into_active_model()), Some(model) => Ok(model.into_active_model()),
None => Err(DbErr::Exec(format!("Failed to find inserted item: {} {}", E::default().to_string(), res.last_insert_id))), None => Err(DbErr::Exec(format!(
"Failed to find inserted item: {} {}",
E::default().to_string(),
res.last_insert_id
))),
} }
} else { } else {
Ok(A::default()) Ok(A::default())
@ -243,6 +247,7 @@ where
exec.await exec.await
} }
/// Delete an active model by its primary key
pub async fn delete_active_model<A, E>( pub async fn delete_active_model<A, E>(
mut am: A, mut am: A,
db: &DatabaseConnection, db: &DatabaseConnection,

View File

@ -18,7 +18,18 @@ pub trait EntityName: IdenStatic + Default {
Self::table_name(self) Self::table_name(self)
} }
} }
/// Each table in database correspond to a Entity implemented [`EntityTrait`].
///
/// This trait provides an API for you to inspect it's properties
/// - Column (implemented [`ColumnTrait`])
/// - Relation (implemented [`RelationTrait`])
/// - Primary Key (implemented [`PrimaryKeyTrait`] and [`PrimaryKeyToColumn`])
///
/// This trait also provides an API for CRUD actions
/// - Select: `find`, `find_*`
/// - Insert: `insert`, `insert_*`
/// - Update: `update`, `update_*`
/// - Delete: `delete`, `delete_*`
pub trait EntityTrait: EntityName { pub trait EntityTrait: EntityName {
type Model: ModelTrait<Entity = Self> + FromQueryResult; type Model: ModelTrait<Entity = Self> + FromQueryResult;
@ -49,16 +60,66 @@ pub trait EntityTrait: EntityName {
RelationBuilder::from_rel(RelationType::HasMany, R::to().rev()) RelationBuilder::from_rel(RelationType::HasMany, R::to().rev())
} }
/// Construct select statement to find one / all models
///
/// - To select columns, join tables and group by expressions, see [`QuerySelect`](crate::query::QuerySelect)
/// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter)
/// - To apply order by expressions, see [`QueryOrder`](crate::query::QueryOrder)
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_query_results(vec![
/// # vec![
/// # cake::Model {
/// # id: 1,
/// # name: "New York Cheese".to_owned(),
/// # },
/// # ],
/// # vec![
/// # cake::Model {
/// # id: 1,
/// # name: "New York Cheese".to_owned(),
/// # },
/// # cake::Model {
/// # id: 2,
/// # name: "Chocolate Forest".to_owned(),
/// # },
/// # ],
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// use sea_orm::{entity::*, query::*, tests_cfg::cake};
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// cake::Entity::find().one(&db).await; /// #
/// cake::Entity::find().all(&db).await; /// assert_eq!(
/// cake::Entity::find().one(&db).await?,
/// Some(cake::Model {
/// id: 1,
/// name: "New York Cheese".to_owned(),
/// })
/// );
///
/// assert_eq!(
/// cake::Entity::find().all(&db).await?,
/// vec![
/// cake::Model {
/// id: 1,
/// name: "New York Cheese".to_owned(),
/// },
/// cake::Model {
/// id: 2,
/// name: "Chocolate Forest".to_owned(),
/// },
/// ]
/// );
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -77,15 +138,37 @@ pub trait EntityTrait: EntityName {
} }
/// Find a model by primary key /// Find a model by primary key
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_query_results(vec![
/// # vec![
/// # cake::Model {
/// # id: 11,
/// # name: "Sponge Cake".to_owned(),
/// # },
/// # ],
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// use sea_orm::{entity::*, query::*, tests_cfg::cake};
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// cake::Entity::find_by_id(11).all(&db).await; /// #
/// assert_eq!(
/// cake::Entity::find_by_id(11).all(&db).await?,
/// vec![cake::Model {
/// id: 11,
/// name: "Sponge Cake".to_owned(),
/// }]
/// );
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -97,13 +180,32 @@ pub trait EntityTrait: EntityName {
/// Find by composite key /// Find by composite key
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_query_results(vec![
/// # vec![
/// # cake_filling::Model {
/// # cake_id: 2,
/// # filling_id: 3,
/// # },
/// # ],
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling}; /// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling};
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = 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)).all(&db).await?,
/// vec![cake_filling::Model {
/// cake_id: 2,
/// filling_id: 3,
/// }]
/// );
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -135,10 +237,22 @@ pub trait EntityTrait: EntityName {
select select
} }
/// Insert an model into database
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, MockExecResult, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 15,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// use sea_orm::{entity::*, query::*, tests_cfg::cake};
/// ///
@ -147,8 +261,14 @@ pub trait EntityTrait: EntityName {
/// ..Default::default() /// ..Default::default()
/// }; /// };
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// cake::Entity::insert(apple).exec(&db).await; /// #
/// let insert_result = cake::Entity::insert(apple).exec(&db).await?;
///
/// assert_eq!(insert_result.last_insert_id, 15);
/// // assert_eq!(insert_result.rows_affected, 1);
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -164,10 +284,22 @@ pub trait EntityTrait: EntityName {
Insert::one(model) Insert::one(model)
} }
/// Insert many models into database
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, MockExecResult, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 28,
/// # rows_affected: 2,
/// # },
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// use sea_orm::{entity::*, query::*, tests_cfg::cake};
/// ///
@ -180,8 +312,14 @@ pub trait EntityTrait: EntityName {
/// ..Default::default() /// ..Default::default()
/// }; /// };
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// cake::Entity::insert_many(vec![apple, orange]).exec(&db).await; /// #
/// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?;
///
/// assert_eq!(insert_result.last_insert_id, 28);
/// // assert_eq!(insert_result.rows_affected, 2);
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -199,10 +337,24 @@ pub trait EntityTrait: EntityName {
Insert::many(models) Insert::many(models)
} }
/// Update an model in database
///
/// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter)
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, MockExecResult, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; /// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
/// ///
@ -212,8 +364,14 @@ pub trait EntityTrait: EntityName {
/// ..Default::default() /// ..Default::default()
/// }; /// };
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// fruit::Entity::update(orange).exec(&db).await; /// #
/// assert_eq!(
/// fruit::Entity::update(orange.clone()).exec(&db).await?, // Clone here because we need to assert_eq
/// orange
/// );
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -229,19 +387,38 @@ pub trait EntityTrait: EntityName {
Update::one(model) Update::one(model)
} }
/// Update many models in database
///
/// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter)
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, MockExecResult, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 5,
/// # },
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::{Expr, Value}}; /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::{Expr, Value}};
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// fruit::Entity::update_many() /// #
/// let update_result = fruit::Entity::update_many()
/// .col_expr(fruit::Column::CakeId, Expr::value(Value::Null)) /// .col_expr(fruit::Column::CakeId, Expr::value(Value::Null))
/// .filter(fruit::Column::Name.contains("Apple")) /// .filter(fruit::Column::Name.contains("Apple"))
/// .exec(&db) /// .exec(&db)
/// .await; /// .await?;
///
/// assert_eq!(update_result.rows_affected, 5);
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -254,10 +431,24 @@ pub trait EntityTrait: EntityName {
Update::many(Self::default()) Update::many(Self::default())
} }
/// Delete an model from database
///
/// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter)
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, MockExecResult, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; /// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
/// ///
@ -266,8 +457,13 @@ pub trait EntityTrait: EntityName {
/// ..Default::default() /// ..Default::default()
/// }; /// };
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// fruit::Entity::delete(orange).exec(&db).await; /// #
/// let delete_result = fruit::Entity::delete(orange).exec(&db).await?;
///
/// assert_eq!(delete_result.rows_affected, 1);
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(
@ -283,18 +479,37 @@ pub trait EntityTrait: EntityName {
Delete::one(model) Delete::one(model)
} }
/// Delete many models from database
///
/// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter)
///
/// # Example
///
/// ``` /// ```
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{MockDatabase, Transaction}; /// # use sea_orm::{error::*, MockDatabase, MockExecResult, Transaction, tests_cfg::*};
/// # let db = MockDatabase::new().into_connection(); /// #
/// # let db = MockDatabase::new()
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 5,
/// # },
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; /// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
/// ///
/// # async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// fruit::Entity::delete_many() /// #
/// let delete_result = fruit::Entity::delete_many()
/// .filter(fruit::Column::Name.contains("Apple")) /// .filter(fruit::Column::Name.contains("Apple"))
/// .exec(&db) /// .exec(&db)
/// .await; /// .await?;
///
/// assert_eq!(delete_result.rows_affected, 5);
/// #
/// # Ok(())
/// # }); /// # });
/// ///
/// assert_eq!( /// assert_eq!(

View File

@ -66,6 +66,7 @@ macro_rules! bind_vec_func {
} }
// LINT: when the operand value does not match column type // LINT: when the operand value does not match column type
/// Wrapper of the identically named method in [`sea_query::Expr`]
pub trait ColumnTrait: IdenStatic + Iterable { pub trait ColumnTrait: IdenStatic + Iterable {
type EntityName: EntityName; type EntityName: EntityName;

View File

@ -1,4 +1,4 @@
use crate::{EntityTrait, DbErr, QueryFilter, QueryResult, Related, Select}; use crate::{DbErr, EntityTrait, QueryFilter, QueryResult, Related, Select};
pub use sea_query::Value; pub use sea_query::Value;
use std::fmt::Debug; use std::fmt::Debug;

View File

@ -62,10 +62,7 @@ async fn exec_delete_only(
} }
// Only Statement impl Send // Only Statement impl Send
async fn exec_delete( async fn exec_delete(statement: Statement, db: &DatabaseConnection) -> Result<DeleteResult, DbErr> {
statement: Statement,
db: &DatabaseConnection,
) -> Result<DeleteResult, DbErr> {
let result = db.execute(statement).await?; let result = db.execute(statement).await?;
Ok(DeleteResult { Ok(DeleteResult {
rows_affected: result.rows_affected(), rows_affected: result.rows_affected(),

View File

@ -40,10 +40,7 @@ impl Inserter {
} }
// Only Statement impl Send // Only Statement impl Send
async fn exec_insert( async fn exec_insert(statement: Statement, db: &DatabaseConnection) -> Result<InsertResult, DbErr> {
statement: Statement,
db: &DatabaseConnection,
) -> Result<InsertResult, DbErr> {
let result = db.execute(statement).await?; let result = db.execute(statement).await?;
// TODO: Postgres instead use query_one + returning clause // TODO: Postgres instead use query_one + returning clause
Ok(InsertResult { Ok(InsertResult {

View File

@ -1,4 +1,5 @@
use crate::DbErr; use crate::DbErr;
use chrono::NaiveDateTime;
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
@ -56,12 +57,14 @@ macro_rules! try_getable_all {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
@ -109,7 +112,8 @@ macro_rules! try_getable_mysql {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => { QueryResultRow::SqlxSqlite(_) => {
@ -160,6 +164,7 @@ try_getable_mysql!(u64);
try_getable_all!(f32); try_getable_all!(f32);
try_getable_all!(f64); try_getable_all!(f64);
try_getable_all!(String); try_getable_all!(String);
try_getable_all!(NaiveDateTime);
#[cfg(feature = "with-rust_decimal")] #[cfg(feature = "with-rust_decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
@ -172,14 +177,18 @@ impl TryGetable for Decimal {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
let val: f64 = row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err)?; let val: f64 = row
.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)?;
use rust_decimal::prelude::FromPrimitive; use rust_decimal::prelude::FromPrimitive;
Decimal::from_f64(val).ok_or_else(|| DbErr::Query("Failed to convert f64 into Decimal".to_owned())) Decimal::from_f64(val)
.ok_or_else(|| DbErr::Query("Failed to convert f64 into Decimal".to_owned()))
} }
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),

View File

@ -18,10 +18,7 @@ impl<'a, A: 'a> UpdateOne<A>
where where
A: ActiveModelTrait, A: ActiveModelTrait,
{ {
pub fn exec( pub fn exec(self, db: &'a DatabaseConnection) -> impl Future<Output = Result<A, DbErr>> + 'a {
self,
db: &'a DatabaseConnection,
) -> impl Future<Output = Result<A, DbErr>> + 'a {
// so that self is dropped before entering await // so that self is dropped before entering await
exec_update_and_return_original(self.query, self.model, db) exec_update_and_return_original(self.query, self.model, db)
} }
@ -74,10 +71,7 @@ where
} }
// Only Statement impl Send // Only Statement impl Send
async fn exec_update( async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result<UpdateResult, DbErr> {
statement: Statement,
db: &DatabaseConnection,
) -> Result<UpdateResult, DbErr> {
let result = db.execute(statement).await?; let result = db.execute(statement).await?;
Ok(UpdateResult { Ok(UpdateResult {
rows_affected: result.rows_affected(), rows_affected: result.rows_affected(),

View File

@ -177,9 +177,9 @@
//! Licensed under either of //! Licensed under either of
//! //!
//! - Apache License, Version 2.0 //! - Apache License, Version 2.0
//! ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) //! ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
//! - MIT license //! - MIT license
//! ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) //! ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
//! //!
//! at your option. //! at your option.
//! //!
@ -215,5 +215,5 @@ pub use sea_orm_macros::{
}; };
pub use sea_query; pub use sea_query;
pub use sea_query::Iden; pub use sea_query::Iden;
pub use strum::EnumIter;
pub use strum; pub use strum;
pub use strum::EnumIter;

View File

@ -1,4 +1,4 @@
use crate::{FromQueryResult, DbErr, QueryResult, QueryResultRow}; use crate::{DbErr, FromQueryResult, QueryResult, QueryResultRow};
use serde_json::Map; use serde_json::Map;
pub use serde_json::Value as JsonValue; pub use serde_json::Value as JsonValue;

View File

@ -1,4 +1,4 @@
use crate::Statement; use crate::{Statement, Syntax};
use sea_query::{QueryBuilder, QueryStatementBuilder}; use sea_query::{QueryBuilder, QueryStatementBuilder};
pub trait QueryTrait { pub trait QueryTrait {
@ -16,8 +16,12 @@ pub trait QueryTrait {
/// Build the query as [`Statement`] /// Build the query as [`Statement`]
fn build<B>(&self, builder: B) -> Statement fn build<B>(&self, builder: B) -> Statement
where where
B: QueryBuilder, B: QueryBuilderWithSyntax,
{ {
self.as_query().build(builder).into() Statement::from_string_values_tuple(builder.syntax(), self.as_query().build(builder))
} }
} }
pub trait QueryBuilderWithSyntax: QueryBuilder {
fn syntax(&self) -> Syntax;
}

View File

@ -1,3 +1,4 @@
use chrono::NaiveDateTime;
use rust_decimal::prelude::*; use rust_decimal::prelude::*;
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@ -16,7 +17,7 @@ pub struct Model {
pub total: Decimal, pub total: Decimal,
pub bakery_id: Option<i32>, pub bakery_id: Option<i32>,
pub customer_id: Option<i32>, pub customer_id: Option<i32>,
pub placed_at: String, pub placed_at: NaiveDateTime,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]

View File

@ -1,4 +1,4 @@
use sea_orm::{entity::*, error::*, sea_query, tests_cfg::*, DbConn}; use sea_orm::{entity::*, error::*, sea_query, tests_cfg::*, DbConn, Statement, Syntax};
mod setup; mod setup;
@ -27,7 +27,9 @@ async fn setup_schema(db: &DbConn) {
.col(ColumnDef::new(cake::Column::Name).string()) .col(ColumnDef::new(cake::Column::Name).string())
.build(SqliteQueryBuilder); .build(SqliteQueryBuilder);
let result = db.execute(stmt.into()).await; let result = db
.execute(Statement::from_string(Syntax::Sqlite, stmt))
.await;
println!("Create table cake: {:?}", result); println!("Create table cake: {:?}", result);
} }

View File

@ -1,4 +1,5 @@
pub use super::*; pub use super::*;
use chrono::offset::Utc;
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
pub async fn test_create_lineitem(db: &DbConn) { pub async fn test_create_lineitem(db: &DbConn) {
@ -64,7 +65,7 @@ pub async fn test_create_lineitem(db: &DbConn) {
let order_1 = order::ActiveModel { let order_1 = order::ActiveModel {
bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)),
customer_id: Set(Some(customer_insert_res.last_insert_id as i32)), customer_id: Set(Some(customer_insert_res.last_insert_id as i32)),
placed_at: Set("placeholder".to_string()), placed_at: Set(Utc::now().naive_utc()),
..Default::default() ..Default::default()
}; };
let order_insert_res: InsertResult = Order::insert(order_1) let order_insert_res: InsertResult = Order::insert(order_1)

View File

@ -1,4 +1,5 @@
pub use super::*; pub use super::*;
use chrono::offset::Utc;
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
pub async fn test_create_order(db: &DbConn) { pub async fn test_create_order(db: &DbConn) {
@ -65,7 +66,7 @@ pub async fn test_create_order(db: &DbConn) {
bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)),
customer_id: Set(Some(customer_insert_res.last_insert_id as i32)), customer_id: Set(Some(customer_insert_res.last_insert_id as i32)),
total: Set(dec!(15.10)), total: Set(dec!(15.10)),
placed_at: Set("placeholder".to_string()), placed_at: Set(Utc::now().naive_utc()),
..Default::default() ..Default::default()
}; };
let order_insert_res: InsertResult = Order::insert(order_1) let order_insert_res: InsertResult = Order::insert(order_1)

View File

@ -4,208 +4,208 @@ use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateState
pub use super::bakery_chain::*; pub use super::bakery_chain::*;
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> { async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> {
let builder = db.get_schema_builder_backend(); let builder = db.get_schema_builder_backend();
db.execute(builder.build(stmt)).await db.execute(builder.build(stmt)).await
} }
pub async fn create_bakery_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_bakery_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(bakery::Entity) .table(bakery::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(bakery::Column::Id) ColumnDef::new(bakery::Column::Id)
.integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(bakery::Column::Name).string()) .col(ColumnDef::new(bakery::Column::Name).string())
.col(ColumnDef::new(bakery::Column::ProfitMargin).float()) .col(ColumnDef::new(bakery::Column::ProfitMargin).float())
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }
pub async fn create_baker_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_baker_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(baker::Entity) .table(baker::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(baker::Column::Id) ColumnDef::new(baker::Column::Id)
.integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(baker::Column::Name).string()) .col(ColumnDef::new(baker::Column::Name).string())
.col(ColumnDef::new(baker::Column::BakeryId).integer()) .col(ColumnDef::new(baker::Column::BakeryId).integer())
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("FK_baker_bakery") .name("FK_baker_bakery")
.from(baker::Entity, baker::Column::BakeryId) .from(baker::Entity, baker::Column::BakeryId)
.to(bakery::Entity, bakery::Column::Id) .to(bakery::Entity, bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade) .on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade), .on_update(ForeignKeyAction::Cascade),
) )
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }
pub async fn create_customer_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_customer_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(customer::Entity) .table(customer::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(customer::Column::Id) ColumnDef::new(customer::Column::Id)
.integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(customer::Column::Name).string()) .col(ColumnDef::new(customer::Column::Name).string())
.col(ColumnDef::new(customer::Column::Notes).text()) .col(ColumnDef::new(customer::Column::Notes).text())
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }
pub async fn create_order_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_order_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(order::Entity) .table(order::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(order::Column::Id) ColumnDef::new(order::Column::Id)
.integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(order::Column::Total).float()) .col(ColumnDef::new(order::Column::Total).float())
.col(ColumnDef::new(order::Column::BakeryId).integer().not_null()) .col(ColumnDef::new(order::Column::BakeryId).integer().not_null())
.col( .col(
ColumnDef::new(order::Column::CustomerId) ColumnDef::new(order::Column::CustomerId)
.integer() .integer()
.not_null(), .not_null(),
) )
.col( .col(
ColumnDef::new(order::Column::PlacedAt) ColumnDef::new(order::Column::PlacedAt)
.date_time() .date_time()
.not_null(), .not_null(),
) )
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("FK_order_bakery") .name("FK_order_bakery")
.from(order::Entity, order::Column::BakeryId) .from(order::Entity, order::Column::BakeryId)
.to(bakery::Entity, bakery::Column::Id) .to(bakery::Entity, bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade) .on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade), .on_update(ForeignKeyAction::Cascade),
) )
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("FK_order_customer") .name("FK_order_customer")
.from(order::Entity, order::Column::CustomerId) .from(order::Entity, order::Column::CustomerId)
.to(customer::Entity, customer::Column::Id) .to(customer::Entity, customer::Column::Id)
.on_delete(ForeignKeyAction::Cascade) .on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade), .on_update(ForeignKeyAction::Cascade),
) )
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }
pub async fn create_lineitem_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_lineitem_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(lineitem::Entity) .table(lineitem::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(lineitem::Column::Id) ColumnDef::new(lineitem::Column::Id)
.integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(lineitem::Column::Price).decimal()) .col(ColumnDef::new(lineitem::Column::Price).decimal())
.col(ColumnDef::new(lineitem::Column::Quantity).integer()) .col(ColumnDef::new(lineitem::Column::Quantity).integer())
.col( .col(
ColumnDef::new(lineitem::Column::OrderId) ColumnDef::new(lineitem::Column::OrderId)
.integer() .integer()
.not_null(), .not_null(),
) )
.col( .col(
ColumnDef::new(lineitem::Column::CakeId) ColumnDef::new(lineitem::Column::CakeId)
.integer() .integer()
.not_null(), .not_null(),
) )
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("FK_lineitem_order") .name("FK_lineitem_order")
.from(lineitem::Entity, lineitem::Column::OrderId) .from(lineitem::Entity, lineitem::Column::OrderId)
.to(order::Entity, order::Column::Id) .to(order::Entity, order::Column::Id)
.on_delete(ForeignKeyAction::Cascade) .on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade), .on_update(ForeignKeyAction::Cascade),
) )
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("FK_lineitem_cake") .name("FK_lineitem_cake")
.from(lineitem::Entity, lineitem::Column::CakeId) .from(lineitem::Entity, lineitem::Column::CakeId)
.to(cake::Entity, cake::Column::Id) .to(cake::Entity, cake::Column::Id)
.on_delete(ForeignKeyAction::Cascade) .on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade), .on_update(ForeignKeyAction::Cascade),
) )
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }
pub async fn create_cakes_bakers_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_cakes_bakers_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(cakes_bakers::Entity) .table(cakes_bakers::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(cakes_bakers::Column::CakeId) ColumnDef::new(cakes_bakers::Column::CakeId)
.integer() .integer()
.not_null(), .not_null(),
) )
.col( .col(
ColumnDef::new(cakes_bakers::Column::BakerId) ColumnDef::new(cakes_bakers::Column::BakerId)
.integer() .integer()
.not_null(), .not_null(),
) )
.primary_key( .primary_key(
Index::create() Index::create()
.col(cakes_bakers::Column::CakeId) .col(cakes_bakers::Column::CakeId)
.col(cakes_bakers::Column::BakerId), .col(cakes_bakers::Column::BakerId),
) )
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }
pub async fn create_cake_table(db: &DbConn) -> Result<ExecResult, DbErr> { pub async fn create_cake_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create() let stmt = sea_query::Table::create()
.table(cake::Entity) .table(cake::Entity)
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(cake::Column::Id) ColumnDef::new(cake::Column::Id)
.integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(cake::Column::Name).string()) .col(ColumnDef::new(cake::Column::Name).string())
.col(ColumnDef::new(cake::Column::Price).float()) .col(ColumnDef::new(cake::Column::Price).float())
.col(ColumnDef::new(cake::Column::BakeryId).integer().not_null()) .col(ColumnDef::new(cake::Column::BakeryId).integer().not_null())
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("FK_cake_bakery") .name("FK_cake_bakery")
.from(cake::Entity, cake::Column::BakeryId) .from(cake::Entity, cake::Column::BakeryId)
.to(bakery::Entity, bakery::Column::Id) .to(bakery::Entity, bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade) .on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade), .on_update(ForeignKeyAction::Cascade),
) )
.col(ColumnDef::new(cake::Column::GlutenFree).boolean()) .col(ColumnDef::new(cake::Column::GlutenFree).boolean())
.to_owned(); .to_owned();
create_table(db, &stmt).await create_table(db, &stmt).await
} }