Skip to content

Commit cc281ff

Browse files
authored
Merge pull request #22 from Embucket/snowflake-structured-object
Snowflake: parse structured OBJECT types
2 parents 0d3f879 + 5c9be32 commit cc281ff

5 files changed

Lines changed: 88 additions & 0 deletions

File tree

‎src/ast/data_type.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,10 @@ pub enum DataType {
451451
///
452452
/// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nested-data-structures/nested
453453
Nested(Vec<ColumnDef>),
454+
/// Structured object type, see [Snowflake].
455+
///
456+
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/data-types-structured#structured-object-types
457+
Object(Vec<ColumnDef>),
454458
/// Enum type.
455459
Enum(Vec<EnumMember>, Option<u8>),
456460
/// Set type.
@@ -794,6 +798,9 @@ impl fmt::Display for DataType {
794798
DataType::Nested(fields) => {
795799
write!(f, "Nested({})", display_comma_separated(fields))
796800
}
801+
DataType::Object(fields) => {
802+
write!(f, "OBJECT({})", display_comma_separated(fields))
803+
}
797804
DataType::Unspecified => Ok(()),
798805
DataType::Trigger => write!(f, "TRIGGER"),
799806
DataType::AnyType => write!(f, "ANY TYPE"),

‎src/dialect/mod.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,16 @@ pub trait Dialect: Debug + Any {
10761076
false
10771077
}
10781078

1079+
/// Returns true if this dialect supports structured `OBJECT` types.
1080+
///
1081+
/// Example:
1082+
/// ```sql
1083+
/// CREATE TABLE t (o OBJECT(city VARCHAR, zip NUMBER NOT NULL));
1084+
/// ```
1085+
fn supports_structured_object_type(&self) -> bool {
1086+
false
1087+
}
1088+
10791089
/// Returns true if this dialect supports extra parentheses around
10801090
/// lone table names or derived tables in the `FROM` clause.
10811091
///

‎src/dialect/snowflake.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ impl Dialect for SnowflakeDialect {
242242
true
243243
}
244244

245+
/// See [doc](https://docs.snowflake.com/en/sql-reference/data-types-structured#structured-object-types)
246+
fn supports_structured_object_type(&self) -> bool {
247+
true
248+
}
249+
245250
/// See [doc](https://docs.snowflake.com/en/sql-reference/constructs/from)
246251
fn supports_parens_around_table_factor(&self) -> bool {
247252
true

‎src/parser/mod.rs‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12646,6 +12646,12 @@ impl<'a> Parser<'a> {
1264612646
))))
1264712647
}
1264812648
}
12649+
Keyword::OBJECT
12650+
if self.dialect.supports_structured_object_type()
12651+
&& self.peek_token_ref().token == Token::LParen =>
12652+
{
12653+
Ok(DataType::Object(self.parse_structured_object_type_def()?))
12654+
}
1264912655
Keyword::STRUCT if dialect_is!(dialect is DuckDbDialect) => {
1265012656
self.prev_token();
1265112657
let field_defs = self.parse_duckdb_struct_type_def()?;
@@ -13847,6 +13853,29 @@ impl<'a> Parser<'a> {
1384713853
}
1384813854
}
1384913855

13856+
fn parse_structured_object_type_def(&mut self) -> Result<Vec<ColumnDef>, ParserError> {
13857+
self.expect_token(&Token::LParen)?;
13858+
let fields = self.parse_comma_separated(|parser| {
13859+
let name = parser.parse_identifier()?;
13860+
let data_type = parser.parse_data_type()?;
13861+
let options = if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
13862+
vec![ColumnOptionDef {
13863+
name: None,
13864+
option: ColumnOption::NotNull,
13865+
}]
13866+
} else {
13867+
vec![]
13868+
};
13869+
Ok(ColumnDef {
13870+
name,
13871+
data_type,
13872+
options,
13873+
})
13874+
})?;
13875+
self.expect_token(&Token::RParen)?;
13876+
Ok(fields)
13877+
}
13878+
1385013879
/// Parse a parenthesized sub data type
1385113880
fn parse_sub_type<F>(&mut self, parent_type: F) -> Result<DataType, ParserError>
1385213881
where

‎tests/sqlparser_snowflake.rs‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5202,3 +5202,40 @@ fn test_external_volume() {
52025202
"Unexpected error: {err}"
52035203
);
52045204
}
5205+
5206+
#[test]
5207+
fn test_structured_object_type() {
5208+
snowflake().one_statement_parses_to(
5209+
"SELECT payload::OBJECT(tags ARRAY(VARCHAR), address OBJECT(city VARCHAR NOT NULL)) FROM t",
5210+
"SELECT payload::OBJECT(tags Array(VARCHAR), address OBJECT(city VARCHAR NOT NULL)) FROM t",
5211+
);
5212+
5213+
let select = snowflake().verified_only_select(
5214+
"SELECT CAST(payload AS OBJECT(city VARCHAR, zip NUMBER NOT NULL)) FROM t",
5215+
);
5216+
let Expr::Cast { data_type, .. } = expr_from_projection(only(&select.projection)) else {
5217+
unreachable!();
5218+
};
5219+
let DataType::Object(fields) = data_type else {
5220+
unreachable!();
5221+
};
5222+
assert_eq!(fields.len(), 2);
5223+
assert_eq!(fields[0].name, Ident::new("city"));
5224+
assert!(fields[0].options.is_empty());
5225+
assert_eq!(fields[1].name, Ident::new("zip"));
5226+
assert_eq!(fields[1].options.len(), 1);
5227+
assert_eq!(fields[1].options[0].option, ColumnOption::NotNull);
5228+
5229+
snowflake().verified_stmt("CREATE TABLE t (o OBJECT)");
5230+
}
5231+
5232+
#[test]
5233+
fn test_structured_object_type_errors() {
5234+
for sql in [
5235+
"CREATE TABLE t (o OBJECT(VARCHAR))",
5236+
"CREATE TABLE t (o OBJECT(city VARCHAR NULL))",
5237+
"CREATE TABLE t (o OBJECT(city VARCHAR)",
5238+
] {
5239+
assert!(snowflake().parse_sql_statements(sql).is_err(), "{sql}");
5240+
}
5241+
}

0 commit comments

Comments
 (0)