diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index dde9e0b7..f8523e84 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -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), + /// [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), /// 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"), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 819819f9..e121066f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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()?, )), diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 0857bae3..5d75aa50 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -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)";