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