MySQL: Allow optional SIGNED suffix on integer data types

In MySQL, a data type like `INT(20) SIGNED` is equivalent to `INT(20)`.
In other dialects, this may be interpreted differently; e.g. in
Postgres, `SELECT 1::integer signed` indicates an alias of "signed". So
we parse the optional `SIGNED` suffix only on dialects that allow it (I
currently don't know of any other than MySQL).
This commit is contained in:
Michael Victor Zink 2025-07-30 10:01:22 -07:00
parent 3d2db8c69b
commit 28d2183ff3
5 changed files with 83 additions and 0 deletions

View file

@ -183,4 +183,8 @@ impl Dialect for GenericDialect {
fn supports_select_wildcard_exclude(&self) -> bool {
true
}
fn supports_data_type_signed_suffix(&self) -> bool {
true
}
}

View file

@ -1136,6 +1136,18 @@ pub trait Dialect: Debug + Any {
fn supports_notnull_operator(&self) -> bool {
false
}
/// Returns true if this dialect allows an optional `SIGNED` suffix after integer data types.
///
/// Example:
/// ```sql
/// CREATE TABLE t (i INT(20) SIGNED);
/// ```
///
/// Note that this is canonicalized to `INT(20)`.
fn supports_data_type_signed_suffix(&self) -> bool {
false
}
}
/// This represents the operators for which precedence must be defined

View file

@ -154,6 +154,10 @@ impl Dialect for MySqlDialect {
fn supports_comma_separated_set_assignments(&self) -> bool {
true
}
fn supports_data_type_signed_suffix(&self) -> bool {
true
}
}
/// `LOCK TABLES`

View file

@ -9848,6 +9848,9 @@ impl<'a> Parser<'a> {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::TinyIntUnsigned(optional_precision?))
} else {
if dialect.supports_data_type_signed_suffix() {
let _ = self.parse_keyword(Keyword::SIGNED);
}
Ok(DataType::TinyInt(optional_precision?))
}
}
@ -9864,6 +9867,9 @@ impl<'a> Parser<'a> {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::SmallIntUnsigned(optional_precision?))
} else {
if dialect.supports_data_type_signed_suffix() {
let _ = self.parse_keyword(Keyword::SIGNED);
}
Ok(DataType::SmallInt(optional_precision?))
}
}
@ -9872,6 +9878,9 @@ impl<'a> Parser<'a> {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::MediumIntUnsigned(optional_precision?))
} else {
if dialect.supports_data_type_signed_suffix() {
let _ = self.parse_keyword(Keyword::SIGNED);
}
Ok(DataType::MediumInt(optional_precision?))
}
}
@ -9880,6 +9889,9 @@ impl<'a> Parser<'a> {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::IntUnsigned(optional_precision?))
} else {
if dialect.supports_data_type_signed_suffix() {
let _ = self.parse_keyword(Keyword::SIGNED);
}
Ok(DataType::Int(optional_precision?))
}
}
@ -9909,6 +9921,9 @@ impl<'a> Parser<'a> {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::IntegerUnsigned(optional_precision?))
} else {
if dialect.supports_data_type_signed_suffix() {
let _ = self.parse_keyword(Keyword::SIGNED);
}
Ok(DataType::Integer(optional_precision?))
}
}
@ -9917,6 +9932,9 @@ impl<'a> Parser<'a> {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::BigIntUnsigned(optional_precision?))
} else {
if dialect.supports_data_type_signed_suffix() {
let _ = self.parse_keyword(Keyword::SIGNED);
}
Ok(DataType::BigInt(optional_precision?))
}
}

View file

@ -1705,6 +1705,51 @@ fn parse_create_table_unsigned() {
}
}
#[test]
fn parse_signed_data_types() {
let sql = "CREATE TABLE foo (bar_tinyint TINYINT(3) SIGNED, bar_smallint SMALLINT(5) SIGNED, bar_mediumint MEDIUMINT(13) SIGNED, bar_int INT(11) SIGNED, bar_bigint BIGINT(20) SIGNED)";
let canonical = "CREATE TABLE foo (bar_tinyint TINYINT(3), bar_smallint SMALLINT(5), bar_mediumint MEDIUMINT(13), bar_int INT(11), bar_bigint BIGINT(20))";
match mysql().one_statement_parses_to(sql, canonical) {
Statement::CreateTable(CreateTable { name, columns, .. }) => {
assert_eq!(name.to_string(), "foo");
assert_eq!(
vec![
ColumnDef {
name: Ident::new("bar_tinyint"),
data_type: DataType::TinyInt(Some(3)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_smallint"),
data_type: DataType::SmallInt(Some(5)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_mediumint"),
data_type: DataType::MediumInt(Some(13)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_int"),
data_type: DataType::Int(Some(11)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_bigint"),
data_type: DataType::BigInt(Some(20)),
options: vec![],
},
],
columns
);
}
_ => unreachable!(),
}
all_dialects_except(|d| d.supports_data_type_signed_suffix())
.run_parser_method(sql, |p| p.parse_statement())
.expect_err("SIGNED suffix should not be allowed");
}
#[test]
fn parse_simple_insert() {
let sql = r"INSERT INTO tasks (title, priority) VALUES ('Test Some Inserts', 1), ('Test Entry 2', 2), ('Test Entry 3', 3)";