Skip to content

Commit 3c7fd73

Browse files
committed
generalize struct support and add databricks
1 parent fad2ddd commit 3c7fd73

File tree

7 files changed

+108
-15
lines changed

7 files changed

+108
-15
lines changed

src/ast/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -853,16 +853,16 @@ pub enum Expr {
853853
Rollup(Vec<Vec<Expr>>),
854854
/// ROW / TUPLE a single value, such as `SELECT (1, 2)`
855855
Tuple(Vec<Expr>),
856-
/// `BigQuery` specific `Struct` literal expression [1]
856+
/// `Struct` literal expression
857857
/// Syntax:
858858
/// ```sql
859859
/// STRUCT<[field_name] field_type, ...>( expr1 [, ... ])
860860
/// ```
861-
/// [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#struct_type
862861
Struct {
863862
/// Struct values.
864863
values: Vec<Expr>,
865-
/// Struct field definitions.
864+
/// BigQuery specific: Struct field definitions.
865+
/// see https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#struct_type
866866
fields: Vec<StructField>,
867867
},
868868
/// `BigQuery` specific: An named expression in a typeless struct [1]

src/dialect/bigquery.rs

+10
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,14 @@ impl Dialect for BigQueryDialect {
7272
fn require_interval_qualifier(&self) -> bool {
7373
true
7474
}
75+
76+
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#constructing_a_struct
77+
fn supports_struct_literal(&self) -> bool {
78+
true
79+
}
80+
81+
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#typed_struct_syntax
82+
fn supports_typed_struct_syntax(&self) -> bool {
83+
true
84+
}
7585
}

src/dialect/databricks.rs

+5
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,9 @@ impl Dialect for DatabricksDialect {
5959
fn require_interval_qualifier(&self) -> bool {
6060
true
6161
}
62+
63+
// See https://docs.databricks.com/en/sql/language-manual/functions/struct.html
64+
fn supports_struct_literal(&self) -> bool {
65+
true
66+
}
6267
}

src/dialect/generic.rs

+8
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,12 @@ impl Dialect for GenericDialect {
123123
fn supports_named_fn_args_with_assignment_operator(&self) -> bool {
124124
true
125125
}
126+
127+
fn supports_struct_literal(&self) -> bool {
128+
true
129+
}
130+
131+
fn supports_typed_struct_syntax(&self) -> bool {
132+
true
133+
}
126134
}

src/dialect/mod.rs

+20
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,26 @@ pub trait Dialect: Debug + Any {
365365
self.supports_trailing_commas()
366366
}
367367

368+
/// Return true if the dialect supports the STRUCT literal
369+
///
370+
/// Example
371+
/// ```sql
372+
/// SELECT STRUCT(1 as one, 'foo' as foo, false)
373+
/// ```
374+
fn supports_struct_literal(&self) -> bool {
375+
false
376+
}
377+
378+
/// Return true if the dialect supports typed struct syntax
379+
///
380+
/// Example for bigquery
381+
/// ```sql
382+
/// SELECT STRUCT<x int64, y string>(1, 'foo')
383+
/// ```
384+
fn supports_typed_struct_syntax(&self) -> bool {
385+
false
386+
}
387+
368388
/// Dialect-specific infix parser override
369389
///
370390
/// This method is called to parse the next infix expression.

src/parser/mod.rs

+17-12
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,8 @@ impl<'a> Parser<'a> {
11601160
Keyword::MATCH if dialect_of!(self is MySqlDialect | GenericDialect) => {
11611161
self.parse_match_against()
11621162
}
1163-
Keyword::STRUCT if dialect_of!(self is BigQueryDialect | GenericDialect) => {
1164-
self.prev_token();
1165-
self.parse_bigquery_struct_literal()
1163+
Keyword::STRUCT if self.dialect.supports_struct_literal() => {
1164+
self.parse_struct_literal()
11661165
}
11671166
Keyword::PRIOR if matches!(self.state, ParserState::ConnectBy) => {
11681167
let expr = self.parse_subexpr(self.dialect.prec_value(Precedence::PlusMinus))?;
@@ -2328,19 +2327,25 @@ impl<'a> Parser<'a> {
23282327
}
23292328
}
23302329

2331-
/// Bigquery specific: Parse a struct literal
23322330
/// Syntax
23332331
/// ```sql
2334-
/// -- typed
2332+
/// -- typed, specific to bigquery
23352333
/// STRUCT<[field_name] field_type, ...>( expr1 [, ... ])
23362334
/// -- typeless
23372335
/// STRUCT( expr1 [AS field_name] [, ... ])
23382336
/// ```
2339-
fn parse_bigquery_struct_literal(&mut self) -> Result<Expr, ParserError> {
2340-
let (fields, trailing_bracket) =
2341-
self.parse_struct_type_def(Self::parse_struct_field_def)?;
2342-
if trailing_bracket.0 {
2343-
return parser_err!("unmatched > in STRUCT literal", self.peek_token().location);
2337+
fn parse_struct_literal(&mut self) -> Result<Expr, ParserError> {
2338+
let mut fields = vec![];
2339+
// Typed struct syntax is only supported by BigQuery
2340+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#typed_struct_syntax
2341+
if self.dialect.supports_typed_struct_syntax() {
2342+
self.prev_token();
2343+
let trailing_bracket;
2344+
(fields, trailing_bracket) =
2345+
self.parse_struct_type_def(Self::parse_struct_field_def)?;
2346+
if trailing_bracket.0 {
2347+
return parser_err!("unmatched > in STRUCT literal", self.peek_token().location);
2348+
}
23442349
}
23452350

23462351
self.expect_token(&Token::LParen)?;
@@ -2351,13 +2356,13 @@ impl<'a> Parser<'a> {
23512356
Ok(Expr::Struct { values, fields })
23522357
}
23532358

2354-
/// Parse an expression value for a bigquery struct [1]
2359+
/// Parse an expression value for a struct literal
23552360
/// Syntax
23562361
/// ```sql
23572362
/// expr [AS name]
23582363
/// ```
23592364
///
2360-
/// Parameter typed_syntax is set to true if the expression
2365+
/// For biquery [1], Parameter typed_syntax is set to true if the expression
23612366
/// is to be parsed as a field expression declared using typed
23622367
/// struct syntax [2], and false if using typeless struct syntax [3].
23632368
///

tests/sqlparser_databricks.rs

+45
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,48 @@ fn parse_use() {
277277
);
278278
}
279279
}
280+
281+
#[test]
282+
fn parse_databricks_struct_function() {
283+
assert_eq!(
284+
databricks()
285+
.verified_only_select("SELECT STRUCT(1, 'foo')")
286+
.projection[0],
287+
SelectItem::UnnamedExpr(Expr::Struct {
288+
values: vec![
289+
Expr::Value(number("1")),
290+
Expr::Value(Value::SingleQuotedString("foo".to_string()))
291+
],
292+
fields: vec![]
293+
})
294+
);
295+
assert_eq!(
296+
databricks()
297+
.verified_only_select("SELECT STRUCT(1 AS one, 'foo' AS foo, false)")
298+
.projection[0],
299+
SelectItem::UnnamedExpr(Expr::Struct {
300+
values: vec![
301+
Expr::Named {
302+
expr: Expr::Value(number("1")).into(),
303+
name: Ident::new("one")
304+
},
305+
Expr::Named {
306+
expr: Expr::Value(Value::SingleQuotedString("foo".to_string())).into(),
307+
name: Ident::new("foo")
308+
},
309+
Expr::Value(Value::Boolean(false))
310+
],
311+
fields: vec![]
312+
})
313+
);
314+
}
315+
316+
#[test]
317+
fn parse_invalid_struct_function() {
318+
assert_eq!(
319+
databricks()
320+
.parse_sql_statements("SELECT STRUCT<INT64>(1)") // This works only in BigQuery
321+
.unwrap_err(),
322+
ParserError::ParserError("Expected: (, found: <".to_string())
323+
);
324+
}

0 commit comments

Comments
 (0)