Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -452,6 +452,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 @@ -802,6 +806,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 @@ -1083,6 +1083,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
}
Comment thread
osipovartem marked this conversation as resolved.
Outdated

/// 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 @@ -240,6 +240,11 @@ impl Dialect for SnowflakeDialect {
true
}

/// See [doc](https://docs.snowflake.com/en/sql-reference/data-types-structured#specifying-a-structured-object-type)
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 @@ -13116,6 +13116,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 @@ -14333,6 +14339,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
35 changes: 35 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4912,3 +4912,38 @@ 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]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[test]
#[test]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the missing blank line before the test in 0d58c18.

fn test_structured_object_type() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're missing a test case for how the PR handles plain OBJECT as a type (as mentioned in my previous review comment)

@osipovartem osipovartem Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an explicit test_plain_object_type in 0d58c18. It verifies both Snowflake and Generic dialect parsing and asserts that plain OBJECT remains DataType::Custom("OBJECT", []), while OBJECT(...) uses the new structured representation. cargo test --all-targets and cargo clippy --all-targets --all-features -- -D warnings pass locally.

snowflake().verified_stmt(
"SELECT payload::OBJECT(address OBJECT(city VARCHAR NOT NULL), zip NUMBER) 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() {
Comment thread
osipovartem marked this conversation as resolved.
Outdated
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