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/09/14 08:18:21 UTC

(datafusion-sqlparser-rs) 01/01: Databricks: support INSERT BY NAME (#2403)

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-2403-b5950628a53de0ac1336d20cd1c618830e2706f0
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 9296011a1c2ba0905310f10c7c1287d9cc12b62c
Author: finchxxia <13...@163.com>
AuthorDate: Mon Sep 14 08:17:56 2026 +0000

    Databricks: support INSERT BY NAME (#2403)
    
    Co-authored-by: Ifeanyi Ubah <if...@yahoo.com>
    Co-authored-by: Cursor <cu...@cursor.com>
    Co-authored-by: finchxia <fi...@aviagames.com>
---
 src/ast/dml.rs                | 19 ++++++++++++++++++
 src/ast/spans.rs              |  1 +
 src/dialect/snowflake.rs      |  1 +
 src/parser/mod.rs             |  5 +++++
 tests/sqlparser_common.rs     | 45 +++++++++++++++++++++++++++++++++++++++++++
 tests/sqlparser_databricks.rs |  8 ++++++++
 tests/sqlparser_postgres.rs   | 22 +++++++++++++++++++++
 7 files changed, 101 insertions(+)

diff --git a/src/ast/dml.rs b/src/ast/dml.rs
index b804e4fc..291fc9fa 100644
--- a/src/ast/dml.rs
+++ b/src/ast/dml.rs
@@ -62,6 +62,20 @@ pub struct Insert {
     pub table_alias: Option<TableAliasWithoutColumns>,
     /// COLUMNS
     pub columns: Vec<ObjectName>,
+    /// `BY NAME` clause used by Databricks SQL.
+    ///
+    /// When present, columns from the source query are matched to columns in
+    /// the target table by name instead of by position. The syntax is:
+    ///
+    /// ```sql
+    /// INSERT INTO [TABLE] table_name
+    ///     [PARTITION (...)]
+    ///     [(column_name [, ...]) | BY NAME]
+    ///     query
+    /// ```
+    ///
+    /// See <https://docs.databricks.com/gcp/en/sql/language-manual/sql-ref-syntax-dml-insert-into>.
+    pub by_name: bool,
     /// Overwrite (Hive)
     pub overwrite: bool,
     /// A SQL query that specifies what to insert
@@ -201,6 +215,11 @@ impl Display for Insert {
             }
         }
 
+        if self.by_name {
+            write!(f, "BY NAME")?;
+            SpaceOrNewline.fmt(f)?;
+        }
+
         if !self.after_columns.is_empty() {
             write!(f, "({})", display_comma_separated(&self.after_columns))?;
             SpaceOrNewline.fmt(f)?;
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index cb06b789..7acbd7d0 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1354,6 +1354,7 @@ impl Spanned for Insert {
             table,
             table_alias,
             columns,
+            by_name: _,   // bool
             overwrite: _, // bool
             source,
             partitioned,
diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs
index 0bedb12a..0bbd859e 100644
--- a/src/dialect/snowflake.rs
+++ b/src/dialect/snowflake.rs
@@ -1871,6 +1871,7 @@ fn parse_multi_table_insert(
         table: TableObject::TableName(ObjectName(vec![])), // Not used for multi-table insert
         table_alias: None,
         columns: vec![],
+        by_name: false,
         overwrite,
         source: Some(source),
         assignments: vec![],
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index c23b2705..15f135ff 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -18481,7 +18481,9 @@ 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.
             let table_alias = if self.dialect.supports_insert_table_alias()
+                && !self.peek_keywords(&[Keyword::BY, Keyword::NAME])
                 && !self.peek_sub_query()
                 && self
                     .peek_one_of_keywords(&[Keyword::DEFAULT, Keyword::VALUES])
@@ -18505,6 +18507,7 @@ 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])
             {
@@ -18515,6 +18518,7 @@ impl<'a> Parser<'a> {
                         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)?
@@ -18639,6 +18643,7 @@ impl<'a> Parser<'a> {
                 ignore,
                 into,
                 overwrite,
+                by_name,
                 partitioned,
                 columns,
                 after_columns,
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 29b060a8..2de6062b 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -20015,3 +20015,48 @@ fn parse_function_arg_call_chain_no_exponential_blowup() {
     rx.recv_timeout(Duration::from_secs(5))
         .expect("parser should reject this quickly, not loop exponentially");
 }
+
+#[test]
+fn parse_insert_by_name() {
+    verified_stmt("INSERT INTO target BY NAME SELECT 1 AS a");
+
+    match verified_stmt("INSERT INTO target (a) BY NAME SELECT 1 AS a") {
+        Statement::Insert(Insert {
+            by_name, columns, ..
+        }) => {
+            assert!(by_name);
+            assert_eq!(columns.len(), 1);
+        }
+        _ => unreachable!(),
+    }
+
+    let dialects = all_dialects_where(|d| !d.supports_insert_table_alias());
+    match dialects.verified_stmt("INSERT INTO TABLE target PARTITION (p = 1) BY NAME SELECT 1 AS a")
+    {
+        Statement::Insert(Insert {
+            by_name,
+            has_table_keyword,
+            partitioned,
+            ..
+        }) => {
+            assert!(by_name);
+            assert!(has_table_keyword);
+            assert_eq!(partitioned.unwrap().len(), 1);
+        }
+        _ => unreachable!(),
+    }
+
+    // `BY NAME` does not shadow a table alias in dialects supporting one.
+    let dialects = all_dialects_where(|d| d.supports_insert_table_alias());
+    match dialects.verified_stmt("INSERT INTO target AS t BY NAME SELECT 1 AS a") {
+        Statement::Insert(Insert {
+            by_name,
+            table_alias,
+            ..
+        }) => {
+            assert!(by_name);
+            assert_eq!(table_alias.unwrap().alias.value, "t");
+        }
+        _ => unreachable!(),
+    }
+}
diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs
index 62068ef5..dd51bd19 100644
--- a/tests/sqlparser_databricks.rs
+++ b/tests/sqlparser_databricks.rs
@@ -738,6 +738,14 @@ fn parse_cte_without_as() {
         .is_err());
 }
 
+#[test]
+fn test_databricks_insert_by_name() {
+    databricks_and_generic().verified_stmt("INSERT INTO target BY NAME SELECT 1 AS a");
+    databricks_and_generic().verified_stmt(
+        "INSERT INTO TABLE lakehouse.dwd.dwd_event_quality_sla_metric_di BY NAME WITH day AS (SELECT 1 AS event_data_id) SELECT event_data_id FROM day",
+    );
+}
+
 #[test]
 fn parse_databricks_query_entry_points() {
     databricks().verified_stmt("CREATE TABLE t (attrs MAP<STRING, ARRAY<INT>>)");
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 8f390b5e..d71e49b2 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6121,6 +6121,7 @@ fn test_simple_postgres_insert_with_alias() {
                     span: Span::empty(),
                 })
             ],
+            by_name: false,
             overwrite: false,
             source: Some(Box::new(Query {
                 with: None,
@@ -6201,6 +6202,7 @@ fn test_simple_postgres_insert_with_alias() {
                     span: Span::empty(),
                 })
             ],
+            by_name: false,
             overwrite: false,
             source: Some(Box::new(Query {
                 with: None,
@@ -6283,6 +6285,7 @@ fn test_simple_insert_with_quoted_alias() {
                     span: Span::empty(),
                 })
             ],
+            by_name: false,
             overwrite: false,
             source: Some(Box::new(Query {
                 with: None,
@@ -9931,3 +9934,22 @@ fn parse_non_reserved_keywords_as_table_alias() {
         ));
     }
 }
+
+#[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
+    // treats `BY` as the table name and `NAME` as its implicit table alias.
+    match pg().verified_stmt("INSERT INTO BY NAME SELECT 1 AS a") {
+        Statement::Insert(Insert {
+            table: TableObject::TableName(table),
+            table_alias: Some(table_alias),
+            by_name,
+            ..
+        }) => {
+            assert_eq!(table.to_string(), "BY");
+            assert_eq!(table_alias.alias.value, "NAME");
+            assert!(!by_name);
+        }
+        statement => panic!("Expected INSERT statement, got: {statement:?}"),
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org