MySQL: Add support for unsigned numeric types (#2031)

This commit is contained in:
Mohamed Abdeen 2025-09-19 11:04:56 +03:00 committed by GitHub
parent f642dd573c
commit ea7f9026f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 182 additions and 15 deletions

View file

@ -131,6 +131,11 @@ pub enum DataType {
///
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#exact-numeric-type
Decimal(ExactNumberInfo),
/// [MySQL] unsigned decimal with optional precision and scale, e.g. DECIMAL UNSIGNED or DECIMAL(10,2) UNSIGNED.
/// Note: Using UNSIGNED with DECIMAL is deprecated in recent versions of MySQL.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html
DecimalUnsigned(ExactNumberInfo),
/// [BigNumeric] type used in BigQuery.
///
/// [BigNumeric]: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#bignumeric_literals
@ -143,8 +148,19 @@ pub enum DataType {
///
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#exact-numeric-type
Dec(ExactNumberInfo),
/// Floating point with optional precision, e.g. FLOAT(8).
Float(Option<u64>),
/// [MySQL] unsigned decimal (DEC alias) with optional precision and scale, e.g. DEC UNSIGNED or DEC(10,2) UNSIGNED.
/// Note: Using UNSIGNED with DEC is deprecated in recent versions of MySQL.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html
DecUnsigned(ExactNumberInfo),
/// Floating point with optional precision and scale, e.g. FLOAT, FLOAT(8), or FLOAT(8,2).
Float(ExactNumberInfo),
/// [MySQL] unsigned floating point with optional precision and scale, e.g.
/// FLOAT UNSIGNED, FLOAT(10) UNSIGNED or FLOAT(10,2) UNSIGNED.
/// Note: Using UNSIGNED with FLOAT is deprecated in recent versions of MySQL.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html
FloatUnsigned(ExactNumberInfo),
/// Tiny integer with optional display width, e.g. TINYINT or TINYINT(3).
TinyInt(Option<u64>),
/// Unsigned tiny integer with optional display width,
@ -302,17 +318,32 @@ pub enum DataType {
Float64,
/// Floating point, e.g. REAL.
Real,
/// [MySQL] unsigned real, e.g. REAL UNSIGNED.
/// Note: Using UNSIGNED with REAL is deprecated in recent versions of MySQL.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html
RealUnsigned,
/// Float8 is an alias for Double in [PostgreSQL].
///
/// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html
Float8,
/// Double
Double(ExactNumberInfo),
/// [MySQL] unsigned double precision with optional precision, e.g. DOUBLE UNSIGNED or DOUBLE(10,2) UNSIGNED.
/// Note: Using UNSIGNED with DOUBLE is deprecated in recent versions of MySQL.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html
DoubleUnsigned(ExactNumberInfo),
/// Double Precision, see [SQL Standard], [PostgreSQL].
///
/// [SQL Standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#approximate-numeric-type
/// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-numeric.html
DoublePrecision,
/// [MySQL] unsigned double precision, e.g. DOUBLE PRECISION UNSIGNED.
/// Note: Using UNSIGNED with DOUBLE PRECISION is deprecated in recent versions of MySQL.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html
DoublePrecisionUnsigned,
/// Bool is an alias for Boolean, see [PostgreSQL].
///
/// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html
@ -497,12 +528,19 @@ impl fmt::Display for DataType {
DataType::Decimal(info) => {
write!(f, "DECIMAL{info}")
}
DataType::DecimalUnsigned(info) => {
write!(f, "DECIMAL{info} UNSIGNED")
}
DataType::Dec(info) => {
write!(f, "DEC{info}")
}
DataType::DecUnsigned(info) => {
write!(f, "DEC{info} UNSIGNED")
}
DataType::BigNumeric(info) => write!(f, "BIGNUMERIC{info}"),
DataType::BigDecimal(info) => write!(f, "BIGDECIMAL{info}"),
DataType::Float(size) => format_type_with_optional_length(f, "FLOAT", size, false),
DataType::Float(info) => write!(f, "FLOAT{info}"),
DataType::FloatUnsigned(info) => write!(f, "FLOAT{info} UNSIGNED"),
DataType::TinyInt(zerofill) => {
format_type_with_optional_length(f, "TINYINT", zerofill, false)
}
@ -616,12 +654,15 @@ impl fmt::Display for DataType {
write!(f, "UNSIGNED INTEGER")
}
DataType::Real => write!(f, "REAL"),
DataType::RealUnsigned => write!(f, "REAL UNSIGNED"),
DataType::Float4 => write!(f, "FLOAT4"),
DataType::Float32 => write!(f, "Float32"),
DataType::Float64 => write!(f, "FLOAT64"),
DataType::Double(info) => write!(f, "DOUBLE{info}"),
DataType::DoubleUnsigned(info) => write!(f, "DOUBLE{info} UNSIGNED"),
DataType::Float8 => write!(f, "FLOAT8"),
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
DataType::DoublePrecisionUnsigned => write!(f, "DOUBLE PRECISION UNSIGNED"),
DataType::Bool => write!(f, "BOOL"),
DataType::Boolean => write!(f, "BOOLEAN"),
DataType::Date => write!(f, "DATE"),

View file

@ -10181,19 +10181,41 @@ impl<'a> Parser<'a> {
Token::Word(w) => match w.keyword {
Keyword::BOOLEAN => Ok(DataType::Boolean),
Keyword::BOOL => Ok(DataType::Bool),
Keyword::FLOAT => Ok(DataType::Float(self.parse_optional_precision()?)),
Keyword::REAL => Ok(DataType::Real),
Keyword::FLOAT => {
let precision = self.parse_exact_number_optional_precision_scale()?;
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::FloatUnsigned(precision))
} else {
Ok(DataType::Float(precision))
}
}
Keyword::REAL => {
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::RealUnsigned)
} else {
Ok(DataType::Real)
}
}
Keyword::FLOAT4 => Ok(DataType::Float4),
Keyword::FLOAT32 => Ok(DataType::Float32),
Keyword::FLOAT64 => Ok(DataType::Float64),
Keyword::FLOAT8 => Ok(DataType::Float8),
Keyword::DOUBLE => {
if self.parse_keyword(Keyword::PRECISION) {
Ok(DataType::DoublePrecision)
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::DoublePrecisionUnsigned)
} else {
Ok(DataType::DoublePrecision)
}
} else {
Ok(DataType::Double(
self.parse_exact_number_optional_precision_scale()?,
))
let precision = self.parse_exact_number_optional_precision_scale()?;
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::DoubleUnsigned(precision))
} else {
Ok(DataType::Double(precision))
}
}
}
Keyword::TINYINT => {
@ -10420,12 +10442,24 @@ impl<'a> Parser<'a> {
Keyword::NUMERIC => Ok(DataType::Numeric(
self.parse_exact_number_optional_precision_scale()?,
)),
Keyword::DECIMAL => Ok(DataType::Decimal(
self.parse_exact_number_optional_precision_scale()?,
)),
Keyword::DEC => Ok(DataType::Dec(
self.parse_exact_number_optional_precision_scale()?,
)),
Keyword::DECIMAL => {
let precision = self.parse_exact_number_optional_precision_scale()?;
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::DecimalUnsigned(precision))
} else {
Ok(DataType::Decimal(precision))
}
}
Keyword::DEC => {
let precision = self.parse_exact_number_optional_precision_scale()?;
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::DecUnsigned(precision))
} else {
Ok(DataType::Dec(precision))
}
}
Keyword::BIGNUMERIC => Ok(DataType::BigNumeric(
self.parse_exact_number_optional_precision_scale()?,
)),

View file

@ -1757,6 +1757,98 @@ fn parse_signed_data_types() {
.expect_err("SIGNED suffix should not be allowed");
}
#[test]
fn parse_deprecated_mysql_unsigned_data_types() {
let sql = "CREATE TABLE foo (bar_decimal DECIMAL UNSIGNED, bar_decimal_prec DECIMAL(10) UNSIGNED, bar_decimal_scale DECIMAL(10,2) UNSIGNED, bar_dec DEC UNSIGNED, bar_dec_prec DEC(10) UNSIGNED, bar_dec_scale DEC(10,2) UNSIGNED, bar_float FLOAT UNSIGNED, bar_float_prec FLOAT(10) UNSIGNED, bar_float_scale FLOAT(10,2) UNSIGNED, bar_double DOUBLE UNSIGNED, bar_double_prec DOUBLE(10) UNSIGNED, bar_double_scale DOUBLE(10,2) UNSIGNED, bar_real REAL UNSIGNED, bar_double_precision DOUBLE PRECISION UNSIGNED)";
match mysql().verified_stmt(sql) {
Statement::CreateTable(CreateTable { name, columns, .. }) => {
assert_eq!(name.to_string(), "foo");
assert_eq!(
vec![
ColumnDef {
name: Ident::new("bar_decimal"),
data_type: DataType::DecimalUnsigned(ExactNumberInfo::None),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_decimal_prec"),
data_type: DataType::DecimalUnsigned(ExactNumberInfo::Precision(10)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_decimal_scale"),
data_type: DataType::DecimalUnsigned(ExactNumberInfo::PrecisionAndScale(
10, 2
)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_dec"),
data_type: DataType::DecUnsigned(ExactNumberInfo::None),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_dec_prec"),
data_type: DataType::DecUnsigned(ExactNumberInfo::Precision(10)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_dec_scale"),
data_type: DataType::DecUnsigned(ExactNumberInfo::PrecisionAndScale(10, 2)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_float"),
data_type: DataType::FloatUnsigned(ExactNumberInfo::None),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_float_prec"),
data_type: DataType::FloatUnsigned(ExactNumberInfo::Precision(10)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_float_scale"),
data_type: DataType::FloatUnsigned(ExactNumberInfo::PrecisionAndScale(
10, 2
)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_double"),
data_type: DataType::DoubleUnsigned(ExactNumberInfo::None),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_double_prec"),
data_type: DataType::DoubleUnsigned(ExactNumberInfo::Precision(10)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_double_scale"),
data_type: DataType::DoubleUnsigned(ExactNumberInfo::PrecisionAndScale(
10, 2
)),
options: vec![],
},
ColumnDef {
name: Ident::new("bar_real"),
data_type: DataType::RealUnsigned,
options: vec![],
},
ColumnDef {
name: Ident::new("bar_double_precision"),
data_type: DataType::DoublePrecisionUnsigned,
options: vec![],
},
],
columns
);
}
_ => unreachable!(),
}
}
#[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)";