diff --git a/examples/cast.sql b/examples/cast.sql new file mode 100644 index 000000000..7eb78e0c5 --- /dev/null +++ b/examples/cast.sql @@ -0,0 +1,2 @@ +SELECT CAST('test' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin; + diff --git a/examples/show_full.sql b/examples/show_full.sql new file mode 100644 index 000000000..9965f65df --- /dev/null +++ b/examples/show_full.sql @@ -0,0 +1 @@ +show full tables from `host_monitor_db`; diff --git a/examples/show_var.sql b/examples/show_var.sql new file mode 100644 index 000000000..af0524019 --- /dev/null +++ b/examples/show_var.sql @@ -0,0 +1 @@ + show session variables where variable_name in ('max_allowed_packet','system_time_zone','time_zone','auto_increment_increment') diff --git a/examples/transacion_variables.sql b/examples/transacion_variables.sql new file mode 100644 index 000000000..ba8ca971e --- /dev/null +++ b/examples/transacion_variables.sql @@ -0,0 +1 @@ +select @@session.transaction_read_only diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index dc434a8f0..9ea193d98 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -25,8 +25,8 @@ use super::value::escape_single_quote_string; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum DataType { - /// Fixed-length character type e.g. CHAR(10) - Char(Option), + /// Fixed-length character type e.g. CHAR(10), CHAR CHARACTER set utf8mb4 + Char(Option, Option), /// Variable-length character type e.g. VARCHAR(10) Varchar(Option), /// Variable-length character type e.g. NVARCHAR(10) @@ -98,7 +98,7 @@ 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, false), + DataType::Char(size, _) => format_type_with_optional_length(f, "CHAR", size, false), DataType::Varchar(size) => { format_type_with_optional_length(f, "CHARACTER VARYING", size, false) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a2cdf8981..75d97f7f8 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -278,6 +278,7 @@ pub enum Expr { Cast { expr: Box, data_type: DataType, + collation: Option, }, /// TRY_CAST an expression to a different data type e.g. `TRY_CAST(foo AS VARCHAR(123))` // this differs from CAST in the choice of how to implement invalid conversions @@ -435,7 +436,9 @@ impl fmt::Display for Expr { write!(f, "{} {}", op, expr) } } - Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type), + Expr::Cast { + expr, data_type, .. + } => write!(f, "CAST({} AS {})", expr, data_type), Expr::TryCast { expr, data_type } => write!(f, "TRY_CAST({} AS {})", expr, data_type), Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr), Expr::Position { expr, r#in } => write!(f, "POSITION({} IN {})", expr, r#in), @@ -943,6 +946,15 @@ pub enum Statement { obj_type: ShowCreateObject, obj_name: ObjectName, }, + /// SHOW Tables + /// + /// Note: this is a MySQL-specific statement. + ShowTables { + extended: bool, + full: bool, + db_name: ObjectName, + filter: Option, + }, /// SHOW COLUMNS /// /// Note: this is a MySQL-specific statement. @@ -1671,6 +1683,24 @@ impl fmt::Display for Statement { )?; Ok(()) } + Statement::ShowTables { + extended, + full, + db_name, + filter, + } => { + write!( + f, + "SHOW {extended}{full}TABLES FROM {db_name}", + extended = if *extended { "EXTENDED " } else { "" }, + full = if *full { "FULL " } else { "" }, + db_name = db_name, + )?; + if let Some(filter) = filter { + write!(f, " {}", filter)?; + } + Ok(()) + } Statement::ShowColumns { extended, full, diff --git a/src/ast/operator.rs b/src/ast/operator.rs index 244ea517c..2a63d2b49 100644 --- a/src/ast/operator.rs +++ b/src/ast/operator.rs @@ -34,6 +34,8 @@ pub enum UnaryOperator { PGPrefixFactorial, /// Absolute value, e.g. `@ -9` (PostgreSQL-specific) PGAbs, + /// operators for mysql system variables + DoubleAt, } impl fmt::Display for UnaryOperator { @@ -48,6 +50,7 @@ impl fmt::Display for UnaryOperator { UnaryOperator::PGPostfixFactorial => "!", UnaryOperator::PGPrefixFactorial => "!!", UnaryOperator::PGAbs => "@", + UnaryOperator::DoubleAt => "@@", }) } } diff --git a/src/parser.rs b/src/parser.rs index 383248b16..0c66e399d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -478,6 +478,16 @@ impl<'a> Parser<'a> { expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?), }) } + tok @ Token::DoubleAt if dialect_of!(self is MySqlDialect) => { + let op = match tok { + Token::DoubleAt => UnaryOperator::DoubleAt, + _ => unreachable!(), + }; + Ok(Expr::UnaryOp { + op, + expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?), + }) + } tok @ Token::DoubleExclamationMark | tok @ Token::PGSquareRoot | tok @ Token::PGCubeRoot @@ -761,9 +771,19 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::AS)?; let data_type = self.parse_data_type()?; self.expect_token(&Token::RParen)?; + let collation = if self.parse_keywords(&[Keyword::COLLATE]) { + match self.next_token() { + Token::Word(w) => Some(w.value), + unexpected => self.expected("identifier", unexpected)?, + } + } else { + None + }; + Ok(Expr::Cast { expr: Box::new(expr), data_type, + collation, }) } @@ -1320,6 +1340,7 @@ impl<'a> Parser<'a> { Ok(Expr::Cast { expr: Box::new(expr), data_type: self.parse_data_type()?, + collation: None, }) } @@ -2787,10 +2808,17 @@ impl<'a> Parser<'a> { Keyword::VARCHAR => Ok(DataType::Varchar(self.parse_optional_precision()?)), Keyword::NVARCHAR => Ok(DataType::Nvarchar(self.parse_optional_precision()?)), Keyword::CHAR | Keyword::CHARACTER => { - if self.parse_keyword(Keyword::VARYING) { - Ok(DataType::Varchar(self.parse_optional_precision()?)) - } else { - Ok(DataType::Char(self.parse_optional_precision()?)) + let is_var = self.parse_keyword(Keyword::VARYING); + let precision = self.parse_optional_precision()?; + let mut charset: Option = None; + // handle CAST('test' AS CHAR CHARACTER SET utf8mb4); + if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) { + let name = self.parse_identifier()?; + charset = Some(name.value); + } + match is_var { + true => Ok(DataType::Varchar(precision)), + _ => Ok(DataType::Char(precision, charset)), } } Keyword::UUID => Ok(DataType::Uuid), @@ -2968,6 +2996,9 @@ impl<'a> Parser<'a> { Token::Word(w) => { idents.push(w.to_ident()); } + Token::SingleQuotedString(s) => { + idents.push(Ident::with_quote('\'', s)); + } Token::EOF => break, _ => {} } @@ -3519,19 +3550,34 @@ impl<'a> Parser<'a> { pub fn parse_show_columns(&mut self) -> Result { let extended = self.parse_keyword(Keyword::EXTENDED); let full = self.parse_keyword(Keyword::FULL); - self.expect_one_of_keywords(&[Keyword::COLUMNS, Keyword::FIELDS])?; - self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?; - let table_name = self.parse_object_name()?; - // MySQL also supports FROM here. In other words, MySQL - // allows both FROM FROM and FROM .
, - // while we only support the latter for now. - let filter = self.parse_show_statement_filter()?; - Ok(Statement::ShowColumns { - extended, - full, - table_name, - filter, - }) + if self.parse_keyword(Keyword::TABLES) { + self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?; + let db_name = self.parse_object_name()?; + // MySQL also supports FROM here. In other words, MySQL + // allows both FROM
FROM and FROM .
, + // while we only support the latter for now. + let filter = self.parse_show_statement_filter()?; + Ok(Statement::ShowTables { + extended, + full, + db_name, + filter, + }) + } else { + self.expect_one_of_keywords(&[Keyword::COLUMNS, Keyword::FIELDS])?; + self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?; + let table_name = self.parse_object_name()?; + // MySQL also supports FROM here. In other words, MySQL + // allows both FROM
FROM and FROM .
, + // while we only support the latter for now. + let filter = self.parse_show_statement_filter()?; + Ok(Statement::ShowColumns { + extended, + full, + table_name, + filter, + }) + } } pub fn parse_show_statement_filter( diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 91cb16a80..75241f496 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -135,6 +135,8 @@ pub enum Token { ExclamationMark, /// Double Exclamation Mark `!!` used for PostgreSQL prefix factorial operator DoubleExclamationMark, + /// Double `@@` used for mysql system variables + DoubleAt, /// AtSign `@` used for PostgreSQL abs operator AtSign, /// `|/`, a square root math operator in PostgreSQL @@ -198,6 +200,7 @@ impl fmt::Display for Token { Token::Sharp => f.write_str("#"), Token::ExclamationMark => f.write_str("!"), Token::DoubleExclamationMark => f.write_str("!!"), + Token::DoubleAt => f.write_str("@@"), Token::Tilde => f.write_str("~"), Token::TildeAsterisk => f.write_str("~*"), Token::ExclamationMarkTilde => f.write_str("!~"), @@ -661,7 +664,13 @@ impl<'a> Tokenizer<'a> { _ => Ok(Some(Token::Sharp)), } } - '@' => self.consume_and_return(chars, Token::AtSign), + '@' => { + chars.next(); // consume + match chars.peek() { + Some('@') => self.consume_and_return(chars, Token::DoubleAt), + _ => Ok(Some(Token::AtSign)), + } + } '?' => self.consume_and_return(chars, Token::Placeholder(String::from("?"))), '$' => { chars.next(); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index bc715a096..c4a734813 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1417,7 +1417,8 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::BigInt(None) + data_type: DataType::BigInt(None), + collation: None }, expr_from_projection(only(&select.projection)) ); @@ -1427,7 +1428,8 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::TinyInt(None) + data_type: DataType::TinyInt(None), + collation: None }, expr_from_projection(only(&select.projection)) ); @@ -1454,7 +1456,8 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Nvarchar(Some(50)) + data_type: DataType::Nvarchar(Some(50)), + collation: None, }, expr_from_projection(only(&select.projection)) ); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 84d3b2088..1faa878f3 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1222,7 +1222,8 @@ fn parse_array_index_expr() { })), data_type: DataType::Array(Box::new(DataType::Array(Box::new(DataType::Int( None - ))))) + ))))), + collation: None, }))), indexes: vec![num[1].clone(), num[2].clone()], },