From 96db201e3b667b62385c3b1b436111f218bde331 Mon Sep 17 00:00:00 2001 From: zvonimir-dd Date: Tue, 13 Oct 2026 14:18:52 +0000 Subject: [PATCH 1/2] Postgres: support OVERRIDING { SYSTEM | USER } VALUE in INSERT PostgreSQL's INSERT takes `OVERRIDING { SYSTEM | USER } VALUE` between the column list and the source, to say whether values supplied for an identity column are used. Parse it into a new `Insert::overriding` field and print it back in the same position. PostgreSQL also accepts an INSERT table alias without `AS`, so without a column list `OVERRIDING` was taken as the alias; it is now excluded there, as `BY NAME` already is. Fixes #2624. Environment: Datadog workspace Co-Authored-By: Claude Opus 5.5 --- src/ast/dml.rs | 32 +++++++++++ src/ast/mod.rs | 4 +- src/ast/spans.rs | 1 + src/dialect/snowflake.rs | 1 + src/keywords.rs | 1 + src/parser/mod.rs | 102 ++++++++++++++++++++++-------------- tests/sqlparser_postgres.rs | 67 +++++++++++++++++++++++ 7 files changed, 167 insertions(+), 41 deletions(-) diff --git a/src/ast/dml.rs b/src/ast/dml.rs index 291fc9fad9..c8d55ee6f1 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -37,6 +37,29 @@ use super::{ Values, }; +/// The `OVERRIDING` clause of a PostgreSQL `INSERT`: whether values supplied for an identity +/// column are used instead of the values its sequence would generate. +/// +/// See +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum InsertOverriding { + /// `OVERRIDING SYSTEM VALUE` + System, + /// `OVERRIDING USER VALUE` + User, +} + +impl Display for InsertOverriding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InsertOverriding::System => write!(f, "OVERRIDING SYSTEM VALUE"), + InsertOverriding::User => write!(f, "OVERRIDING USER VALUE"), + } + } +} + /// INSERT statement. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -87,6 +110,10 @@ pub struct Insert { pub partitioned: Option>, /// Columns defined after PARTITION pub after_columns: Vec, + /// PostgreSQL `OVERRIDING { SYSTEM | USER } VALUE`, between the column list and the source. + /// + /// See + pub overriding: Option, /// whether the insert has the table keyword (Hive) pub has_table_keyword: bool, /// ON INSERT @@ -225,6 +252,11 @@ impl Display for Insert { SpaceOrNewline.fmt(f)?; } + if let Some(overriding) = &self.overriding { + write!(f, "{overriding}")?; + SpaceOrNewline.fmt(f)?; + } + if let Some(output) = &self.output { write!(f, "{output}")?; SpaceOrNewline.fmt(f)?; diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9d3ee77e8c..fcd013aa97 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -88,8 +88,8 @@ pub use self::ddl::{ UserDefinedTypeStorage, ViewColumnDef, WithData, }; pub use self::dml::{ - Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, - MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, MultiTableInsertIntoClause, + Delete, Insert, InsertOverriding, Merge, MergeAction, MergeClause, MergeClauseKind, + MergeInsertExpr, MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, MultiTableInsertIntoClause, MultiTableInsertType, MultiTableInsertValue, MultiTableInsertValues, MultiTableInsertWhenClause, OutputClause, Update, }; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index cae24f959e..6c4a11c91a 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1363,6 +1363,7 @@ impl Spanned for Insert { source, partitioned, after_columns, + overriding: _, // enum, postgres specific has_table_keyword: _, // bool on, returning, diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 3ebc168645..a25f286313 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -1906,6 +1906,7 @@ fn parse_multi_table_insert( assignments: vec![], partitioned: None, after_columns: vec![], + overriding: None, has_table_keyword: false, on: None, returning: None, diff --git a/src/keywords.rs b/src/keywords.rs index deae7bbd94..e25cb34cad 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -762,6 +762,7 @@ define_keywords!( OVERLAPS, OVERLAY, OVERRIDE, + OVERRIDING, OVERWRITE, OWNED, OWNER, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6daad0f6e5..d6e5be3654 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18672,9 +18672,12 @@ impl<'a> Parser<'a> { let table = self.parse_keyword(Keyword::TABLE); let table_object = self.parse_table_object()?; - // `BY NAME` is an INSERT clause, not a table alias. + // `BY NAME` and `OVERRIDING { SYSTEM | USER } VALUE` are INSERT clauses, not table + // aliases. let table_alias = if self.dialect.supports_insert_table_alias() && !self.peek_keywords(&[Keyword::BY, Keyword::NAME]) + && !self.peek_keywords(&[Keyword::OVERRIDING, Keyword::SYSTEM]) + && !self.peek_keywords(&[Keyword::OVERRIDING, Keyword::USER]) && !self.peek_sub_query() && self .peek_one_of_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) @@ -18699,49 +18702,52 @@ impl<'a> Parser<'a> { let is_mysql = dialect_of!(self is MySqlDialect); let mut by_name = false; - let (columns, partitioned, after_columns, output, source, assignments) = if self - .parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) - { - (vec![], None, vec![], None, None, vec![]) - } else { - let (columns, partitioned, after_columns) = if !self.peek_subquery_start() { - let columns = - self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?; - - let partitioned = self.parse_insert_partition()?; - by_name = self.parse_keywords(&[Keyword::BY, Keyword::NAME]); - // Hive allows you to specify columns after partitions as well if you want. - let after_columns = if dialect_of!(self is HiveDialect) { - self.parse_parenthesized_column_list(Optional, false)? + let (columns, partitioned, after_columns, overriding, output, source, assignments) = + if self.parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) { + (vec![], None, vec![], None, None, None, vec![]) + } else { + let (columns, partitioned, after_columns) = if !self.peek_subquery_start() { + let columns = + self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?; + + let partitioned = self.parse_insert_partition()?; + by_name = self.parse_keywords(&[Keyword::BY, Keyword::NAME]); + // Hive allows you to specify columns after partitions as well if you want. + let after_columns = if dialect_of!(self is HiveDialect) { + self.parse_parenthesized_column_list(Optional, false)? + } else { + vec![] + }; + (columns, partitioned, after_columns) } else { - vec![] + Default::default() }; - (columns, partitioned, after_columns) - } else { - Default::default() - }; - let output = self.maybe_parse_output_clause()?; + let overriding = self.parse_insert_overriding()?; - let (source, assignments) = if self.peek_keyword(Keyword::FORMAT) - || self.peek_keyword(Keyword::SETTINGS) - { - (None, vec![]) - } else if self.dialect.supports_insert_set() && self.parse_keyword(Keyword::SET) { - (None, self.parse_comma_separated(Parser::parse_assignment)?) - } else { - (Some(self.parse_query()?), vec![]) - }; + let output = self.maybe_parse_output_clause()?; - ( - columns, - partitioned, - after_columns, - output, - source, - assignments, - ) - }; + let (source, assignments) = if self.peek_keyword(Keyword::FORMAT) + || self.peek_keyword(Keyword::SETTINGS) + { + (None, vec![]) + } else if self.dialect.supports_insert_set() && self.parse_keyword(Keyword::SET) + { + (None, self.parse_comma_separated(Parser::parse_assignment)?) + } else { + (Some(self.parse_query()?), vec![]) + }; + + ( + columns, + partitioned, + after_columns, + overriding, + output, + source, + assignments, + ) + }; let (format_clause, settings) = if self.dialect.supports_insert_format() { // Settings always comes before `FORMAT` for ClickHouse: @@ -18838,6 +18844,7 @@ impl<'a> Parser<'a> { partitioned, columns, after_columns, + overriding, source, assignments, has_table_keyword: table, @@ -18926,6 +18933,23 @@ impl<'a> Parser<'a> { } } + /// Parse PostgreSQL's `OVERRIDING { SYSTEM | USER } VALUE` in an `INSERT`, if present. + fn parse_insert_overriding(&mut self) -> Result, ParserError> { + if !self.parse_keyword(Keyword::OVERRIDING) { + return Ok(None); + } + let overriding = match self.expect_one_of_keywords(&[Keyword::SYSTEM, Keyword::USER])? { + Keyword::SYSTEM => InsertOverriding::System, + Keyword::USER => InsertOverriding::User, + _ => { + self.prev_token(); + return self.expected_ref("SYSTEM or USER", self.peek_token_ref()); + } + }; + self.expect_keyword_is(Keyword::VALUE)?; + Ok(Some(overriding)) + } + /// Parse an optional `PARTITION (...)` clause for INSERT statements. pub fn parse_insert_partition(&mut self) -> Result>, ParserError> { if self.parse_keyword(Keyword::PARTITION) { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index fb99851077..ffd9c9bba8 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6146,6 +6146,7 @@ fn test_simple_postgres_insert_with_alias() { assignments: vec![], partitioned: None, after_columns: vec![], + overriding: None, has_table_keyword: false, on: None, returning: None, @@ -6230,6 +6231,7 @@ fn test_simple_postgres_insert_with_alias() { assignments: vec![], partitioned: None, after_columns: vec![], + overriding: None, has_table_keyword: false, on: None, returning: None, @@ -6312,6 +6314,7 @@ fn test_simple_insert_with_quoted_alias() { assignments: vec![], partitioned: None, after_columns: vec![], + overriding: None, has_table_keyword: false, on: None, returning: None, @@ -10037,6 +10040,70 @@ fn parse_trim_from_without_characters() { pg().one_statement_parses_to("SELECT TRIM(FROM ' x ')", "SELECT TRIM(' x ')"); } +#[test] +fn parse_insert_overriding() { + for (sql, expected) in [ + ( + "INSERT INTO t (id, a) OVERRIDING SYSTEM VALUE VALUES (1, 'x')", + InsertOverriding::System, + ), + ( + "INSERT INTO t (id, a) OVERRIDING USER VALUE VALUES (1, 'x')", + InsertOverriding::User, + ), + ( + "INSERT INTO t (id, a) OVERRIDING SYSTEM VALUE SELECT id, a FROM u", + InsertOverriding::System, + ), + ( + "INSERT INTO t (id) OVERRIDING USER VALUE VALUES (1) ON CONFLICT DO NOTHING RETURNING id", + InsertOverriding::User, + ), + ] { + match pg_and_generic().verified_stmt(sql) { + Statement::Insert(Insert { overriding, .. }) => assert_eq!(overriding, Some(expected)), + statement => panic!("Expected INSERT statement, got: {statement:?}"), + } + } + + // Without a column list, PostgreSQL's implicit table alias must not swallow `OVERRIDING`. + for sql in [ + "INSERT INTO t OVERRIDING SYSTEM VALUE VALUES (1, 'x')", + "INSERT INTO t OVERRIDING USER VALUE SELECT * FROM u", + ] { + match pg().verified_stmt(sql) { + Statement::Insert(Insert { + table_alias, + overriding, + .. + }) => { + assert_eq!(table_alias, None); + assert!(overriding.is_some()); + } + statement => panic!("Expected INSERT statement, got: {statement:?}"), + } + } + pg().verified_stmt("INSERT INTO t AS x (id) OVERRIDING SYSTEM VALUE VALUES (1)"); + + // An INSERT without the clause has none. + match pg_and_generic().verified_stmt("INSERT INTO t (id) VALUES (1)") { + Statement::Insert(Insert { overriding, .. }) => assert_eq!(overriding, None), + statement => panic!("Expected INSERT statement, got: {statement:?}"), + } + + // `OVERRIDING` takes `SYSTEM` or `USER`, then `VALUE`. + for sql in [ + "INSERT INTO t (id) OVERRIDING VALUE VALUES (1)", + "INSERT INTO t (id) OVERRIDING SYSTEM VALUES (1)", + "INSERT INTO t (id) OVERRIDING DEFAULT VALUE VALUES (1)", + ] { + assert!( + pg_and_generic().parse_sql_statements(sql).is_err(), + "{sql} should not parse" + ); + } +} + #[test] fn parse_insert_by_name_keywords_as_table_and_alias() { // Without a table name, `BY NAME` is not an INSERT BY NAME clause. PostgreSQL From 5f5c495186116ad6b6a6ce88be429375191ca3cb Mon Sep 17 00:00:00 2001 From: Zvonimir Date: Tue, 13 Oct 2026 23:47:28 -0600 Subject: [PATCH 2/2] Parse OVERRIDING beside BY NAME instead of in the INSERT source tuple Adding `overriding` to the tuple the INSERT's source is parsed into re-indented the whole block, so lines this change does not touch counted as changed. It is now a local set in the branch that parses a column list, as `by_name` is, and the block is as before. `parse_insert_overriding` tries `SYSTEM` and then `USER` instead of matching the result of `expect_one_of_keywords`, which needed an arm it could never take. A test now also covers PostgreSQL's grammar not allowing `OVERRIDING` before `DEFAULT VALUES`. Environment: Datadog workspace Co-Authored-By: Claude Opus 5.5 --- src/parser/mod.rs | 93 ++++++++++++++++++------------------- tests/sqlparser_postgres.rs | 4 +- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d6e5be3654..e7f26c1ac0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18702,53 +18702,53 @@ impl<'a> Parser<'a> { let is_mysql = dialect_of!(self is MySqlDialect); let mut by_name = false; - let (columns, partitioned, after_columns, overriding, output, source, assignments) = - if self.parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) { - (vec![], None, vec![], None, None, None, vec![]) - } else { - let (columns, partitioned, after_columns) = if !self.peek_subquery_start() { - let columns = - self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?; - - let partitioned = self.parse_insert_partition()?; - by_name = self.parse_keywords(&[Keyword::BY, Keyword::NAME]); - // Hive allows you to specify columns after partitions as well if you want. - let after_columns = if dialect_of!(self is HiveDialect) { - self.parse_parenthesized_column_list(Optional, false)? - } else { - vec![] - }; - (columns, partitioned, after_columns) + let mut overriding = None; + let (columns, partitioned, after_columns, output, source, assignments) = if self + .parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) + { + (vec![], None, vec![], None, None, vec![]) + } else { + let (columns, partitioned, after_columns) = if !self.peek_subquery_start() { + let columns = + self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?; + + let partitioned = self.parse_insert_partition()?; + by_name = self.parse_keywords(&[Keyword::BY, Keyword::NAME]); + // Hive allows you to specify columns after partitions as well if you want. + let after_columns = if dialect_of!(self is HiveDialect) { + self.parse_parenthesized_column_list(Optional, false)? } else { - Default::default() + vec![] }; + (columns, partitioned, after_columns) + } else { + Default::default() + }; - let overriding = self.parse_insert_overriding()?; - - let output = self.maybe_parse_output_clause()?; + overriding = self.parse_insert_overriding()?; - let (source, assignments) = if self.peek_keyword(Keyword::FORMAT) - || self.peek_keyword(Keyword::SETTINGS) - { - (None, vec![]) - } else if self.dialect.supports_insert_set() && self.parse_keyword(Keyword::SET) - { - (None, self.parse_comma_separated(Parser::parse_assignment)?) - } else { - (Some(self.parse_query()?), vec![]) - }; + let output = self.maybe_parse_output_clause()?; - ( - columns, - partitioned, - after_columns, - overriding, - output, - source, - assignments, - ) + let (source, assignments) = if self.peek_keyword(Keyword::FORMAT) + || self.peek_keyword(Keyword::SETTINGS) + { + (None, vec![]) + } else if self.dialect.supports_insert_set() && self.parse_keyword(Keyword::SET) { + (None, self.parse_comma_separated(Parser::parse_assignment)?) + } else { + (Some(self.parse_query()?), vec![]) }; + ( + columns, + partitioned, + after_columns, + output, + source, + assignments, + ) + }; + let (format_clause, settings) = if self.dialect.supports_insert_format() { // Settings always comes before `FORMAT` for ClickHouse: // @@ -18938,13 +18938,12 @@ impl<'a> Parser<'a> { if !self.parse_keyword(Keyword::OVERRIDING) { return Ok(None); } - let overriding = match self.expect_one_of_keywords(&[Keyword::SYSTEM, Keyword::USER])? { - Keyword::SYSTEM => InsertOverriding::System, - Keyword::USER => InsertOverriding::User, - _ => { - self.prev_token(); - return self.expected_ref("SYSTEM or USER", self.peek_token_ref()); - } + let overriding = if self.parse_keyword(Keyword::SYSTEM) { + InsertOverriding::System + } else if self.parse_keyword(Keyword::USER) { + InsertOverriding::User + } else { + return self.expected_ref("SYSTEM or USER", self.peek_token_ref()); }; self.expect_keyword_is(Keyword::VALUE)?; Ok(Some(overriding)) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ffd9c9bba8..dbc61db8d8 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10091,11 +10091,13 @@ fn parse_insert_overriding() { statement => panic!("Expected INSERT statement, got: {statement:?}"), } - // `OVERRIDING` takes `SYSTEM` or `USER`, then `VALUE`. + // `OVERRIDING` takes `SYSTEM` or `USER`, then `VALUE`, and is followed by a query: PostgreSQL's + // grammar does not allow it before `DEFAULT VALUES`. for sql in [ "INSERT INTO t (id) OVERRIDING VALUE VALUES (1)", "INSERT INTO t (id) OVERRIDING SYSTEM VALUES (1)", "INSERT INTO t (id) OVERRIDING DEFAULT VALUE VALUES (1)", + "INSERT INTO t OVERRIDING SYSTEM VALUE DEFAULT VALUES", ] { assert!( pg_and_generic().parse_sql_statements(sql).is_err(),