This is an automated email from the ASF dual-hosted git repository.
github-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 e7a30191 MySQL: Support `CAST(... AS ... ARRAY)` syntax (#2151)
e7a30191 is described below
commit e7a30191f3b643da867306d2187be6efb50b1809
Author: Michael Victor Zink <[email protected]>
AuthorDate: Fri Jan 23 11:19:06 2026 -0800
MySQL: Support `CAST(... AS ... ARRAY)` syntax (#2151)
---
src/ast/mod.rs | 16 +++++++++++++---
src/ast/spans.rs | 3 ++-
src/parser/mod.rs | 5 +++++
tests/sqlparser_common.rs | 18 ++++++++++++++++++
tests/sqlparser_databricks.rs | 1 +
tests/sqlparser_duckdb.rs | 1 +
tests/sqlparser_mysql.rs | 27 +++++++++++++++++++++++++++
tests/sqlparser_postgres.rs | 5 +++++
tests/sqlparser_snowflake.rs | 12 +++++++-----
9 files changed, 79 insertions(+), 9 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index d77186bc..0470d6a8 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -1033,6 +1033,12 @@ pub enum Expr {
expr: Box<Expr>,
/// Target data type.
data_type: DataType,
+ /// [MySQL] allows CAST(... AS type ARRAY) in functional index
definitions for InnoDB
+ /// multi-valued indices. It's not really a datatype, and is only
allowed in `CAST` in key
+ /// specifications, so it's a flag here.
+ ///
+ /// [MySQL]:
https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html#function_cast
+ array: bool,
/// Optional CAST(string_expression AS type FORMAT
format_string_expression) as used by [BigQuery]
///
/// [BigQuery]:
https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
@@ -1879,14 +1885,18 @@ impl fmt::Display for Expr {
kind,
expr,
data_type,
+ array,
format,
} => match kind {
CastKind::Cast => {
+ write!(f, "CAST({expr} AS {data_type}")?;
+ if *array {
+ write!(f, " ARRAY")?;
+ }
if let Some(format) = format {
- write!(f, "CAST({expr} AS {data_type} FORMAT
{format})")
- } else {
- write!(f, "CAST({expr} AS {data_type})")
+ write!(f, " FORMAT {format}")?;
}
+ write!(f, ")")
}
CastKind::TryCast => {
if let Some(format) = format {
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 488c8862..1c5cc473 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1540,6 +1540,7 @@ impl Spanned for Expr {
kind: _,
expr,
data_type: _,
+ array: _,
format: _,
} => expr.span(),
Expr::AtTimeZone {
@@ -2801,7 +2802,7 @@ WHERE id = 1
UPDATE SET target_table.description = source_table.description
WHEN MATCHED AND target_table.x != 'X' THEN DELETE
- WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
+ WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
"#;
let r = Parser::parse_sql(&crate::dialect::GenericDialect,
sql).unwrap();
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index cfc173d7..586c2f6b 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1650,6 +1650,7 @@ impl<'a> Parser<'a> {
kind: CastKind::Cast,
expr: Box::new(parser.parse_expr()?),
data_type: DataType::Binary(None),
+ array: false,
format: None,
})
}
@@ -2655,12 +2656,14 @@ impl<'a> Parser<'a> {
let expr = self.parse_expr()?;
self.expect_keyword_is(Keyword::AS)?;
let data_type = self.parse_data_type()?;
+ let array = self.parse_keyword(Keyword::ARRAY);
let format = self.parse_optional_cast_format()?;
self.expect_token(&Token::RParen)?;
Ok(Expr::Cast {
kind,
expr: Box::new(expr),
data_type,
+ array,
format,
})
}
@@ -3938,6 +3941,7 @@ impl<'a> Parser<'a> {
kind: CastKind::DoubleColon,
expr: Box::new(expr),
data_type: self.parse_data_type()?,
+ array: false,
format: None,
})
} else if Token::ExclamationMark == *tok &&
self.dialect.supports_factorial_operator() {
@@ -4178,6 +4182,7 @@ impl<'a> Parser<'a> {
kind: CastKind::DoubleColon,
expr: Box::new(expr),
data_type: self.parse_data_type()?,
+ array: false,
format: None,
})
}
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index f892bf7a..dcc92207 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -3027,6 +3027,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::BigInt(None),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3039,6 +3040,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::TinyInt(None),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3070,6 +3072,7 @@ fn parse_cast() {
length: 50,
unit: None,
})),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3082,6 +3085,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Clob(None),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3094,6 +3098,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Clob(Some(50)),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3106,6 +3111,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Binary(Some(50)),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3118,6 +3124,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Varbinary(Some(BinaryLength::IntegerLength {
length: 50 })),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3130,6 +3137,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Blob(None),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3142,6 +3150,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Blob(Some(50)),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3154,6 +3163,7 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("details"))),
data_type: DataType::JSONB,
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -3169,6 +3179,7 @@ fn parse_try_cast() {
kind: CastKind::TryCast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::BigInt(None),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -6505,6 +6516,7 @@ fn interval_disallow_interval_expr_double_colon() {
fractional_seconds_precision: None,
})),
data_type: DataType::Text,
+ array: false,
format: None,
}
)
@@ -9220,6 +9232,7 @@ fn parse_double_colon_cast_at_timezone() {
.with_empty_span()
)),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
+ array: false,
format: None
}),
time_zone: Box::new(Expr::Value(
@@ -13352,6 +13365,7 @@ fn test_dictionary_syntax() {
(Value::SingleQuotedString("2023-04-01".to_owned())).with_empty_span(),
)),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
+ array: false,
format: None,
}),
},
@@ -13363,6 +13377,7 @@ fn test_dictionary_syntax() {
(Value::SingleQuotedString("2023-04-05".to_owned())).with_empty_span(),
)),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
+ array: false,
format: None,
}),
},
@@ -13606,6 +13621,7 @@ fn test_extract_seconds_ok() {
fields: None,
precision: None
},
+ array: false,
format: None,
}),
}
@@ -13634,6 +13650,7 @@ fn test_extract_seconds_ok() {
fields: None,
precision: None,
},
+ array: false,
format: None,
}),
})],
@@ -13691,6 +13708,7 @@ fn test_extract_seconds_single_quote_ok() {
fields: None,
precision: None
},
+ array: false,
format: None,
}),
}
diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs
index 7f5ec6c3..b088afd7 100644
--- a/tests/sqlparser_databricks.rs
+++ b/tests/sqlparser_databricks.rs
@@ -349,6 +349,7 @@ fn data_type_timestamp_ntz() {
"created_at".into()
)))),
data_type: DataType::TimestampNtz(None),
+ array: false,
format: None
}
);
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs
index 80a15eb1..bdfe4f50 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -380,6 +380,7 @@ fn test_duckdb_specific_int_types() {
Value::Number("123".parse().unwrap(),
false).with_empty_span()
)),
data_type: data_type.clone(),
+ array: false,
format: None,
},
expr_from_projection(&select.projection[0])
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index e847d3ed..4a620538 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -874,6 +874,25 @@ fn test_functional_key_part() {
)),
}),
data_type: DataType::Unsigned,
+ array: false,
+ format: None,
+ })),
+ );
+ assert_eq!(
+ index_column(mysql_and_generic().verified_stmt(
+ r#"CREATE TABLE t (jsoncol JSON, PRIMARY KEY ((CAST(col ->>
'$.fields' AS UNSIGNED ARRAY)) ASC))"#
+ )),
+ Expr::Nested(Box::new(Expr::Cast {
+ kind: CastKind::Cast,
+ expr: Box::new(Expr::BinaryOp {
+ left: Box::new(Expr::Identifier(Ident::new("col"))),
+ op: BinaryOperator::LongArrow,
+ right: Box::new(Expr::Value(
+
Value::SingleQuotedString("$.fields".to_string()).with_empty_span()
+ )),
+ }),
+ data_type: DataType::Unsigned,
+ array: true,
format: None,
})),
);
@@ -4096,6 +4115,14 @@ fn parse_cast_integers() {
.expect_err("CAST doesn't allow display width");
}
+#[test]
+fn parse_cast_array() {
+ mysql().verified_expr("CAST(foo AS SIGNED ARRAY)");
+ mysql()
+ .run_parser_method("CAST(foo AS ARRAY)", |p| p.parse_expr())
+ .expect_err("ARRAY alone is not a type");
+}
+
#[test]
fn parse_match_against_with_alias() {
let sql = "SELECT tbl.ProjectID FROM surveys.tbl1 AS tbl WHERE MATCH
(tbl.ReferenceID) AGAINST ('AAA' IN BOOLEAN MODE)";
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 57bddc65..7c194c1c 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -1706,6 +1706,7 @@ fn parse_execute() {
(Value::Number("1337".parse().unwrap(),
false)).with_empty_span()
)),
data_type: DataType::SmallInt(None),
+ array: false,
format: None
},
alias: None
@@ -1717,6 +1718,7 @@ fn parse_execute() {
(Value::Number("7331".parse().unwrap(),
false)).with_empty_span()
)),
data_type: DataType::SmallInt(None),
+ array: false,
format: None
},
alias: None
@@ -2343,6 +2345,7 @@ fn parse_array_index_expr() {
))),
None
)),
+ array: false,
format: None,
}))),
access_chain: vec![
@@ -5573,6 +5576,7 @@ fn parse_at_time_zone() {
Value::SingleQuotedString("America/Los_Angeles".to_owned()).with_empty_span(),
)),
data_type: DataType::Text,
+ array: false,
format: None,
}),
}),
@@ -6389,6 +6393,7 @@ fn arrow_cast_precedence() {
(Value::SingleQuotedString("bar".to_string())).with_empty_span()
)),
data_type: DataType::Text,
+ array: false,
format: None,
}),
}
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index 72f60f1a..ede912eb 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -1101,8 +1101,8 @@ fn parse_create_dynamic_table() {
" EXTERNAL_VOLUME='my_external_volume'",
" CATALOG='SNOWFLAKE'",
" BASE_LOCATION='my_iceberg_table'",
- " TARGET_LAG='20 minutes'",
- " WAREHOUSE=mywh",
+ " TARGET_LAG='20 minutes'",
+ " WAREHOUSE=mywh",
" AS SELECT product_id, product_name FROM staging_table"
));
@@ -1250,6 +1250,7 @@ fn parse_array() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("a"))),
data_type: DataType::Array(ArrayElemTypeDef::None),
+ array: false,
format: None,
},
expr_from_projection(only(&select.projection))
@@ -1349,8 +1350,6 @@ fn parse_semi_structured_data_traversal() {
Expr::JsonAccess {
value: Box::new(Expr::Cast {
kind: CastKind::DoubleColon,
- data_type: DataType::Array(ArrayElemTypeDef::None),
- format: None,
expr: Box::new(Expr::JsonAccess {
value: Box::new(Expr::Identifier(Ident::new("a"))),
path: JsonPath {
@@ -1359,7 +1358,10 @@ fn parse_semi_structured_data_traversal() {
quoted: false
}]
}
- })
+ }),
+ data_type: DataType::Array(ArrayElemTypeDef::None),
+ array: false,
+ format: None,
}),
path: JsonPath {
path: vec![JsonPathElem::Bracket {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]