Skip to content

Commit 2877163

Browse files
committed
Parse signed/unsigned integer data type in MySQL CAST
MySQL doesn't have the same set of possible CAST types as for e.g. column definitions. For example, it raises a syntax error for `CAST(1 AS INTEGER SIGNED)` and instead expects `CAST(1 AS SIGNED INTEGER)`. We retain the current somewhat permissive datatype parsing behavior (e.g. allowing `CAST(1 AS BIGINT)` even though MySQL would raise a syntax error), and add two datatypes for this specific case (`SIGNED [INTEGER]` and `UNSIGNED [INTEGER]`). Closes #1589
1 parent 3ace97c commit 2877163

File tree

5 files changed

+56
-2
lines changed

5 files changed

+56
-2
lines changed

src/ast/data_type.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,26 @@ pub enum DataType {
238238
UnsignedBigInt(Option<u64>),
239239
/// Unsigned Int8 with optional display width e.g. INT8 UNSIGNED or INT8(11) UNSIGNED
240240
UnsignedInt8(Option<u64>),
241+
/// Signed integer as used in [MySQL CAST] target types, with optional `INTEGER` suffix:
242+
/// `SIGNED [INTEGER]`
243+
///
244+
/// Note that this doesn't accept a display width and is reversed from the syntax used in column
245+
/// definitions ([`DataType::Int`]): `INTEGER [SIGNED]`
246+
///
247+
/// Semantically equivalent to `BIGINT`.
248+
///
249+
/// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html
250+
Signed(bool),
251+
/// Unsigned integer as used in [MySQL CAST] target types, with optional `INTEGER` suffix:
252+
/// `UNSIGNED [INTEGER]`
253+
///
254+
/// Note that this doesn't accept a display widths and is reversed from the syntax used in
255+
/// column definitions ([`DataType::UnsignedInteger`]): `INTEGER [UNSIGNED]`
256+
///
257+
/// Semantically equivalent to `BIGINT UNSIGNED`.
258+
///
259+
/// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html
260+
Unsigned(bool),
241261
/// Float4 as alias for Real in [postgresql]
242262
///
243263
/// [postgresql]: https://www.postgresql.org/docs/15/datatype.html
@@ -515,6 +535,12 @@ impl fmt::Display for DataType {
515535
DataType::UInt256 => {
516536
write!(f, "UInt256")
517537
}
538+
DataType::Signed(integer) => {
539+
write!(f, "SIGNED{}", if *integer { " INTEGER" } else { "" })
540+
}
541+
DataType::Unsigned(integer) => {
542+
write!(f, "UNSIGNED{}", if *integer { " INTEGER" } else { "" })
543+
}
518544
DataType::Real => write!(f, "REAL"),
519545
DataType::Float4 => write!(f, "FLOAT4"),
520546
DataType::Float32 => write!(f, "Float32"),

src/ast/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,8 +798,9 @@ pub enum Expr {
798798
kind: CastKind,
799799
expr: Box<Expr>,
800800
data_type: DataType,
801-
// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery
802-
// https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
801+
/// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by [BigQuery]
802+
///
803+
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
803804
format: Option<CastFormat>,
804805
},
805806
/// AT a timestamp to a different timezone e.g. `FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00'`

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ define_keywords!(
790790
SHARE,
791791
SHARING,
792792
SHOW,
793+
SIGNED,
793794
SIMILAR,
794795
SKIP,
795796
SLOW,

src/parser/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9110,6 +9110,14 @@ impl<'a> Parser<'a> {
91109110
let columns = self.parse_returns_table_columns()?;
91119111
Ok(DataType::Table(columns))
91129112
}
9113+
Keyword::SIGNED => {
9114+
let integer = self.parse_keyword(Keyword::INTEGER);
9115+
Ok(DataType::Signed(integer))
9116+
}
9117+
Keyword::UNSIGNED => {
9118+
let integer = self.parse_keyword(Keyword::INTEGER);
9119+
Ok(DataType::Unsigned(integer))
9120+
}
91139121
_ => {
91149122
self.prev_token();
91159123
let type_name = self.parse_object_name(false)?;

tests/sqlparser_mysql.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3255,3 +3255,21 @@ fn parse_looks_like_single_line_comment() {
32553255
"UPDATE account SET balance = balance WHERE account_id = 5752",
32563256
);
32573257
}
3258+
3259+
#[test]
3260+
fn parse_cast_integers() {
3261+
mysql().verified_expr("CAST(foo AS UNSIGNED)");
3262+
mysql().verified_expr("CAST(foo AS SIGNED)");
3263+
mysql().verified_expr("CAST(foo AS UNSIGNED INTEGER)");
3264+
mysql().verified_expr("CAST(foo AS SIGNED INTEGER)");
3265+
3266+
mysql()
3267+
.run_parser_method("CAST(foo AS UNSIGNED(3))", |p| p.parse_expr())
3268+
.expect_err("CAST doesn't allow display width");
3269+
mysql()
3270+
.run_parser_method("CAST(foo AS UNSIGNED(3) INTEGER)", |p| p.parse_expr())
3271+
.expect_err("CAST doesn't allow display width");
3272+
mysql()
3273+
.run_parser_method("CAST(foo AS UNSIGNED INTEGER(3))", |p| p.parse_expr())
3274+
.expect_err("CAST doesn't allow display width");
3275+
}

0 commit comments

Comments
 (0)