diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index f129a557c..861d84795 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -465,6 +465,10 @@ pub enum DataType { /// /// [DuckDB]: https://duckdb.org/docs/sql/data_types/union.html Union(Vec), + /// Object type, see [Snowflake]. + /// + /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object + Object(Vec), /// Nullable - special marker NULL represents in ClickHouse as a data type. /// /// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nullable @@ -776,6 +780,9 @@ impl fmt::Display for DataType { DataType::Union(fields) => { write!(f, "UNION({})", display_comma_separated(fields)) } + DataType::Object(fields) => { + write!(f, "OBJECT({})", display_comma_separated(fields)) + } // ClickHouse DataType::Nullable(data_type) => { write!(f, "Nullable({data_type})") diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3c6185193..8c2781460 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -12733,6 +12733,31 @@ impl<'a> Parser<'a> { let fields = self.parse_union_type_def()?; Ok(DataType::Union(fields)) } + Keyword::OBJECT if dialect_is!(dialect is SnowflakeDialect | GenericDialect) => { + self.prev_token(); + self.expect_keyword_is(Keyword::OBJECT)?; + // Object type may have no fields: OBJECT or OBJECT() + if !self.peek_token_ref().token.eq(&Token::LParen) { + Ok(DataType::Object(vec![])) + } else { + self.expect_token(&Token::LParen)?; + let fields = if self.peek_token_ref().token == Token::RParen { + vec![] + } else { + self.parse_comma_separated(|parser| { + let field_name = parser.parse_identifier()?; + let field_type = parser.parse_data_type()?; + Ok(StructField { + field_name: Some(field_name), + field_type, + options: None, + }) + })? + }; + self.expect_token(&Token::RParen)?; + Ok(DataType::Object(fields)) + } + } Keyword::NULLABLE if dialect_is!(dialect is ClickHouseDialect | GenericDialect) => { Ok(self.parse_sub_type(DataType::Nullable)?) } diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 0000b0a3d..c2d86fcb5 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -5254,3 +5254,12 @@ fn test_select_dollar_column_from_stage() { // With table function args, without alias snowflake().verified_stmt("SELECT $1, $2 FROM @mystage1(file_format => 'myformat')"); } + +#[test] +fn parse_nested_object() { + let sql = r#"SELECT TRY_CAST(PARSE_JSON('{"obj_field":{"field":"value",}}') AS OBJECT(obj_field OBJECT( + field VARCHAR +)));"#; + + snowflake().parse_sql_statements(sql).unwrap(); +} \ No newline at end of file