mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 23:49:10 +00:00
Add support for DuckDB struct literal syntax (#1194)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
4472789171
commit
e747c9c2af
3 changed files with 158 additions and 0 deletions
|
@ -347,6 +347,23 @@ impl fmt::Display for StructField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A dictionary field within a dictionary.
|
||||||
|
///
|
||||||
|
/// [duckdb]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct DictionaryField {
|
||||||
|
pub key: Ident,
|
||||||
|
pub value: Box<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DictionaryField {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}: {}", self.key, self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Options for `CAST` / `TRY_CAST`
|
/// Options for `CAST` / `TRY_CAST`
|
||||||
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax>
|
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax>
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
@ -687,6 +704,14 @@ pub enum Expr {
|
||||||
expr: Box<Expr>,
|
expr: Box<Expr>,
|
||||||
name: Ident,
|
name: Ident,
|
||||||
},
|
},
|
||||||
|
/// `DuckDB` specific `Struct` literal expression [1]
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
/// ```sql
|
||||||
|
/// syntax: {'field_name': expr1[, ... ]}
|
||||||
|
/// ```
|
||||||
|
/// [1]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||||
|
Dictionary(Vec<DictionaryField>),
|
||||||
/// An array index expression e.g. `(ARRAY[1, 2])[1]` or `(current_schemas(FALSE))[1]`
|
/// An array index expression e.g. `(ARRAY[1, 2])[1]` or `(current_schemas(FALSE))[1]`
|
||||||
ArrayIndex {
|
ArrayIndex {
|
||||||
obj: Box<Expr>,
|
obj: Box<Expr>,
|
||||||
|
@ -1146,6 +1171,9 @@ impl fmt::Display for Expr {
|
||||||
Expr::Named { expr, name } => {
|
Expr::Named { expr, name } => {
|
||||||
write!(f, "{} AS {}", expr, name)
|
write!(f, "{} AS {}", expr, name)
|
||||||
}
|
}
|
||||||
|
Expr::Dictionary(fields) => {
|
||||||
|
write!(f, "{{{}}}", display_comma_separated(fields))
|
||||||
|
}
|
||||||
Expr::ArrayIndex { obj, indexes } => {
|
Expr::ArrayIndex { obj, indexes } => {
|
||||||
write!(f, "{obj}")?;
|
write!(f, "{obj}")?;
|
||||||
for i in indexes {
|
for i in indexes {
|
||||||
|
|
|
@ -1117,6 +1117,10 @@ impl<'a> Parser<'a> {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
Ok(Expr::Value(self.parse_value()?))
|
Ok(Expr::Value(self.parse_value()?))
|
||||||
}
|
}
|
||||||
|
Token::LBrace if dialect_of!(self is DuckDbDialect | GenericDialect) => {
|
||||||
|
self.prev_token();
|
||||||
|
self.parse_duckdb_struct_literal()
|
||||||
|
}
|
||||||
_ => self.expected("an expression:", next_token),
|
_ => self.expected("an expression:", next_token),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
@ -2127,6 +2131,45 @@ impl<'a> Parser<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DuckDB specific: Parse a duckdb dictionary [1]
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
///
|
||||||
|
/// ```sql
|
||||||
|
/// {'field_name': expr1[, ... ]}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [1]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||||
|
fn parse_duckdb_struct_literal(&mut self) -> Result<Expr, ParserError> {
|
||||||
|
self.expect_token(&Token::LBrace)?;
|
||||||
|
|
||||||
|
let fields = self.parse_comma_separated(Self::parse_duckdb_dictionary_field)?;
|
||||||
|
|
||||||
|
self.expect_token(&Token::RBrace)?;
|
||||||
|
|
||||||
|
Ok(Expr::Dictionary(fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a field for a duckdb dictionary [1]
|
||||||
|
/// Syntax
|
||||||
|
/// ```sql
|
||||||
|
/// 'name': expr
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [1]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||||
|
fn parse_duckdb_dictionary_field(&mut self) -> Result<DictionaryField, ParserError> {
|
||||||
|
let key = self.parse_identifier(false)?;
|
||||||
|
|
||||||
|
self.expect_token(&Token::Colon)?;
|
||||||
|
|
||||||
|
let expr = self.parse_expr()?;
|
||||||
|
|
||||||
|
Ok(DictionaryField {
|
||||||
|
key,
|
||||||
|
value: Box::new(expr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// For nested types that use the angle bracket syntax, this matches either
|
/// For nested types that use the angle bracket syntax, this matches either
|
||||||
/// `>`, `>>` or nothing depending on which variant is expected (specified by the previously
|
/// `>`, `>>` or nothing depending on which variant is expected (specified by the previously
|
||||||
/// matched `trailing_bracket` argument). It returns whether there is a trailing
|
/// matched `trailing_bracket` argument). It returns whether there is a trailing
|
||||||
|
|
|
@ -246,3 +246,90 @@ fn test_duckdb_load_extension() {
|
||||||
stmt
|
stmt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_duckdb_struct_literal() {
|
||||||
|
//struct literal syntax https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||||
|
//syntax: {'field_name': expr1[, ... ]}
|
||||||
|
let sql = "SELECT {'a': 1, 'b': 2, 'c': 3}, [{'a': 'abc'}], {'a': 1, 'b': [t.str_col]}, {'a': 1, 'b': 'abc'}, {'abc': str_col}, {'a': {'aa': 1}}";
|
||||||
|
let select = duckdb_and_generic().verified_only_select(sql);
|
||||||
|
assert_eq!(6, select.projection.len());
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Dictionary(vec![
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "a"),
|
||||||
|
value: Box::new(Expr::Value(number("1"))),
|
||||||
|
},
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "b"),
|
||||||
|
value: Box::new(Expr::Value(number("2"))),
|
||||||
|
},
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "c"),
|
||||||
|
value: Box::new(Expr::Value(number("3"))),
|
||||||
|
},
|
||||||
|
],),
|
||||||
|
expr_from_projection(&select.projection[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Array(Array {
|
||||||
|
elem: vec![Expr::Dictionary(vec![DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "a"),
|
||||||
|
value: Box::new(Expr::Value(Value::SingleQuotedString("abc".to_string()))),
|
||||||
|
},],)],
|
||||||
|
named: false
|
||||||
|
}),
|
||||||
|
expr_from_projection(&select.projection[1])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Dictionary(vec![
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "a"),
|
||||||
|
value: Box::new(Expr::Value(number("1"))),
|
||||||
|
},
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "b"),
|
||||||
|
value: Box::new(Expr::Array(Array {
|
||||||
|
elem: vec![Expr::CompoundIdentifier(vec![
|
||||||
|
Ident::from("t"),
|
||||||
|
Ident::from("str_col")
|
||||||
|
])],
|
||||||
|
named: false
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],),
|
||||||
|
expr_from_projection(&select.projection[2])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Dictionary(vec![
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "a"),
|
||||||
|
value: Expr::Value(number("1")).into(),
|
||||||
|
},
|
||||||
|
DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "b"),
|
||||||
|
value: Expr::Value(Value::SingleQuotedString("abc".to_string())).into(),
|
||||||
|
},
|
||||||
|
],),
|
||||||
|
expr_from_projection(&select.projection[3])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Dictionary(vec![DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "abc"),
|
||||||
|
value: Expr::Identifier(Ident::from("str_col")).into(),
|
||||||
|
}],),
|
||||||
|
expr_from_projection(&select.projection[4])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Dictionary(vec![DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "a"),
|
||||||
|
value: Expr::Dictionary(vec![DictionaryField {
|
||||||
|
key: Ident::with_quote('\'', "aa"),
|
||||||
|
value: Expr::Value(number("1")).into(),
|
||||||
|
}],)
|
||||||
|
.into(),
|
||||||
|
}],),
|
||||||
|
expr_from_projection(&select.projection[5])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue