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
45 changes: 42 additions & 3 deletions src/ast/dml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,13 @@ pub enum MergeInsertKind {
/// ```
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
Row,
/// Snowflake's name-based shorthand for inserting every target column.
///
/// Example:
/// ```sql
/// INSERT ALL BY NAME
/// ```
AllByName,
}

impl Display for MergeInsertKind {
Expand All @@ -660,6 +667,9 @@ impl Display for MergeInsertKind {
MergeInsertKind::Row => {
write!(f, "ROW")
}
MergeInsertKind::AllByName => {
write!(f, "ALL BY NAME")
}
}
}
}
Expand Down Expand Up @@ -710,6 +720,35 @@ 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.
Set(Vec<Assignment>),
/// Snowflake's name-based shorthand for updating every target column.
///
/// Example:
/// ```sql
/// UPDATE ALL BY NAME
/// ```
AllByName,
}

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::AllByName => {
write!(f, "ALL BY NAME")
}
}
}
}

/// The expression used to update rows within a `MERGE` statement.
///
/// Examples
Expand All @@ -726,8 +765,8 @@ impl Display for MergeInsertExpr {
pub struct MergeUpdateExpr {
/// The `UPDATE` token that starts the sub-expression.
pub update_token: AttachedToken,
/// The update assiment expressions
pub assignments: Vec<Assignment>,
/// The update kind.
pub kind: MergeUpdateKind,
/// `where_clause` for the update (Oralce specific)
pub update_predicate: Option<Expr>,
/// `delete_clause` for the update "delete where" (Oracle specific)
Expand All @@ -736,7 +775,7 @@ pub struct MergeUpdateExpr {

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
37 changes: 18 additions & 19 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 @@ -2540,7 +2540,8 @@ 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::AllByName => Span::empty(),
// `ROW` and `ALL BY NAME` are covered by `kind_token`.
},
]
.into_iter()
Expand All @@ -2552,9 +2553,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::AllByName => Span::empty(),
};
union_spans(
core::iter::once(self.update_token.0.span)
.chain(self.assignments.iter().map(Spanned::span))
[self.update_token.0.span, kind_span]
.into_iter()
.chain(self.update_predicate.iter().map(Spanned::span))
.chain(self.delete_predicate.iter().map(Spanned::span)),
)
Expand Down Expand Up @@ -2934,13 +2939,7 @@ WHERE id = 1
clauses[1].when_token.0.span,
Span::new(Location::new(12, 17), Location::new(12, 21))
);
if let MergeAction::Update(MergeUpdateExpr {
update_token,
assignments: _,
update_predicate: _,
delete_predicate: _,
}) = &clauses[1].action
{
if let MergeAction::Update(MergeUpdateExpr { update_token, .. }) = &clauses[1].action {
assert_eq!(
update_token.0.span,
Span::new(Location::new(13, 13), Location::new(13, 19))
Expand Down
44 changes: 30 additions & 14 deletions src/parser/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ 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},
dialect::{BigQueryDialect, GenericDialect, MySqlDialect, SnowflakeDialect},
keywords::Keyword,
parser::IsOptional,
tokenizer::TokenWithSpan,
Expand Down Expand Up @@ -119,8 +119,14 @@ 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 dialect_of!(self is SnowflakeDialect)
&& self.parse_keywords(&[Keyword::ALL, Keyword::BY, Keyword::NAME])
{
MergeUpdateKind::AllByName
} else {
self.expect_keyword_is(Keyword::SET)?;
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 +140,7 @@ impl Parser<'_> {
};
MergeAction::Update(MergeUpdateExpr {
update_token: update_token.into(),
assignments,
kind,
update_predicate,
delete_predicate,
})
Expand Down Expand Up @@ -168,17 +174,27 @@ 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)
let (columns, kind, kind_token) = if dialect_of!(self is SnowflakeDialect)
&& self.parse_keywords(&[Keyword::ALL, Keyword::BY, Keyword::NAME])
{
(MergeInsertKind::Row, self.get_current_token().clone())
(
vec![],
MergeInsertKind::AllByName,
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 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)
};
(columns, kind, kind_token)
};
let insert_predicate = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
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
23 changes: 23 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ fn parse_order_by_all() {
);
}

#[test]
fn parse_merge_all_by_name() {
let sql = "MERGE INTO target AS t USING source AS s ON t.id = s.id WHEN MATCHED THEN UPDATE ALL BY NAME WHEN NOT MATCHED THEN INSERT ALL BY NAME";
let Statement::Merge(merge) = snowflake().verified_stmt(sql) else {
unreachable!();
};

assert!(matches!(
merge.clauses[0].action,
MergeAction::Update(MergeUpdateExpr {
kind: MergeUpdateKind::AllByName,
..
})
));
assert!(matches!(
merge.clauses[1].action,
MergeAction::Insert(MergeInsertExpr {
kind: MergeInsertKind::AllByName,
..
})
));
}

#[test]
fn test_snowflake_create_table_timestamp_ntz_precision_ctas_values() {
let sql = "CREATE TABLE t (x TIMESTAMP_NTZ(3)) AS SELECT * FROM VALUES ('2025-04-09T21:11:23')";
Expand Down
Loading