From f03424e7f7e7fc8f31f2b764f5e0109708752ff0 Mon Sep 17 00:00:00 2001 From: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Date: Wed, 18 Jan 2023 11:45:35 +0100 Subject: [PATCH 01/12] fix(docs): Fix a small typo (#1391) --- src/executor/select.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/select.rs b/src/executor/select.rs index fbae345161..c36753f1eb 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -70,7 +70,7 @@ where model: PhantomData, } -/// Defines a type to get two Modelss +/// Defines a type to get two Models #[derive(Clone, Debug)] pub struct SelectTwoModel where From e24d1a3344944e5ebe56992fb74e6b81376da45b Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 18 Jan 2023 18:46:47 +0800 Subject: [PATCH 02/12] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5380bfe90f..4070786489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Removed dependency when not needed https://github.com/SeaQL/sea-orm/pull/1213 * Changed all version = "^x.y.z" into version = "x.y.z" and disabled default features and enable only the needed ones https://github.com/SeaQL/sea-orm/pull/1300 * Cleanup the use of `vec!` macros https://github.com/SeaQL/sea-orm/pull/1367 +* Fixed a small typo https://github.com/SeaQL/sea-orm/pull/1391 ### Bug Fixes From e5b69ebb73c1502ede9c825eb7024cff1d5bbefc Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 18 Jan 2023 19:06:06 +0800 Subject: [PATCH 03/12] Don't call last_insert_id if not needed --- src/executor/insert.rs | 65 ++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 796cd36290..9a68699771 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,7 @@ use crate::{ - error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, - Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, ExecResult, Insert, + IntoActiveModel, Iterable, PrimaryKeyTrait, QueryResult, SelectModel, SelectorRaw, Statement, + TryFromU64, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -137,29 +138,49 @@ where { type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - let last_insert_id_opt = match db.support_returning() { - true => { - let cols = PrimaryKey::::iter() - .map(|col| col.to_string()) - .collect::>(); - let rows = db.query_all(statement).await?; - let res = rows.last().ok_or_else(|| { - DbErr::RecordNotInserted("None of the records are being inserted".to_owned()) - })?; - res.try_get_many("", cols.as_ref()).ok() - } - false => { - let last_insert_id = db.execute(statement).await?.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id).ok() + + enum QueryOrExecResult { + Query(QueryResult), + Exec(ExecResult), + } + + let insert_result = if db.support_returning() { + let mut rows = db.query_all(statement).await?; + if rows.is_empty() { + return Err(DbErr::RecordNotInserted( + "None of the records are being inserted".to_owned(), + )); } + QueryOrExecResult::Query(rows.remove(rows.len() - 1)) + } else { + QueryOrExecResult::Exec(db.execute(statement).await?) }; + let last_insert_id = match primary_key { - Some(value_tuple) => FromValueTuple::from_value_tuple(value_tuple), - None => match last_insert_id_opt { - Some(last_insert_id) => last_insert_id, - None => return Err(DbErr::UnpackInsertId), - }, - }; + Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), + None => { + if db.support_returning() { + match insert_result { + QueryOrExecResult::Query(row) => { + let cols = PrimaryKey::::iter() + .map(|col| col.to_string()) + .collect::>(); + row.try_get_many("", cols.as_ref()) + } + _ => unreachable!(), + } + } else { + match insert_result { + QueryOrExecResult::Exec(res) => { + let last_insert_id = res.last_insert_id(); + ValueTypeOf::::try_from_u64(last_insert_id) + } + _ => unreachable!(), + } + } + } + } + .map_err(|_| DbErr::UnpackInsertId)?; Ok(InsertResult { last_insert_id }) } From bb39684f48788c728d3ff883055cb6d88e7e2d56 Mon Sep 17 00:00:00 2001 From: Forest Anderson Date: Wed, 18 Jan 2023 06:09:01 -0500 Subject: [PATCH 04/12] Add JsonBinary attribute (#1346) * Add JsonBinary attribute to column * Add Postgres test section, test binary json * Added expanded entity format test * Fixed unit test --- sea-orm-codegen/src/entity/column.rs | 1 + sea-orm-codegen/src/entity/writer.rs | 113 ++++++++++++++++++ sea-orm-codegen/tests/postgres/binary_json.rs | 21 ++++ .../tests/postgres/binary_json_expanded.rs | 65 ++++++++++ 4 files changed, 200 insertions(+) create mode 100644 sea-orm-codegen/tests/postgres/binary_json.rs create mode 100644 sea-orm-codegen/tests/postgres/binary_json_expanded.rs diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index bcc1932b3a..fecb71fa44 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -94,6 +94,7 @@ impl Column { ColumnType::Decimal(Some((p, s))) => Some(format!("Decimal(Some(({}, {})))", p, s)), ColumnType::Money(Some((p, s))) => Some(format!("Money(Some({}, {}))", p, s)), ColumnType::Text => Some("Text".to_owned()), + ColumnType::JsonBinary => Some("JsonBinary".to_owned()), ColumnType::Custom(iden) => { Some(format!("Custom(\"{}\".to_owned())", iden.to_string())) } diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 21568380b2..e14883e154 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -1957,4 +1957,117 @@ mod tests { Ok(expected.to_string()) } + + #[test] + fn test_gen_postgres() -> io::Result<()> { + let entities = vec![ + // This tests that the JsonBinary column type is annotated + // correctly in compact entity form. More information can be found + // in this issue: + // + // https://github.com/SeaQL/sea-orm/issues/1344 + Entity { + table_name: "task".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer, + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "payload".to_owned(), + col_type: ColumnType::Json, + auto_increment: false, + not_null: true, + unique: false, + }, + Column { + name: "payload_binary".to_owned(), + col_type: ColumnType::JsonBinary, + auto_increment: false, + not_null: true, + unique: false, + }, + ], + relations: vec![], + conjunct_relations: vec![], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }, + ]; + const ENTITY_FILES: [&str; 1] = [include_str!("../../tests/postgres/binary_json.rs")]; + + const ENTITY_FILES_EXPANDED: [&str; 1] = + [include_str!("../../tests/postgres/binary_json_expanded.rs")]; + + assert_eq!(entities.len(), ENTITY_FILES.len()); + + for (i, entity) in entities.iter().enumerate() { + assert_eq!( + parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(), + EntityWriter::gen_compact_code_blocks( + entity, + &crate::WithSerde::None, + &crate::DateTimeCrate::Chrono, + &None, + false, + false, + &TokenStream::new(), + &TokenStream::new(), + ) + .into_iter() + .skip(1) + .fold(TokenStream::new(), |mut acc, tok| { + acc.extend(tok); + acc + }) + .to_string() + ); + assert_eq!( + parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(), + EntityWriter::gen_compact_code_blocks( + entity, + &crate::WithSerde::None, + &crate::DateTimeCrate::Chrono, + &Some("public".to_owned()), + false, + false, + &TokenStream::new(), + &TokenStream::new(), + ) + .into_iter() + .skip(1) + .fold(TokenStream::new(), |mut acc, tok| { + acc.extend(tok); + acc + }) + .to_string() + ); + assert_eq!( + parse_from_file(ENTITY_FILES_EXPANDED[i].as_bytes())?.to_string(), + EntityWriter::gen_expanded_code_blocks( + entity, + &crate::WithSerde::None, + &crate::DateTimeCrate::Chrono, + &Some("schema_name".to_owned()), + false, + false, + &TokenStream::new(), + &TokenStream::new(), + ) + .into_iter() + .skip(1) + .fold(TokenStream::new(), |mut acc, tok| { + acc.extend(tok); + acc + }) + .to_string() + ); + } + + Ok(()) + } } diff --git a/sea-orm-codegen/tests/postgres/binary_json.rs b/sea-orm-codegen/tests/postgres/binary_json.rs new file mode 100644 index 0000000000..3d7cf95075 --- /dev/null +++ b/sea-orm-codegen/tests/postgres/binary_json.rs @@ -0,0 +1,21 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 +//! +//! This file tests that the JsonBinary column type is annotated correctly is +//! compact entity form. More information can be found in this issue: +//! +//! https://github.com/SeaQL/sea-orm/issues/1344 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "task")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub payload: Json, + #[sea_orm(column_type = "JsonBinary")] + pub payload_binary: Json, +} +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/postgres/binary_json_expanded.rs b/sea-orm-codegen/tests/postgres/binary_json_expanded.rs new file mode 100644 index 0000000000..543e77f1f6 --- /dev/null +++ b/sea-orm-codegen/tests/postgres/binary_json_expanded.rs @@ -0,0 +1,65 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 +//! +//! This file tests that the JsonBinary column type is annotated correctly is +//! expanded entity form. More information can be found in this issue: +//! +//! https://github.com/SeaQL/sea-orm/issues/1344 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; +impl EntityName for Entity { + fn schema_name(&self) -> Option< &str > { + Some("schema_name") + } + fn table_name(&self) -> &str { + "task" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] +pub struct Model { + pub id: i32, + pub payload: Json, + pub payload_binary: Json, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Payload, + PayloadBinary, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + // This is the part that is being tested. + Self::Payload => ColumnType::Json.def(), + Self::PayloadBinary => ColumnType::JsonBinary.def(), + } + } +} +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} +impl ActiveModelBehavior for ActiveModel {} From f50dc1dd1c6eefa464b6fbc3bdf622281a8da564 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 18 Jan 2023 19:23:29 +0800 Subject: [PATCH 05/12] [issues] test case --- issues/1357/Cargo.toml | 18 +++++++++++++++++ issues/1357/src/entity.rs | 14 +++++++++++++ issues/1357/src/main.rs | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 issues/1357/Cargo.toml create mode 100644 issues/1357/src/entity.rs create mode 100644 issues/1357/src/main.rs diff --git a/issues/1357/Cargo.toml b/issues/1357/Cargo.toml new file mode 100644 index 0000000000..55e7bfc336 --- /dev/null +++ b/issues/1357/Cargo.toml @@ -0,0 +1,18 @@ +[workspace] +# A separate workspace + +[package] +name = "sea-orm-issues-1357" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +serde = "1" +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } + +[dependencies.sea-orm] +path = "../../" +default-features = false +features = ["macros", "runtime-tokio-native-tls", "sqlx-sqlite"] diff --git a/issues/1357/src/entity.rs b/issues/1357/src/entity.rs new file mode 100644 index 0000000000..3ad25f9ff7 --- /dev/null +++ b/issues/1357/src/entity.rs @@ -0,0 +1,14 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "pool")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: i64, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/issues/1357/src/main.rs b/issues/1357/src/main.rs new file mode 100644 index 0000000000..f7488beb40 --- /dev/null +++ b/issues/1357/src/main.rs @@ -0,0 +1,42 @@ +use anyhow::Result; +use sea_orm::{ConnectionTrait, Database, EntityTrait, IntoActiveModel, Schema}; + +mod entity; + +use entity::*; + +#[tokio::main] +async fn main() -> Result<()> { + let db = Database::connect("sqlite::memory:").await.unwrap(); + + let builder = db.get_database_backend(); + let schema = Schema::new(builder); + let stmt = schema.create_table_from_entity(Entity); + db.execute(builder.build(&stmt)).await?; + + let model = Model { + id: 100, + name: "Hello".to_owned(), + }; + + let res = Entity::insert(model.clone().into_active_model()) + .exec(&db) + .await?; + + assert_eq!(Entity::find().one(&db).await?, Some(model.clone())); + assert_eq!(res.last_insert_id, model.id); + + let model = Model { + id: -10, + name: "World".to_owned(), + }; + + let res = Entity::insert(model.clone().into_active_model()) + .exec(&db) + .await?; + + assert_eq!(Entity::find().one(&db).await?, Some(model.clone())); + assert_eq!(res.last_insert_id, model.id); + + Ok(()) +} From 475bd63d13be7830c3724bbea2f0283e8785ab60 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 18 Jan 2023 19:35:59 +0800 Subject: [PATCH 06/12] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4070786489..32758c7c17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * `MockDatabase::append_exec_results()`, `MockDatabase::append_query_results()`, `MockDatabase::append_exec_errors()` and `MockDatabase::append_query_errors()` take any types implemented `IntoIterator` trait https://github.com/SeaQL/sea-orm/pull/1367 * `find_by_id` and `delete_by_id` take any Into primary key value https://github.com/SeaQL/sea-orm/pull/1362 * Added `ActiveValue::reset` to convert `Unchanged` into `Set` https://github.com/SeaQL/sea-orm/pull/1177 +* Generate compact entity with `#[sea_orm(column_type = "JsonBinary")]` macro attribute https://github.com/SeaQL/sea-orm/pull/1346 ### Upgrades From 07d5f781ca4ceb5158f94f552c8ef2113f98a132 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 18 Jan 2023 20:07:42 +0800 Subject: [PATCH 07/12] Refactor --- src/executor/insert.rs | 64 +++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 9a68699771..6d7ab4267b 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,7 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, ExecResult, Insert, - IntoActiveModel, Iterable, PrimaryKeyTrait, QueryResult, SelectModel, SelectorRaw, Statement, - TryFromU64, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, + Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -139,48 +138,37 @@ where type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - enum QueryOrExecResult { - Query(QueryResult), - Exec(ExecResult), - } - - let insert_result = if db.support_returning() { + let last_insert_id = if db.support_returning() { let mut rows = db.query_all(statement).await?; - if rows.is_empty() { - return Err(DbErr::RecordNotInserted( - "None of the records are being inserted".to_owned(), - )); + let row = match rows.pop() { + Some(row) => row, + None => { + return Err(DbErr::RecordNotInserted( + "None of the records are being inserted".to_owned(), + )) + } + }; + match primary_key { + Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), + None => { + let cols = PrimaryKey::::iter() + .map(|col| col.to_string()) + .collect::>(); + row.try_get_many("", cols.as_ref()) + } } - QueryOrExecResult::Query(rows.remove(rows.len() - 1)) } else { - QueryOrExecResult::Exec(db.execute(statement).await?) - }; - - let last_insert_id = match primary_key { - Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), - None => { - if db.support_returning() { - match insert_result { - QueryOrExecResult::Query(row) => { - let cols = PrimaryKey::::iter() - .map(|col| col.to_string()) - .collect::>(); - row.try_get_many("", cols.as_ref()) - } - _ => unreachable!(), - } - } else { - match insert_result { - QueryOrExecResult::Exec(res) => { - let last_insert_id = res.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id) - } - _ => unreachable!(), - } + let res = db.execute(statement).await?; + match primary_key { + Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), + None => { + let last_insert_id = res.last_insert_id(); + ValueTypeOf::::try_from_u64(last_insert_id) } } } .map_err(|_| DbErr::UnpackInsertId)?; + Ok(InsertResult { last_insert_id }) } From a465d1ebac7f72008e3e4451b1991b7041032854 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 18 Jan 2023 20:30:15 +0800 Subject: [PATCH 08/12] Refactor 2 --- src/executor/insert.rs | 53 ++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 6d7ab4267b..882f3df624 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -138,36 +138,33 @@ where type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - let last_insert_id = if db.support_returning() { - let mut rows = db.query_all(statement).await?; - let row = match rows.pop() { - Some(row) => row, - None => { - return Err(DbErr::RecordNotInserted( - "None of the records are being inserted".to_owned(), - )) - } - }; - match primary_key { - Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), - None => { - let cols = PrimaryKey::::iter() - .map(|col| col.to_string()) - .collect::>(); - row.try_get_many("", cols.as_ref()) - } + let last_insert_id = match (primary_key, db.support_returning()) { + (Some(value_tuple), _) => { + db.execute(statement).await?; + FromValueTuple::from_value_tuple(value_tuple) } - } else { - let res = db.execute(statement).await?; - match primary_key { - Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), - None => { - let last_insert_id = res.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id) - } + (None, true) => { + let mut rows = db.query_all(statement).await?; + let row = match rows.pop() { + Some(row) => row, + None => { + return Err(DbErr::RecordNotInserted( + "None of the records are being inserted".to_owned(), + )) + } + }; + let cols = PrimaryKey::::iter() + .map(|col| col.to_string()) + .collect::>(); + row.try_get_many("", cols.as_ref()) + .map_err(|_| DbErr::UnpackInsertId)? } - } - .map_err(|_| DbErr::UnpackInsertId)?; + (None, false) => { + let res = db.execute(statement).await?; + let last_insert_id = res.last_insert_id(); + ValueTypeOf::::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)? + } + }; Ok(InsertResult { last_insert_id }) } From 03207fbda9fc9eb7239d6c558d6d031539fc7730 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 18 Jan 2023 21:11:35 +0800 Subject: [PATCH 09/12] Use `rows_affected` when DB does not support returning --- src/error.rs | 4 ++-- src/executor/execute.rs | 2 +- src/executor/insert.rs | 14 ++++++++------ tests/upsert_tests.rs | 7 +------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7988bd1b8a..2ed87ffdca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,8 +58,8 @@ pub enum DbErr { /// None of the records are being inserted into the database, /// if you insert with upsert expression that means /// all of them conflict with existing records in the database - #[error("RecordNotInserted Error: {0}")] - RecordNotInserted(String), + #[error("None of the records are being inserted")] + RecordNotInserted, } /// Runtime error diff --git a/src/executor/execute.rs b/src/executor/execute.rs index 3da4ec8a34..f3a7150de5 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -52,7 +52,7 @@ impl ExecResult { } } - /// Get the number of rows affedted by the operation + /// Get the number of rows affected by the operation pub fn rows_affected(&self) -> u64 { match &self.result { #[cfg(feature = "sqlx-mysql")] diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 882f3df624..c4c988ba9d 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -140,18 +140,17 @@ where let last_insert_id = match (primary_key, db.support_returning()) { (Some(value_tuple), _) => { - db.execute(statement).await?; + let res = db.execute(statement).await?; + if res.rows_affected() == 0 { + return Err(DbErr::RecordNotInserted); + } FromValueTuple::from_value_tuple(value_tuple) } (None, true) => { let mut rows = db.query_all(statement).await?; let row = match rows.pop() { Some(row) => row, - None => { - return Err(DbErr::RecordNotInserted( - "None of the records are being inserted".to_owned(), - )) - } + None => return Err(DbErr::RecordNotInserted), }; let cols = PrimaryKey::::iter() .map(|col| col.to_string()) @@ -161,6 +160,9 @@ where } (None, false) => { let res = db.execute(statement).await?; + if res.rows_affected() == 0 { + return Err(DbErr::RecordNotInserted); + } let last_insert_id = res.last_insert_id(); ValueTypeOf::::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)? } diff --git a/tests/upsert_tests.rs b/tests/upsert_tests.rs index ad874e79c8..748d5b0a33 100644 --- a/tests/upsert_tests.rs +++ b/tests/upsert_tests.rs @@ -54,12 +54,7 @@ pub async fn create_insert_default(db: &DatabaseConnection) -> Result<(), DbErr> .exec(db) .await; - assert_eq!( - res.err(), - Some(DbErr::RecordNotInserted( - "None of the records are being inserted".to_owned() - )) - ); + assert_eq!(res.err(), Some(DbErr::RecordNotInserted)); Ok(()) } From 5cdcdbfc1c051b6105a6ecd50dd10c424040705c Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 19 Jan 2023 01:23:21 +0800 Subject: [PATCH 10/12] Add testcase --- tests/string_primary_key_tests.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/string_primary_key_tests.rs b/tests/string_primary_key_tests.rs index 94d1ae554c..9ed4df4763 100644 --- a/tests/string_primary_key_tests.rs +++ b/tests/string_primary_key_tests.rs @@ -2,7 +2,7 @@ pub mod common; pub use common::{features::*, setup::*, TestContext}; use pretty_assertions::assert_eq; -use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; +use sea_orm::{entity::prelude::*, entity::*, sea_query::OnConflict, DatabaseConnection}; use serde_json::json; #[sea_orm_macros::test] @@ -30,7 +30,7 @@ pub async fn insert_and_delete_repository(db: &DatabaseConnection) -> Result<(), } .into_active_model(); - let result = repository.insert(db).await?; + let result = repository.clone().insert(db).await?; assert_eq!( result, @@ -42,6 +42,17 @@ pub async fn insert_and_delete_repository(db: &DatabaseConnection) -> Result<(), } ); + #[cfg(any(feature = "sqlx-sqlite", feature = "sqlx-postgres"))] + { + let err = Repository::insert(repository) + // MySQL does not support DO NOTHING, we might workaround that later + .on_conflict(OnConflict::new().do_nothing().to_owned()) + .exec(db) + .await; + + assert_eq!(err.err(), Some(DbErr::RecordNotInserted)); + } + result.delete(db).await?; assert_eq!( From 89e047c8b141e6d43f1504aaf5d7e7c54ea81ce3 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 19 Jan 2023 04:11:55 +0800 Subject: [PATCH 11/12] Changelog --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32758c7c17..b94f5b45ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Added `DatabaseConnection::close` https://github.com/SeaQL/sea-orm/pull/1236 * Refactor schema module to expose functions for database alteration https://github.com/SeaQL/sea-orm/pull/1256 * Added `is_null` getter for `ColumnDef` struct https://github.com/SeaQL/sea-orm/pull/1381 -* Postgres insert many will throw `RecordNotInserted` error if non of them are being inserted https://github.com/SeaQL/sea-orm/pull/1021 * `MockDatabase::append_exec_results()`, `MockDatabase::append_query_results()`, `MockDatabase::append_exec_errors()` and `MockDatabase::append_query_errors()` take any types implemented `IntoIterator` trait https://github.com/SeaQL/sea-orm/pull/1367 * `find_by_id` and `delete_by_id` take any Into primary key value https://github.com/SeaQL/sea-orm/pull/1362 * Added `ActiveValue::reset` to convert `Unchanged` into `Set` https://github.com/SeaQL/sea-orm/pull/1177 @@ -54,6 +53,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Fixes `DeriveColumn` (by qualifying `IdenStatic::as_str`) https://github.com/SeaQL/sea-orm/pull/1280 * Prevent returning connections to pool with a positive transaction depth https://github.com/SeaQL/sea-orm/pull/1283 +* Postgres insert many will throw `RecordNotInserted` error if non of them are being inserted https://github.com/SeaQL/sea-orm/pull/1021 + * Fixes inserting active models by `insert_many` with `on_conflict` and `do_nothing` panics if no rows are inserted on Postgres https://github.com/SeaQL/sea-orm/issues/899 +* Don't call `last_insert_id` if not needed https://github.com/SeaQL/sea-orm/pull/1403 + * Fixes hitting 'negative last_insert_rowid' panic with Sqlite https://github.com/SeaQL/sea-orm/issues/1357 ### Breaking changes @@ -85,6 +88,13 @@ impl ActiveModelBehavior for ActiveModel { } ``` +## 0.10.7 - 2023-01-19 + +### Bug Fixes + +* Inserting active models by `insert_many` with `on_conflict` and `do_nothing` panics if no rows are inserted on Postgres https://github.com/SeaQL/sea-orm/issues/899 +* Hitting 'negative last_insert_rowid' panic with Sqlite https://github.com/SeaQL/sea-orm/issues/1357 + ## 0.10.6 - 2022-12-23 ### Enhancements From fce1c76c851294136e8618037410f533bc69da09 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 19 Jan 2023 03:45:06 +0800 Subject: [PATCH 12/12] Fix jsonrpsee example --- examples/jsonrpsee_example/api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsonrpsee_example/api/Cargo.toml b/examples/jsonrpsee_example/api/Cargo.toml index 9e98a03c3a..6440e00417 100644 --- a/examples/jsonrpsee_example/api/Cargo.toml +++ b/examples/jsonrpsee_example/api/Cargo.toml @@ -16,4 +16,4 @@ migration = { path = "../migration" } anyhow = "1.0.52" async-trait = "0.1.52" log = { version = "0.4", features = ["std"] } -simplelog = "*" +simplelog = "0.12"