diff --git a/src/frontend/src/instance.rs b/src/frontend/src/instance.rs index d1f3df5aeacb..69a6cc2a12bc 100644 --- a/src/frontend/src/instance.rs +++ b/src/frontend/src/instance.rs @@ -491,11 +491,20 @@ pub fn check_permission( // database ops won't be checked Statement::CreateDatabase(_) | Statement::ShowDatabases(_) | Statement::DropDatabase(_) => { } - // show create table and alter are not supported yet - Statement::ShowCreateTable(_) | Statement::CreateExternalTable(_) | Statement::Alter(_) => { + + Statement::ShowCreateTable(stmt) => { + validate_param(&stmt.table_name, query_ctx)?; + } + Statement::CreateExternalTable(stmt) => { + validate_param(&stmt.name, query_ctx)?; + } + Statement::Alter(stmt) => { + validate_param(stmt.table_name(), query_ctx)?; } // set/show variable now only alter/show variable in session Statement::SetVariables(_) | Statement::ShowVariables(_) => {} + // show charset and show collation won't be checked + Statement::ShowCharset(_) | Statement::ShowCollation(_) => {} Statement::Insert(insert) => { validate_param(insert.table_name(), query_ctx)?; diff --git a/src/operator/src/statement.rs b/src/operator/src/statement.rs index 2d60fa1f2d82..2ecb35e91319 100644 --- a/src/operator/src/statement.rs +++ b/src/operator/src/statement.rs @@ -118,6 +118,10 @@ impl StatementExecutor { Statement::ShowTables(stmt) => self.show_tables(stmt, query_ctx).await, + Statement::ShowCollation(kind) => self.show_collation(kind, query_ctx).await, + + Statement::ShowCharset(kind) => self.show_charset(kind, query_ctx).await, + Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => { let req = to_copy_table_request(stmt, query_ctx.clone())?; match req.direction { diff --git a/src/operator/src/statement/show.rs b/src/operator/src/statement/show.rs index 694ba1951e2b..7ba8bc999f88 100644 --- a/src/operator/src/statement/show.rs +++ b/src/operator/src/statement/show.rs @@ -21,7 +21,9 @@ use session::context::QueryContextRef; use snafu::ResultExt; use sql::ast::Ident; use sql::statements::create::Partitions; -use sql::statements::show::{ShowColumns, ShowDatabases, ShowIndex, ShowTables, ShowVariables}; +use sql::statements::show::{ + ShowColumns, ShowDatabases, ShowIndex, ShowKind, ShowTables, ShowVariables, +}; use table::TableRef; use crate::error::{self, ExecuteStatementSnafu, Result}; @@ -97,6 +99,24 @@ impl StatementExecutor { pub fn show_variable(&self, stmt: ShowVariables, query_ctx: QueryContextRef) -> Result { query::sql::show_variable(stmt, query_ctx).context(error::ExecuteStatementSnafu) } + + #[tracing::instrument(skip_all)] + pub async fn show_collation( + &self, + kind: ShowKind, + query_ctx: QueryContextRef, + ) -> Result { + query::sql::show_collations(kind, &self.query_engine, &self.catalog_manager, query_ctx) + .await + .context(error::ExecuteStatementSnafu) + } + + #[tracing::instrument(skip_all)] + pub async fn show_charset(&self, kind: ShowKind, query_ctx: QueryContextRef) -> Result { + query::sql::show_charsets(kind, &self.query_engine, &self.catalog_manager, query_ctx) + .await + .context(error::ExecuteStatementSnafu) + } } pub(crate) fn create_partitions_stmt(partitions: Vec) -> Result> { diff --git a/src/query/src/sql.rs b/src/query/src/sql.rs index 87750f3743da..5aee6cc4ae7a 100644 --- a/src/query/src/sql.rs +++ b/src/query/src/sql.rs @@ -18,7 +18,8 @@ use std::collections::HashMap; use std::sync::Arc; use catalog::information_schema::{ - columns, key_column_usage, schemata, tables, COLUMNS, KEY_COLUMN_USAGE, SCHEMATA, TABLES, + columns, key_column_usage, schemata, tables, CHARACTER_SETS, COLLATIONS, COLUMNS, + KEY_COLUMN_USAGE, SCHEMATA, TABLES, }; use catalog::CatalogManagerRef; use common_catalog::consts::{ @@ -469,6 +470,76 @@ pub async fn show_tables( .await } +/// Execute `SHOW COLLATION` statement and returns the `Output` if success. +pub async fn show_collations( + kind: ShowKind, + query_engine: &QueryEngineRef, + catalog_manager: &CatalogManagerRef, + query_ctx: QueryContextRef, +) -> Result { + // Refer to https://dev.mysql.com/doc/refman/8.0/en/show-collation.html + let projects = vec![ + ("collation_name", "Collation"), + ("character_set_name", "Charset"), + ("id", "Id"), + ("is_default", "Default"), + ("is_compiled", "Compiled"), + ("sortlen", "Sortlen"), + ]; + + let filters = vec![]; + let like_field = Some("collation_name"); + let sort = vec![]; + + query_from_information_schema_table( + query_engine, + catalog_manager, + query_ctx, + COLLATIONS, + vec![], + projects, + filters, + like_field, + sort, + kind, + ) + .await +} + +/// Execute `SHOW CHARSET` statement and returns the `Output` if success. +pub async fn show_charsets( + kind: ShowKind, + query_engine: &QueryEngineRef, + catalog_manager: &CatalogManagerRef, + query_ctx: QueryContextRef, +) -> Result { + // Refer to https://dev.mysql.com/doc/refman/8.0/en/show-character-set.html + let projects = vec![ + ("character_set_name", "Charset"), + ("description", "Description"), + ("default_collate_name", "Default collation"), + ("maxlen", "Maxlen"), + ]; + + let filters = vec![]; + let like_field = Some("character_set_name"); + let sort = vec![]; + + query_from_information_schema_table( + query_engine, + catalog_manager, + query_ctx, + CHARACTER_SETS, + vec![], + projects, + filters, + like_field, + sort, + kind, + ) + .await +} + pub fn show_variable(stmt: ShowVariables, query_ctx: QueryContextRef) -> Result { let variable = stmt.variable.to_string().to_uppercase(); let value = match variable.as_str() { diff --git a/src/servers/src/mysql/federated.rs b/src/servers/src/mysql/federated.rs index d9f16bd654a1..13f8a63bb588 100644 --- a/src/servers/src/mysql/federated.rs +++ b/src/servers/src/mysql/federated.rs @@ -35,8 +35,6 @@ static MYSQL_CONN_JAVA_PATTERN: Lazy = Lazy::new(|| Regex::new("(?i)^(/\\* mysql-connector-j(.*))").unwrap()); static SHOW_LOWER_CASE_PATTERN: Lazy = Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'lower_case_table_names'(.*))").unwrap()); -static SHOW_COLLATION_PATTERN: Lazy = - Lazy::new(|| Regex::new("(?i)^(show collation where(.*))").unwrap()); static SHOW_VARIABLES_LIKE_PATTERN: Lazy = Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES( LIKE (.*))?)").unwrap()); @@ -70,9 +68,6 @@ static OTHER_NOT_SUPPORTED_STMT: Lazy = Lazy::new(|| { "(?i)^(SET @@(.*))", "(?i)^(SET PROFILING(.*))", - "(?i)^(SHOW COLLATION)", - "(?i)^(SHOW CHARSET)", - // mysqlclient. "(?i)^(SELECT \\$\\$)", @@ -96,8 +91,6 @@ static OTHER_NOT_SUPPORTED_STMT: Lazy = Lazy::new(|| { "(?i)^(SHOW WARNINGS)", "(?i)^(/\\* ApplicationName=(.*)SHOW WARNINGS)", "(?i)^(/\\* ApplicationName=(.*)SHOW PLUGINS)", - "(?i)^(/\\* ApplicationName=(.*)SHOW COLLATION)", - "(?i)^(/\\* ApplicationName=(.*)SHOW CHARSET)", "(?i)^(/\\* ApplicationName=(.*)SHOW ENGINES)", "(?i)^(/\\* ApplicationName=(.*)SELECT @@(.*))", "(?i)^(/\\* ApplicationName=(.*)SHOW @@(.*))", @@ -248,8 +241,7 @@ fn check_show_variables(query: &str) -> Option { Some(show_variables("sql_mode", "ONLY_FULL_GROUP_BY STRICT_TRANS_TABLES NO_ZERO_IN_DATE NO_ZERO_DATE ERROR_FOR_DIVISION_BY_ZERO NO_ENGINE_SUBSTITUTION")) } else if SHOW_LOWER_CASE_PATTERN.is_match(query) { Some(show_variables("lower_case_table_names", "0")) - } else if SHOW_COLLATION_PATTERN.is_match(query) || SHOW_VARIABLES_LIKE_PATTERN.is_match(query) - { + } else if SHOW_VARIABLES_LIKE_PATTERN.is_match(query) { Some(show_variables("", "")) } else { None @@ -379,12 +371,6 @@ mod test { +------------------------+-------+"; test(query, expected); - let query = "show collation"; - let expected = "\ -++ -++"; // empty - test(query, expected); - let query = "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())"; let expected = "\ +----------------------------------+ diff --git a/src/sql/src/parsers/show_parser.rs b/src/sql/src/parsers/show_parser.rs index afe2f06b7139..993a1c12df23 100644 --- a/src/sql/src/parsers/show_parser.rs +++ b/src/sql/src/parsers/show_parser.rs @@ -31,11 +31,26 @@ impl<'a> ParserContext<'a> { if self.consume_token("DATABASES") || self.consume_token("SCHEMAS") { self.parse_show_databases() } else if self.matches_keyword(Keyword::TABLES) { - let _ = self.parser.next_token(); + self.parser.next_token(); self.parse_show_tables(false) + } else if self.matches_keyword(Keyword::CHARSET) { + self.parser.next_token(); + Ok(Statement::ShowCharset(self.parse_show_kind()?)) + } else if self.matches_keyword(Keyword::CHARACTER) { + self.parser.next_token(); + + if self.matches_keyword(Keyword::SET) { + self.parser.next_token(); + Ok(Statement::ShowCharset(self.parse_show_kind()?)) + } else { + self.unsupported(self.peek_token_as_string()) + } + } else if self.matches_keyword(Keyword::COLLATION) { + self.parser.next_token(); + Ok(Statement::ShowCollation(self.parse_show_kind()?)) } else if self.matches_keyword(Keyword::COLUMNS) || self.matches_keyword(Keyword::FIELDS) { // SHOW {COLUMNS | FIELDS} - let _ = self.parser.next_token(); + self.parser.next_token(); self.parse_show_columns(false) } else if self.consume_token("INDEX") || self.consume_token("INDEXES") @@ -92,7 +107,7 @@ impl<'a> ParserContext<'a> { } fn parse_show_table_name(&mut self) -> Result { - let _ = self.parser.next_token(); + self.parser.next_token(); let table_name = self .parse_object_name() .with_context(|_| error::UnexpectedSnafu { @@ -115,7 +130,7 @@ impl<'a> ParserContext<'a> { } fn parse_db_name(&mut self) -> Result> { - let _ = self.parser.next_token(); + self.parser.next_token(); let db_name = self .parse_object_name() .with_context(|_| error::UnexpectedSnafu { @@ -172,41 +187,44 @@ impl<'a> ParserContext<'a> { _ => None, }; - let kind = match self.parser.peek_token().token { - Token::EOF | Token::SemiColon => ShowKind::All, - // SHOW COLUMNS [WHERE | LIKE] [EXPR] + let kind = self.parse_show_kind()?; + + Ok(Statement::ShowColumns(ShowColumns { + kind, + database, + table, + full, + })) + } + + fn parse_show_kind(&mut self) -> Result { + match self.parser.peek_token().token { + Token::EOF | Token::SemiColon => Ok(ShowKind::All), Token::Word(w) => match w.keyword { Keyword::LIKE => { - let _ = self.parser.next_token(); - ShowKind::Like(self.parse_identifier().with_context(|_| { - error::UnexpectedSnafu { + self.parser.next_token(); + Ok(ShowKind::Like(self.parse_identifier().with_context( + |_| error::UnexpectedSnafu { sql: self.sql, expected: "LIKE", actual: self.peek_token_as_string(), - } - })?) + }, + )?)) } Keyword::WHERE => { - let _ = self.parser.next_token(); - ShowKind::Where(self.parser.parse_expr().with_context(|_| { - error::UnexpectedSnafu { + self.parser.next_token(); + Ok(ShowKind::Where(self.parser.parse_expr().with_context( + |_| error::UnexpectedSnafu { sql: self.sql, expected: "some valid expression", actual: self.peek_token_as_string(), - } - })?) + }, + )?)) } - _ => return self.unsupported(self.peek_token_as_string()), + _ => self.unsupported(self.peek_token_as_string()), }, - _ => return self.unsupported(self.peek_token_as_string()), - }; - - Ok(Statement::ShowColumns(ShowColumns { - kind, - database, - table, - full, - })) + _ => self.unsupported(self.peek_token_as_string()), + } } fn parse_show_index(&mut self) -> Result { @@ -248,7 +266,7 @@ impl<'a> ParserContext<'a> { // SHOW INDEX [WHERE] [EXPR] Token::Word(w) => match w.keyword { Keyword::WHERE => { - let _ = self.parser.next_token(); + self.parser.next_token(); ShowKind::Where(self.parser.parse_expr().with_context(|_| { error::UnexpectedSnafu { sql: self.sql, @@ -288,34 +306,7 @@ impl<'a> ParserContext<'a> { _ => None, }; - let kind = match self.parser.peek_token().token { - Token::EOF | Token::SemiColon => ShowKind::All, - // SHOW TABLES [WHERE | LIKE] [EXPR] - Token::Word(w) => match w.keyword { - Keyword::LIKE => { - let _ = self.parser.next_token(); - ShowKind::Like(self.parse_identifier().with_context(|_| { - error::UnexpectedSnafu { - sql: self.sql, - expected: "LIKE", - actual: self.peek_token_as_string(), - } - })?) - } - Keyword::WHERE => { - let _ = self.parser.next_token(); - ShowKind::Where(self.parser.parse_expr().with_context(|_| { - error::UnexpectedSnafu { - sql: self.sql, - expected: "some valid expression", - actual: self.peek_token_as_string(), - } - })?) - } - _ => return self.unsupported(self.peek_token_as_string()), - }, - _ => return self.unsupported(self.peek_token_as_string()), - }; + let kind = self.parse_show_kind()?; Ok(Statement::ShowTables(ShowTables { kind, @@ -718,4 +709,66 @@ mod tests { .. }) if table == "test" && expr.to_string() == "Field = 'disk'")); } + + #[test] + fn parse_show_collation() { + let sql = "SHOW COLLATION"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCollation(ShowKind::All) + )); + + let sql = "SHOW COLLATION WHERE Charset = 'latin1'"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCollation(ShowKind::Where(_)) + )); + + let sql = "SHOW COLLATION LIKE 'latin1'"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCollation(ShowKind::Like(_)) + )); + } + + #[test] + fn parse_show_charset() { + let sql = "SHOW CHARSET"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCharset(ShowKind::All) + )); + + let sql = "SHOW CHARACTER SET"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCharset(ShowKind::All) + )); + + let sql = "SHOW CHARSET WHERE Charset = 'latin1'"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCharset(ShowKind::Where(_)) + )); + + let sql = "SHOW CHARACTER SET LIKE 'latin1'"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); + assert!(matches!( + result.unwrap()[0], + Statement::ShowCharset(ShowKind::Like(_)) + )); + } } diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index 54ca637283ef..5d1b5f47f893 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -30,7 +30,9 @@ use crate::statements::explain::Explain; use crate::statements::insert::Insert; use crate::statements::query::Query; use crate::statements::set_variables::SetVariables; -use crate::statements::show::{ShowColumns, ShowCreateTable, ShowDatabases, ShowIndex, ShowTables}; +use crate::statements::show::{ + ShowColumns, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowTables, +}; use crate::statements::tql::Tql; use crate::statements::truncate::TruncateTable; @@ -64,6 +66,10 @@ pub enum Statement { ShowTables(ShowTables), // SHOW COLUMNS ShowColumns(ShowColumns), + // SHOW CHARSET or SHOW CHARACTER SET + ShowCharset(ShowKind), + // SHOW COLLATION + ShowCollation(ShowKind), // SHOW INDEX ShowIndex(ShowIndex), // SHOW CREATE TABLE diff --git a/tests/cases/standalone/common/show/show_charset.result b/tests/cases/standalone/common/show/show_charset.result new file mode 100644 index 000000000000..10b25864d3a6 --- /dev/null +++ b/tests/cases/standalone/common/show/show_charset.result @@ -0,0 +1,58 @@ +SHOW CHARACTER SET; + ++---------+---------------+-------------------+--------+ +| Charset | Description | Default collation | Maxlen | ++---------+---------------+-------------------+--------+ +| utf8 | UTF-8 Unicode | utf8_bin | 4 | ++---------+---------------+-------------------+--------+ + +SHOW CHARSET; + ++---------+---------------+-------------------+--------+ +| Charset | Description | Default collation | Maxlen | ++---------+---------------+-------------------+--------+ +| utf8 | UTF-8 Unicode | utf8_bin | 4 | ++---------+---------------+-------------------+--------+ + +SHOW CHARACTER SET LIKE 'utf8'; + ++---------+---------------+-------------------+--------+ +| Charset | Description | Default collation | Maxlen | ++---------+---------------+-------------------+--------+ +| utf8 | UTF-8 Unicode | utf8_bin | 4 | ++---------+---------------+-------------------+--------+ + +SHOW CHARACTER SET LIKE 'latin1'; + +++ +++ + +SHOW CHARSET LIKE 'utf8'; + ++---------+---------------+-------------------+--------+ +| Charset | Description | Default collation | Maxlen | ++---------+---------------+-------------------+--------+ +| utf8 | UTF-8 Unicode | utf8_bin | 4 | ++---------+---------------+-------------------+--------+ + +SHOW CHARACTER SET WHERE Charset = 'utf8'; + ++---------+---------------+-------------------+--------+ +| Charset | Description | Default collation | Maxlen | ++---------+---------------+-------------------+--------+ +| utf8 | UTF-8 Unicode | utf8_bin | 4 | ++---------+---------------+-------------------+--------+ + +SHOW CHARSET WHERE Charset = 'utf8'; + ++---------+---------------+-------------------+--------+ +| Charset | Description | Default collation | Maxlen | ++---------+---------------+-------------------+--------+ +| utf8 | UTF-8 Unicode | utf8_bin | 4 | ++---------+---------------+-------------------+--------+ + +SHOW CHARSET WHERE Charset = 'latin1'; + +++ +++ + diff --git a/tests/cases/standalone/common/show/show_charset.sql b/tests/cases/standalone/common/show/show_charset.sql new file mode 100644 index 000000000000..9b0ce0d31dea --- /dev/null +++ b/tests/cases/standalone/common/show/show_charset.sql @@ -0,0 +1,15 @@ +SHOW CHARACTER SET; + +SHOW CHARSET; + +SHOW CHARACTER SET LIKE 'utf8'; + +SHOW CHARACTER SET LIKE 'latin1'; + +SHOW CHARSET LIKE 'utf8'; + +SHOW CHARACTER SET WHERE Charset = 'utf8'; + +SHOW CHARSET WHERE Charset = 'utf8'; + +SHOW CHARSET WHERE Charset = 'latin1'; diff --git a/tests/cases/standalone/common/show/show_collation.result b/tests/cases/standalone/common/show/show_collation.result new file mode 100644 index 000000000000..4dcf9530595a --- /dev/null +++ b/tests/cases/standalone/common/show/show_collation.result @@ -0,0 +1,31 @@ +SHOW COLLATION; + ++-----------+---------+----+---------+----------+---------+ +| Collation | Charset | Id | Default | Compiled | Sortlen | ++-----------+---------+----+---------+----------+---------+ +| utf8_bin | utf8 | 1 | Yes | Yes | 1 | ++-----------+---------+----+---------+----------+---------+ + +SHOW COLLATION LIKE 'utf8'; + +++ +++ + +SHOW COLLATION WHERE Charset = 'utf8'; + ++-----------+---------+----+---------+----------+---------+ +| Collation | Charset | Id | Default | Compiled | Sortlen | ++-----------+---------+----+---------+----------+---------+ +| utf8_bin | utf8 | 1 | Yes | Yes | 1 | ++-----------+---------+----+---------+----------+---------+ + +SHOW COLLATION WHERE Charset = 'latin1'; + +++ +++ + +SHOW COLLATION LIKE 'latin1'; + +++ +++ + diff --git a/tests/cases/standalone/common/show/show_collation.sql b/tests/cases/standalone/common/show/show_collation.sql new file mode 100644 index 000000000000..13eb6f8d36ac --- /dev/null +++ b/tests/cases/standalone/common/show/show_collation.sql @@ -0,0 +1,10 @@ +SHOW COLLATION; + +SHOW COLLATION LIKE 'utf8'; + + +SHOW COLLATION WHERE Charset = 'utf8'; + +SHOW COLLATION WHERE Charset = 'latin1'; + +SHOW COLLATION LIKE 'latin1';