Skip to content

Commit

Permalink
On conflict update by values and expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
billy1624 committed Mar 28, 2022
1 parent 5440ba1 commit ae692a9
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 36 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ time = { version = "^0.2", optional = true }

[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
pretty_assertions = { version = "^1" }

[features]
backend-mysql = []
Expand Down
12 changes: 12 additions & 0 deletions src/backend/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,18 @@ pub trait QueryBuilder: QuotedBuilder {
false
});
}
OnConflictAction::UpdateExprs(column_exprs) => {
self.prepare_on_conflict_do_update_keywords(sql, collector);
column_exprs.iter().fold(true, |first, (col, expr)| {
if !first {
write!(sql, ", ").unwrap()
}
col.prepare(sql, self.quote());
write!(sql, " = ").unwrap();
self.prepare_simple_expr(expr, sql, collector);
false
});
}
}
}
}
Expand Down
33 changes: 3 additions & 30 deletions src/query/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,36 +295,9 @@ impl InsertStatement {
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::insert()
/// .into_table(Glyph::Table)
/// .columns(vec![Glyph::Aspect, Glyph::Image])
/// .values_panic(vec![
/// 2.into(),
/// 3.into(),
/// ])
/// .on_conflict(
/// OnConflict::column(Glyph::Id)
/// .update_columns([Glyph::Aspect, Glyph::Image])
/// .to_owned(),
/// )
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `aspect` = VALUES(`aspect`), `image` = VALUES(`image`)"#
/// );
/// assert_eq!(
/// query.to_string(PostgresQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""#
/// );
/// assert_eq!(
/// query.to_string(SqliteQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""#
/// );
/// ```
/// - [`OnConflict::update_columns`]: Update column value of existing row with inserting value
/// - [`OnConflict::update_values`]: Update column value of existing row with value
/// - [`OnConflict::update_exprs`]: Update column value of existing row with expression
pub fn on_conflict(&mut self, on_conflict: OnConflict) -> &mut Self {
self.on_conflict = Some(on_conflict);
self
Expand Down
164 changes: 158 additions & 6 deletions src/query/on_conflict.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{DynIden, IntoIden};
use crate::{DynIden, Expr, IntoIden, SimpleExpr, Value};

#[derive(Debug, Clone)]
pub struct OnConflict {
Expand All @@ -16,24 +16,28 @@ pub enum OnConflictTarget {
/// Represents ON CONFLICT (upsert) actions
#[derive(Debug, Clone)]
pub enum OnConflictAction {
/// Update the column values of existing row instead of insert
/// Update column value of existing row with inserting value
UpdateColumns(Vec<DynIden>),
/// Update column value of existing row with expression
UpdateExprs(Vec<(DynIden, SimpleExpr)>),
}

impl OnConflict {
/// Create a ON CONFLICT expression without target column,
/// a special method designed for MySQL
pub fn new() -> Self {
Default::default()
}

// Set ON CONFLICT target column
/// Set ON CONFLICT target column
pub fn column<C>(column: C) -> Self
where
C: IntoIden,
{
Self::columns(vec![column])
}

// Set ON CONFLICT target columns
/// Set ON CONFLICT target columns
pub fn columns<I, C>(columns: I) -> Self
where
C: IntoIden,
Expand All @@ -47,15 +51,48 @@ impl OnConflict {
}
}

// Set ON CONFLICT update column
/// Set ON CONFLICT update column
pub fn update_column<C>(&mut self, column: C) -> &mut Self
where
C: IntoIden,
{
self.update_columns(vec![column])
}

// Set ON CONFLICT update columns
/// Set ON CONFLICT update columns
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::insert()
/// .into_table(Glyph::Table)
/// .columns(vec![Glyph::Aspect, Glyph::Image])
/// .values_panic(vec![
/// 2.into(),
/// 3.into(),
/// ])
/// .on_conflict(
/// OnConflict::column(Glyph::Id)
/// .update_columns([Glyph::Aspect, Glyph::Image])
/// .to_owned(),
/// )
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `aspect` = VALUES(`aspect`), `image` = VALUES(`image`)"#
/// );
/// assert_eq!(
/// query.to_string(PostgresQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""#
/// );
/// assert_eq!(
/// query.to_string(SqliteQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""#
/// );
/// ```
pub fn update_columns<C, I>(&mut self, columns: I) -> &mut Self
where
C: IntoIden,
Expand All @@ -66,6 +103,121 @@ impl OnConflict {
));
self
}

/// Set ON CONFLICT update value
pub fn update_value<C>(&mut self, column_value: (C, Value)) -> &mut Self
where
C: IntoIden,
{
self.update_values(vec![column_value])
}

/// Set ON CONFLICT update values
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::insert()
/// .into_table(Glyph::Table)
/// .columns(vec![Glyph::Aspect, Glyph::Image])
/// .values_panic(vec![
/// 2.into(),
/// 3.into(),
/// ])
/// .on_conflict(
/// OnConflict::column(Glyph::Id)
/// .update_values([
/// (Glyph::Aspect, "04108048005887010020060000204E0180400400".into()),
/// (Glyph::Image, 3.1415.into()),
/// ])
/// .to_owned()
/// )
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `aspect` = '04108048005887010020060000204E0180400400', `image` = 3.1415"#
/// );
/// assert_eq!(
/// query.to_string(PostgresQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = '04108048005887010020060000204E0180400400', "image" = 3.1415"#
/// );
/// assert_eq!(
/// query.to_string(SqliteQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = '04108048005887010020060000204E0180400400', "image" = 3.1415"#
/// );
/// ```
pub fn update_values<C, I>(&mut self, column_values: I) -> &mut Self
where
C: IntoIden,
I: IntoIterator<Item = (C, Value)>,
{
self.action = Some(OnConflictAction::UpdateExprs(
column_values
.into_iter()
.map(|(c, v)| (c.into_iden(), Expr::val(v).into()))
.collect(),
));
self
}

/// Set ON CONFLICT update expr
pub fn update_expr<C>(&mut self, column_expr: (C, SimpleExpr)) -> &mut Self
where
C: IntoIden,
{
self.update_exprs(vec![column_expr])
}

/// Set ON CONFLICT update exprs
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::insert()
/// .into_table(Glyph::Table)
/// .columns(vec![Glyph::Aspect, Glyph::Image])
/// .values_panic(vec![
/// 2.into(),
/// 3.into(),
/// ])
/// .on_conflict(
/// OnConflict::column(Glyph::Id)
/// .update_expr((Glyph::Image, Expr::val(1).add(2)))
/// .to_owned()
/// )
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `image` = 1 + 2"#
/// );
/// assert_eq!(
/// query.to_string(PostgresQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "image" = 1 + 2"#
/// );
/// assert_eq!(
/// query.to_string(SqliteQueryBuilder),
/// r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "image" = 1 + 2"#
/// );
/// ```
pub fn update_exprs<C, I>(&mut self, column_exprs: I) -> &mut Self
where
C: IntoIden,
I: IntoIterator<Item = (C, SimpleExpr)>,
{
self.action = Some(OnConflictAction::UpdateExprs(
column_exprs
.into_iter()
.map(|(c, e)| (c.into_iden(), e))
.collect(),
));
self
}
}

impl Default for OnConflict {
Expand Down
56 changes: 56 additions & 0 deletions tests/mysql/query.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use pretty_assertions::assert_eq;

#[test]
fn select_1() {
Expand Down Expand Up @@ -1162,6 +1163,61 @@ fn insert_on_conflict_2() {
);
}

#[test]
#[allow(clippy::approx_constant)]
fn insert_on_conflict_3() {
assert_eq!(
Query::insert()
.into_table(Glyph::Table)
.columns(vec![Glyph::Aspect, Glyph::Image])
.values_panic(vec![
"04108048005887010020060000204E0180400400".into(),
3.1415.into(),
])
.on_conflict(
OnConflict::columns([Glyph::Id, Glyph::Aspect])
.update_values([
(Glyph::Aspect, "04108048005887010020060000204E0180400400".into()),
(Glyph::Image, 3.1415.into()),
])
.to_owned()
)
.to_string(MysqlQueryBuilder),
[
r#"INSERT INTO `glyph` (`aspect`, `image`)"#,
r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#,
r#"ON DUPLICATE KEY UPDATE `aspect` = '04108048005887010020060000204E0180400400', `image` = 3.1415"#,
]
.join(" ")
);
}

#[test]
#[allow(clippy::approx_constant)]
fn insert_on_conflict_4() {
assert_eq!(
Query::insert()
.into_table(Glyph::Table)
.columns(vec![Glyph::Aspect, Glyph::Image])
.values_panic(vec![
"04108048005887010020060000204E0180400400".into(),
3.1415.into(),
])
.on_conflict(
OnConflict::columns([Glyph::Id, Glyph::Aspect])
.update_expr((Glyph::Image, Expr::val(1).add(2)))
.to_owned()
)
.to_string(MysqlQueryBuilder),
[
r#"INSERT INTO `glyph` (`aspect`, `image`)"#,
r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#,
r#"ON DUPLICATE KEY UPDATE `image` = 1 + 2"#,
]
.join(" ")
);
}

#[test]
fn update_1() {
assert_eq!(
Expand Down
Loading

0 comments on commit ae692a9

Please sign in to comment.