Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/ast/dml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)?;
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,7 @@ impl Spanned for Insert {
table,
table_alias,
columns,
by_name: _, // bool
overwrite: _, // bool
source,
partitioned,
Expand Down
1 change: 1 addition & 0 deletions src/dialect/snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![],
Expand Down
5 changes: 5 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Comment thread
finchxxia marked this conversation as resolved.
&& !self.peek_sub_query()
&& self
.peek_one_of_keywords(&[Keyword::DEFAULT, Keyword::VALUES])
Expand All @@ -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])
{
Expand All @@ -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)?
Expand Down Expand Up @@ -18639,6 +18643,7 @@ impl<'a> Parser<'a> {
ignore,
into,
overwrite,
by_name,
partitioned,
columns,
after_columns,
Expand Down
45 changes: 45 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Comment thread
finchxxia marked this conversation as resolved.

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!(),
}
}
8 changes: 8 additions & 0 deletions tests/sqlparser_databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>)");
Expand Down
22 changes: 22 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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") {
Comment thread
finchxxia marked this conversation as resolved.
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:?}"),
}
}
Loading