mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-23 15:34:09 +00:00
Generalize conflict target (#762)
Postgres supports `ON CONFLICT ON CONSTRAINT <constraint_name>` to explicitly name the constraint that fails. Support this.
This commit is contained in:
parent
6d6eb4bc9b
commit
3d5cc54dcf
3 changed files with 70 additions and 15 deletions
|
@ -2728,11 +2728,17 @@ pub enum OnInsert {
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct OnConflict {
|
pub struct OnConflict {
|
||||||
pub conflict_target: Vec<Ident>,
|
pub conflict_target: Option<ConflictTarget>,
|
||||||
pub action: OnConflictAction,
|
pub action: OnConflictAction,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum ConflictTarget {
|
||||||
|
Columns(Vec<Ident>),
|
||||||
|
OnConstraint(ObjectName),
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum OnConflictAction {
|
pub enum OnConflictAction {
|
||||||
DoNothing,
|
DoNothing,
|
||||||
DoUpdate(DoUpdate),
|
DoUpdate(DoUpdate),
|
||||||
|
@ -2762,12 +2768,20 @@ impl fmt::Display for OnInsert {
|
||||||
impl fmt::Display for OnConflict {
|
impl fmt::Display for OnConflict {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, " ON CONFLICT")?;
|
write!(f, " ON CONFLICT")?;
|
||||||
if !self.conflict_target.is_empty() {
|
if let Some(target) = &self.conflict_target {
|
||||||
write!(f, "({})", display_comma_separated(&self.conflict_target))?;
|
write!(f, "{}", target)?;
|
||||||
}
|
}
|
||||||
write!(f, " {}", self.action)
|
write!(f, " {}", self.action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl fmt::Display for ConflictTarget {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConflictTarget::Columns(cols) => write!(f, "({})", display_comma_separated(cols)),
|
||||||
|
ConflictTarget::OnConstraint(name) => write!(f, " ON CONSTRAINT {}", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl fmt::Display for OnConflictAction {
|
impl fmt::Display for OnConflictAction {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -5630,7 +5630,15 @@ impl<'a> Parser<'a> {
|
||||||
let on = if self.parse_keyword(Keyword::ON) {
|
let on = if self.parse_keyword(Keyword::ON) {
|
||||||
if self.parse_keyword(Keyword::CONFLICT) {
|
if self.parse_keyword(Keyword::CONFLICT) {
|
||||||
let conflict_target =
|
let conflict_target =
|
||||||
self.parse_parenthesized_column_list(IsOptional::Optional)?;
|
if self.parse_keywords(&[Keyword::ON, Keyword::CONSTRAINT]) {
|
||||||
|
Some(ConflictTarget::OnConstraint(self.parse_object_name()?))
|
||||||
|
} else if self.peek_token() == Token::LParen {
|
||||||
|
Some(ConflictTarget::Columns(
|
||||||
|
self.parse_parenthesized_column_list(IsOptional::Mandatory)?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
self.expect_keyword(Keyword::DO)?;
|
self.expect_keyword(Keyword::DO)?;
|
||||||
let action = if self.parse_keyword(Keyword::NOTHING) {
|
let action = if self.parse_keyword(Keyword::NOTHING) {
|
||||||
|
|
|
@ -1112,12 +1112,12 @@ fn parse_pg_on_conflict() {
|
||||||
Statement::Insert {
|
Statement::Insert {
|
||||||
on:
|
on:
|
||||||
Some(OnInsert::OnConflict(OnConflict {
|
Some(OnInsert::OnConflict(OnConflict {
|
||||||
conflict_target,
|
conflict_target: Some(ConflictTarget::Columns(cols)),
|
||||||
action,
|
action,
|
||||||
})),
|
})),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(vec![Ident::from("did")], conflict_target);
|
assert_eq!(vec![Ident::from("did")], cols);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
OnConflictAction::DoUpdate(DoUpdate {
|
OnConflictAction::DoUpdate(DoUpdate {
|
||||||
assignments: vec![Assignment {
|
assignments: vec![Assignment {
|
||||||
|
@ -1142,15 +1142,12 @@ fn parse_pg_on_conflict() {
|
||||||
Statement::Insert {
|
Statement::Insert {
|
||||||
on:
|
on:
|
||||||
Some(OnInsert::OnConflict(OnConflict {
|
Some(OnInsert::OnConflict(OnConflict {
|
||||||
conflict_target,
|
conflict_target: Some(ConflictTarget::Columns(cols)),
|
||||||
action,
|
action,
|
||||||
})),
|
})),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(
|
assert_eq!(vec![Ident::from("did"), Ident::from("area"),], cols);
|
||||||
vec![Ident::from("did"), Ident::from("area"),],
|
|
||||||
conflict_target
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
OnConflictAction::DoUpdate(DoUpdate {
|
OnConflictAction::DoUpdate(DoUpdate {
|
||||||
assignments: vec![
|
assignments: vec![
|
||||||
|
@ -1183,12 +1180,11 @@ fn parse_pg_on_conflict() {
|
||||||
Statement::Insert {
|
Statement::Insert {
|
||||||
on:
|
on:
|
||||||
Some(OnInsert::OnConflict(OnConflict {
|
Some(OnInsert::OnConflict(OnConflict {
|
||||||
conflict_target,
|
conflict_target: None,
|
||||||
action,
|
action,
|
||||||
})),
|
})),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(Vec::<Ident>::new(), conflict_target);
|
|
||||||
assert_eq!(OnConflictAction::DoNothing, action);
|
assert_eq!(OnConflictAction::DoNothing, action);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -1204,12 +1200,49 @@ fn parse_pg_on_conflict() {
|
||||||
Statement::Insert {
|
Statement::Insert {
|
||||||
on:
|
on:
|
||||||
Some(OnInsert::OnConflict(OnConflict {
|
Some(OnInsert::OnConflict(OnConflict {
|
||||||
conflict_target,
|
conflict_target: Some(ConflictTarget::Columns(cols)),
|
||||||
action,
|
action,
|
||||||
})),
|
})),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(vec![Ident::from("did")], conflict_target);
|
assert_eq!(vec![Ident::from("did")], cols);
|
||||||
|
assert_eq!(
|
||||||
|
OnConflictAction::DoUpdate(DoUpdate {
|
||||||
|
assignments: vec![Assignment {
|
||||||
|
id: vec!["dname".into()],
|
||||||
|
value: Expr::Value(Value::Placeholder("$1".to_string()))
|
||||||
|
},],
|
||||||
|
selection: Some(Expr::BinaryOp {
|
||||||
|
left: Box::new(Expr::Identifier(Ident {
|
||||||
|
value: "dsize".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
})),
|
||||||
|
op: BinaryOperator::Gt,
|
||||||
|
right: Box::new(Expr::Value(Value::Placeholder("$2".to_string())))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let stmt = pg_and_generic().verified_stmt(
|
||||||
|
"INSERT INTO distributors (did, dname, dsize) \
|
||||||
|
VALUES (5, 'Gizmo Transglobal', 1000), (6, 'Associated Computing, Inc', 1010) \
|
||||||
|
ON CONFLICT ON CONSTRAINT distributors_did_pkey \
|
||||||
|
DO UPDATE SET dname = $1 WHERE dsize > $2",
|
||||||
|
);
|
||||||
|
match stmt {
|
||||||
|
Statement::Insert {
|
||||||
|
on:
|
||||||
|
Some(OnInsert::OnConflict(OnConflict {
|
||||||
|
conflict_target: Some(ConflictTarget::OnConstraint(cname)),
|
||||||
|
action,
|
||||||
|
})),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!(vec![Ident::from("distributors_did_pkey")], cname.0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
OnConflictAction::DoUpdate(DoUpdate {
|
OnConflictAction::DoUpdate(DoUpdate {
|
||||||
assignments: vec![Assignment {
|
assignments: vec![Assignment {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue