mysql unsigned datatype (#428)

* support MySQL UNSIGNED

* fix: 🐛 `unsigned` is not column option

* test: 💍 add `unsigned` test

* fix: 🐛 `unsigned` is not column option

* feat: 🎸 declare unsigned data_types

* feat: 🎸 display unsigned

* fix: 🐛 unsigned is not column type option

* feat: 🎸 parse_data_type can parse unsigned

* feat: 🎸 int or decimal or float is unsigned selectable

* fix: 🐛 FLOAT/DOUBLE/DECIMAL + UNSIGNED is not recommended

https://dev.mysql.com/doc/refman/8.0/en/numeric-type-attributes.html

* test: 💍 add test

* style: 💄 fmt
This commit is contained in:
Wataru Kurashima 2022-03-08 07:01:48 +09:00 committed by GitHub
parent 3af3ca07b6
commit 994b86a45c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 14 deletions

View file

@ -45,12 +45,20 @@ pub enum DataType {
Float(Option<u64>),
/// Tiny integer with optional display width e.g. TINYINT or TINYINT(3)
TinyInt(Option<u64>),
/// Unsigned tiny integer with optional display width e.g. TINYINT UNSIGNED or TINYINT(3) UNSIGNED
UnsignedTinyInt(Option<u64>),
/// Small integer with optional display width e.g. SMALLINT or SMALLINT(5)
SmallInt(Option<u64>),
/// Unsigned small integer with optional display width e.g. SMALLINT UNSIGNED or SMALLINT(5) UNSIGNED
UnsignedSmallInt(Option<u64>),
/// Integer with optional display width e.g. INT or INT(11)
Int(Option<u64>),
/// Unsigned integer with optional display width e.g. INT UNSIGNED or INT(11) UNSIGNED
UnsignedInt(Option<u64>),
/// Big integer with optional display width e.g. BIGINT or BIGINT(20)
BigInt(Option<u64>),
/// Unsigned big integer with optional display width e.g. BIGINT UNSIGNED or BIGINT(20) UNSIGNED
UnsignedBigInt(Option<u64>),
/// Floating point e.g. REAL
Real,
/// Double e.g. DOUBLE PRECISION
@ -86,9 +94,9 @@ pub enum DataType {
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DataType::Char(size) => format_type_with_optional_length(f, "CHAR", size),
DataType::Char(size) => format_type_with_optional_length(f, "CHAR", size, false),
DataType::Varchar(size) => {
format_type_with_optional_length(f, "CHARACTER VARYING", size)
format_type_with_optional_length(f, "CHARACTER VARYING", size, false)
}
DataType::Uuid => write!(f, "UUID"),
DataType::Clob(size) => write!(f, "CLOB({})", size),
@ -99,16 +107,32 @@ impl fmt::Display for DataType {
if let Some(scale) = scale {
write!(f, "NUMERIC({},{})", precision.unwrap(), scale)
} else {
format_type_with_optional_length(f, "NUMERIC", precision)
format_type_with_optional_length(f, "NUMERIC", precision, false)
}
}
DataType::Float(size) => format_type_with_optional_length(f, "FLOAT", size),
DataType::TinyInt(zerofill) => format_type_with_optional_length(f, "TINYINT", zerofill),
DataType::SmallInt(zerofill) => {
format_type_with_optional_length(f, "SMALLINT", zerofill)
DataType::Float(size) => format_type_with_optional_length(f, "FLOAT", size, false),
DataType::TinyInt(zerofill) => {
format_type_with_optional_length(f, "TINYINT", zerofill, false)
}
DataType::UnsignedTinyInt(zerofill) => {
format_type_with_optional_length(f, "TINYINT", zerofill, true)
}
DataType::SmallInt(zerofill) => {
format_type_with_optional_length(f, "SMALLINT", zerofill, false)
}
DataType::UnsignedSmallInt(zerofill) => {
format_type_with_optional_length(f, "SMALLINT", zerofill, true)
}
DataType::Int(zerofill) => format_type_with_optional_length(f, "INT", zerofill, false),
DataType::UnsignedInt(zerofill) => {
format_type_with_optional_length(f, "INT", zerofill, true)
}
DataType::BigInt(zerofill) => {
format_type_with_optional_length(f, "BIGINT", zerofill, false)
}
DataType::UnsignedBigInt(zerofill) => {
format_type_with_optional_length(f, "BIGINT", zerofill, true)
}
DataType::Int(zerofill) => format_type_with_optional_length(f, "INT", zerofill),
DataType::BigInt(zerofill) => format_type_with_optional_length(f, "BIGINT", zerofill),
DataType::Real => write!(f, "REAL"),
DataType::Double => write!(f, "DOUBLE"),
DataType::Boolean => write!(f, "BOOLEAN"),
@ -150,10 +174,14 @@ fn format_type_with_optional_length(
f: &mut fmt::Formatter,
sql_type: &'static str,
len: &Option<u64>,
unsigned: bool,
) -> fmt::Result {
write!(f, "{}", sql_type)?;
if let Some(len) = len {
write!(f, "({})", len)?;
}
if unsigned {
write!(f, " UNSIGNED")?;
}
Ok(())
}

View file

@ -499,6 +499,7 @@ define_keywords!(
UNIQUE,
UNKNOWN,
UNNEST,
UNSIGNED,
UPDATE,
UPPER,
USAGE,

View file

@ -2382,12 +2382,38 @@ impl<'a> Parser<'a> {
let _ = self.parse_keyword(Keyword::PRECISION);
Ok(DataType::Double)
}
Keyword::TINYINT => Ok(DataType::TinyInt(self.parse_optional_precision()?)),
Keyword::SMALLINT => Ok(DataType::SmallInt(self.parse_optional_precision()?)),
Keyword::INT | Keyword::INTEGER => {
Ok(DataType::Int(self.parse_optional_precision()?))
Keyword::TINYINT => {
let optional_precision = self.parse_optional_precision();
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::UnsignedTinyInt(optional_precision?))
} else {
Ok(DataType::TinyInt(optional_precision?))
}
}
Keyword::SMALLINT => {
let optional_precision = self.parse_optional_precision();
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::UnsignedSmallInt(optional_precision?))
} else {
Ok(DataType::SmallInt(optional_precision?))
}
}
Keyword::INT | Keyword::INTEGER => {
let optional_precision = self.parse_optional_precision();
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::UnsignedInt(optional_precision?))
} else {
Ok(DataType::Int(optional_precision?))
}
}
Keyword::BIGINT => {
let optional_precision = self.parse_optional_precision();
if self.parse_keyword(Keyword::UNSIGNED) {
Ok(DataType::UnsignedBigInt(optional_precision?))
} else {
Ok(DataType::BigInt(optional_precision?))
}
}
Keyword::BIGINT => Ok(DataType::BigInt(self.parse_optional_precision()?)),
Keyword::VARCHAR => Ok(DataType::Varchar(self.parse_optional_precision()?)),
Keyword::CHAR | Keyword::CHARACTER => {
if self.parse_keyword(Keyword::VARYING) {

View file

@ -407,6 +407,46 @@ fn parse_create_table_with_minimum_display_width() {
}
}
#[test]
fn parse_create_table_unsigned() {
let sql = "CREATE TABLE foo (bar_tinyint TINYINT(3) UNSIGNED, bar_smallint SMALLINT(5) UNSIGNED, bar_int INT(11) UNSIGNED, bar_bigint BIGINT(20) UNSIGNED)";
match mysql().verified_stmt(sql) {
Statement::CreateTable { name, columns, .. } => {
assert_eq!(name.to_string(), "foo");
assert_eq!(
vec![
ColumnDef {
name: Ident::new("bar_tinyint"),
data_type: DataType::UnsignedTinyInt(Some(3)),
collation: None,
options: vec![],
},
ColumnDef {
name: Ident::new("bar_smallint"),
data_type: DataType::UnsignedSmallInt(Some(5)),
collation: None,
options: vec![],
},
ColumnDef {
name: Ident::new("bar_int"),
data_type: DataType::UnsignedInt(Some(11)),
collation: None,
options: vec![],
},
ColumnDef {
name: Ident::new("bar_bigint"),
data_type: DataType::UnsignedBigInt(Some(20)),
collation: None,
options: vec![],
},
],
columns
);
}
_ => unreachable!(),
}
}
#[test]
#[cfg(not(feature = "bigdecimal"))]
fn parse_simple_insert() {