Skip to content

Commit 55c0741

Browse files
committed
Merge remote-tracking branch 'origin/main' into postgres_mat_cte
2 parents 1a0556f + f75bb4b commit 55c0741

11 files changed

+269
-23
lines changed

src/ast/mod.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ pub use self::ddl::{
3939
};
4040
pub use self::operator::{BinaryOperator, UnaryOperator};
4141
pub use self::query::{
42-
Cte, CteAsMaterialized, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause,
43-
ForJson, ForXml, GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator,
44-
JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
45-
NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem,
46-
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator,
47-
SetQuantifier, Table, TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity,
42+
Cte, CteAsMaterialized, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml,
43+
GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator, JsonTableColumn,
44+
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, NamedWindowDefinition,
45+
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement,
46+
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table,
47+
TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode,
4848
Values, WildcardAdditionalOptions, With,
4949
};
5050
pub use self::value::{
@@ -713,6 +713,21 @@ pub enum Expr {
713713
/// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
714714
/// (Same caveats apply to `QualifiedWildcard` as to `Wildcard`.)
715715
QualifiedWildcard(ObjectName),
716+
/// Some dialects support an older syntax for outer joins where columns are
717+
/// marked with the `(+)` operator in the WHERE clause, for example:
718+
///
719+
/// ```sql
720+
/// SELECT t1.c1, t2.c2 FROM t1, t2 WHERE t1.c1 = t2.c2 (+)
721+
/// ```
722+
///
723+
/// which is equivalent to
724+
///
725+
/// ```sql
726+
/// SELECT t1.c1, t2.c2 FROM t1 LEFT OUTER JOIN t2 ON t1.c1 = t2.c2
727+
/// ```
728+
///
729+
/// See <https://docs.snowflake.com/en/sql-reference/constructs/where#joins-in-the-where-clause>.
730+
OuterJoin(Box<Expr>),
716731
}
717732

718733
impl fmt::Display for CastFormat {
@@ -1174,6 +1189,9 @@ impl fmt::Display for Expr {
11741189

11751190
Ok(())
11761191
}
1192+
Expr::OuterJoin(expr) => {
1193+
write!(f, "{expr} (+)")
1194+
}
11771195
}
11781196
}
11791197
}

src/ast/query.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,18 @@ pub struct Select {
245245
pub named_window: Vec<NamedWindowDefinition>,
246246
/// QUALIFY (Snowflake)
247247
pub qualify: Option<Expr>,
248+
/// BigQuery syntax: `SELECT AS VALUE | SELECT AS STRUCT`
249+
pub value_table_mode: Option<ValueTableMode>,
248250
}
249251

250252
impl fmt::Display for Select {
251253
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252254
write!(f, "SELECT")?;
255+
256+
if let Some(value_table_mode) = self.value_table_mode {
257+
write!(f, " {value_table_mode}")?;
258+
}
259+
253260
if let Some(ref distinct) = self.distinct {
254261
write!(f, " {distinct}")?;
255262
}
@@ -1612,3 +1619,24 @@ impl fmt::Display for JsonTableColumnErrorHandling {
16121619
}
16131620
}
16141621
}
1622+
1623+
/// BigQuery supports ValueTables which have 2 modes:
1624+
/// `SELECT AS STRUCT`
1625+
/// `SELECT AS VALUE`
1626+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#value_tables>
1627+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1628+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1629+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1630+
pub enum ValueTableMode {
1631+
AsStruct,
1632+
AsValue,
1633+
}
1634+
1635+
impl fmt::Display for ValueTableMode {
1636+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1637+
match self {
1638+
ValueTableMode::AsStruct => write!(f, "AS STRUCT"),
1639+
ValueTableMode::AsValue => write!(f, "AS VALUE"),
1640+
}
1641+
}
1642+
}

src/parser/mod.rs

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -998,8 +998,19 @@ impl<'a> Parser<'a> {
998998
if ends_with_wildcard {
999999
Ok(Expr::QualifiedWildcard(ObjectName(id_parts)))
10001000
} else if self.consume_token(&Token::LParen) {
1001-
self.prev_token();
1002-
self.parse_function(ObjectName(id_parts))
1001+
if dialect_of!(self is SnowflakeDialect | MsSqlDialect)
1002+
&& self.consume_tokens(&[Token::Plus, Token::RParen])
1003+
{
1004+
Ok(Expr::OuterJoin(Box::new(
1005+
match <[Ident; 1]>::try_from(id_parts) {
1006+
Ok([ident]) => Expr::Identifier(ident),
1007+
Err(parts) => Expr::CompoundIdentifier(parts),
1008+
},
1009+
)))
1010+
} else {
1011+
self.prev_token();
1012+
self.parse_function(ObjectName(id_parts))
1013+
}
10031014
} else {
10041015
Ok(Expr::CompoundIdentifier(id_parts))
10051016
}
@@ -2780,6 +2791,31 @@ impl<'a> Parser<'a> {
27802791
}
27812792
}
27822793

2794+
/// If the current token is the `expected` keyword followed by
2795+
/// specified tokens, consume them and returns true.
2796+
/// Otherwise, no tokens are consumed and returns false.
2797+
///
2798+
/// Note that if the length of `tokens` is too long, this function will
2799+
/// not be efficient as it does a loop on the tokens with `peek_nth_token`
2800+
/// each time.
2801+
pub fn parse_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
2802+
match self.peek_token().token {
2803+
Token::Word(w) if expected == w.keyword => {
2804+
for (idx, token) in tokens.iter().enumerate() {
2805+
if self.peek_nth_token(idx + 1).token != *token {
2806+
return false;
2807+
}
2808+
}
2809+
// consume all tokens
2810+
for _ in 0..(tokens.len() + 1) {
2811+
self.next_token();
2812+
}
2813+
true
2814+
}
2815+
_ => false,
2816+
}
2817+
}
2818+
27832819
/// If the current and subsequent tokens exactly match the `keywords`
27842820
/// sequence, consume them and returns true. Otherwise, no tokens are
27852821
/// consumed and returns false
@@ -2860,6 +2896,21 @@ impl<'a> Parser<'a> {
28602896
}
28612897
}
28622898

2899+
/// If the current and subsequent tokens exactly match the `tokens`
2900+
/// sequence, consume them and returns true. Otherwise, no tokens are
2901+
/// consumed and returns false
2902+
#[must_use]
2903+
pub fn consume_tokens(&mut self, tokens: &[Token]) -> bool {
2904+
let index = self.index;
2905+
for token in tokens {
2906+
if !self.consume_token(token) {
2907+
self.index = index;
2908+
return false;
2909+
}
2910+
}
2911+
true
2912+
}
2913+
28632914
/// Bail out if the current token is not an expected keyword, or consume it if it is
28642915
pub fn expect_token(&mut self, expected: &Token) -> Result<(), ParserError> {
28652916
if self.consume_token(expected) {
@@ -6818,6 +6869,19 @@ impl<'a> Parser<'a> {
68186869
/// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`),
68196870
/// assuming the initial `SELECT` was already consumed
68206871
pub fn parse_select(&mut self) -> Result<Select, ParserError> {
6872+
let value_table_mode =
6873+
if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::AS) {
6874+
if self.parse_keyword(Keyword::VALUE) {
6875+
Some(ValueTableMode::AsValue)
6876+
} else if self.parse_keyword(Keyword::STRUCT) {
6877+
Some(ValueTableMode::AsStruct)
6878+
} else {
6879+
self.expected("VALUE or STRUCT", self.peek_token())?
6880+
}
6881+
} else {
6882+
None
6883+
};
6884+
68216885
let distinct = self.parse_all_or_distinct()?;
68226886

68236887
let top = if self.parse_keyword(Keyword::TOP) {
@@ -6954,6 +7018,7 @@ impl<'a> Parser<'a> {
69547018
having,
69557019
named_window: named_windows,
69567020
qualify,
7021+
value_table_mode,
69577022
})
69587023
}
69597024

@@ -7533,12 +7598,7 @@ impl<'a> Parser<'a> {
75337598
with_offset,
75347599
with_offset_alias,
75357600
})
7536-
} else if matches!(
7537-
self.peek_token().token, Token::Word(w)
7538-
if w.keyword == Keyword::JSON_TABLE && self.peek_nth_token(1).token == Token::LParen
7539-
) {
7540-
self.expect_keyword(Keyword::JSON_TABLE)?;
7541-
self.expect_token(&Token::LParen)?;
7601+
} else if self.parse_keyword_with_tokens(Keyword::JSON_TABLE, &[Token::LParen]) {
75427602
let json_expr = self.parse_expr()?;
75437603
self.expect_token(&Token::Comma)?;
75447604
let json_path = self.parse_value()?;
@@ -7912,7 +7972,7 @@ impl<'a> Parser<'a> {
79127972
return parser_err!("Unsupported statement REPLACE", self.peek_token().location);
79137973
}
79147974

7915-
let insert = &mut self.parse_insert().unwrap();
7975+
let insert = &mut self.parse_insert()?;
79167976
if let Statement::Insert { replace_into, .. } = insert {
79177977
*replace_into = true;
79187978
}
@@ -9680,4 +9740,42 @@ mod tests {
96809740
panic!("fail to parse mysql partition selection");
96819741
}
96829742
}
9743+
9744+
#[test]
9745+
fn test_replace_into_placeholders() {
9746+
let sql = "REPLACE INTO t (a) VALUES (&a)";
9747+
9748+
assert!(Parser::parse_sql(&GenericDialect {}, sql).is_err());
9749+
}
9750+
9751+
#[test]
9752+
fn test_replace_into_set() {
9753+
// NOTE: This is actually valid MySQL syntax, REPLACE and INSERT,
9754+
// but the parser does not yet support it.
9755+
// https://dev.mysql.com/doc/refman/8.3/en/insert.html
9756+
let sql = "REPLACE INTO t SET a='1'";
9757+
9758+
assert!(Parser::parse_sql(&MySqlDialect {}, sql).is_err());
9759+
}
9760+
9761+
#[test]
9762+
fn test_replace_into_set_placeholder() {
9763+
let sql = "REPLACE INTO t SET ?";
9764+
9765+
assert!(Parser::parse_sql(&GenericDialect {}, sql).is_err());
9766+
}
9767+
9768+
#[test]
9769+
fn test_replace_into_select() {
9770+
let sql = r#"REPLACE INTO t1 (a, b, c) (SELECT * FROM t2)"#;
9771+
9772+
assert!(Parser::parse_sql(&GenericDialect {}, sql).is_err());
9773+
}
9774+
9775+
#[test]
9776+
fn test_replace_incomplete() {
9777+
let sql = r#"REPLACE"#;
9778+
9779+
assert!(Parser::parse_sql(&MySqlDialect {}, sql).is_err());
9780+
}
96839781
}

tests/sqlparser_bigquery.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,3 +1352,19 @@ fn test_bigquery_trim() {
13521352
bigquery().parse_sql_statements(error_sql).unwrap_err()
13531353
);
13541354
}
1355+
1356+
#[test]
1357+
fn test_select_as_struct() {
1358+
bigquery().verified_only_select("SELECT * FROM (SELECT AS VALUE STRUCT(123 AS a, false AS b))");
1359+
let select = bigquery().verified_only_select("SELECT AS STRUCT 1 AS a, 2 AS b");
1360+
assert_eq!(Some(ValueTableMode::AsStruct), select.value_table_mode);
1361+
}
1362+
1363+
#[test]
1364+
fn test_select_as_value() {
1365+
bigquery().verified_only_select(
1366+
"SELECT * FROM (SELECT AS VALUE STRUCT(5 AS star_rating, false AS up_down_rating))",
1367+
);
1368+
let select = bigquery().verified_only_select("SELECT AS VALUE STRUCT(1 AS a, 2 AS b) AS xyz");
1369+
assert_eq!(Some(ValueTableMode::AsValue), select.value_table_mode);
1370+
}

tests/sqlparser_clickhouse.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ fn parse_map_access_expr() {
110110
having: None,
111111
named_window: vec![],
112112
qualify: None,
113+
value_table_mode: None,
113114
},
114115
select
115116
);

tests/sqlparser_common.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,8 @@ fn parse_update_set_from() {
400400
sort_by: vec![],
401401
having: None,
402402
named_window: vec![],
403-
qualify: None
403+
qualify: None,
404+
value_table_mode: None,
404405
}))),
405406
order_by: vec![],
406407
limit: None,
@@ -4212,6 +4213,7 @@ fn test_parse_named_window() {
42124213
),
42134214
],
42144215
qualify: None,
4216+
value_table_mode: None,
42154217
};
42164218
assert_eq!(actual_select_only, expected);
42174219
}
@@ -4567,6 +4569,7 @@ fn parse_interval_and_or_xor() {
45674569
having: None,
45684570
named_window: vec![],
45694571
qualify: None,
4572+
value_table_mode: None,
45704573
}))),
45714574
order_by: vec![],
45724575
limit: None,
@@ -6551,6 +6554,7 @@ fn lateral_function() {
65516554
having: None,
65526555
named_window: vec![],
65536556
qualify: None,
6557+
value_table_mode: None,
65546558
};
65556559
assert_eq!(actual_select_only, expected);
65566560
}
@@ -7194,6 +7198,7 @@ fn parse_merge() {
71947198
having: None,
71957199
named_window: vec![],
71967200
qualify: None,
7201+
value_table_mode: None,
71977202
}))),
71987203
order_by: vec![],
71997204
limit: None,

tests/sqlparser_duckdb.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ fn test_select_union_by_name() {
177177
having: None,
178178
named_window: vec![],
179179
qualify: None,
180+
value_table_mode: None,
180181
}))),
181182
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
182183
distinct: None,
@@ -211,6 +212,7 @@ fn test_select_union_by_name() {
211212
having: None,
212213
named_window: vec![],
213214
qualify: None,
215+
value_table_mode: None,
214216
}))),
215217
});
216218
assert_eq!(ast.body, expected);

tests/sqlparser_mssql.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ fn parse_create_procedure() {
112112
sort_by: vec![],
113113
having: None,
114114
named_window: vec![],
115-
qualify: None
115+
qualify: None,
116+
value_table_mode: None,
116117
})))
117118
}))],
118119
params: Some(vec![
@@ -595,7 +596,8 @@ fn parse_substring_in_select() {
595596
sort_by: vec![],
596597
having: None,
597598
named_window: vec![],
598-
qualify: None
599+
qualify: None,
600+
value_table_mode: None,
599601
}))),
600602
order_by: vec![],
601603
limit: None,

0 commit comments

Comments
 (0)