Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
56 changes: 52 additions & 4 deletions src/ast/dml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,14 @@ pub enum MergeInsertKind {
/// ```
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
Row,
/// The insert expression uses the `*` shorthand to insert all columns.
///
/// Example:
/// ```sql
/// INSERT *
/// ```
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html)
Star,
Comment thread
finchxxia marked this conversation as resolved.
Outdated
}

impl Display for MergeInsertKind {
Expand All @@ -660,6 +668,9 @@ impl Display for MergeInsertKind {
MergeInsertKind::Row => {
write!(f, "ROW")
}
MergeInsertKind::Star => {
write!(f, "*")
}
}
}
}
Expand Down Expand Up @@ -710,33 +721,70 @@ impl Display for MergeInsertExpr {
}
}

/// The kind of update used within a `MERGE` statement.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MergeUpdateKind {
/// Standard update with explicit assignments.
///
/// Example:
/// ```sql
/// UPDATE SET quantity = source.quantity, name = source.name
/// ```
Set(Vec<Assignment>),
/// The `*` shorthand to update all columns from the source.
///
/// Example:
/// ```sql
/// UPDATE SET *
/// ```
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html)
Star,
}

impl Display for MergeUpdateKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MergeUpdateKind::Set(assignments) => {
write!(f, "SET {}", display_comma_separated(assignments))
}
MergeUpdateKind::Star => {
write!(f, "SET *")
}
}
}
}

/// The expression used to update rows within a `MERGE` statement.
///
/// Examples
/// ```sql
/// UPDATE SET quantity = T.quantity + S.quantity
/// UPDATE SET *
/// ```
///
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MergeUpdateExpr {
/// The `UPDATE` token that starts the sub-expression.
pub update_token: AttachedToken,
/// The update assiment expressions
pub assignments: Vec<Assignment>,
/// `where_clause` for the update (Oralce specific)
/// The kind of update: explicit assignments or `*` shorthand.
pub kind: MergeUpdateKind,
/// `where_clause` for the update (Oracle specific)
pub update_predicate: Option<Expr>,
/// `delete_clause` for the update "delete where" (Oracle specific)
pub delete_predicate: Option<Expr>,
}

impl Display for MergeUpdateExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SET {}", display_comma_separated(&self.assignments))?;
write!(f, "{}", self.kind)?;
if let Some(predicate) = self.update_predicate.as_ref() {
write!(f, " WHERE {predicate}")?;
}
Expand Down
6 changes: 3 additions & 3 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ pub use self::ddl::{
};
pub use self::dml::{
Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
MergeInsertKind, MergeUpdateExpr, MultiTableInsertIntoClause, MultiTableInsertType,
MultiTableInsertValue, MultiTableInsertValues, MultiTableInsertWhenClause, OutputClause,
Update,
MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, MultiTableInsertIntoClause,
MultiTableInsertType, MultiTableInsertValue, MultiTableInsertValues,
MultiTableInsertWhenClause, OutputClause, Update,
};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Expand Down
28 changes: 16 additions & 12 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ use super::{
IfStatement, IlikeSelectItem, IndexColumn, Insert, Interpolate, InterpolateExpr, Join,
JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
MergeInsertKind, MergeUpdateExpr, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
OrderByExpr, OrderByKind, OutputClause, Parens, Partition, PartitionBoundValue,
PivotValueSource, ProjectionSelect, Query, RaiseStatement, RaiseStatementValue,
ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef, WhileStatement,
WildcardAdditionalOptions, With, WithFill,
MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, NamedParenthesizedList,
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, Parens, Partition,
PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, RaiseStatement,
RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef,
WhileStatement, WildcardAdditionalOptions, With, WithFill,
};

/// Given an iterator of spans, return the [Span::union] of all spans.
Expand Down Expand Up @@ -2531,7 +2531,7 @@ impl Spanned for MergeInsertExpr {
self.kind_token.0.span,
match self.kind {
MergeInsertKind::Values(ref values) => values.span(),
MergeInsertKind::Row => Span::empty(), // ~ covered by `kind_token`
MergeInsertKind::Row | MergeInsertKind::Star => Span::empty(),
},
]
.into_iter()
Expand All @@ -2543,9 +2543,13 @@ impl Spanned for MergeInsertExpr {

impl Spanned for MergeUpdateExpr {
fn span(&self) -> Span {
let kind_span = match &self.kind {
MergeUpdateKind::Set(assignments) => union_spans(assignments.iter().map(Spanned::span)),
MergeUpdateKind::Star => Span::empty(),
};
union_spans(
core::iter::once(self.update_token.0.span)
.chain(self.assignments.iter().map(Spanned::span))
.chain(core::iter::once(kind_span))
.chain(self.update_predicate.iter().map(Spanned::span))
.chain(self.delete_predicate.iter().map(Spanned::span)),
)
Expand Down Expand Up @@ -2927,7 +2931,7 @@ WHERE id = 1
);
if let MergeAction::Update(MergeUpdateExpr {
update_token,
assignments: _,
kind: _,
update_predicate: _,
delete_predicate: _,
}) = &clauses[1].action
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,9 @@ impl Dialect for DatabricksDialect {
fn supports_select_item_multi_column_alias(&self) -> bool {
true
}

/// See <https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html>
fn supports_merge_star_syntax(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,8 @@ impl Dialect for GenericDialect {
fn supports_xml_expressions(&self) -> bool {
true
}

fn supports_merge_star_syntax(&self) -> bool {
true
}
}
7 changes: 7 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,13 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports `UPDATE SET *` and `INSERT *`
/// shorthand syntax in `MERGE` statements.
/// <https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html>
fn supports_merge_star_syntax(&self) -> bool {
false
}

/// Returns true if the dialect supports the `LISTEN`, `UNLISTEN` and `NOTIFY` statements
fn supports_listen_notify(&self) -> bool {
false
Expand Down
69 changes: 44 additions & 25 deletions src/parser/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ use alloc::{boxed::Box, format, vec, vec::Vec};
use crate::{
ast::{
Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, MergeInsertKind,
MergeUpdateExpr, ObjectName, OutputClause, SetExpr,
MergeUpdateExpr, MergeUpdateKind, ObjectName, OutputClause, SetExpr,
},
dialect::{BigQueryDialect, GenericDialect, MySqlDialect},
keywords::Keyword,
parser::IsOptional,
tokenizer::Token,
tokenizer::TokenWithSpan,
};

Expand Down Expand Up @@ -120,7 +121,13 @@ impl Parser<'_> {

let update_token = self.get_current_token().clone();
self.expect_keyword_is(Keyword::SET)?;
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
let kind = if self.dialect.supports_merge_star_syntax()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looks like we should be able to drop the dialect method and let the parser accept the * syntax anytime it shows up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looks like we should be able to drop the dialect method and let the parser accept the * syntax anytime it shows up?

done

&& self.consume_token(&Token::Mul)
{
MergeUpdateKind::Star
} else {
MergeUpdateKind::Set(self.parse_comma_separated(Parser::parse_assignment)?)
};
let update_predicate = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
} else {
Expand All @@ -134,7 +141,7 @@ impl Parser<'_> {
};
MergeAction::Update(MergeUpdateExpr {
update_token: update_token.into(),
assignments,
kind,
update_predicate,
delete_predicate,
})
Expand Down Expand Up @@ -167,32 +174,44 @@ impl Parser<'_> {
};

let insert_token = self.get_current_token().clone();
let is_mysql = dialect_of!(self is MySqlDialect);

let columns = self.parse_merge_clause_insert_columns(is_mysql)?;
let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
&& self.parse_keyword(Keyword::ROW)
if self.dialect.supports_merge_star_syntax() && self.consume_token(&Token::Mul)
{
(MergeInsertKind::Row, self.get_current_token().clone())
} else {
self.expect_keyword_is(Keyword::VALUES)?;
let values_token = self.get_current_token().clone();
let values = self.parse_values(is_mysql, false)?;
(MergeInsertKind::Values(values), values_token)
};
let insert_predicate = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
let star_token = self.get_current_token().clone();
MergeAction::Insert(MergeInsertExpr {
insert_token: insert_token.into(),
columns: vec![],
kind_token: star_token.into(),
kind: MergeInsertKind::Star,
insert_predicate: None,
})
} else {
None
};
let is_mysql = dialect_of!(self is MySqlDialect);
let columns = self.parse_merge_clause_insert_columns(is_mysql)?;
let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
&& self.parse_keyword(Keyword::ROW)
{
(MergeInsertKind::Row, self.get_current_token().clone())
} else {
self.expect_keyword_is(Keyword::VALUES)?;
let values_token = self.get_current_token().clone();
let values = self.parse_values(is_mysql, false)?;
(MergeInsertKind::Values(values), values_token)
};
let insert_predicate = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
} else {
None
};

MergeAction::Insert(MergeInsertExpr {
insert_token: insert_token.into(),
columns,
kind_token: kind_token.into(),
kind,
insert_predicate,
})
MergeAction::Insert(MergeInsertExpr {
insert_token: insert_token.into(),
columns,
kind_token: kind_token.into(),
kind,
insert_predicate,
})
}
}
_ => {
return parser_err!(
Expand Down
4 changes: 2 additions & 2 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,7 @@ fn parse_merge() {
});
let update_action = MergeAction::Update(MergeUpdateExpr {
update_token: AttachedToken::empty(),
assignments: vec![
kind: MergeUpdateKind::Set(vec![
Assignment {
target: AssignmentTarget::ColumnName(ObjectName::from(vec![Ident::new("a")])),
value: Expr::value(number("1")),
Expand All @@ -1839,7 +1839,7 @@ fn parse_merge() {
target: AssignmentTarget::ColumnName(ObjectName::from(vec![Ident::new("b")])),
value: Expr::value(number("2")),
},
],
]),
update_predicate: None,
delete_predicate: None,
});
Expand Down
4 changes: 2 additions & 2 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10157,7 +10157,7 @@ fn parse_merge() {
}),
action: MergeAction::Update(MergeUpdateExpr {
update_token: AttachedToken::empty(),
assignments: vec![
kind: MergeUpdateKind::Set(vec![
Assignment {
target: AssignmentTarget::ColumnName(ObjectName::from(vec![
Ident::new("dest"),
Expand All @@ -10178,7 +10178,7 @@ fn parse_merge() {
Ident::new("G"),
]),
},
],
]),
update_predicate: None,
delete_predicate: None,
}),
Expand Down
Loading
Loading