-
Notifications
You must be signed in to change notification settings - Fork 691
MSSQL: Add support for OUTPUT clause on INSERT/UPDATE/DELETE #2228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13288,6 +13288,15 @@ impl<'a> Parser<'a> { | |||||
| }; | ||||||
|
|
||||||
| let from = self.parse_comma_separated(Parser::parse_table_and_joins)?; | ||||||
|
|
||||||
| // MSSQL OUTPUT clause appears after FROM table, before USING/WHERE | ||||||
| // https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql | ||||||
|
Comment on lines
+13292
to
+13293
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| let output = if self.parse_keyword(Keyword::OUTPUT) { | ||||||
| Some(self.parse_output(Keyword::OUTPUT, self.get_current_token().clone())?) | ||||||
| } else { | ||||||
| None | ||||||
| }; | ||||||
|
|
||||||
| let using = if self.parse_keyword(Keyword::USING) { | ||||||
| Some(self.parse_comma_separated(Parser::parse_table_and_joins)?) | ||||||
| } else { | ||||||
|
|
@@ -13326,6 +13335,7 @@ impl<'a> Parser<'a> { | |||||
| using, | ||||||
| selection, | ||||||
| returning, | ||||||
| output, | ||||||
| order_by, | ||||||
| limit, | ||||||
| })) | ||||||
|
|
@@ -17238,10 +17248,10 @@ impl<'a> Parser<'a> { | |||||
|
|
||||||
| let is_mysql = dialect_of!(self is MySqlDialect); | ||||||
|
|
||||||
| let (columns, partitioned, after_columns, source, assignments) = if self | ||||||
| let (columns, partitioned, after_columns, output, source, assignments) = if self | ||||||
| .parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) | ||||||
| { | ||||||
| (vec![], None, vec![], None, vec![]) | ||||||
| (vec![], None, vec![], None, None, vec![]) | ||||||
| } else { | ||||||
| let (columns, partitioned, after_columns) = if !self.peek_subquery_start() { | ||||||
| let columns = self.parse_parenthesized_column_list(Optional, is_mysql)?; | ||||||
|
|
@@ -17258,6 +17268,14 @@ impl<'a> Parser<'a> { | |||||
| Default::default() | ||||||
| }; | ||||||
|
|
||||||
| // MSSQL OUTPUT clause appears between columns and source | ||||||
| // https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql | ||||||
|
Comment on lines
+17271
to
+17272
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| let output = if self.parse_keyword(Keyword::OUTPUT) { | ||||||
| Some(self.parse_output(Keyword::OUTPUT, self.get_current_token().clone())?) | ||||||
| } else { | ||||||
| None | ||||||
| }; | ||||||
|
|
||||||
| let (source, assignments) = if self.peek_keyword(Keyword::FORMAT) | ||||||
| || self.peek_keyword(Keyword::SETTINGS) | ||||||
| { | ||||||
|
|
@@ -17268,7 +17286,14 @@ impl<'a> Parser<'a> { | |||||
| (Some(self.parse_query()?), vec![]) | ||||||
| }; | ||||||
|
|
||||||
| (columns, partitioned, after_columns, source, assignments) | ||||||
| ( | ||||||
| columns, | ||||||
| partitioned, | ||||||
| after_columns, | ||||||
| output, | ||||||
| source, | ||||||
| assignments, | ||||||
| ) | ||||||
| }; | ||||||
|
|
||||||
| let (format_clause, settings) = if self.dialect.supports_insert_format() { | ||||||
|
|
@@ -17370,6 +17395,7 @@ impl<'a> Parser<'a> { | |||||
| has_table_keyword: table, | ||||||
| on, | ||||||
| returning, | ||||||
| output, | ||||||
| replace_into, | ||||||
| priority, | ||||||
| insert_alias, | ||||||
|
|
@@ -17475,6 +17501,15 @@ impl<'a> Parser<'a> { | |||||
| }; | ||||||
| self.expect_keyword(Keyword::SET)?; | ||||||
| let assignments = self.parse_comma_separated(Parser::parse_assignment)?; | ||||||
|
|
||||||
| // MSSQL OUTPUT clause appears after SET, before FROM/WHERE | ||||||
| // https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql | ||||||
|
Comment on lines
+17505
to
+17506
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
maybe we can introduce a helper function |
||||||
| let output = if self.parse_keyword(Keyword::OUTPUT) { | ||||||
| Some(self.parse_output(Keyword::OUTPUT, self.get_current_token().clone())?) | ||||||
| } else { | ||||||
| None | ||||||
| }; | ||||||
|
|
||||||
| let from = if from_before_set.is_none() && self.parse_keyword(Keyword::FROM) { | ||||||
| Some(UpdateTableFromKind::AfterSet( | ||||||
| self.parse_table_with_joins()?, | ||||||
|
|
@@ -17505,6 +17540,7 @@ impl<'a> Parser<'a> { | |||||
| from, | ||||||
| selection, | ||||||
| returning, | ||||||
| output, | ||||||
| or, | ||||||
| limit, | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2730,3 +2730,46 @@ fn parse_mssql_tran_shorthand() { | |||||
| // ROLLBACK TRAN normalizes to ROLLBACK (same as ROLLBACK TRANSACTION) | ||||||
| ms().one_statement_parses_to("ROLLBACK TRAN", "ROLLBACK"); | ||||||
| } | ||||||
|
|
||||||
| // MSSQL OUTPUT clause on INSERT/UPDATE/DELETE | ||||||
| // https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql | ||||||
|
|
||||||
|
Comment on lines
+2734
to
+2736
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| #[test] | ||||||
| fn parse_mssql_insert_with_output() { | ||||||
| ms_and_generic().verified_stmt( | ||||||
| "INSERT INTO customers (name, email) OUTPUT INSERTED.id, INSERTED.name VALUES ('John', 'john@example.com')", | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn parse_mssql_insert_with_output_into() { | ||||||
| ms_and_generic().verified_stmt( | ||||||
| "INSERT INTO customers (name, email) OUTPUT INSERTED.id, INSERTED.name INTO @new_ids VALUES ('John', 'john@example.com')", | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn parse_mssql_delete_with_output() { | ||||||
| ms_and_generic().verified_stmt("DELETE FROM customers OUTPUT DELETED.* WHERE id = 1"); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn parse_mssql_delete_with_output_into() { | ||||||
| ms_and_generic().verified_stmt( | ||||||
| "DELETE FROM customers OUTPUT DELETED.id, DELETED.name INTO @deleted_rows WHERE active = 0", | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn parse_mssql_update_with_output() { | ||||||
| ms_and_generic().verified_stmt( | ||||||
| "UPDATE employees SET salary = salary * 1.1 OUTPUT INSERTED.id, DELETED.salary, INSERTED.salary WHERE department = 'Engineering'", | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn parse_mssql_update_with_output_into() { | ||||||
| ms_and_generic().verified_stmt( | ||||||
| "UPDATE employees SET salary = salary * 1.1 OUTPUT INSERTED.id, DELETED.salary, INSERTED.salary INTO @changes WHERE department = 'Engineering'", | ||||||
| ); | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for these comments, could we include the link to the mssql docs describing the syntax? it would help with nagivation when folks look at the struct