diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index f08545b99..e141f941f 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -37,4 +37,11 @@ impl Dialect for DuckDbDialect { fn supports_named_fn_args_with_eq_operator(&self) -> bool { true } + + // DuckDB uses this syntax for `STRUCT`s. + // + // https://duckdb.org/docs/sql/data_types/struct.html#creating-structs + fn supports_dictionary_syntax(&self) -> bool { + true + } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 21a15cd10..17383b526 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -46,4 +46,8 @@ impl Dialect for GenericDialect { fn supports_start_transaction_modifier(&self) -> bool { true } + + fn supports_dictionary_syntax(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 79f4ce811..418526bcc 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -168,6 +168,11 @@ pub trait Dialect: Debug + Any { fn supports_named_fn_args_with_eq_operator(&self) -> bool { false } + /// Returns true if the dialect supports defining structs or objects using a + /// syntax like `{'x': 1, 'y': 2, 'z': 3}`. + fn supports_dictionary_syntax(&self) -> bool { + false + } /// Returns true if the dialect has a CONVERT function which accepts a type first /// and an expression second, e.g. `CONVERT(varchar, 1)` fn convert_type_before_value(&self) -> bool { diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index e8637ba57..e42dbd73d 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -59,6 +59,14 @@ impl Dialect for SnowflakeDialect { true } + // Snowflake uses this syntax for "object constants" (the values of which + // are not actually required to be constants). + // + // https://docs.snowflake.com/en/sql-reference/data-types-semistructured#label-object-constant + fn supports_dictionary_syntax(&self) -> bool { + true + } + fn parse_statement(&self, parser: &mut Parser) -> Option> { if parser.parse_keyword(Keyword::CREATE) { // possibly CREATE STAGE diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d9a354dfa..b3ebd0e77 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1192,7 +1192,7 @@ impl<'a> Parser<'a> { self.prev_token(); Ok(Expr::Value(self.parse_value()?)) } - Token::LBrace if dialect_of!(self is DuckDbDialect | GenericDialect) => { + Token::LBrace if self.dialect.supports_dictionary_syntax() => { self.prev_token(); self.parse_duckdb_struct_literal() } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 9fc020fd5..b23636d82 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -9288,3 +9288,59 @@ fn insert_into_with_parentheses() { }; dialects.verified_stmt("INSERT INTO t1 (id, name) (SELECT t2.id, t2.name FROM t2)"); } + +#[test] +fn test_dictionary_syntax() { + fn check(sql: &str, expect: Expr) { + assert_eq!( + all_dialects_where(|d| d.supports_dictionary_syntax()).verified_expr(sql), + expect + ); + } + + check( + "{'Alberta': 'Edmonton', 'Manitoba': 'Winnipeg'}", + Expr::Dictionary(vec![ + DictionaryField { + key: Ident::with_quote('\'', "Alberta"), + value: Box::new(Expr::Value(Value::SingleQuotedString( + "Edmonton".to_owned(), + ))), + }, + DictionaryField { + key: Ident::with_quote('\'', "Manitoba"), + value: Box::new(Expr::Value(Value::SingleQuotedString( + "Winnipeg".to_owned(), + ))), + }, + ]), + ); + + check( + "{'start': CAST('2023-04-01' AS TIMESTAMP), 'end': CAST('2023-04-05' AS TIMESTAMP)}", + Expr::Dictionary(vec![ + DictionaryField { + key: Ident::with_quote('\'', "start"), + value: Box::new(Expr::Cast { + kind: CastKind::Cast, + expr: Box::new(Expr::Value(Value::SingleQuotedString( + "2023-04-01".to_owned(), + ))), + data_type: DataType::Timestamp(None, TimezoneInfo::None), + format: None, + }), + }, + DictionaryField { + key: Ident::with_quote('\'', "end"), + value: Box::new(Expr::Cast { + kind: CastKind::Cast, + expr: Box::new(Expr::Value(Value::SingleQuotedString( + "2023-04-05".to_owned(), + ))), + data_type: DataType::Timestamp(None, TimezoneInfo::None), + format: None, + }), + }, + ]), + ) +}