Skip to content

Commit e7a3019

Browse files
authored
MySQL: Support CAST(... AS ... ARRAY) syntax (apache#2151)
1 parent 6550ec8 commit e7a3019

9 files changed

Lines changed: 79 additions & 9 deletions

File tree

src/ast/mod.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,12 @@ pub enum Expr {
10331033
expr: Box<Expr>,
10341034
/// Target data type.
10351035
data_type: DataType,
1036+
/// [MySQL] allows CAST(... AS type ARRAY) in functional index definitions for InnoDB
1037+
/// multi-valued indices. It's not really a datatype, and is only allowed in `CAST` in key
1038+
/// specifications, so it's a flag here.
1039+
///
1040+
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html#function_cast
1041+
array: bool,
10361042
/// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by [BigQuery]
10371043
///
10381044
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
@@ -1879,14 +1885,18 @@ impl fmt::Display for Expr {
18791885
kind,
18801886
expr,
18811887
data_type,
1888+
array,
18821889
format,
18831890
} => match kind {
18841891
CastKind::Cast => {
1892+
write!(f, "CAST({expr} AS {data_type}")?;
1893+
if *array {
1894+
write!(f, " ARRAY")?;
1895+
}
18851896
if let Some(format) = format {
1886-
write!(f, "CAST({expr} AS {data_type} FORMAT {format})")
1887-
} else {
1888-
write!(f, "CAST({expr} AS {data_type})")
1897+
write!(f, " FORMAT {format}")?;
18891898
}
1899+
write!(f, ")")
18901900
}
18911901
CastKind::TryCast => {
18921902
if let Some(format) = format {

src/ast/spans.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,7 @@ impl Spanned for Expr {
15401540
kind: _,
15411541
expr,
15421542
data_type: _,
1543+
array: _,
15431544
format: _,
15441545
} => expr.span(),
15451546
Expr::AtTimeZone {
@@ -2801,7 +2802,7 @@ WHERE id = 1
28012802
UPDATE SET target_table.description = source_table.description
28022803
28032804
WHEN MATCHED AND target_table.x != 'X' THEN DELETE
2804-
WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
2805+
WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
28052806
"#;
28062807

28072808
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();

src/parser/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,6 +1650,7 @@ impl<'a> Parser<'a> {
16501650
kind: CastKind::Cast,
16511651
expr: Box::new(parser.parse_expr()?),
16521652
data_type: DataType::Binary(None),
1653+
array: false,
16531654
format: None,
16541655
})
16551656
}
@@ -2655,12 +2656,14 @@ impl<'a> Parser<'a> {
26552656
let expr = self.parse_expr()?;
26562657
self.expect_keyword_is(Keyword::AS)?;
26572658
let data_type = self.parse_data_type()?;
2659+
let array = self.parse_keyword(Keyword::ARRAY);
26582660
let format = self.parse_optional_cast_format()?;
26592661
self.expect_token(&Token::RParen)?;
26602662
Ok(Expr::Cast {
26612663
kind,
26622664
expr: Box::new(expr),
26632665
data_type,
2666+
array,
26642667
format,
26652668
})
26662669
}
@@ -3938,6 +3941,7 @@ impl<'a> Parser<'a> {
39383941
kind: CastKind::DoubleColon,
39393942
expr: Box::new(expr),
39403943
data_type: self.parse_data_type()?,
3944+
array: false,
39413945
format: None,
39423946
})
39433947
} else if Token::ExclamationMark == *tok && self.dialect.supports_factorial_operator() {
@@ -4178,6 +4182,7 @@ impl<'a> Parser<'a> {
41784182
kind: CastKind::DoubleColon,
41794183
expr: Box::new(expr),
41804184
data_type: self.parse_data_type()?,
4185+
array: false,
41814186
format: None,
41824187
})
41834188
}

tests/sqlparser_common.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3027,6 +3027,7 @@ fn parse_cast() {
30273027
kind: CastKind::Cast,
30283028
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30293029
data_type: DataType::BigInt(None),
3030+
array: false,
30303031
format: None,
30313032
},
30323033
expr_from_projection(only(&select.projection))
@@ -3039,6 +3040,7 @@ fn parse_cast() {
30393040
kind: CastKind::Cast,
30403041
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30413042
data_type: DataType::TinyInt(None),
3043+
array: false,
30423044
format: None,
30433045
},
30443046
expr_from_projection(only(&select.projection))
@@ -3070,6 +3072,7 @@ fn parse_cast() {
30703072
length: 50,
30713073
unit: None,
30723074
})),
3075+
array: false,
30733076
format: None,
30743077
},
30753078
expr_from_projection(only(&select.projection))
@@ -3082,6 +3085,7 @@ fn parse_cast() {
30823085
kind: CastKind::Cast,
30833086
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30843087
data_type: DataType::Clob(None),
3088+
array: false,
30853089
format: None,
30863090
},
30873091
expr_from_projection(only(&select.projection))
@@ -3094,6 +3098,7 @@ fn parse_cast() {
30943098
kind: CastKind::Cast,
30953099
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30963100
data_type: DataType::Clob(Some(50)),
3101+
array: false,
30973102
format: None,
30983103
},
30993104
expr_from_projection(only(&select.projection))
@@ -3106,6 +3111,7 @@ fn parse_cast() {
31063111
kind: CastKind::Cast,
31073112
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31083113
data_type: DataType::Binary(Some(50)),
3114+
array: false,
31093115
format: None,
31103116
},
31113117
expr_from_projection(only(&select.projection))
@@ -3118,6 +3124,7 @@ fn parse_cast() {
31183124
kind: CastKind::Cast,
31193125
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31203126
data_type: DataType::Varbinary(Some(BinaryLength::IntegerLength { length: 50 })),
3127+
array: false,
31213128
format: None,
31223129
},
31233130
expr_from_projection(only(&select.projection))
@@ -3130,6 +3137,7 @@ fn parse_cast() {
31303137
kind: CastKind::Cast,
31313138
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31323139
data_type: DataType::Blob(None),
3140+
array: false,
31333141
format: None,
31343142
},
31353143
expr_from_projection(only(&select.projection))
@@ -3142,6 +3150,7 @@ fn parse_cast() {
31423150
kind: CastKind::Cast,
31433151
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31443152
data_type: DataType::Blob(Some(50)),
3153+
array: false,
31453154
format: None,
31463155
},
31473156
expr_from_projection(only(&select.projection))
@@ -3154,6 +3163,7 @@ fn parse_cast() {
31543163
kind: CastKind::Cast,
31553164
expr: Box::new(Expr::Identifier(Ident::new("details"))),
31563165
data_type: DataType::JSONB,
3166+
array: false,
31573167
format: None,
31583168
},
31593169
expr_from_projection(only(&select.projection))
@@ -3169,6 +3179,7 @@ fn parse_try_cast() {
31693179
kind: CastKind::TryCast,
31703180
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31713181
data_type: DataType::BigInt(None),
3182+
array: false,
31723183
format: None,
31733184
},
31743185
expr_from_projection(only(&select.projection))
@@ -6505,6 +6516,7 @@ fn interval_disallow_interval_expr_double_colon() {
65056516
fractional_seconds_precision: None,
65066517
})),
65076518
data_type: DataType::Text,
6519+
array: false,
65086520
format: None,
65096521
}
65106522
)
@@ -9220,6 +9232,7 @@ fn parse_double_colon_cast_at_timezone() {
92209232
.with_empty_span()
92219233
)),
92229234
data_type: DataType::Timestamp(None, TimezoneInfo::None),
9235+
array: false,
92239236
format: None
92249237
}),
92259238
time_zone: Box::new(Expr::Value(
@@ -13352,6 +13365,7 @@ fn test_dictionary_syntax() {
1335213365
(Value::SingleQuotedString("2023-04-01".to_owned())).with_empty_span(),
1335313366
)),
1335413367
data_type: DataType::Timestamp(None, TimezoneInfo::None),
13368+
array: false,
1335513369
format: None,
1335613370
}),
1335713371
},
@@ -13363,6 +13377,7 @@ fn test_dictionary_syntax() {
1336313377
(Value::SingleQuotedString("2023-04-05".to_owned())).with_empty_span(),
1336413378
)),
1336513379
data_type: DataType::Timestamp(None, TimezoneInfo::None),
13380+
array: false,
1336613381
format: None,
1336713382
}),
1336813383
},
@@ -13606,6 +13621,7 @@ fn test_extract_seconds_ok() {
1360613621
fields: None,
1360713622
precision: None
1360813623
},
13624+
array: false,
1360913625
format: None,
1361013626
}),
1361113627
}
@@ -13634,6 +13650,7 @@ fn test_extract_seconds_ok() {
1363413650
fields: None,
1363513651
precision: None,
1363613652
},
13653+
array: false,
1363713654
format: None,
1363813655
}),
1363913656
})],
@@ -13691,6 +13708,7 @@ fn test_extract_seconds_single_quote_ok() {
1369113708
fields: None,
1369213709
precision: None
1369313710
},
13711+
array: false,
1369413712
format: None,
1369513713
}),
1369613714
}

tests/sqlparser_databricks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ fn data_type_timestamp_ntz() {
349349
"created_at".into()
350350
)))),
351351
data_type: DataType::TimestampNtz(None),
352+
array: false,
352353
format: None
353354
}
354355
);

tests/sqlparser_duckdb.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ fn test_duckdb_specific_int_types() {
380380
Value::Number("123".parse().unwrap(), false).with_empty_span()
381381
)),
382382
data_type: data_type.clone(),
383+
array: false,
383384
format: None,
384385
},
385386
expr_from_projection(&select.projection[0])

tests/sqlparser_mysql.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,25 @@ fn test_functional_key_part() {
874874
)),
875875
}),
876876
data_type: DataType::Unsigned,
877+
array: false,
878+
format: None,
879+
})),
880+
);
881+
assert_eq!(
882+
index_column(mysql_and_generic().verified_stmt(
883+
r#"CREATE TABLE t (jsoncol JSON, PRIMARY KEY ((CAST(col ->> '$.fields' AS UNSIGNED ARRAY)) ASC))"#
884+
)),
885+
Expr::Nested(Box::new(Expr::Cast {
886+
kind: CastKind::Cast,
887+
expr: Box::new(Expr::BinaryOp {
888+
left: Box::new(Expr::Identifier(Ident::new("col"))),
889+
op: BinaryOperator::LongArrow,
890+
right: Box::new(Expr::Value(
891+
Value::SingleQuotedString("$.fields".to_string()).with_empty_span()
892+
)),
893+
}),
894+
data_type: DataType::Unsigned,
895+
array: true,
877896
format: None,
878897
})),
879898
);
@@ -4096,6 +4115,14 @@ fn parse_cast_integers() {
40964115
.expect_err("CAST doesn't allow display width");
40974116
}
40984117

4118+
#[test]
4119+
fn parse_cast_array() {
4120+
mysql().verified_expr("CAST(foo AS SIGNED ARRAY)");
4121+
mysql()
4122+
.run_parser_method("CAST(foo AS ARRAY)", |p| p.parse_expr())
4123+
.expect_err("ARRAY alone is not a type");
4124+
}
4125+
40994126
#[test]
41004127
fn parse_match_against_with_alias() {
41014128
let sql = "SELECT tbl.ProjectID FROM surveys.tbl1 AS tbl WHERE MATCH (tbl.ReferenceID) AGAINST ('AAA' IN BOOLEAN MODE)";

tests/sqlparser_postgres.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,7 @@ fn parse_execute() {
17061706
(Value::Number("1337".parse().unwrap(), false)).with_empty_span()
17071707
)),
17081708
data_type: DataType::SmallInt(None),
1709+
array: false,
17091710
format: None
17101711
},
17111712
alias: None
@@ -1717,6 +1718,7 @@ fn parse_execute() {
17171718
(Value::Number("7331".parse().unwrap(), false)).with_empty_span()
17181719
)),
17191720
data_type: DataType::SmallInt(None),
1721+
array: false,
17201722
format: None
17211723
},
17221724
alias: None
@@ -2343,6 +2345,7 @@ fn parse_array_index_expr() {
23432345
))),
23442346
None
23452347
)),
2348+
array: false,
23462349
format: None,
23472350
}))),
23482351
access_chain: vec![
@@ -5573,6 +5576,7 @@ fn parse_at_time_zone() {
55735576
Value::SingleQuotedString("America/Los_Angeles".to_owned()).with_empty_span(),
55745577
)),
55755578
data_type: DataType::Text,
5579+
array: false,
55765580
format: None,
55775581
}),
55785582
}),
@@ -6389,6 +6393,7 @@ fn arrow_cast_precedence() {
63896393
(Value::SingleQuotedString("bar".to_string())).with_empty_span()
63906394
)),
63916395
data_type: DataType::Text,
6396+
array: false,
63926397
format: None,
63936398
}),
63946399
}

tests/sqlparser_snowflake.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,8 +1101,8 @@ fn parse_create_dynamic_table() {
11011101
" EXTERNAL_VOLUME='my_external_volume'",
11021102
" CATALOG='SNOWFLAKE'",
11031103
" BASE_LOCATION='my_iceberg_table'",
1104-
" TARGET_LAG='20 minutes'",
1105-
" WAREHOUSE=mywh",
1104+
" TARGET_LAG='20 minutes'",
1105+
" WAREHOUSE=mywh",
11061106
" AS SELECT product_id, product_name FROM staging_table"
11071107
));
11081108

@@ -1250,6 +1250,7 @@ fn parse_array() {
12501250
kind: CastKind::Cast,
12511251
expr: Box::new(Expr::Identifier(Ident::new("a"))),
12521252
data_type: DataType::Array(ArrayElemTypeDef::None),
1253+
array: false,
12531254
format: None,
12541255
},
12551256
expr_from_projection(only(&select.projection))
@@ -1349,8 +1350,6 @@ fn parse_semi_structured_data_traversal() {
13491350
Expr::JsonAccess {
13501351
value: Box::new(Expr::Cast {
13511352
kind: CastKind::DoubleColon,
1352-
data_type: DataType::Array(ArrayElemTypeDef::None),
1353-
format: None,
13541353
expr: Box::new(Expr::JsonAccess {
13551354
value: Box::new(Expr::Identifier(Ident::new("a"))),
13561355
path: JsonPath {
@@ -1359,7 +1358,10 @@ fn parse_semi_structured_data_traversal() {
13591358
quoted: false
13601359
}]
13611360
}
1362-
})
1361+
}),
1362+
data_type: DataType::Array(ArrayElemTypeDef::None),
1363+
array: false,
1364+
format: None,
13631365
}),
13641366
path: JsonPath {
13651367
path: vec![JsonPathElem::Bracket {

0 commit comments

Comments
 (0)