diff --git a/src/ast/dml.rs b/src/ast/dml.rs index c2fe2ffd6..b804e4fc3 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -608,6 +608,13 @@ pub enum MergeAction { /// The `DELETE` token that starts the sub-expression. delete_token: AttachedToken, }, + /// A `DO NOTHING` clause. + DoNothing { + /// The `DO` token that starts the sub-expression. + do_token: AttachedToken, + /// The `NOTHING` token that ends the sub-expression. + nothing_token: AttachedToken, + }, } impl Display for MergeAction { @@ -622,6 +629,9 @@ impl Display for MergeAction { MergeAction::Delete { .. } => { write!(f, "DELETE") } + MergeAction::DoNothing { .. } => { + write!(f, "DO NOTHING") + } } } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index a34fe66d9..cb06b7899 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2557,6 +2557,10 @@ impl Spanned for MergeAction { MergeAction::Insert(expr) => expr.span(), MergeAction::Update(expr) => expr.span(), MergeAction::Delete { delete_token } => delete_token.0.span, + MergeAction::DoNothing { + do_token, + nothing_token, + } => do_token.0.span.union(¬hing_token.0.span), } } } @@ -2909,6 +2913,7 @@ WHERE id = 1 WHEN MATCHED AND target_table.x != 'X' THEN DELETE WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW + WHEN MATCHED THEN DO NOTHING "#; let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap(); @@ -2917,7 +2922,7 @@ WHERE id = 1 // ~ assert the span of the whole statement let stmt_span = r[0].span(); assert_eq!(stmt_span.start, (4, 9).into()); - assert_eq!(stmt_span.end, (16, 67).into()); + assert_eq!(stmt_span.end, (17, 37).into()); // ~ individual tokens within the statement let Statement::Merge(Merge { @@ -2937,7 +2942,7 @@ WHERE id = 1 merge_token.0.span, Span::new(Location::new(4, 9), Location::new(4, 14)) ); - assert_eq!(clauses.len(), 4); + assert_eq!(clauses.len(), 5); // ~ the INSERT clause's TOKENs assert_eq!( @@ -3019,6 +3024,31 @@ WHERE id = 1 panic!("not a MERGE INSERT clause"); } + assert_eq!( + clauses[4].when_token.0.span, + Span::new(Location::new(17, 9), Location::new(17, 13)) + ); + if let MergeAction::DoNothing { + do_token, + nothing_token, + } = &clauses[4].action + { + assert_eq!( + do_token.0.span, + Span::new(Location::new(17, 27), Location::new(17, 29)) + ); + assert_eq!( + nothing_token.0.span, + Span::new(Location::new(17, 30), Location::new(17, 37)) + ); + assert_eq!( + clauses[4].action.span(), + Span::new(Location::new(17, 27), Location::new(17, 37)) + ); + } else { + panic!("not a MERGE DO NOTHING clause"); + } + assert!(output.is_none()); } diff --git a/src/parser/merge.rs b/src/parser/merge.rs index 21e9d6087..79a77df9d 100644 --- a/src/parser/merge.rs +++ b/src/parser/merge.rs @@ -107,7 +107,16 @@ impl Parser<'_> { Keyword::UPDATE, Keyword::INSERT, Keyword::DELETE, + Keyword::DO, ]) { + Some(Keyword::DO) => { + let do_token = self.get_current_token().clone(); + let nothing_token = self.expect_keyword(Keyword::NOTHING)?; + MergeAction::DoNothing { + do_token: do_token.into(), + nothing_token: nothing_token.into(), + } + } Some(Keyword::UPDATE) => { if matches!( clause_kind, @@ -212,7 +221,7 @@ impl Parser<'_> { } _ => { return parser_err!( - "expected UPDATE, DELETE or INSERT in merge clause", + "expected UPDATE, DELETE, INSERT or DO NOTHING in merge clause", self.peek_token_ref().span.start ); } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 57a5400f7..b98820e7a 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9852,3 +9852,39 @@ fn parse_alter_table_constraint_check_no_inherit() { } pg_and_generic().verified_stmt("ALTER TABLE docs ADD CONSTRAINT c CHECK (id > 0) NO INHERIT"); } + +#[test] +fn parse_merge_do_nothing() { + let Statement::Merge(merge) = pg_and_generic().verified_stmt( + "MERGE INTO target USING source ON target.id = source.id WHEN MATCHED THEN DO NOTHING WHEN NOT MATCHED THEN DO NOTHING", + ) else { + panic!("expected MERGE statement"); + }; + assert!(matches!( + merge.clauses.as_slice(), + [ + MergeClause { + clause_kind: MergeClauseKind::Matched, + action: MergeAction::DoNothing { .. }, + .. + }, + MergeClause { + clause_kind: MergeClauseKind::NotMatched, + action: MergeAction::DoNothing { .. }, + .. + } + ] + )); +} + +#[test] +fn reject_merge_do_without_nothing() { + assert_eq!( + pg_and_generic().parse_sql_statements( + "MERGE INTO target USING source ON target.id = source.id WHEN MATCHED THEN DO UPDATE" + ), + Err(ParserError::ParserError( + "Expected: NOTHING, found: UPDATE".into() + )) + ); +}