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]

Reply via email to