Skip to content

Commit c913ecf

Browse files
committed
Add support for ON CONFLICT conflict_target DO NOTHING
This adds support for the next third of diesel-rs#196, adding the ability to specify the conflict target. This does not yet add support for `DO UPDATE`, that will come in a later PR. This does not support all possible conflict targets. They're described in the pg syntax as ``` one of: ( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ] ON CONSTRAINT constraint_name ``` The grammar supported by this PR is: ``` one of: ( index_column_name [, ...] ) ON CONSTRAINT constraint_name ``` Supporting expression indexes is a bit more work, since column names have to appear without the table name here. We'd either need to add a new trait which is implemented for everything in the query builder, or modify `QueryFragment#to_sql` to have an additional parameter of whether to include the table name or not. It's something I want to support eventually, but it's low priority (and may even have to wait until 2.0). I also need to verify whether bind parameters can appear in expression position here or not. If the answer is no, we'll just need to provide a raw SQL API for expression indexes no matter what.
1 parent b4266be commit c913ecf

8 files changed

Lines changed: 428 additions & 46 deletions

File tree

diesel/src/pg/upsert/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
mod on_conflict_actions;
12
mod on_conflict_clause;
23
mod on_conflict_extension;
4+
mod on_conflict_target;
35

6+
pub use self::on_conflict_actions::do_nothing;
47
pub use self::on_conflict_extension::OnConflictExtension;
8+
pub use self::on_conflict_target::on_constraint;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Used in conjuction with
2+
/// [`on_conflict`](trait.OnConflictExtension.html#method.on_conflict) to write
3+
/// a query in the form `ON CONFLICT (name) DO NOTHING`. If you want to do
4+
/// nothing when *any* constraint conflicts, use
5+
/// [`on_conflict_do_nothing()`](trait.OnConflictExtension.html#method.on_conflict_do_nothing)
6+
/// instead.
7+
pub fn do_nothing() -> DoNothing {
8+
DoNothing
9+
}
10+
11+
#[doc(hidden)]
12+
#[derive(Debug, Clone, Copy)]
13+
pub struct DoNothing;

diesel/src/pg/upsert/on_conflict_clause.rs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use query_builder::*;
55
use query_builder::insert_statement::*;
66
use query_source::Table;
77
use result::QueryResult;
8+
use super::on_conflict_actions::*;
9+
use super::on_conflict_target::*;
810

911
#[derive(Debug, Clone, Copy)]
1012
pub struct OnConflictDoNothing<T>(T);
@@ -15,6 +17,23 @@ impl<T> OnConflictDoNothing<T> {
1517
}
1618
}
1719

20+
#[derive(Debug, Clone, Copy)]
21+
pub struct OnConflict<Records, Target, Action> {
22+
records: Records,
23+
target: Target,
24+
action: Action,
25+
}
26+
27+
impl<Records, Target, Action> OnConflict<Records, Target, Action> {
28+
pub fn new(records: Records, target: Target, action: Action) -> Self {
29+
OnConflict {
30+
records: records,
31+
target: target,
32+
action: action,
33+
}
34+
}
35+
}
36+
1837
impl<'a, T, Tab, Op> IntoInsertStatement<Tab, Op> for &'a OnConflictDoNothing<T> {
1938
type InsertStatement = InsertStatement<Tab, Self, Op>;
2039

@@ -25,36 +44,78 @@ impl<'a, T, Tab, Op> IntoInsertStatement<Tab, Op> for &'a OnConflictDoNothing<T>
2544
}
2645
}
2746

47+
impl<'a, Recods, Target, Action, Tab, Op> IntoInsertStatement<Tab, Op>
48+
for &'a OnConflict<Recods, Target, Action>
49+
{
50+
type InsertStatement = InsertStatement<Tab, Self, Op>;
51+
52+
fn into_insert_statement(self, target: Tab, operator: Op)
53+
-> Self::InsertStatement
54+
{
55+
InsertStatement::no_returning_clause(target, self, operator)
56+
}
57+
}
58+
2859
impl<'a, T, Tab> Insertable<Tab, Pg> for &'a OnConflictDoNothing<T> where
2960
Tab: Table,
3061
T: Insertable<Tab, Pg> + Copy,
3162
T: UndecoratedInsertRecord<Tab>,
3263
{
33-
type Values = OnConflictDoNothingValues<T::Values>;
64+
type Values = OnConflictValues<T::Values, NoConflictTarget, DoNothing>;
3465

3566
fn values(self) -> Self::Values {
36-
OnConflictDoNothingValues(self.0.values())
67+
OnConflictValues {
68+
values: self.0.values(),
69+
target: NoConflictTarget,
70+
action: DoNothing,
71+
}
72+
}
73+
}
74+
75+
impl<'a, Records, Target, Action, Tab> Insertable<Tab, Pg>
76+
for &'a OnConflict<Records, Target, Action> where
77+
Tab: Table,
78+
Records: Insertable<Tab, Pg> + Copy,
79+
Records: UndecoratedInsertRecord<Tab>,
80+
Target: OnConflictTarget<Tab> + Copy,
81+
Action: Copy,
82+
{
83+
type Values = OnConflictValues<Records::Values, Target, Action>;
84+
85+
fn values(self) -> Self::Values {
86+
OnConflictValues {
87+
values: self.records.values(),
88+
target: self.target,
89+
action: self.action,
90+
}
3791
}
3892
}
3993

4094
#[doc(hidden)]
4195
#[derive(Debug, Clone, Copy)]
42-
pub struct OnConflictDoNothingValues<T>(pub T);
96+
pub struct OnConflictValues<Values, Target, Action> {
97+
values: Values,
98+
target: Target,
99+
action: Action,
100+
}
43101

44-
impl<T> InsertValues<Pg> for OnConflictDoNothingValues<T> where
45-
T: InsertValues<Pg>,
102+
impl<Values, Target, Action> InsertValues<Pg> for OnConflictValues<Values, Target, Action> where
103+
Values: InsertValues<Pg>,
104+
Target: QueryFragment<Pg>,
46105
{
47106
fn column_names(&self, out: &mut <Pg as Backend>::QueryBuilder) -> BuildQueryResult {
48-
self.0.column_names(out)
107+
self.values.column_names(out)
49108
}
50109

51110
fn values_clause(&self, out: &mut <Pg as Backend>::QueryBuilder) -> BuildQueryResult {
52-
try!(self.0.values_clause(out));
53-
out.push_sql(" ON CONFLICT DO NOTHING");
111+
try!(self.values.values_clause(out));
112+
out.push_sql(" ON CONFLICT");
113+
try!(self.target.to_sql(out));
114+
out.push_sql(" DO NOTHING");
54115
Ok(())
55116
}
56117

57118
fn values_bind_params(&self, out: &mut <Pg as Backend>::BindCollector) -> QueryResult<()> {
58-
self.0.values_bind_params(out)
119+
self.values.values_bind_params(out)
59120
}
60121
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
include!("src/doctest_setup.rs");
2+
3+
table! {
4+
users {
5+
id -> Integer,
6+
name -> VarChar,
7+
}
8+
}
9+
10+
#[derive(Clone, Copy, Insertable)]
11+
#[table_name="users"]
12+
struct User<'a> {
13+
id: i32,
14+
name: &'a str,
15+
}

diesel/src/pg/upsert/on_conflict_extension.rs

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub use super::on_conflict_clause::*;
2+
pub use super::on_conflict_target::*;
23

34
/// Adds extension methods related to PG upsert
45
pub trait OnConflictExtension {
@@ -12,21 +13,7 @@ pub trait OnConflictExtension {
1213
/// ```rust
1314
/// # #[macro_use] extern crate diesel;
1415
/// # #[macro_use] extern crate diesel_codegen;
15-
/// # include!("src/doctest_setup.rs");
16-
/// #
17-
/// # table! {
18-
/// # users {
19-
/// # id -> Integer,
20-
/// # name -> VarChar,
21-
/// # }
22-
/// # }
23-
/// #
24-
/// # #[derive(Clone, Copy, Insertable)]
25-
/// # #[table_name="users"]
26-
/// # struct User<'a> {
27-
/// # id: i32,
28-
/// # name: &'a str,
29-
/// # }
16+
/// # include!("src/pg/upsert/on_conflict_docs_setup.rs");
3017
/// #
3118
/// # fn main() {
3219
/// # use self::users::dsl::*;
@@ -51,21 +38,7 @@ pub trait OnConflictExtension {
5138
/// ```rust
5239
/// # #[macro_use] extern crate diesel;
5340
/// # #[macro_use] extern crate diesel_codegen;
54-
/// # include!("src/doctest_setup.rs");
55-
/// #
56-
/// # table! {
57-
/// # users {
58-
/// # id -> Integer,
59-
/// # name -> VarChar,
60-
/// # }
61-
/// # }
62-
/// #
63-
/// # #[derive(Clone, Copy, Insertable)]
64-
/// # #[table_name="users"]
65-
/// # struct User<'a> {
66-
/// # id: i32,
67-
/// # name: &'a str,
68-
/// # }
41+
/// # include!("src/pg/upsert/on_conflict_docs_setup.rs");
6942
/// #
7043
/// # fn main() {
7144
/// # use self::users::dsl::*;
@@ -86,12 +59,86 @@ pub trait OnConflictExtension {
8659
/// ```rust
8760
/// # #[macro_use] extern crate diesel;
8861
/// # #[macro_use] extern crate diesel_codegen;
62+
/// # include!("src/pg/upsert/on_conflict_docs_setup.rs");
63+
/// #
64+
/// # fn main() {
65+
/// # use self::users::dsl::*;
66+
/// use self::diesel::pg::upsert::*;
67+
///
68+
/// # let conn = establish_connection();
69+
/// # conn.execute("TRUNCATE TABLE users").unwrap();
70+
/// let user = User { id: 1, name: "Sean", };
71+
///
72+
/// let new_users: &[User] = &[user, user];
73+
/// let inserted_row_count = diesel::insert(&new_users.on_conflict_do_nothing())
74+
/// .into(users).execute(&conn);
75+
/// assert_eq!(Ok(1), inserted_row_count);
76+
/// # }
77+
/// ```
78+
fn on_conflict_do_nothing(&self) -> OnConflictDoNothing<&Self> {
79+
OnConflictDoNothing::new(self)
80+
}
81+
82+
/// Adds an `ON CONFLICT` to the insert statement, performing the action
83+
/// specified by `Action` if a conflict occurs for `Target`.
84+
///
85+
/// `Target` can be one of:
86+
///
87+
/// - A column
88+
/// - A tuple of columns
89+
/// - [`on_constraint("constraint_name")`](fn.on_constraint.html)
90+
///
91+
/// `Action` can be one of:
92+
///
93+
/// - [`do_nothing()`](fn.do_nothing.html)
94+
/// - [`do_update()`]
95+
///
96+
/// # Examples
97+
///
98+
/// ### Specifying a column as the target
99+
///
100+
/// ```rust
101+
/// # #[macro_use] extern crate diesel;
102+
/// # #[macro_use] extern crate diesel_codegen;
103+
/// # include!("src/pg/upsert/on_conflict_docs_setup.rs");
104+
/// #
105+
/// # fn main() {
106+
/// # use self::users::dsl::*;
107+
/// use self::diesel::pg::upsert::*;
108+
///
109+
/// # let conn = establish_connection();
110+
/// # conn.execute("TRUNCATE TABLE users").unwrap();
111+
/// conn.execute("CREATE UNIQUE INDEX users_name ON users (name)").unwrap();
112+
/// let user = User { id: 1, name: "Sean", };
113+
/// let same_name_different_id = User { id: 2, name: "Sean" };
114+
/// let same_id_different_name = User { id: 1, name: "Pascal" };
115+
///
116+
/// assert_eq!(Ok(1), diesel::insert(&user).into(users).execute(&conn));
117+
///
118+
/// let inserted_row_count = diesel::insert(
119+
/// &same_name_different_id.on_conflict(name, do_nothing())
120+
/// ).into(users).execute(&conn);
121+
/// assert_eq!(Ok(0), inserted_row_count);
122+
///
123+
/// let pk_conflict_result = diesel::insert(
124+
/// &same_id_different_name.on_conflict(name, do_nothing())
125+
/// ).into(users).execute(&conn);
126+
/// assert!(pk_conflict_result.is_err());
127+
/// # }
128+
/// ```
129+
///
130+
/// ### Specifying multiple columns as the target
131+
///
132+
/// ```rust
133+
/// # #[macro_use] extern crate diesel;
134+
/// # #[macro_use] extern crate diesel_codegen;
89135
/// # include!("src/doctest_setup.rs");
90136
/// #
91137
/// # table! {
92138
/// # users {
93139
/// # id -> Integer,
94140
/// # name -> VarChar,
141+
/// # hair_color -> VarChar,
95142
/// # }
96143
/// # }
97144
/// #
@@ -100,24 +147,41 @@ pub trait OnConflictExtension {
100147
/// # struct User<'a> {
101148
/// # id: i32,
102149
/// # name: &'a str,
150+
/// # hair_color: &'a str,
103151
/// # }
104152
/// #
105153
/// # fn main() {
106154
/// # use self::users::dsl::*;
107155
/// use self::diesel::pg::upsert::*;
108156
///
109157
/// # let conn = establish_connection();
110-
/// # conn.execute("TRUNCATE TABLE users").unwrap();
111-
/// let user = User { id: 1, name: "Sean", };
158+
/// # conn.execute("DROP TABLE users").unwrap();
159+
/// # conn.execute("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, hair_color TEXT)").unwrap();
160+
/// conn.execute("CREATE UNIQUE INDEX users_name_hair_color ON users (name, hair_color)").unwrap();
161+
/// let user = User { id: 1, name: "Sean", hair_color: "black" };
162+
/// let same_name_different_hair_color = User { id: 2, name: "Sean", hair_color: "brown" };
163+
/// let same_same_name_same_hair_color = User { id: 3, name: "Sean", hair_color: "black" };
112164
///
113-
/// let new_users: &[User] = &[user, user];
114-
/// let inserted_row_count = diesel::insert(&new_users.on_conflict_do_nothing())
115-
/// .into(users).execute(&conn);
165+
/// assert_eq!(Ok(1), diesel::insert(&user).into(users).execute(&conn));
166+
///
167+
/// let inserted_row_count = diesel::insert(
168+
/// &same_name_different_hair_color.on_conflict((name, hair_color), do_nothing())
169+
/// ).into(users).execute(&conn);
116170
/// assert_eq!(Ok(1), inserted_row_count);
171+
///
172+
/// let inserted_row_count = diesel::insert(
173+
/// &same_same_name_same_hair_color.on_conflict((name, hair_color), do_nothing())
174+
/// ).into(users).execute(&conn);
175+
/// assert_eq!(Ok(0), inserted_row_count);
117176
/// # }
118177
/// ```
119-
fn on_conflict_do_nothing(&self) -> OnConflictDoNothing<&Self> {
120-
OnConflictDoNothing::new(self)
178+
///
179+
/// See the documentation for [`on_constraint`](fn.on_constraint.html) and [`do_update`] for
180+
/// more examples.
181+
fn on_conflict<Target, Action>(&self, target: Target, action: Action)
182+
-> OnConflict<&Self, ConflictTarget<Target>, Action>
183+
{
184+
OnConflict::new(self, ConflictTarget(target), action)
121185
}
122186
}
123187

0 commit comments

Comments
 (0)