Skip to content

Commit 506cc2e

Browse files
committed
fix(syntax): name DELETE USING subqueries, semantic views and FIRST/LAST join aliases as what they are
A DELETE USING subquery was a table with an empty name and the query in its hints; it is now a derived table in using_items, as a FROM subquery is, with its alias, column aliases and AS spelling. A semantic view's name was a column node; it is now a table reference, and an explicit AS in its clauses is recorded. On a join with no ON or USING, FIRST or LAST followed by parentheses is the joined table's alias and column aliases, as Snowflake reads it, not a read-order clause.
1 parent 48f2895 commit 506cc2e

4 files changed

Lines changed: 191 additions & 20 deletions

File tree

‎crates/sqlquack-syntax-snowflake/src/parser/_helpers/queries/joins.rs‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,36 @@ impl Parser {
3636
Ok((Some(side.to_owned()), Some(alias)))
3737
}
3838

39+
/// The read-order clause after a join; with no ON, USING or MATCH_CONDITION (`conditionless`), Snowflake reads `FIRST ( x )` or `LAST ( x )` as the joined table's alias and column alias instead.
40+
fn parse_snowflake_join_read_order(
41+
&mut self,
42+
table: Expression,
43+
conditionless: bool,
44+
) -> Result<(Expression, Option<String>, Option<Identifier>)> {
45+
let first_last_at = self.current;
46+
let (first_last, first_last_alias) = self.parse_snowflake_join_first_last()?;
47+
if !conditionless || first_last.is_none() {
48+
return Ok((table, first_last, first_last_alias));
49+
}
50+
let keyword = &self.tokens[first_last_at];
51+
let mut alias = Identifier::new(keyword.text.to_string());
52+
alias.span = Some(keyword.span);
53+
let columns: Vec<Identifier> = first_last_alias.iter().cloned().collect();
54+
Ok(match table {
55+
Expression::Table(mut reference) if reference.alias.is_none() => {
56+
reference.alias = Some(alias);
57+
reference.column_aliases = columns;
58+
(Expression::Table(reference), None, None)
59+
}
60+
Expression::Subquery(mut derived) if derived.alias.is_none() => {
61+
derived.alias = Some(alias);
62+
derived.column_aliases = columns;
63+
(Expression::Subquery(derived), None, None)
64+
}
65+
other => (other, first_last, first_last_alias),
66+
})
67+
}
68+
3969
/// Parses joins; chained ON/USING clauses bind right to left, as `a JOIN b JOIN c ON x ON y`.
4070
pub(crate) fn parse_joins(&mut self) -> Result<Vec<Join>> {
4171
let mut joins = Vec::with_capacity(2);
@@ -127,7 +157,9 @@ impl Parser {
127157
} else {
128158
(None, Vec::new())
129159
};
130-
let (first_last, first_last_alias) = self.parse_snowflake_join_first_last()?;
160+
let conditionless = on.is_none() && using.is_empty() && match_condition.is_none();
161+
let (table, first_last, first_last_alias) =
162+
self.parse_snowflake_join_read_order(table, conditionless)?;
131163

132164
joins.push(Join {
133165
using_alias: None,

‎crates/sqlquack-syntax-snowflake/src/parser/_helpers/statements/dml.rs‎

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use sqlquack_syntax::expressions::names::models::TableRef;
1414
use sqlquack_syntax::expressions::operators::models::ConditionalInsert;
1515
use sqlquack_syntax::expressions::operators::models::MultitableInserts;
1616
use sqlquack_syntax::expressions::queries::models::Join;
17+
use sqlquack_syntax::expressions::queries::models::Subquery;
1718
use sqlquack_syntax::expressions::queries::models::Where;
1819
use sqlquack_syntax::expressions::schema_objects::models::FunctionSecurity;
1920
use sqlquack_syntax::expressions::statements::models::Delete;
@@ -110,8 +111,8 @@ impl Parser {
110111

111112
let mut joins = self.parse_joins()?;
112113

113-
let using;
114-
(using, joins) = self.parse_delete_using(joins)?;
114+
let (using, using_items);
115+
(using, using_items, joins) = self.parse_delete_using(joins)?;
115116

116117
let output = if early_output.is_some() {
117118
early_output
@@ -148,7 +149,7 @@ impl Parser {
148149
};
149150

150151
Ok(Expression::Delete(Box::new(Delete {
151-
using_items: Vec::new(),
152+
using_items,
152153
using_joins: Vec::new(),
153154
table,
154155
hint,
@@ -208,8 +209,31 @@ impl Parser {
208209
)
209210
}
210211

211-
fn parse_delete_using(&mut self, mut joins: Vec<Join>) -> Result<(Vec<TableRef>, Vec<Join>)> {
212-
let mut using: Vec<TableRef> = Vec::new();
212+
/// The tables after DELETE's USING, or, when any is a derived table, every item instead; then the joins after them.
213+
fn parse_delete_using(
214+
&mut self,
215+
joins: Vec<Join>,
216+
) -> Result<(Vec<TableRef>, Vec<Expression>, Vec<Join>)> {
217+
let (items, joins) = self.parse_delete_using_items(joins)?;
218+
let tables: Option<Vec<TableRef>> = items
219+
.iter()
220+
.map(|item| match item {
221+
Expression::Table(table) => Some((**table).clone()),
222+
_ => None,
223+
})
224+
.collect();
225+
Ok(match tables {
226+
Some(tables) => (tables, Vec::new(), joins),
227+
None => (Vec::new(), items, joins),
228+
})
229+
}
230+
231+
/// The items after DELETE's USING, each a table or a derived table, and the joins after them.
232+
fn parse_delete_using_items(
233+
&mut self,
234+
mut joins: Vec<Join>,
235+
) -> Result<(Vec<Expression>, Vec<Join>)> {
236+
let mut using: Vec<Expression> = Vec::new();
213237
if self.match_token(TokenType::Using) {
214238
loop {
215239
if self.check(TokenType::LParen) {
@@ -237,6 +261,11 @@ impl Parser {
237261
self.parse_primary()?
238262
};
239263
let using_alias = if self.match_token(TokenType::As) {
264+
let token = self.previous();
265+
let keyword = match self.source.is_some() {
266+
true => self.source_text_range(token.span.start, token.span.end),
267+
false => token.text.to_string(),
268+
};
240269
let alias_name = self.expect_identifier_with_quoted()?;
241270
let col_aliases = if self.match_token(TokenType::LParen) {
242271
let aliases = self.parse_identifier_list()?;
@@ -245,24 +274,45 @@ impl Parser {
245274
} else {
246275
Vec::new()
247276
};
248-
Some((alias_name, col_aliases))
277+
Some((keyword, alias_name, col_aliases))
249278
} else {
250279
None
251280
};
252-
let mut tref = TableRef::new("");
253-
if let Some((alias_name, col_aliases)) = using_alias {
254-
tref.alias = Some(alias_name);
255-
tref.alias_explicit_as = true;
256-
tref.column_aliases = col_aliases;
281+
let mut derived = match subquery {
282+
Expression::Subquery(derived) => *derived,
283+
Expression::Paren(values) => Subquery {
284+
this: values.this,
285+
alias: None,
286+
column_aliases: Vec::new(),
287+
alias_explicit_as: false,
288+
alias_keyword: None,
289+
order_by: None,
290+
limit: None,
291+
offset: None,
292+
distribute_by: None,
293+
sort_by: None,
294+
cluster_by: None,
295+
lateral: false,
296+
modifiers_inside: false,
297+
trailing_comments: values.trailing_comments,
298+
inferred_type: None,
299+
span: values.span,
300+
},
301+
_ => return Err(self.parse_error("Expected a query in DELETE USING ( )")),
302+
};
303+
if let Some((keyword, alias_name, col_aliases)) = using_alias {
304+
derived.alias = Some(alias_name);
305+
derived.alias_explicit_as = true;
306+
derived.alias_keyword = Some(keyword);
307+
derived.column_aliases = col_aliases;
257308
}
258-
tref.hints = vec![subquery];
259-
using.push(tref);
309+
using.push(Expression::Subquery(Box::new(derived)));
260310
} else {
261311
let using_table = self.expect_identifier_with_quoted()?;
262312
let mut using_ref = TableRef::from_identifier(using_table);
263313

264314
if self.check_join_keyword() {
265-
using.push(using_ref);
315+
using.push(Expression::Table(Box::new(using_ref)));
266316
let mut using_joins = self.parse_joins()?;
267317
joins.append(&mut using_joins);
268318
break;
@@ -277,7 +327,7 @@ impl Parser {
277327
{
278328
using_ref.alias = Some(self.expect_identifier_with_quoted()?);
279329
}
280-
using.push(using_ref);
330+
using.push(Expression::Table(Box::new(using_ref)));
281331
}
282332
if !self.match_token(TokenType::Comma) {
283333
break;

‎crates/sqlquack-syntax-snowflake/src/parser/_helpers/statements/sessions.rs‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,7 +1841,10 @@ impl Parser {
18411841
if self.is_snowflake() && self.check(TokenType::String) {
18421842
return Err(self.parse_error("Expected a semantic view name"));
18431843
}
1844-
let this = self.parse_primary()?;
1844+
let this = match self.is_snowflake() {
1845+
true => Expression::Table(Box::new(self.parse_object_name()?)),
1846+
false => self.parse_primary()?,
1847+
};
18451848

18461849
let mut metrics = None;
18471850
let mut dimensions = None;
@@ -1916,16 +1919,24 @@ impl Parser {
19161919
|| !crate::parser::constants::SNOWFLAKE_SEMANTIC_VIEW_CLAUSES
19171920
.iter()
19181921
.any(|word| self.peek_text().eq_ignore_ascii_case(word)));
1919-
if bare_alias || self.match_token(TokenType::As) {
1922+
let explicit_as = !bare_alias && self.match_token(TokenType::As);
1923+
if bare_alias || explicit_as {
1924+
let alias_keyword = explicit_as.then(|| {
1925+
let token = self.previous();
1926+
match self.source.is_some() {
1927+
true => self.source_text_range(token.span.start, token.span.end),
1928+
false => token.text.to_string(),
1929+
}
1930+
});
19201931
let alias = self.expect_identifier_or_keyword_with_quoted()?;
19211932
Ok(Expression::Alias(Box::new(
19221933
sqlquack_syntax::expressions::operators::models::Alias {
19231934
span: SourceSpan::default(),
19241935
this: expr,
19251936
alias,
19261937
column_aliases: Vec::new(),
1927-
alias_explicit_as: false,
1928-
alias_keyword: None,
1938+
alias_explicit_as: explicit_as,
1939+
alias_keyword,
19291940
pre_alias_comments: Vec::new(),
19301941
trailing_comments: Vec::new(),
19311942
inferred_type: None,

‎crates/sqlquack-syntax-snowflake/tests/parser/test_faithful.rs‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,81 @@ fn given_an_interval_qualifier_with_precisions_when_parsing_faithfully_then_both
214214
);
215215
}
216216
}
217+
218+
#[test]
219+
fn given_snowflake_derived_tables_join_aliases_and_semantic_views_when_parsing_then_the_tree_names_what_they_are(
220+
) {
221+
let test_cases = [
222+
ProjectionTestCase {
223+
description: "a DELETE USING subquery is a derived table with its alias",
224+
sql: "DELETE FROM v1 USING ( SELECT 1 ) AS s ( c ) , t2 WHERE TRUE ;",
225+
pointer: "/0/delete/using_items/0/subquery/alias/name",
226+
expected_value: json!("s"),
227+
},
228+
ProjectionTestCase {
229+
description: "a DELETE USING subquery keeps its column aliases",
230+
sql: "DELETE FROM v1 USING ( SELECT 1 ) AS s ( c ) , t2 WHERE TRUE ;",
231+
pointer: "/0/delete/using_items/0/subquery/column_aliases/0/name",
232+
expected_value: json!("c"),
233+
},
234+
ProjectionTestCase {
235+
description: "a table beside a DELETE USING subquery stays a table",
236+
sql: "DELETE FROM v1 USING ( SELECT 1 ) AS s ( c ) , t2 WHERE TRUE ;",
237+
pointer: "/0/delete/using_items/1/table/name/name",
238+
expected_value: json!("t2"),
239+
},
240+
ProjectionTestCase {
241+
description: "a DELETE USING only tables keeps its table list",
242+
sql: "DELETE FROM v1 USING t2 , t3 AS x ;",
243+
pointer: "/0/delete/using/1/alias/name",
244+
expected_value: json!("x"),
245+
},
246+
ProjectionTestCase {
247+
description: "FIRST on a join with no condition is the joined table's alias",
248+
sql: "SELECT 1 FROM t1 JOIN t2 FIRST ( v1 ) ;",
249+
pointer: "/0/select/joins/0/this/table/alias/name",
250+
expected_value: json!("FIRST"),
251+
},
252+
ProjectionTestCase {
253+
description: "the parenthesised name after a FIRST alias is a column alias",
254+
sql: "SELECT 1 FROM t1 JOIN t2 FIRST ( v1 ) ;",
255+
pointer: "/0/select/joins/0/this/table/column_aliases/0/name",
256+
expected_value: json!("v1"),
257+
},
258+
ProjectionTestCase {
259+
description: "FIRST after ON stays the read-order clause",
260+
sql: "SELECT 1 FROM t1 INNER DIRECTED JOIN t2 ON TRUE FIRST ( v1 ) ;",
261+
pointer: "/0/select/joins/0/first_last",
262+
expected_value: json!("FIRST"),
263+
},
264+
ProjectionTestCase {
265+
description: "a semantic view's qualified name is a table reference",
266+
sql: "SELECT * FROM SEMANTIC_VIEW ( d1 . v1 METRICS 1 AS v1 DIMENSIONS 1 v1 ) ;",
267+
pointer: "/0/select/from/expressions/0/semantic_view/this/table/schema/name",
268+
expected_value: json!("d1"),
269+
},
270+
ProjectionTestCase {
271+
description: "an explicit AS in a semantic view clause is recorded",
272+
sql: "SELECT * FROM SEMANTIC_VIEW ( d1 . v1 METRICS 1 AS v1 DIMENSIONS 1 v1 ) ;",
273+
pointer: "/0/select/from/expressions/0/semantic_view/metrics/tuple/expressions/0/alias/alias_explicit_as",
274+
expected_value: json!(true),
275+
},
276+
ProjectionTestCase {
277+
description: "a bare alias in a semantic view clause has no AS",
278+
sql: "SELECT * FROM SEMANTIC_VIEW ( d1 . v1 METRICS 1 AS v1 DIMENSIONS 1 v1 ) ;",
279+
pointer: "/0/select/from/expressions/0/semantic_view/dimensions/tuple/expressions/0/alias/alias_explicit_as",
280+
expected_value: json!(false),
281+
},
282+
];
283+
for test_case in test_cases {
284+
assert_eq!(
285+
statements(test_case.sql, Dialect::Snowflake, CallMode::Faithful)
286+
.pointer(test_case.pointer)
287+
.cloned()
288+
.unwrap_or_default(),
289+
test_case.expected_value,
290+
"{}",
291+
test_case.description
292+
);
293+
}
294+
}

0 commit comments

Comments
 (0)