diff --git a/Cargo.toml b/Cargo.toml index 4c743a0d..d04cab49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ postgres-types = { version = "0", default-features = false, optional = true } rust_decimal = { version = "1", default-features = false, optional = true } bigdecimal = { version = "0.3", default-features = false, optional = true } uuid = { version = "1", default-features = false, optional = true } -time = { version = "0.3", default-features = false, optional = true, features = ["macros", "formatting"] } +time = { version = "=0.3.34", default-features = false, optional = true, features = ["macros", "formatting"] } ipnetwork = { version = "0.20", default-features = false, optional = true } mac_address = { version = "1.1", default-features = false, optional = true } ordered-float = { version = "3.4", default-features = false, optional = true } diff --git a/sea-query-binder/Cargo.toml b/sea-query-binder/Cargo.toml index 0e3515be..0f24206a 100644 --- a/sea-query-binder/Cargo.toml +++ b/sea-query-binder/Cargo.toml @@ -24,7 +24,7 @@ chrono = { version = "0.4", default-features = false, optional = true, features rust_decimal = { version = "1", default-features = false, optional = true } bigdecimal = { version = "0.3", default-features = false, optional = true } uuid = { version = "1", default-features = false, optional = true } -time = { version = "0.3", default-features = false, optional = true, features = ["macros", "formatting"] } +time = { version = "=0.3.34", default-features = false, optional = true, features = ["macros", "formatting"] } ipnetwork = { version = "0.20", default-features = false, optional = true } mac_address = { version = "1.1", default-features = false, optional = true } diff --git a/src/backend/mysql/query.rs b/src/backend/mysql/query.rs index 4d0700b4..2adb257c 100644 --- a/src/backend/mysql/query.rs +++ b/src/backend/mysql/query.rs @@ -96,6 +96,28 @@ impl QueryBuilder for MysqlQueryBuilder { // MySQL doesn't support declaring ON CONFLICT target. } + fn prepare_on_conflict_action( + &self, + on_conflict_action: &Option, + sql: &mut dyn SqlWriter, + ) { + match dbg!(on_conflict_action) { + Some(OnConflictAction::DoNothing(pk_cols)) => { + self.prepare_on_conflict_do_update_keywords(sql); + pk_cols.iter().fold(true, |first, pk_col| { + if !first { + write!(sql, ", ").unwrap() + } + pk_col.prepare(sql.as_writer(), self.quote()); + write!(sql, " = ").unwrap(); + pk_col.prepare(sql.as_writer(), self.quote()); + false + }); + } + _ => self.prepare_on_conflict_action_common(on_conflict_action, sql), + } + } + fn prepare_on_conflict_keywords(&self, sql: &mut dyn SqlWriter) { write!(sql, " ON DUPLICATE KEY").unwrap(); } diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index 827ce5ca..e3823eae 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -1174,10 +1174,18 @@ pub trait QueryBuilder: &self, on_conflict_action: &Option, sql: &mut dyn SqlWriter, + ) { + self.prepare_on_conflict_action_common(on_conflict_action, sql); + } + + fn prepare_on_conflict_action_common( + &self, + on_conflict_action: &Option, + sql: &mut dyn SqlWriter, ) { if let Some(action) = on_conflict_action { match action { - OnConflictAction::DoNothing => { + OnConflictAction::DoNothing(_) => { write!(sql, " DO NOTHING").unwrap(); } OnConflictAction::Update(update_strats) => { diff --git a/src/query/on_conflict.rs b/src/query/on_conflict.rs index 47d45c0f..13d4c970 100644 --- a/src/query/on_conflict.rs +++ b/src/query/on_conflict.rs @@ -21,7 +21,7 @@ pub enum OnConflictTarget { #[derive(Debug, Clone, PartialEq)] pub enum OnConflictAction { /// Do nothing - DoNothing, + DoNothing(Vec), /// Update column value of existing row Update(Vec), } @@ -137,8 +137,104 @@ impl OnConflict { self } + /// Set ON CONFLICT do nothing. + /// + /// Please use [`Self::do_nothing_on()`] and provide primary keys if you are using MySql. + /// + /// # Examples + /// + /// ``` + /// use sea_query::{tests_cfg::*, *}; + /// + /// let query = Query::insert() + /// .into_table(Glyph::Table) + /// .columns([Glyph::Aspect, Glyph::Image]) + /// .values_panic(["abcd".into(), 3.1415.into()]) + /// .on_conflict( + /// OnConflict::columns([Glyph::Id, Glyph::Aspect]) + /// .do_nothing() + /// .to_owned(), + /// ) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// [ + /// r#"INSERT INTO "glyph" ("aspect", "image")"#, + /// r#"VALUES ('abcd', 3.1415)"#, + /// r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + /// ] + /// .join(" ") + /// ); + /// assert_eq!( + /// query.to_string(SqliteQueryBuilder), + /// [ + /// r#"INSERT INTO "glyph" ("aspect", "image")"#, + /// r#"VALUES ('abcd', 3.1415)"#, + /// r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + /// ] + /// .join(" ") + /// ); + /// ``` pub fn do_nothing(&mut self) -> &mut Self { - self.action = Some(OnConflictAction::DoNothing); + self.action = Some(OnConflictAction::DoNothing(vec![])); + self + } + + /// Set ON CONFLICT do nothing. MySQL only. + /// + /// # Examples + /// + /// ``` + /// use sea_query::{tests_cfg::*, *}; + /// + /// let query = Query::insert() + /// .into_table(Glyph::Table) + /// .columns([Glyph::Aspect, Glyph::Image]) + /// .values_panic(["abcd".into(), 3.1415.into()]) + /// .on_conflict( + /// OnConflict::columns([Glyph::Id, Glyph::Aspect]) + /// .do_nothing_on([Glyph::Id]) + /// .to_owned(), + /// ) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(MysqlQueryBuilder), + /// [ + /// r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + /// r#"VALUES ('abcd', 3.1415)"#, + /// r#"ON DUPLICATE KEY UPDATE `id` = `id`"#, + /// ] + /// .join(" ") + /// ); + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// [ + /// r#"INSERT INTO "glyph" ("aspect", "image")"#, + /// r#"VALUES ('abcd', 3.1415)"#, + /// r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + /// ] + /// .join(" ") + /// ); + /// assert_eq!( + /// query.to_string(SqliteQueryBuilder), + /// [ + /// r#"INSERT INTO "glyph" ("aspect", "image")"#, + /// r#"VALUES ('abcd', 3.1415)"#, + /// r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + /// ] + /// .join(" ") + /// ); + /// ``` + pub fn do_nothing_on(&mut self, pk_cols: I) -> &mut Self + where + C: IntoIden, + I: IntoIterator, + { + self.action = Some(OnConflictAction::DoNothing( + pk_cols.into_iter().map(IntoIden::into_iden).collect(), + )); self } @@ -247,7 +343,7 @@ impl OnConflict { Some(OnConflictAction::Update(v)) => { v.append(&mut update_strats); } - Some(OnConflictAction::DoNothing) | None => { + Some(OnConflictAction::DoNothing(_)) | None => { self.action = Some(OnConflictAction::Update(update_strats)); } }; @@ -302,7 +398,7 @@ impl OnConflict { Some(OnConflictAction::Update(v)) => { v.append(&mut update_exprs); } - Some(OnConflictAction::DoNothing) | None => { + Some(OnConflictAction::DoNothing(_)) | None => { self.action = Some(OnConflictAction::Update(update_exprs)); } }; diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index 4994ab11..a9e46525 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1384,6 +1384,29 @@ fn insert_on_conflict_6() { ); } +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_do_nothing_on() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic(["abcd".into(), 3.1415.into()]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .do_nothing_on([Glyph::Id]) + .to_owned(), + ) + .to_string(MysqlQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('abcd', 3.1415)"#, + r#"ON DUPLICATE KEY UPDATE `id` = `id`"#, + ] + .join(" ") + ); +} + #[test] fn update_1() { assert_eq!( diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index 5df9ed28..a00d5751 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -1517,6 +1517,52 @@ fn insert_on_conflict_9() { ); } +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_do_nothing() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic(["abcd".into(), 3.1415.into()]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .do_nothing() + .to_owned(), + ) + .to_string(PostgresQueryBuilder), + [ + r#"INSERT INTO "glyph" ("aspect", "image")"#, + r#"VALUES ('abcd', 3.1415)"#, + r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_do_nothing_on() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic(["abcd".into(), 3.1415.into()]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .do_nothing_on([Glyph::Id]) + .to_owned(), + ) + .to_string(PostgresQueryBuilder), + [ + r#"INSERT INTO "glyph" ("aspect", "image")"#, + r#"VALUES ('abcd', 3.1415)"#, + r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + ] + .join(" ") + ); +} + #[test] #[allow(clippy::approx_constant)] fn insert_returning_all_columns() { diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 9aec37f6..9a9c8fcf 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1471,6 +1471,52 @@ fn insert_on_conflict_9() { ); } +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_do_nothing() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic(["abcd".into(), 3.1415.into()]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .do_nothing() + .to_owned(), + ) + .to_string(SqliteQueryBuilder), + [ + r#"INSERT INTO "glyph" ("aspect", "image")"#, + r#"VALUES ('abcd', 3.1415)"#, + r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_do_nothing_on() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic(["abcd".into(), 3.1415.into()]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .do_nothing_on([Glyph::Id]) + .to_owned(), + ) + .to_string(SqliteQueryBuilder), + [ + r#"INSERT INTO "glyph" ("aspect", "image")"#, + r#"VALUES ('abcd', 3.1415)"#, + r#"ON CONFLICT ("id", "aspect") DO NOTHING"#, + ] + .join(" ") + ); +} + #[test] #[allow(clippy::approx_constant)] fn insert_returning_all_columns() {