Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/ast/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ pub enum DataType {
///
/// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nested-data-structures/nested
Nested(Vec<ColumnDef>),
/// Structured object type, see [Snowflake].
///
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/data-types-structured#structured-object-types
Object(Vec<ColumnDef>),
/// Enum type.
Enum(Vec<EnumMember>, Option<u8>),
/// Set type.
Expand Down Expand Up @@ -794,6 +798,9 @@ impl fmt::Display for DataType {
DataType::Nested(fields) => {
write!(f, "Nested({})", display_comma_separated(fields))
}
DataType::Object(fields) => {
write!(f, "OBJECT({})", display_comma_separated(fields))
}
DataType::Unspecified => Ok(()),
DataType::Trigger => write!(f, "TRIGGER"),
DataType::AnyType => write!(f, "ANY TYPE"),
Expand Down
10 changes: 10 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,16 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if this dialect supports structured `OBJECT` types.
///
/// Example:
/// ```sql
/// CREATE TABLE t (o OBJECT(city VARCHAR, zip NUMBER NOT NULL));
/// ```
fn supports_structured_object_type(&self) -> bool {
false
}

/// Returns true if this dialect supports extra parentheses around
/// lone table names or derived tables in the `FROM` clause.
///
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ impl Dialect for SnowflakeDialect {
true
}

/// See [doc](https://docs.snowflake.com/en/sql-reference/data-types-structured#structured-object-types)
fn supports_structured_object_type(&self) -> bool {
true
}

/// See [doc](https://docs.snowflake.com/en/sql-reference/constructs/from)
fn supports_parens_around_table_factor(&self) -> bool {
true
Expand Down
29 changes: 29 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12646,6 +12646,12 @@ impl<'a> Parser<'a> {
))))
}
}
Keyword::OBJECT
if self.dialect.supports_structured_object_type()
&& self.peek_token_ref().token == Token::LParen =>
{
Ok(DataType::Object(self.parse_structured_object_type_def()?))
}
Keyword::STRUCT if dialect_is!(dialect is DuckDbDialect) => {
self.prev_token();
let field_defs = self.parse_duckdb_struct_type_def()?;
Expand Down Expand Up @@ -13847,6 +13853,29 @@ impl<'a> Parser<'a> {
}
}

fn parse_structured_object_type_def(&mut self) -> Result<Vec<ColumnDef>, ParserError> {
self.expect_token(&Token::LParen)?;
let fields = self.parse_comma_separated(|parser| {
let name = parser.parse_identifier()?;
let data_type = parser.parse_data_type()?;
let options = if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
vec![ColumnOptionDef {
name: None,
option: ColumnOption::NotNull,
}]
} else {
vec![]
};
Ok(ColumnDef {
name,
data_type,
options,
})
})?;
self.expect_token(&Token::RParen)?;
Ok(fields)
}

/// Parse a parenthesized sub data type
fn parse_sub_type<F>(&mut self, parent_type: F) -> Result<DataType, ParserError>
where
Expand Down
37 changes: 37 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5202,3 +5202,40 @@ fn test_external_volume() {
"Unexpected error: {err}"
);
}

#[test]
fn test_structured_object_type() {
snowflake().one_statement_parses_to(
"SELECT payload::OBJECT(tags ARRAY(VARCHAR), address OBJECT(city VARCHAR NOT NULL)) FROM t",
"SELECT payload::OBJECT(tags Array(VARCHAR), address OBJECT(city VARCHAR NOT NULL)) FROM t",
);

let select = snowflake().verified_only_select(
"SELECT CAST(payload AS OBJECT(city VARCHAR, zip NUMBER NOT NULL)) FROM t",
);
let Expr::Cast { data_type, .. } = expr_from_projection(only(&select.projection)) else {
unreachable!();
};
let DataType::Object(fields) = data_type else {
unreachable!();
};
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name, Ident::new("city"));
assert!(fields[0].options.is_empty());
assert_eq!(fields[1].name, Ident::new("zip"));
assert_eq!(fields[1].options.len(), 1);
assert_eq!(fields[1].options[0].option, ColumnOption::NotNull);

snowflake().verified_stmt("CREATE TABLE t (o OBJECT)");
}

#[test]
fn test_structured_object_type_errors() {
for sql in [
"CREATE TABLE t (o OBJECT(VARCHAR))",
"CREATE TABLE t (o OBJECT(city VARCHAR NULL))",
"CREATE TABLE t (o OBJECT(city VARCHAR)",
] {
assert!(snowflake().parse_sql_statements(sql).is_err(), "{sql}");
}
}
Loading