Skip to content

Commit

Permalink
MySQL: ON DUPLICATE KEY DO NOTHING
Browse files Browse the repository at this point in the history
  • Loading branch information
billy1624 committed Apr 10, 2024
1 parent d9a7044 commit a0b2160
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 5 deletions.
22 changes: 22 additions & 0 deletions src/backend/mysql/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<OnConflictAction>,
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();
}
Expand Down
10 changes: 9 additions & 1 deletion src/backend/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1174,10 +1174,18 @@ pub trait QueryBuilder:
&self,
on_conflict_action: &Option<OnConflictAction>,
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<OnConflictAction>,
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) => {
Expand Down
104 changes: 100 additions & 4 deletions src/query/on_conflict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum OnConflictTarget {
#[derive(Debug, Clone, PartialEq)]
pub enum OnConflictAction {
/// Do nothing
DoNothing,
DoNothing(Vec<DynIden>),
/// Update column value of existing row
Update(Vec<OnConflictUpdate>),
}
Expand Down Expand Up @@ -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<C, I>(&mut self, pk_cols: I) -> &mut Self
where
C: IntoIden,
I: IntoIterator<Item = C>,
{
self.action = Some(OnConflictAction::DoNothing(
pk_cols.into_iter().map(IntoIden::into_iden).collect(),
));
self
}

Expand Down Expand Up @@ -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));
}
};
Expand Down Expand Up @@ -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));
}
};
Expand Down
23 changes: 23 additions & 0 deletions tests/mysql/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
46 changes: 46 additions & 0 deletions tests/postgres/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
46 changes: 46 additions & 0 deletions tests/sqlite/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit a0b2160

Please sign in to comment.