Skip to content

Commit 3d5cc54

Browse files
authored
Generalize conflict target (#762)
Postgres supports `ON CONFLICT ON CONSTRAINT <constraint_name>` to explicitly name the constraint that fails. Support this.
1 parent 6d6eb4b commit 3d5cc54

File tree

3 files changed

+70
-15
lines changed

3 files changed

+70
-15
lines changed

src/ast/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2728,11 +2728,17 @@ pub enum OnInsert {
27282728
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
27292729
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27302730
pub struct OnConflict {
2731-
pub conflict_target: Vec<Ident>,
2731+
pub conflict_target: Option<ConflictTarget>,
27322732
pub action: OnConflictAction,
27332733
}
27342734
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
27352735
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2736+
pub enum ConflictTarget {
2737+
Columns(Vec<Ident>),
2738+
OnConstraint(ObjectName),
2739+
}
2740+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2741+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27362742
pub enum OnConflictAction {
27372743
DoNothing,
27382744
DoUpdate(DoUpdate),
@@ -2762,12 +2768,20 @@ impl fmt::Display for OnInsert {
27622768
impl fmt::Display for OnConflict {
27632769
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27642770
write!(f, " ON CONFLICT")?;
2765-
if !self.conflict_target.is_empty() {
2766-
write!(f, "({})", display_comma_separated(&self.conflict_target))?;
2771+
if let Some(target) = &self.conflict_target {
2772+
write!(f, "{}", target)?;
27672773
}
27682774
write!(f, " {}", self.action)
27692775
}
27702776
}
2777+
impl fmt::Display for ConflictTarget {
2778+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2779+
match self {
2780+
ConflictTarget::Columns(cols) => write!(f, "({})", display_comma_separated(cols)),
2781+
ConflictTarget::OnConstraint(name) => write!(f, " ON CONSTRAINT {}", name),
2782+
}
2783+
}
2784+
}
27712785
impl fmt::Display for OnConflictAction {
27722786
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27732787
match self {

src/parser.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5630,7 +5630,15 @@ impl<'a> Parser<'a> {
56305630
let on = if self.parse_keyword(Keyword::ON) {
56315631
if self.parse_keyword(Keyword::CONFLICT) {
56325632
let conflict_target =
5633-
self.parse_parenthesized_column_list(IsOptional::Optional)?;
5633+
if self.parse_keywords(&[Keyword::ON, Keyword::CONSTRAINT]) {
5634+
Some(ConflictTarget::OnConstraint(self.parse_object_name()?))
5635+
} else if self.peek_token() == Token::LParen {
5636+
Some(ConflictTarget::Columns(
5637+
self.parse_parenthesized_column_list(IsOptional::Mandatory)?,
5638+
))
5639+
} else {
5640+
None
5641+
};
56345642

56355643
self.expect_keyword(Keyword::DO)?;
56365644
let action = if self.parse_keyword(Keyword::NOTHING) {

tests/sqlparser_postgres.rs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,12 +1112,12 @@ fn parse_pg_on_conflict() {
11121112
Statement::Insert {
11131113
on:
11141114
Some(OnInsert::OnConflict(OnConflict {
1115-
conflict_target,
1115+
conflict_target: Some(ConflictTarget::Columns(cols)),
11161116
action,
11171117
})),
11181118
..
11191119
} => {
1120-
assert_eq!(vec![Ident::from("did")], conflict_target);
1120+
assert_eq!(vec![Ident::from("did")], cols);
11211121
assert_eq!(
11221122
OnConflictAction::DoUpdate(DoUpdate {
11231123
assignments: vec![Assignment {
@@ -1142,15 +1142,12 @@ fn parse_pg_on_conflict() {
11421142
Statement::Insert {
11431143
on:
11441144
Some(OnInsert::OnConflict(OnConflict {
1145-
conflict_target,
1145+
conflict_target: Some(ConflictTarget::Columns(cols)),
11461146
action,
11471147
})),
11481148
..
11491149
} => {
1150-
assert_eq!(
1151-
vec![Ident::from("did"), Ident::from("area"),],
1152-
conflict_target
1153-
);
1150+
assert_eq!(vec![Ident::from("did"), Ident::from("area"),], cols);
11541151
assert_eq!(
11551152
OnConflictAction::DoUpdate(DoUpdate {
11561153
assignments: vec![
@@ -1183,12 +1180,11 @@ fn parse_pg_on_conflict() {
11831180
Statement::Insert {
11841181
on:
11851182
Some(OnInsert::OnConflict(OnConflict {
1186-
conflict_target,
1183+
conflict_target: None,
11871184
action,
11881185
})),
11891186
..
11901187
} => {
1191-
assert_eq!(Vec::<Ident>::new(), conflict_target);
11921188
assert_eq!(OnConflictAction::DoNothing, action);
11931189
}
11941190
_ => unreachable!(),
@@ -1204,12 +1200,49 @@ fn parse_pg_on_conflict() {
12041200
Statement::Insert {
12051201
on:
12061202
Some(OnInsert::OnConflict(OnConflict {
1207-
conflict_target,
1203+
conflict_target: Some(ConflictTarget::Columns(cols)),
1204+
action,
1205+
})),
1206+
..
1207+
} => {
1208+
assert_eq!(vec![Ident::from("did")], cols);
1209+
assert_eq!(
1210+
OnConflictAction::DoUpdate(DoUpdate {
1211+
assignments: vec![Assignment {
1212+
id: vec!["dname".into()],
1213+
value: Expr::Value(Value::Placeholder("$1".to_string()))
1214+
},],
1215+
selection: Some(Expr::BinaryOp {
1216+
left: Box::new(Expr::Identifier(Ident {
1217+
value: "dsize".to_string(),
1218+
quote_style: None
1219+
})),
1220+
op: BinaryOperator::Gt,
1221+
right: Box::new(Expr::Value(Value::Placeholder("$2".to_string())))
1222+
})
1223+
}),
1224+
action
1225+
);
1226+
}
1227+
_ => unreachable!(),
1228+
};
1229+
1230+
let stmt = pg_and_generic().verified_stmt(
1231+
"INSERT INTO distributors (did, dname, dsize) \
1232+
VALUES (5, 'Gizmo Transglobal', 1000), (6, 'Associated Computing, Inc', 1010) \
1233+
ON CONFLICT ON CONSTRAINT distributors_did_pkey \
1234+
DO UPDATE SET dname = $1 WHERE dsize > $2",
1235+
);
1236+
match stmt {
1237+
Statement::Insert {
1238+
on:
1239+
Some(OnInsert::OnConflict(OnConflict {
1240+
conflict_target: Some(ConflictTarget::OnConstraint(cname)),
12081241
action,
12091242
})),
12101243
..
12111244
} => {
1212-
assert_eq!(vec![Ident::from("did")], conflict_target);
1245+
assert_eq!(vec![Ident::from("distributors_did_pkey")], cname.0);
12131246
assert_eq!(
12141247
OnConflictAction::DoUpdate(DoUpdate {
12151248
assignments: vec![Assignment {

0 commit comments

Comments
 (0)