Skip to content

Commit

Permalink
Support Snowflake Update-From-Select (#1604)
Browse files Browse the repository at this point in the history
Co-authored-by: Ifeanyi Ubah <[email protected]>
  • Loading branch information
yuval-illumex and iffyio authored Dec 24, 2024
1 parent 14cefc4 commit 024a878
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 15 deletions.
11 changes: 7 additions & 4 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ pub use self::query::{
TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableSample,
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With,
WithFill,
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
WildcardAdditionalOptions, With, WithFill,
};

pub use self::trigger::{
Expand Down Expand Up @@ -2473,7 +2473,7 @@ pub enum Statement {
/// Column assignments
assignments: Vec<Assignment>,
/// Table which provide value to be set
from: Option<TableWithJoins>,
from: Option<UpdateTableFromKind>,
/// WHERE
selection: Option<Expr>,
/// RETURNING
Expand Down Expand Up @@ -3745,10 +3745,13 @@ impl fmt::Display for Statement {
write!(f, "{or} ")?;
}
write!(f, "{table}")?;
if let Some(UpdateTableFromKind::BeforeSet(from)) = from {
write!(f, " FROM {from}")?;
}
if !assignments.is_empty() {
write!(f, " SET {}", display_comma_separated(assignments))?;
}
if let Some(from) = from {
if let Some(UpdateTableFromKind::AfterSet(from)) = from {
write!(f, " FROM {from}")?;
}
if let Some(selection) = selection {
Expand Down
13 changes: 13 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2790,3 +2790,16 @@ impl fmt::Display for ValueTableMode {
}
}
}

/// The `FROM` clause of an `UPDATE TABLE` 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 UpdateTableFromKind {
/// Update Statment where the 'FROM' clause is before the 'SET' keyword (Supported by Snowflake)
/// For Example: `UPDATE FROM t1 SET t1.name='aaa'`
BeforeSet(TableWithJoins),
/// Update Statment where the 'FROM' clause is after the 'SET' keyword (Which is the standard way)
/// For Example: `UPDATE SET t1.name='aaa' FROM t1`
AfterSet(TableWithJoins),
}
13 changes: 11 additions & 2 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ use super::{
OrderBy, OrderByExpr, Partition, PivotValueSource, ProjectionSelect, Query, ReferentialAction,
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
TableConstraint, TableFactor, TableOptionsClustered, TableWithJoins, Use, Value, Values,
ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
TableConstraint, TableFactor, TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use,
Value, Values, ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
};

/// Given an iterator of spans, return the [Span::union] of all spans.
Expand Down Expand Up @@ -2106,6 +2106,15 @@ impl Spanned for SelectInto {
}
}

impl Spanned for UpdateTableFromKind {
fn span(&self) -> Span {
match self {
UpdateTableFromKind::BeforeSet(from) => from.span(),
UpdateTableFromKind::AfterSet(from) => from.span(),
}
}
}

#[cfg(test)]
pub mod tests {
use crate::dialect::{Dialect, GenericDialect, SnowflakeDialect};
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
// Reserved for Snowflake table sample
Keyword::SAMPLE,
Keyword::TABLESAMPLE,
Keyword::FROM,
];

/// Can't be used as a column alias, so that `SELECT <expr> alias`
Expand Down
15 changes: 10 additions & 5 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11791,14 +11791,19 @@ impl<'a> Parser<'a> {
pub fn parse_update(&mut self) -> Result<Statement, ParserError> {
let or = self.parse_conflict_clause();
let table = self.parse_table_and_joins()?;
let from_before_set = if self.parse_keyword(Keyword::FROM) {
Some(UpdateTableFromKind::BeforeSet(
self.parse_table_and_joins()?,
))
} else {
None
};
self.expect_keyword(Keyword::SET)?;
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
let from = if self.parse_keyword(Keyword::FROM)
&& dialect_of!(self is GenericDialect | PostgreSqlDialect | DuckDbDialect | BigQueryDialect | SnowflakeDialect | RedshiftSqlDialect | MsSqlDialect | SQLiteDialect )
{
Some(self.parse_table_and_joins()?)
let from = if from_before_set.is_none() && self.parse_keyword(Keyword::FROM) {
Some(UpdateTableFromKind::AfterSet(self.parse_table_and_joins()?))
} else {
None
from_before_set
};
let selection = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
Expand Down
20 changes: 16 additions & 4 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ fn parse_update_set_from() {
target: AssignmentTarget::ColumnName(ObjectName(vec![Ident::new("name")])),
value: Expr::CompoundIdentifier(vec![Ident::new("t2"), Ident::new("name")])
}],
from: Some(TableWithJoins {
from: Some(UpdateTableFromKind::AfterSet(TableWithJoins {
relation: TableFactor::Derived {
lateral: false,
subquery: Box::new(Query {
Expand Down Expand Up @@ -417,8 +417,8 @@ fn parse_update_set_from() {
columns: vec![],
})
},
joins: vec![],
}),
joins: vec![]
})),
selection: Some(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("t1"),
Expand Down Expand Up @@ -12577,9 +12577,21 @@ fn overflow() {
let statement = statements.pop().unwrap();
assert_eq!(statement.to_string(), sql);
}

#[test]
fn parse_select_without_projection() {
let dialects = all_dialects_where(|d| d.supports_empty_projections());
dialects.verified_stmt("SELECT FROM users");
}

#[test]
fn parse_update_from_before_select() {
all_dialects()
.verified_stmt("UPDATE t1 FROM (SELECT name, id FROM t1 GROUP BY id) AS t2 SET name = t2.name WHERE t1.id = t2.id");

let query =
"UPDATE t1 FROM (SELECT name, id FROM t1 GROUP BY id) AS t2 SET name = t2.name FROM (SELECT name from t2) AS t2";
assert_eq!(
ParserError::ParserError("Expected: end of statement, found: FROM".to_string()),
parse_sql_statements(query).unwrap_err()
);
}

0 comments on commit 024a878

Please sign in to comment.