Support Snowflake Update-From-Select (#1604)

Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
Yuval Shkolar 2024-12-24 17:00:59 +02:00 committed by GitHub
parent 14cefc47ed
commit 024a878ee7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 59 additions and 16 deletions

View file

@ -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::{
@ -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
@ -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 {

View file

@ -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),
}

View file

@ -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.
@ -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};

View file

@ -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`

View file

@ -11791,15 +11791,20 @@ 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()?;
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_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 from_before_set.is_none() && self.parse_keyword(Keyword::FROM) {
Some(UpdateTableFromKind::AfterSet(self.parse_table_and_joins()?))
} else {
from_before_set
};
let selection = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
} else {

View file

@ -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 {
@ -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"),
@ -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()
);
}