diff --git a/src/ast/dml.rs b/src/ast/dml.rs index c2fe2ffd6..5fed3e970 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -62,6 +62,8 @@ pub struct Insert { pub table_alias: Option, /// COLUMNS pub columns: Vec, + /// BY NAME + pub by_name: bool, /// Overwrite (Hive) pub overwrite: bool, /// A SQL query that specifies what to insert @@ -201,6 +203,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 e1fa0c752..6ac722a8c 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 0bedb12a5..0bbd859ec 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 2d51d4717..f5b060308 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18404,7 +18404,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]) @@ -18428,6 +18430,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]) { @@ -18438,6 +18441,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)? @@ -18562,6 +18566,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 bad083602..aed7216ec 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -19665,3 +19665,18 @@ 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!(), + } +} diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index 7c582546f..bf98f2f77 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -737,3 +737,34 @@ fn parse_cte_without_as() { .parse_sql_statements("WITH cte (SELECT 1) SELECT * FROM cte") .is_err()); } + +#[test] +fn test_databricks_insert_by_name() { + match databricks_and_generic().verified_stmt("INSERT INTO target BY NAME SELECT 1 AS a") { + Statement::Insert(Insert { + by_name, + columns, + has_table_keyword, + .. + }) => { + assert!(by_name); + assert!(columns.is_empty()); + assert!(!has_table_keyword); + } + _ => unreachable!(), + } + + match 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", + ) { + Statement::Insert(Insert { + by_name, + has_table_keyword, + .. + }) => { + assert!(by_name); + assert!(has_table_keyword); + } + _ => unreachable!(), + } +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index a7128eafd..d2864be1e 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6093,6 +6093,7 @@ fn test_simple_postgres_insert_with_alias() { span: Span::empty(), }) ], + by_name: false, overwrite: false, source: Some(Box::new(Query { with: None, @@ -6173,6 +6174,7 @@ fn test_simple_postgres_insert_with_alias() { span: Span::empty(), }) ], + by_name: false, overwrite: false, source: Some(Box::new(Query { with: None, @@ -6255,6 +6257,7 @@ fn test_simple_insert_with_quoted_alias() { span: Span::empty(), }) ], + by_name: false, overwrite: false, source: Some(Box::new(Query { with: None,