Merge branch 'master' into rust-keywords

This commit is contained in:
Billy Chan 2021-10-06 22:04:55 +08:00
commit a970e43f50
No known key found for this signature in database
GPG Key ID: A2D690CAC7DF3CC7
14 changed files with 254 additions and 32 deletions

View File

@ -5,6 +5,7 @@ on:
push: push:
branches: branches:
- master - master
- 0.2.x
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always

View File

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## 0.2.5 - 2021-10-06
- [[#227]] Resolve "Inserting actual none value of Option<Date> results in panic"
- [[#219]] [sea-orm-cli] Add `--tables` option
- [[#189]] Add `debug_query` and `debug_query_stmt` macro
[#227]: https://github.com/SeaQL/sea-orm/issues/227
[#219]: https://github.com/SeaQL/sea-orm/pull/219
[#189]: https://github.com/SeaQL/sea-orm/pull/189
## 0.2.4 - 2021-10-01 ## 0.2.4 - 2021-10-01
- [[#186]] [sea-orm-cli] Foreign key handling - [[#186]] [sea-orm-cli] Foreign key handling

View File

@ -3,7 +3,7 @@ members = [".", "sea-orm-macros", "sea-orm-codegen"]
[package] [package]
name = "sea-orm" name = "sea-orm"
version = "0.2.4" version = "0.2.5"
authors = ["Chris Tsang <tyt2y7@gmail.com>"] authors = ["Chris Tsang <tyt2y7@gmail.com>"]
edition = "2018" edition = "2018"
description = "🐚 An async & dynamic ORM for Rust" description = "🐚 An async & dynamic ORM for Rust"
@ -29,8 +29,8 @@ futures = { version = "^0.3" }
futures-util = { version = "^0.3" } futures-util = { version = "^0.3" }
log = { version = "^0.4", optional = true } log = { version = "^0.4", optional = true }
rust_decimal = { version = "^1", optional = true } rust_decimal = { version = "^1", optional = true }
sea-orm-macros = { version = "^0.2.4", path = "sea-orm-macros", optional = true } sea-orm-macros = { version = "^0.2.5", path = "sea-orm-macros", optional = true }
sea-query = { version = "^0.16.5", features = ["thread-safe"] } sea-query = { version = "^0.17.0", features = ["thread-safe"] }
sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] }
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1", optional = true } serde_json = { version = "^1", optional = true }

View File

@ -3,7 +3,7 @@
[package] [package]
name = "sea-orm-cli" name = "sea-orm-cli"
version = "0.2.4" version = "0.2.5"
authors = [ "Billy Chan <ccw.billy.123@gmail.com>" ] authors = [ "Billy Chan <ccw.billy.123@gmail.com>" ]
edition = "2018" edition = "2018"
description = "Command line utility for SeaORM" description = "Command line utility for SeaORM"
@ -21,7 +21,7 @@ path = "src/main.rs"
clap = { version = "^2.33.3" } clap = { version = "^2.33.3" }
dotenv = { version = "^0.15" } dotenv = { version = "^0.15" }
async-std = { version = "^1.9", features = [ "attributes" ] } async-std = { version = "^1.9", features = [ "attributes" ] }
sea-orm-codegen = { version = "^0.2.4", path = "../sea-orm-codegen" } sea-orm-codegen = { version = "^0.2.5", path = "../sea-orm-codegen" }
sea-schema = { version = "^0.2.9", default-features = false, features = [ sea-schema = { version = "^0.2.9", default-features = false, features = [
"debug-print", "debug-print",
"sqlx-mysql", "sqlx-mysql",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "sea-orm-codegen" name = "sea-orm-codegen"
version = "0.2.4" version = "0.2.5"
authors = ["Billy Chan <ccw.billy.123@gmail.com>"] authors = ["Billy Chan <ccw.billy.123@gmail.com>"]
edition = "2018" edition = "2018"
description = "Code Generator for SeaORM" description = "Code Generator for SeaORM"

View File

@ -28,8 +28,6 @@ impl Column {
ColumnType::Char(_) ColumnType::Char(_)
| ColumnType::String(_) | ColumnType::String(_)
| ColumnType::Text | ColumnType::Text
| ColumnType::Time(_)
| ColumnType::Date
| ColumnType::Custom(_) => "String", | ColumnType::Custom(_) => "String",
ColumnType::TinyInteger(_) => "i8", ColumnType::TinyInteger(_) => "i8",
ColumnType::SmallInteger(_) => "i16", ColumnType::SmallInteger(_) => "i16",
@ -38,6 +36,8 @@ impl Column {
ColumnType::Float(_) => "f32", ColumnType::Float(_) => "f32",
ColumnType::Double(_) => "f64", ColumnType::Double(_) => "f64",
ColumnType::Json | ColumnType::JsonBinary => "Json", ColumnType::Json | ColumnType::JsonBinary => "Json",
ColumnType::Date => "Date",
ColumnType::Time(_) => "Time",
ColumnType::DateTime(_) | ColumnType::Timestamp(_) => "DateTime", ColumnType::DateTime(_) | ColumnType::Timestamp(_) => "DateTime",
ColumnType::TimestampWithTimeZone(_) => "DateTimeWithTimeZone", ColumnType::TimestampWithTimeZone(_) => "DateTimeWithTimeZone",
ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal", ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal",
@ -195,6 +195,11 @@ mod tests {
make_col!("CAKE_FILLING_ID", ColumnType::Double(None)), make_col!("CAKE_FILLING_ID", ColumnType::Double(None)),
make_col!("CAKE-FILLING-ID", ColumnType::Binary(None)), make_col!("CAKE-FILLING-ID", ColumnType::Binary(None)),
make_col!("CAKE", ColumnType::Boolean), make_col!("CAKE", ColumnType::Boolean),
make_col!("date", ColumnType::Date),
make_col!("time", ColumnType::Time(None)),
make_col!("date_time", ColumnType::DateTime(None)),
make_col!("timestamp", ColumnType::Timestamp(None)),
make_col!("timestamp_tz", ColumnType::TimestampWithTimeZone(None)),
] ]
} }
@ -212,6 +217,11 @@ mod tests {
"cake_filling_id", "cake_filling_id",
"cake_filling_id", "cake_filling_id",
"cake", "cake",
"date",
"time",
"date_time",
"timestamp",
"timestamp_tz",
]; ];
for (col, snack_case) in columns.into_iter().zip(snack_cases) { for (col, snack_case) in columns.into_iter().zip(snack_cases) {
assert_eq!(col.get_name_snake_case().to_string(), snack_case); assert_eq!(col.get_name_snake_case().to_string(), snack_case);
@ -232,6 +242,11 @@ mod tests {
"CakeFillingId", "CakeFillingId",
"CakeFillingId", "CakeFillingId",
"Cake", "Cake",
"Date",
"Time",
"DateTime",
"Timestamp",
"TimestampTz",
]; ];
for (col, camel_case) in columns.into_iter().zip(camel_cases) { for (col, camel_case) in columns.into_iter().zip(camel_cases) {
assert_eq!(col.get_name_camel_case().to_string(), camel_case); assert_eq!(col.get_name_camel_case().to_string(), camel_case);
@ -242,7 +257,21 @@ mod tests {
fn test_get_rs_type() { fn test_get_rs_type() {
let columns = setup(); let columns = setup();
let rs_types = vec![ let rs_types = vec![
"String", "String", "i8", "i16", "i32", "i64", "f32", "f64", "Vec<u8>", "bool", "String",
"String",
"i8",
"i16",
"i32",
"i64",
"f32",
"f64",
"Vec<u8>",
"bool",
"Date",
"Time",
"DateTime",
"DateTime",
"DateTimeWithTimeZone",
]; ];
for (mut col, rs_type) in columns.into_iter().zip(rs_types) { for (mut col, rs_type) in columns.into_iter().zip(rs_types) {
let rs_type: TokenStream = rs_type.parse().unwrap(); let rs_type: TokenStream = rs_type.parse().unwrap();
@ -272,6 +301,11 @@ mod tests {
"ColumnType::Double.def()", "ColumnType::Double.def()",
"ColumnType::Binary.def()", "ColumnType::Binary.def()",
"ColumnType::Boolean.def()", "ColumnType::Boolean.def()",
"ColumnType::Date.def()",
"ColumnType::Time.def()",
"ColumnType::DateTime.def()",
"ColumnType::Timestamp.def()",
"ColumnType::TimestampWithTimeZone.def()",
]; ];
for (mut col, col_def) in columns.into_iter().zip(col_defs) { for (mut col, col_def) in columns.into_iter().zip(col_defs) {
let mut col_def: TokenStream = col_def.parse().unwrap(); let mut col_def: TokenStream = col_def.parse().unwrap();

View File

@ -1,6 +1,6 @@
[package] [package]
name = "sea-orm-macros" name = "sea-orm-macros"
version = "0.2.4" version = "0.2.5"
authors = [ "Billy Chan <ccw.billy.123@gmail.com>" ] authors = [ "Billy Chan <ccw.billy.123@gmail.com>" ]
edition = "2018" edition = "2018"
description = "Derive macros for SeaORM" description = "Derive macros for SeaORM"

View File

@ -3,6 +3,7 @@ pub enum DbErr {
Conn(String), Conn(String),
Exec(String), Exec(String),
Query(String), Query(String),
RecordNotFound(String),
} }
impl std::error::Error for DbErr {} impl std::error::Error for DbErr {}
@ -13,6 +14,7 @@ impl std::fmt::Display for DbErr {
Self::Conn(s) => write!(f, "Connection Error: {}", s), Self::Conn(s) => write!(f, "Connection Error: {}", s),
Self::Exec(s) => write!(f, "Execution Error: {}", s), Self::Exec(s) => write!(f, "Execution Error: {}", s),
Self::Query(s) => write!(f, "Query Error: {}", s), Self::Query(s) => write!(f, "Query Error: {}", s),
Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s),
} }
} }
} }

View File

@ -7,9 +7,10 @@ use std::future::Future;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Updater { pub struct Updater {
query: UpdateStatement, query: UpdateStatement,
check_record_exists: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct UpdateResult { pub struct UpdateResult {
pub rows_affected: u64, pub rows_affected: u64,
} }
@ -39,7 +40,15 @@ where
impl Updater { impl Updater {
pub fn new(query: UpdateStatement) -> Self { pub fn new(query: UpdateStatement) -> Self {
Self { query } Self {
query,
check_record_exists: false,
}
}
pub fn check_record_exists(mut self) -> Self {
self.check_record_exists = true;
self
} }
pub fn exec( pub fn exec(
@ -47,7 +56,7 @@ impl Updater {
db: &DatabaseConnection, db: &DatabaseConnection,
) -> impl Future<Output = Result<UpdateResult, DbErr>> + '_ { ) -> impl Future<Output = Result<UpdateResult, DbErr>> + '_ {
let builder = db.get_database_backend(); let builder = db.get_database_backend();
exec_update(builder.build(&self.query), db) exec_update(builder.build(&self.query), db, self.check_record_exists)
} }
} }
@ -66,14 +75,160 @@ async fn exec_update_and_return_original<A>(
where where
A: ActiveModelTrait, A: ActiveModelTrait,
{ {
Updater::new(query).exec(db).await?; Updater::new(query).check_record_exists().exec(db).await?;
Ok(model) Ok(model)
} }
// Only Statement impl Send // Only Statement impl Send
async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result<UpdateResult, DbErr> { async fn exec_update(
statement: Statement,
db: &DatabaseConnection,
check_record_exists: bool,
) -> Result<UpdateResult, DbErr> {
let result = db.execute(statement).await?; let result = db.execute(statement).await?;
if check_record_exists && result.rows_affected() == 0 {
return Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned(),
));
}
Ok(UpdateResult { Ok(UpdateResult {
rows_affected: result.rows_affected(), rows_affected: result.rows_affected(),
}) })
} }
#[cfg(test)]
mod tests {
use crate::{entity::prelude::*, tests_cfg::*, *};
use pretty_assertions::assert_eq;
use sea_query::Expr;
#[smol_potat::test]
async fn update_record_not_found_1() -> Result<(), DbErr> {
let db = MockDatabase::new(DbBackend::Postgres)
.append_exec_results(vec![
MockExecResult {
last_insert_id: 0,
rows_affected: 1,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
])
.into_connection();
let model = cake::Model {
id: 1,
name: "New York Cheese".to_owned(),
};
assert_eq!(
cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.into_active_model()
}
.update(&db)
.await?,
cake::Model {
id: 1,
name: "Cheese Cake".to_owned(),
}
.into_active_model()
);
let model = cake::Model {
id: 2,
name: "New York Cheese".to_owned(),
};
assert_eq!(
cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.clone().into_active_model()
}
.update(&db)
.await,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);
assert_eq!(
cake::Entity::update(cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.clone().into_active_model()
})
.exec(&db)
.await,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);
assert_eq!(
Update::one(cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.into_active_model()
})
.exec(&db)
.await,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);
assert_eq!(
Update::many(cake::Entity)
.col_expr(cake::Column::Name, Expr::value("Cheese Cake".to_owned()))
.filter(cake::Column::Id.eq(2))
.exec(&db)
.await,
Ok(UpdateResult { rows_affected: 0 })
);
assert_eq!(
db.into_transaction_log(),
vec![
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
vec!["Cheese Cake".into(), 1i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
vec!["Cheese Cake".into(), 2i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
vec!["Cheese Cake".into(), 2i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
vec!["Cheese Cake".into(), 2i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
vec!["Cheese Cake".into(), 2i32.into()]
),
]
);
Ok(())
}
}

View File

@ -10,8 +10,8 @@ pub struct Model {
pub key: String, pub key: String,
pub value: String, pub value: String,
pub bytes: Vec<u8>, pub bytes: Vec<u8>,
pub date: Date, pub date: Option<Date>,
pub time: Time, pub time: Option<Time>,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -287,8 +287,8 @@ pub async fn create_metadata_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(metadata::Column::Key).string().not_null()) .col(ColumnDef::new(metadata::Column::Key).string().not_null())
.col(ColumnDef::new(metadata::Column::Value).string().not_null()) .col(ColumnDef::new(metadata::Column::Value).string().not_null())
.col(ColumnDef::new(metadata::Column::Bytes).binary().not_null()) .col(ColumnDef::new(metadata::Column::Bytes).binary().not_null())
.col(ColumnDef::new(metadata::Column::Date).date().not_null()) .col(ColumnDef::new(metadata::Column::Date).date())
.col(ColumnDef::new(metadata::Column::Time).time().not_null()) .col(ColumnDef::new(metadata::Column::Time).time())
.to_owned(); .to_owned();
create_table(db, &stmt, Metadata).await create_table(db, &stmt, Metadata).await

View File

@ -1,5 +1,6 @@
pub use super::*; pub use super::*;
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
use sea_orm::DbErr;
use uuid::Uuid; use uuid::Uuid;
pub async fn test_update_cake(db: &DbConn) { pub async fn test_update_cake(db: &DbConn) {
@ -119,10 +120,14 @@ pub async fn test_update_deleted_customer(db: &DbConn) {
..Default::default() ..Default::default()
}; };
let _customer_update_res: customer::ActiveModel = customer let customer_update_res = customer.update(db).await;
.update(db)
.await assert_eq!(
.expect("could not update customer"); customer_update_res,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);
assert_eq!(Customer::find().count(db).await.unwrap(), init_n_customers); assert_eq!(Customer::find().count(db).await.unwrap(), init_n_customers);

View File

@ -26,8 +26,8 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> {
key: "markup".to_owned(), key: "markup".to_owned(),
value: "1.18".to_owned(), value: "1.18".to_owned(),
bytes: vec![1, 2, 3], bytes: vec![1, 2, 3],
date: Date::from_ymd(2021, 9, 27), date: Some(Date::from_ymd(2021, 9, 27)),
time: Time::from_hms(11, 32, 55), time: Some(Time::from_hms(11, 32, 55)),
}, },
metadata::Model { metadata::Model {
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
@ -35,8 +35,8 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> {
key: "exchange_rate".to_owned(), key: "exchange_rate".to_owned(),
value: "0.78".to_owned(), value: "0.78".to_owned(),
bytes: vec![1, 2, 3], bytes: vec![1, 2, 3],
date: Date::from_ymd(2021, 9, 27), date: Some(Date::from_ymd(2021, 9, 27)),
time: Time::from_hms(11, 32, 55), time: Some(Time::from_hms(11, 32, 55)),
}, },
metadata::Model { metadata::Model {
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
@ -44,8 +44,8 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> {
key: "service_charge".to_owned(), key: "service_charge".to_owned(),
value: "1.1".to_owned(), value: "1.1".to_owned(),
bytes: vec![1, 2, 3], bytes: vec![1, 2, 3],
date: Date::from_ymd(2021, 9, 27), date: None,
time: Time::from_hms(11, 32, 55), time: None,
}, },
]; ];

View File

@ -1,7 +1,7 @@
pub mod common; pub mod common;
pub use common::{bakery_chain::*, setup::*, TestContext}; pub use common::{bakery_chain::*, setup::*, TestContext};
use sea_orm::{entity::prelude::*, DatabaseConnection, IntoActiveModel}; use sea_orm::{entity::prelude::*, DatabaseConnection, IntoActiveModel, Set};
#[sea_orm_macros::test] #[sea_orm_macros::test]
#[cfg(any( #[cfg(any(
@ -24,8 +24,8 @@ pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> {
key: "markup".to_owned(), key: "markup".to_owned(),
value: "1.18".to_owned(), value: "1.18".to_owned(),
bytes: vec![1, 2, 3], bytes: vec![1, 2, 3],
date: Date::from_ymd(2021, 9, 27), date: Some(Date::from_ymd(2021, 9, 27)),
time: Time::from_hms(11, 32, 55), time: Some(Time::from_hms(11, 32, 55)),
}; };
let res = Metadata::insert(metadata.clone().into_active_model()) let res = Metadata::insert(metadata.clone().into_active_model())
@ -43,5 +43,20 @@ pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> {
} }
); );
let update_res = Metadata::update(metadata::ActiveModel {
value: Set("0.22".to_owned()),
..metadata.clone().into_active_model()
})
.filter(metadata::Column::Uuid.eq(Uuid::default()))
.exec(db)
.await;
assert_eq!(
update_res,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);
Ok(()) Ok(())
} }