Skip to content

Commit c00914f

Browse files
mvzinkayman-sigma
authored andcommitted
MySQL: Support CAST(... AS ... ARRAY) syntax (apache#2151)
1 parent 1e6d9d2 commit c00914f

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
@@ -1056,6 +1056,12 @@ pub enum Expr {
10561056
expr: Box<Expr>,
10571057
/// Target data type.
10581058
data_type: DataType,
1059+
/// [MySQL] allows CAST(... AS type ARRAY) in functional index definitions for InnoDB
1060+
/// multi-valued indices. It's not really a datatype, and is only allowed in `CAST` in key
1061+
/// specifications, so it's a flag here.
1062+
///
1063+
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html#function_cast
1064+
array: bool,
10591065
/// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by [BigQuery]
10601066
///
10611067
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
@@ -1913,14 +1919,18 @@ impl fmt::Display for Expr {
19131919
kind,
19141920
expr,
19151921
data_type,
1922+
array,
19161923
format,
19171924
} => match kind {
19181925
CastKind::Cast => {
1926+
write!(f, "CAST({expr} AS {data_type}")?;
1927+
if *array {
1928+
write!(f, " ARRAY")?;
1929+
}
19191930
if let Some(format) = format {
1920-
write!(f, "CAST({expr} AS {data_type} FORMAT {format})")
1921-
} else {
1922-
write!(f, "CAST({expr} AS {data_type})")
1931+
write!(f, " FORMAT {format}")?;
19231932
}
1933+
write!(f, ")")
19241934
}
19251935
CastKind::TryCast => {
19261936
if let Some(format) = format {

src/ast/spans.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,7 @@ impl Spanned for Expr {
15451545
kind: _,
15461546
expr,
15471547
data_type: _,
1548+
array: _,
15481549
format: _,
15491550
} => expr.span(),
15501551
Expr::AtTimeZone {
@@ -2810,7 +2811,7 @@ WHERE id = 1
28102811
UPDATE SET target_table.description = source_table.description
28112812
28122813
WHEN MATCHED AND target_table.x != 'X' THEN DELETE
2813-
WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
2814+
WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
28142815
"#;
28152816

28162817
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() {
@@ -4209,6 +4213,7 @@ impl<'a> Parser<'a> {
42094213
kind: CastKind::DoubleColon,
42104214
expr: Box::new(expr),
42114215
data_type: self.parse_data_type()?,
4216+
array: false,
42124217
format: None,
42134218
})
42144219
}

tests/sqlparser_common.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3028,6 +3028,7 @@ fn parse_cast() {
30283028
kind: CastKind::Cast,
30293029
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30303030
data_type: DataType::BigInt(None),
3031+
array: false,
30313032
format: None,
30323033
},
30333034
expr_from_projection(only(&select.projection))
@@ -3040,6 +3041,7 @@ fn parse_cast() {
30403041
kind: CastKind::Cast,
30413042
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30423043
data_type: DataType::TinyInt(None),
3044+
array: false,
30433045
format: None,
30443046
},
30453047
expr_from_projection(only(&select.projection))
@@ -3071,6 +3073,7 @@ fn parse_cast() {
30713073
length: 50,
30723074
unit: None,
30733075
})),
3076+
array: false,
30743077
format: None,
30753078
},
30763079
expr_from_projection(only(&select.projection))
@@ -3083,6 +3086,7 @@ fn parse_cast() {
30833086
kind: CastKind::Cast,
30843087
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30853088
data_type: DataType::Clob(None),
3089+
array: false,
30863090
format: None,
30873091
},
30883092
expr_from_projection(only(&select.projection))
@@ -3095,6 +3099,7 @@ fn parse_cast() {
30953099
kind: CastKind::Cast,
30963100
expr: Box::new(Expr::Identifier(Ident::new("id"))),
30973101
data_type: DataType::Clob(Some(50)),
3102+
array: false,
30983103
format: None,
30993104
},
31003105
expr_from_projection(only(&select.projection))
@@ -3107,6 +3112,7 @@ fn parse_cast() {
31073112
kind: CastKind::Cast,
31083113
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31093114
data_type: DataType::Binary(Some(50)),
3115+
array: false,
31103116
format: None,
31113117
},
31123118
expr_from_projection(only(&select.projection))
@@ -3119,6 +3125,7 @@ fn parse_cast() {
31193125
kind: CastKind::Cast,
31203126
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31213127
data_type: DataType::Varbinary(Some(BinaryLength::IntegerLength { length: 50 })),
3128+
array: false,
31223129
format: None,
31233130
},
31243131
expr_from_projection(only(&select.projection))
@@ -3131,6 +3138,7 @@ fn parse_cast() {
31313138
kind: CastKind::Cast,
31323139
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31333140
data_type: DataType::Blob(None),
3141+
array: false,
31343142
format: None,
31353143
},
31363144
expr_from_projection(only(&select.projection))
@@ -3143,6 +3151,7 @@ fn parse_cast() {
31433151
kind: CastKind::Cast,
31443152
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31453153
data_type: DataType::Blob(Some(50)),
3154+
array: false,
31463155
format: None,
31473156
},
31483157
expr_from_projection(only(&select.projection))
@@ -3155,6 +3164,7 @@ fn parse_cast() {
31553164
kind: CastKind::Cast,
31563165
expr: Box::new(Expr::Identifier(Ident::new("details"))),
31573166
data_type: DataType::JSONB,
3167+
array: false,
31583168
format: None,
31593169
},
31603170
expr_from_projection(only(&select.projection))
@@ -3170,6 +3180,7 @@ fn parse_try_cast() {
31703180
kind: CastKind::TryCast,
31713181
expr: Box::new(Expr::Identifier(Ident::new("id"))),
31723182
data_type: DataType::BigInt(None),
3183+
array: false,
31733184
format: None,
31743185
},
31753186
expr_from_projection(only(&select.projection))
@@ -6506,6 +6517,7 @@ fn interval_disallow_interval_expr_double_colon() {
65066517
fractional_seconds_precision: None,
65076518
})),
65086519
data_type: DataType::Text,
6520+
array: false,
65096521
format: None,
65106522
}
65116523
)
@@ -9221,6 +9233,7 @@ fn parse_double_colon_cast_at_timezone() {
92219233
.with_empty_span()
92229234
)),
92239235
data_type: DataType::Timestamp(None, TimezoneInfo::None),
9236+
array: false,
92249237
format: None
92259238
}),
92269239
time_zone: Box::new(Expr::Value(
@@ -13355,6 +13368,7 @@ fn test_dictionary_syntax() {
1335513368
(Value::SingleQuotedString("2023-04-01".to_owned())).with_empty_span(),
1335613369
)),
1335713370
data_type: DataType::Timestamp(None, TimezoneInfo::None),
13371+
array: false,
1335813372
format: None,
1335913373
}),
1336013374
},
@@ -13366,6 +13380,7 @@ fn test_dictionary_syntax() {
1336613380
(Value::SingleQuotedString("2023-04-05".to_owned())).with_empty_span(),
1336713381
)),
1336813382
data_type: DataType::Timestamp(None, TimezoneInfo::None),
13383+
array: false,
1336913384
format: None,
1337013385
}),
1337113386
},
@@ -13609,6 +13624,7 @@ fn test_extract_seconds_ok() {
1360913624
fields: None,
1361013625
precision: None
1361113626
},
13627+
array: false,
1361213628
format: None,
1361313629
}),
1361413630
}
@@ -13637,6 +13653,7 @@ fn test_extract_seconds_ok() {
1363713653
fields: None,
1363813654
precision: None,
1363913655
},
13656+
array: false,
1364013657
format: None,
1364113658
}),
1364213659
})],
@@ -13694,6 +13711,7 @@ fn test_extract_seconds_single_quote_ok() {
1369413711
fields: None,
1369513712
precision: None
1369613713
},
13714+
array: false,
1369713715
format: None,
1369813716
}),
1369913717
}

tests/sqlparser_databricks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ fn data_type_timestamp_ntz() {
372372
"created_at".into()
373373
)))),
374374
data_type: DataType::TimestampNtz(None),
375+
array: false,
375376
format: None
376377
}
377378
);

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))
@@ -1472,8 +1473,6 @@ fn parse_semi_structured_data_traversal() {
14721473
Expr::JsonAccess {
14731474
value: Box::new(Expr::Cast {
14741475
kind: CastKind::DoubleColon,
1475-
data_type: DataType::Array(ArrayElemTypeDef::None),
1476-
format: None,
14771476
expr: Box::new(Expr::JsonAccess {
14781477
value: Box::new(Expr::Identifier(Ident::new("a"))),
14791478
path: JsonPath {
@@ -1483,7 +1482,10 @@ fn parse_semi_structured_data_traversal() {
14831482
quoted: false
14841483
}]
14851484
}
1486-
})
1485+
}),
1486+
data_type: DataType::Array(ArrayElemTypeDef::None),
1487+
array: false,
1488+
format: None,
14871489
}),
14881490
path: JsonPath {
14891491
has_colon: false,

0 commit comments

Comments
 (0)