You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/10/14 17:11:25 UTC
(datafusion-sqlparser-rs) 01/01: Postgres: support OVERRIDING { SYSTEM | USER } VALUE in INSERT (#2626)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-2626-dacdbc99cd16c335fccd353ba41f6c18a7cd73b8
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 159c667f5808d02e4212896463bdb7d69eb62575
Author: Zvonimir <zv...@datadoghq.com>
AuthorDate: Wed Oct 14 17:11:03 2026 +0000
Postgres: support OVERRIDING { SYSTEM | USER } VALUE in INSERT (#2626)
Co-authored-by: Claude Opus 5.5 <no...@anthropic.com>
---
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 | 25 +++++++++++++++-
tests/sqlparser_postgres.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 130 insertions(+), 3 deletions(-)
diff --git a/src/ast/dml.rs b/src/ast/dml.rs
index 291fc9fa..c8d55ee6 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 <https://www.postgresql.org/docs/current/sql-insert.html>
+#[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<Vec<Expr>>,
/// Columns defined after PARTITION
pub after_columns: Vec<Ident>,
+ /// PostgreSQL `OVERRIDING { SYSTEM | USER } VALUE`, between the column list and the source.
+ ///
+ /// See <https://www.postgresql.org/docs/current/sql-insert.html>
+ pub overriding: Option<InsertOverriding>,
/// 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 9d3ee77e..fcd013aa 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 cae24f95..6c4a11c9 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 3ebc1686..a25f2863 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 deae7bbd..e25cb34c 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 6daad0f6..e7f26c1a 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,6 +18702,7 @@ impl<'a> Parser<'a> {
let is_mysql = dialect_of!(self is MySqlDialect);
let mut by_name = false;
+ let mut overriding = None;
let (columns, partitioned, after_columns, output, source, assignments) = if self
.parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES])
{
@@ -18721,6 +18725,8 @@ impl<'a> Parser<'a> {
Default::default()
};
+ overriding = self.parse_insert_overriding()?;
+
let output = self.maybe_parse_output_clause()?;
let (source, assignments) = if self.peek_keyword(Keyword::FORMAT)
@@ -18838,6 +18844,7 @@ impl<'a> Parser<'a> {
partitioned,
columns,
after_columns,
+ overriding,
source,
assignments,
has_table_keyword: table,
@@ -18926,6 +18933,22 @@ impl<'a> Parser<'a> {
}
}
+ /// Parse PostgreSQL's `OVERRIDING { SYSTEM | USER } VALUE` in an `INSERT`, if present.
+ fn parse_insert_overriding(&mut self) -> Result<Option<InsertOverriding>, ParserError> {
+ if !self.parse_keyword(Keyword::OVERRIDING) {
+ return Ok(None);
+ }
+ 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))
+ }
+
/// Parse an optional `PARTITION (...)` clause for INSERT statements.
pub fn parse_insert_partition(&mut self) -> Result<Option<Vec<Expr>>, ParserError> {
if self.parse_keyword(Keyword::PARTITION) {
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index fb998510..dbc61db8 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,72 @@ 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`, 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(),
+ "{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
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org