This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 667e81d7f Snowflake: Parse structured ARRAY types (#2488)
667e81d7f is described below
commit 667e81d7f5a682536725e9d9a64a6e62118cd21d
Author: Artem Osipov <[email protected]>
AuthorDate: Thu Sep 24 08:34:46 2026 +0000
Snowflake: Parse structured ARRAY types (#2488)
Co-authored-by: Luca Cappelletti <[email protected]>
---
src/ast/data_type.rs | 5 +++++
src/dialect/clickhouse.rs | 4 ++++
src/dialect/mod.rs | 16 ++++++++++++++++
src/dialect/snowflake.rs | 9 +++++++++
src/parser/mod.rs | 28 +++++++++++++++++++++++-----
tests/sqlparser_clickhouse.rs | 10 ++++++++++
tests/sqlparser_snowflake.rs | 24 ++++++++++++++++++++++++
7 files changed, 91 insertions(+), 5 deletions(-)
diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs
index be1acd1a7..d51ff2341 100644
--- a/src/ast/data_type.rs
+++ b/src/ast/data_type.rs
@@ -720,6 +720,9 @@ impl fmt::Display for DataType {
ArrayElemTypeDef::SquareBracket(t, Some(size)) => write!(f,
"{t}[{size}]"),
ArrayElemTypeDef::AngleBracket(t) => write!(f, "ARRAY<{t}>"),
ArrayElemTypeDef::Parenthesis(t) => write!(f, "Array({t})"),
+ ArrayElemTypeDef::ParenthesisNotNull(t) => {
+ write!(f, "ARRAY({t} NOT NULL)")
+ }
ArrayElemTypeDef::Qualified(t, None) => write!(f, "{t} ARRAY"),
ArrayElemTypeDef::Qualified(t, Some(size)) => write!(f, "{t}
ARRAY[{size}]"),
},
@@ -1165,6 +1168,8 @@ pub enum ArrayElemTypeDef {
SquareBracket(Box<DataType>, Option<u64>),
/// Parenthesis style, e.g. `Array(Int64)`.
Parenthesis(Box<DataType>),
+ /// Parenthesis style with a non-null element constraint, e.g. `ARRAY(INT
NOT NULL)`.
+ ParenthesisNotNull(Box<DataType>),
/// Qualified by a data type and optional size, e.g. `INT ARRAY` or `INT
ARRAY[4]`.
Qualified(Box<DataType>, Option<u64>),
}
diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs
index 880df16e3..adff426b0 100644
--- a/src/dialect/clickhouse.rs
+++ b/src/dialect/clickhouse.rs
@@ -79,6 +79,10 @@ impl Dialect for ClickHouseDialect {
true
}
+ fn supports_array_typedef_with_parentheses(&self) -> bool {
+ true
+ }
+
// ClickHouse uses this for some FORMAT expressions in `INSERT` context,
e.g. when inserting
// with FORMAT JSONEachRow a raw JSON key-value expression is valid and
expected.
//
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index eb5ec1dfa..80d5c5811 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1113,6 +1113,22 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if this dialect supports the `ARRAY(element_type)` syntax.
+ ///
+ /// Example:
+ /// ```sql
+ /// CREATE TABLE t (a ARRAY(VARCHAR));
+ /// ```
+ fn supports_array_typedef_with_parentheses(&self) -> bool {
+ false
+ }
+
+ /// Returns true if this dialect supports `NOT NULL` on an element type in
+ /// an `ARRAY(element_type)` definition.
+ fn supports_array_element_not_null(&self) -> bool {
+ false
+ }
+
/// Returns true if this dialect supports extra parentheses around
/// lone table names or derived tables in the `FROM` clause.
///
diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs
index 41a8f8708..ce9ccfb02 100644
--- a/src/dialect/snowflake.rs
+++ b/src/dialect/snowflake.rs
@@ -240,6 +240,15 @@ impl Dialect for SnowflakeDialect {
true
}
+ /// See
[doc](https://docs.snowflake.com/en/sql-reference/data-types-structured#label-structured-types-array)
+ fn supports_array_typedef_with_parentheses(&self) -> bool {
+ true
+ }
+
+ fn supports_array_element_not_null(&self) -> bool {
+ true
+ }
+
/// See [doc](https://docs.snowflake.com/en/sql-reference/constructs/from)
fn supports_parens_around_table_factor(&self) -> bool {
true
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index cc8f71b17..d48f00e44 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -13120,12 +13120,30 @@ impl<'a> Parser<'a> {
Keyword::ENUM16 =>
Ok(DataType::Enum(self.parse_enum_values()?, Some(16))),
Keyword::SET => Ok(DataType::Set(self.parse_string_values()?)),
Keyword::ARRAY => {
- if
self.dialect.supports_array_typedef_without_element_type() {
+ if self.dialect.supports_array_typedef_with_parentheses() {
+ if self.peek_token_ref().token == Token::LParen {
+ self.expect_token(&Token::LParen)?;
+ let internal_type = self.parse_data_type()?;
+ let not_null =
self.dialect.supports_array_element_not_null()
+ && self.parse_keywords(&[Keyword::NOT,
Keyword::NULL]);
+ self.expect_token(&Token::RParen)?;
+
+ if not_null {
+
Ok(DataType::Array(ArrayElemTypeDef::ParenthesisNotNull(
+ Box::new(internal_type),
+ )))
+ } else {
+
Ok(DataType::Array(ArrayElemTypeDef::Parenthesis(Box::new(
+ internal_type,
+ ))))
+ }
+ } else if
self.dialect.supports_array_typedef_without_element_type() {
+ Ok(DataType::Array(ArrayElemTypeDef::None))
+ } else {
+ self.expected("(", self.peek_token())
+ }
+ } else if
self.dialect.supports_array_typedef_without_element_type() {
Ok(DataType::Array(ArrayElemTypeDef::None))
- } else if dialect_of!(self is ClickHouseDialect) {
- Ok(self.parse_sub_type(|internal_type| {
-
DataType::Array(ArrayElemTypeDef::Parenthesis(internal_type))
- })?)
} else {
self.expect_token(&Token::Lt)?;
let (inside_type, _trailing_bracket) =
self.parse_data_type_helper()?;
diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs
index e680aa0a0..2d55ae058 100644
--- a/tests/sqlparser_clickhouse.rs
+++ b/tests/sqlparser_clickhouse.rs
@@ -779,6 +779,16 @@ fn parse_create_table_with_nested_data_types() {
}
}
+#[test]
+fn reject_angle_bracket_array_type() {
+ assert_eq!(
+ clickhouse()
+ .parse_sql_statements("CREATE TABLE t (a ARRAY<INT>)")
+ .unwrap_err(),
+ ParserError("Expected: (, found: <".to_string())
+ );
+}
+
#[test]
fn parse_create_table_with_primary_key() {
match clickhouse_and_generic().verified_stmt(concat!(
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index 37222341b..ab6e24254 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -4913,6 +4913,30 @@ fn test_select_dollar_column_from_stage() {
snowflake().verified_stmt("SELECT $1, $2 FROM @mystage1(file_format =>
'myformat')");
}
+#[test]
+fn test_structured_array_type() {
+ snowflake().one_statement_parses_to(
+ "CREATE TABLE t (a ARRAY(VARCHAR))",
+ "CREATE TABLE t (a Array(VARCHAR))",
+ );
+ snowflake().one_statement_parses_to(
+ "SELECT CAST(a AS ARRAY(NUMBER(10, 2))) FROM t",
+ "SELECT CAST(a AS Array(NUMBER(10, 2))) FROM t",
+ );
+ snowflake().verified_stmt("CREATE TABLE t (a ARRAY(VARCHAR NOT NULL))");
+ let select =
+ snowflake().verified_only_select("SELECT CAST(a AS ARRAY(VARCHAR NOT
NULL)) FROM t");
+ let Expr::Cast { data_type, .. } =
expr_from_projection(only(&select.projection)) else {
+ unreachable!();
+ };
+ assert_eq!(
+ data_type,
+ &DataType::Array(ArrayElemTypeDef::ParenthesisNotNull(Box::new(
+ DataType::Varchar(None)
+ )))
+ );
+}
+
#[test]
fn test_snowflake_stage_name_with_escaped_quotes() {
snowflake().verified_stmt("REMOVE @````");
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]