Skip to content

Commit 96b5c08

Browse files
authored
Merge pull request diesel-rs#2479 from ropottnik/master
Implement where clauses on upsert for pg
2 parents 70d8309 + 2f29c77 commit 96b5c08

10 files changed

Lines changed: 138 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
target
22
Cargo.lock
33
!diesel_cli/Cargo.lock
4-
.env
4+
.env

diesel/src/backend.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub type RawValue<'a, DB> = <DB as HasRawValue<'a>>::RawValue;
7474
pub trait SupportsReturningClause {}
7575
/// Does this backend support 'ON CONFLICT' clause?
7676
pub trait SupportsOnConflictClause {}
77+
/// Does this backend support 'WHERE' clauses on 'ON CONFLICT' clauses?
78+
pub trait SupportsOnConflictTargetDecorations {}
7779
/// Does this backend support the bare `DEFAULT` keyword?
7880
pub trait SupportsDefaultKeyword {}
7981
/// Does this backend use the standard `SAVEPOINT` syntax?

diesel/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ pub mod prelude {
330330
pub use crate::macros::prelude::*;
331331
#[doc(inline)]
332332
pub use crate::query_builder::AsChangeset;
333+
#[doc(inline)]
334+
pub use crate::query_builder::DecoratableTarget;
333335
#[doc(hidden)]
334336
pub use crate::query_dsl::GroupByDsl;
335337
#[doc(inline)]

diesel/src/pg/backend.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ impl TypeMetadata for Pg {
4545

4646
impl SupportsReturningClause for Pg {}
4747
impl SupportsOnConflictClause for Pg {}
48+
impl SupportsOnConflictTargetDecorations for Pg {}
4849
impl SupportsDefaultKeyword for Pg {}
4950
impl UsesAnsiSavepointSyntax for Pg {}

diesel/src/query_builder/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub use self::sql_query::{BoxedSqlQuery, SqlQuery};
5353
pub use self::update_statement::{
5454
AsChangeset, BoxedUpdateStatement, IntoUpdateTarget, UpdateStatement, UpdateTarget,
5555
};
56+
pub use self::upsert::on_conflict_target_decorations::DecoratableTarget;
5657

5758
pub use self::limit_clause::{LimitClause, NoLimitClause};
5859
pub use self::limit_offset_clause::{BoxedLimitOffsetClause, LimitOffsetClause};

diesel/src/query_builder/upsert/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub(crate) mod into_conflict_clause;
22
pub(crate) mod on_conflict_actions;
33
pub(crate) mod on_conflict_clause;
44
pub(crate) mod on_conflict_target;
5+
pub(crate) mod on_conflict_target_decorations;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use crate::backend::{Backend, SupportsOnConflictClause, SupportsOnConflictTargetDecorations};
2+
use crate::expression::Expression;
3+
use crate::query_builder::upsert::on_conflict_target::{ConflictTarget, NoConflictTarget};
4+
use crate::query_builder::where_clause::{NoWhereClause, WhereAnd, WhereClause};
5+
use crate::query_builder::{AstPass, QueryFragment, QueryResult};
6+
use crate::sql_types::BoolOrNullableBool;
7+
8+
pub trait UndecoratedConflictTarget {}
9+
10+
impl UndecoratedConflictTarget for NoConflictTarget {}
11+
impl<T> UndecoratedConflictTarget for ConflictTarget<T> {}
12+
13+
/// Interface to add information to conflict targets.
14+
/// Designed to be open for further additions to conflict targets like constraints
15+
pub trait DecoratableTarget<P> {
16+
/// Output type of filter_target operation
17+
type FilterOutput;
18+
/// equivalent to filter of FilterDsl but aimed at conflict targets
19+
fn filter_target(self, predicate: P) -> Self::FilterOutput;
20+
}
21+
22+
#[derive(Debug)]
23+
pub struct DecoratedConflictTarget<T, U> {
24+
target: T,
25+
where_clause: U,
26+
}
27+
28+
impl<T, P> DecoratableTarget<P> for T
29+
where
30+
P: Expression,
31+
P::SqlType: BoolOrNullableBool,
32+
T: UndecoratedConflictTarget,
33+
{
34+
type FilterOutput = DecoratedConflictTarget<T, WhereClause<P>>;
35+
36+
fn filter_target(self, predicate: P) -> Self::FilterOutput {
37+
DecoratedConflictTarget {
38+
target: self,
39+
where_clause: NoWhereClause.and(predicate),
40+
}
41+
}
42+
}
43+
44+
impl<T, U, P> DecoratableTarget<P> for DecoratedConflictTarget<T, U>
45+
where
46+
P: Expression,
47+
P::SqlType: BoolOrNullableBool,
48+
U: WhereAnd<P>,
49+
{
50+
type FilterOutput = DecoratedConflictTarget<T, <U as WhereAnd<P>>::Output>;
51+
52+
fn filter_target(self, predicate: P) -> Self::FilterOutput {
53+
DecoratedConflictTarget {
54+
target: self.target,
55+
where_clause: self.where_clause.and(predicate),
56+
}
57+
}
58+
}
59+
60+
impl<DB, T, U> QueryFragment<DB> for DecoratedConflictTarget<T, U>
61+
where
62+
T: QueryFragment<DB>,
63+
U: QueryFragment<DB>,
64+
DB: Backend + SupportsOnConflictClause + SupportsOnConflictTargetDecorations,
65+
{
66+
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
67+
self.target.walk_ast(out.reborrow())?;
68+
self.where_clause.walk_ast(out.reborrow())?;
69+
Ok(())
70+
}
71+
}

diesel/src/upsert/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::query_builder::upsert::on_conflict_actions::Excluded;
1616

1717
mod on_conflict_extension;
1818

19+
pub use self::on_conflict_extension::DecoratableTarget;
1920
pub use self::on_conflict_extension::*;
2021
#[cfg(feature = "postgres")]
2122
pub use crate::pg::query_builder::on_constraint::*;

diesel/src/upsert/on_conflict_extension.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
use crate::expression::Expression;
12
use crate::query_builder::upsert::into_conflict_clause::IntoConflictValueClause;
23
use crate::query_builder::upsert::on_conflict_actions::*;
34
use crate::query_builder::upsert::on_conflict_clause::*;
45
use crate::query_builder::upsert::on_conflict_target::*;
6+
pub use crate::query_builder::upsert::on_conflict_target_decorations::DecoratableTarget;
57
use crate::query_builder::{AsChangeset, InsertStatement, UndecoratedInsertRecord};
68
use crate::query_source::QuerySource;
9+
use crate::sql_types::BoolOrNullableBool;
710

811
impl<T, U, Op, Ret> InsertStatement<T, U, Op, Ret>
912
where
@@ -199,6 +202,21 @@ where
199202
}
200203
}
201204

205+
impl<Stmt, T, P> DecoratableTarget<P> for IncompleteOnConflict<Stmt, T>
206+
where
207+
P: Expression,
208+
P::SqlType: BoolOrNullableBool,
209+
T: DecoratableTarget<P>,
210+
{
211+
type FilterOutput = IncompleteOnConflict<Stmt, <T as DecoratableTarget<P>>::FilterOutput>;
212+
fn filter_target(self, predicate: P) -> Self::FilterOutput {
213+
IncompleteOnConflict {
214+
stmt: self.stmt,
215+
target: self.target.filter_target(predicate),
216+
}
217+
}
218+
}
219+
202220
/// A partially constructed `ON CONFLICT` clause.
203221
#[derive(Debug, Clone, Copy)]
204222
pub struct IncompleteOnConflict<Stmt, Target> {

diesel_tests/tests/debug/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,43 @@ COMMIT;
115115
);
116116
}
117117
}
118+
119+
#[test]
120+
#[cfg(feature = "postgres")]
121+
fn test_upsert() {
122+
// this test ensures we get the right debug string for upserts
123+
use crate::schema::users::dsl::*;
124+
125+
let values = vec![
126+
(name.eq("Sean"), hair_color.eq(Some("black"))),
127+
(name.eq("Tess"), hair_color.eq(None::<&str>)),
128+
];
129+
130+
let upsert_command_single_where = insert_into(users)
131+
.values(&values)
132+
.on_conflict(hair_color)
133+
.filter_target(hair_color.eq("black"))
134+
.do_nothing();
135+
let upsert_single_where_sql_display =
136+
debug_query::<TestBackend, _>(&upsert_command_single_where).to_string();
137+
138+
assert_eq!(
139+
upsert_single_where_sql_display,
140+
r#"INSERT INTO "users" ("name", "hair_color") VALUES ($1, $2), ($3, $4) ON CONFLICT ("hair_color") WHERE "users"."hair_color" = $5 DO NOTHING -- binds: ["Sean", Some("black"), "Tess", None, "black"]"#
141+
);
142+
143+
let upsert_command_second_where = insert_into(users)
144+
.values(&values)
145+
.on_conflict(hair_color)
146+
.filter_target(hair_color.eq("black"))
147+
.filter_target(name.eq("Sean"))
148+
.do_nothing();
149+
150+
let upsert_second_where_sql_display =
151+
debug_query::<TestBackend, _>(&upsert_command_second_where).to_string();
152+
153+
assert_eq!(
154+
upsert_second_where_sql_display,
155+
r#"INSERT INTO "users" ("name", "hair_color") VALUES ($1, $2), ($3, $4) ON CONFLICT ("hair_color") WHERE "users"."hair_color" = $5 AND "users"."name" = $6 DO NOTHING -- binds: ["Sean", Some("black"), "Tess", None, "black", "Sean"]"#
156+
);
157+
}

0 commit comments

Comments
 (0)