diff --git a/src/sql/src/parser.rs b/src/sql/src/parser.rs index f4038771859..aa4c18200d5 100644 --- a/src/sql/src/parser.rs +++ b/src/sql/src/parser.rs @@ -12,19 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use snafu::{ensure, ResultExt}; +use snafu::ResultExt; use sqlparser::dialect::Dialect; use sqlparser::keywords::Keyword; use sqlparser::parser::{Parser, ParserError}; use sqlparser::tokenizer::{Token, TokenWithLocation}; use crate::ast::{Expr, ObjectName}; -use crate::error::{self, InvalidDatabaseNameSnafu, InvalidTableNameSnafu, Result, SyntaxSnafu}; +use crate::error::{self, Result, SyntaxSnafu}; use crate::parsers::tql_parser; -use crate::statements::describe::DescribeTable; -use crate::statements::drop::DropTable; -use crate::statements::explain::Explain; -use crate::statements::show::{ShowCreateTable, ShowDatabases, ShowKind, ShowTables}; use crate::statements::statement::Statement; /// GrepTime SQL parser context, a simple wrapper for Datafusion SQL parser. @@ -153,176 +149,6 @@ impl<'a> ParserContext<'a> { .fail() } - /// Parses SHOW statements - /// todo(hl) support `show settings`/`show create`/`show users` etc. - fn parse_show(&mut self) -> Result { - 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.parse_show_tables() - } else if self.consume_token("CREATE") { - if self.consume_token("TABLE") { - self.parse_show_create_table() - } else { - self.unsupported(self.peek_token_as_string()) - } - } else { - self.unsupported(self.peek_token_as_string()) - } - } - - /// Parse SHOW CREATE TABLE statement - fn parse_show_create_table(&mut self) -> Result { - let table_name = - self.parser - .parse_object_name() - .with_context(|_| error::UnexpectedSnafu { - sql: self.sql, - expected: "a table name", - actual: self.peek_token_as_string(), - })?; - ensure!( - !table_name.0.is_empty(), - InvalidTableNameSnafu { - name: table_name.to_string(), - } - ); - Ok(Statement::ShowCreateTable(ShowCreateTable { table_name })) - } - - fn parse_show_tables(&mut self) -> Result { - let database = match self.parser.peek_token().token { - Token::EOF | Token::SemiColon => { - return Ok(Statement::ShowTables(ShowTables { - kind: ShowKind::All, - database: None, - })); - } - - // SHOW TABLES [in | FROM] [DATABASE] - Token::Word(w) => match w.keyword { - Keyword::IN | Keyword::FROM => { - let _ = self.parser.next_token(); - let db_name = self.parser.parse_object_name().with_context(|_| { - error::UnexpectedSnafu { - sql: self.sql, - expected: "a database name", - actual: self.peek_token_as_string(), - } - })?; - - ensure!( - db_name.0.len() == 1, - InvalidDatabaseNameSnafu { - name: db_name.to_string(), - } - ); - - Some(db_name.to_string()) - } - - _ => None, - }, - _ => 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.parser.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()), - }; - - Ok(Statement::ShowTables(ShowTables { kind, database })) - } - - /// Parses DESCRIBE statements - fn parse_describe(&mut self) -> Result { - if self.matches_keyword(Keyword::TABLE) { - let _ = self.parser.next_token(); - } - self.parse_describe_table() - } - - fn parse_describe_table(&mut self) -> Result { - let table_idents = - self.parser - .parse_object_name() - .with_context(|_| error::UnexpectedSnafu { - sql: self.sql, - expected: "a table name", - actual: self.peek_token_as_string(), - })?; - ensure!( - !table_idents.0.is_empty(), - InvalidTableNameSnafu { - name: table_idents.to_string(), - } - ); - Ok(Statement::DescribeTable(DescribeTable::new(table_idents))) - } - - fn parse_explain(&mut self) -> Result { - let explain_statement = - self.parser - .parse_explain(false) - .with_context(|_| error::UnexpectedSnafu { - sql: self.sql, - expected: "a query statement", - actual: self.peek_token_as_string(), - })?; - - Ok(Statement::Explain(Explain::try_from(explain_statement)?)) - } - - fn parse_drop(&mut self) -> Result { - let _ = self.parser.next_token(); - if !self.matches_keyword(Keyword::TABLE) { - return self.unsupported(self.peek_token_as_string()); - } - let _ = self.parser.next_token(); - - let table_ident = - self.parser - .parse_object_name() - .with_context(|_| error::UnexpectedSnafu { - sql: self.sql, - expected: "a table name", - actual: self.peek_token_as_string(), - })?; - ensure!( - !table_ident.0.is_empty(), - InvalidTableNameSnafu { - name: table_ident.to_string() - } - ); - - Ok(Statement::DropTable(DropTable::new(table_ident))) - } - // Report unexpected token pub(crate) fn expected(&self, expected: &str, found: TokenWithLocation) -> Result { Err(ParserError::ParserError(format!( @@ -351,276 +177,18 @@ impl<'a> ParserContext<'a> { pub(crate) fn peek_token_as_string(&self) -> String { self.parser.peek_token().to_string() } - - /// Parses `SHOW DATABASES` statement. - pub fn parse_show_databases(&mut self) -> Result { - let tok = self.parser.next_token().token; - match &tok { - Token::EOF | Token::SemiColon => { - Ok(Statement::ShowDatabases(ShowDatabases::new(ShowKind::All))) - } - Token::Word(w) => match w.keyword { - Keyword::LIKE => Ok(Statement::ShowDatabases(ShowDatabases::new( - ShowKind::Like(self.parser.parse_identifier().with_context(|_| { - error::UnexpectedSnafu { - sql: self.sql, - expected: "LIKE", - actual: tok.to_string(), - } - })?), - ))), - Keyword::WHERE => Ok(Statement::ShowDatabases(ShowDatabases::new( - ShowKind::Where(self.parser.parse_expr().with_context(|_| { - error::UnexpectedSnafu { - sql: self.sql, - expected: "some valid expression", - actual: self.peek_token_as_string(), - } - })?), - ))), - _ => self.unsupported(self.peek_token_as_string()), - }, - _ => self.unsupported(self.peek_token_as_string()), - } - } } #[cfg(test)] mod tests { - use std::assert_matches::assert_matches; use datatypes::prelude::ConcreteDataType; - use sqlparser::ast::{ - Ident, ObjectName, Query as SpQuery, Statement as SpStatement, WildcardAdditionalOptions, - }; use super::*; use crate::dialect::GreptimeDbDialect; use crate::statements::create::CreateTable; use crate::statements::sql_data_type_to_concrete_data_type; - #[test] - pub fn test_show_database_all() { - let sql = "SHOW DATABASES"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowDatabases(ShowDatabases { - kind: ShowKind::All - }) - ); - } - - #[test] - pub fn test_show_database_like() { - let sql = "SHOW DATABASES LIKE test_database"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowDatabases(ShowDatabases { - kind: ShowKind::Like(sqlparser::ast::Ident { - value: _, - quote_style: None, - }) - }) - ); - } - - #[test] - pub fn test_show_database_where() { - let sql = "SHOW DATABASES WHERE Database LIKE '%whatever1%' OR Database LIKE '%whatever2%'"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowDatabases(ShowDatabases { - kind: ShowKind::Where(sqlparser::ast::Expr::BinaryOp { - left: _, - right: _, - op: sqlparser::ast::BinaryOperator::Or, - }) - }) - ); - } - - #[test] - pub fn test_show_tables_all() { - let sql = "SHOW TABLES"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowTables(ShowTables { - kind: ShowKind::All, - database: None, - }) - ); - } - - #[test] - pub fn test_show_tables_like() { - let sql = "SHOW TABLES LIKE test_table"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowTables(ShowTables { - kind: ShowKind::Like(sqlparser::ast::Ident { - value: _, - quote_style: None, - }), - database: None, - }) - ); - - let sql = "SHOW TABLES in test_db LIKE test_table"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowTables(ShowTables { - kind: ShowKind::Like(sqlparser::ast::Ident { - value: _, - quote_style: None, - }), - database: Some(_), - }) - ); - } - - #[test] - pub fn test_show_tables_where() { - let sql = "SHOW TABLES where name like test_table"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowTables(ShowTables { - kind: ShowKind::Where(sqlparser::ast::Expr::Like { .. }), - database: None, - }) - ); - - let sql = "SHOW TABLES in test_db where name LIKE test_table"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - assert_matches!( - &stmts[0], - Statement::ShowTables(ShowTables { - kind: ShowKind::Where(sqlparser::ast::Expr::Like { .. }), - database: Some(_), - }) - ); - } - - #[test] - pub fn test_explain() { - let sql = "EXPLAIN select * from foo"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let stmts = result.unwrap(); - assert_eq!(1, stmts.len()); - - let select = sqlparser::ast::Select { - distinct: None, - top: None, - projection: vec![sqlparser::ast::SelectItem::Wildcard( - WildcardAdditionalOptions::default(), - )], - into: None, - from: vec![sqlparser::ast::TableWithJoins { - relation: sqlparser::ast::TableFactor::Table { - name: sqlparser::ast::ObjectName(vec![sqlparser::ast::Ident::new("foo")]), - alias: None, - args: None, - with_hints: vec![], - }, - joins: vec![], - }], - lateral_views: vec![], - selection: None, - group_by: vec![], - cluster_by: vec![], - distribute_by: vec![], - sort_by: vec![], - having: None, - qualify: None, - named_window: vec![], - }; - - let sp_statement = SpStatement::Query(Box::new(SpQuery { - with: None, - body: Box::new(sqlparser::ast::SetExpr::Select(Box::new(select))), - order_by: vec![], - limit: None, - offset: None, - fetch: None, - locks: vec![], - })); - - let explain = Explain::try_from(SpStatement::Explain { - describe_alias: false, - analyze: false, - verbose: false, - statement: Box::new(sp_statement), - format: None, - }) - .unwrap(); - - assert_eq!(stmts[0], Statement::Explain(explain)) - } - - #[test] - pub fn test_drop_table() { - let sql = "DROP TABLE foo"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let mut stmts = result.unwrap(); - assert_eq!( - stmts.pop().unwrap(), - Statement::DropTable(DropTable::new(ObjectName(vec![Ident::new("foo")]))) - ); - - let sql = "DROP TABLE my_schema.foo"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let mut stmts = result.unwrap(); - assert_eq!( - stmts.pop().unwrap(), - Statement::DropTable(DropTable::new(ObjectName(vec![ - Ident::new("my_schema"), - Ident::new("foo") - ]))) - ); - - let sql = "DROP TABLE my_catalog.my_schema.foo"; - let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); - let mut stmts = result.unwrap(); - assert_eq!( - stmts.pop().unwrap(), - Statement::DropTable(DropTable::new(ObjectName(vec![ - Ident::new("my_catalog"), - Ident::new("my_schema"), - Ident::new("foo") - ]))) - ) - } - fn test_timestamp_precision(sql: &str, expected_type: ConcreteDataType) { match ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}) .unwrap() @@ -670,27 +238,4 @@ mod tests { ConcreteDataType::timestamp_millisecond_datatype(), ); } - - #[test] - fn test_parse_function() { - let expr = - ParserContext::parse_function("current_timestamp()", &GreptimeDbDialect {}).unwrap(); - assert!(matches!(expr, Expr::Function(_))); - } - - fn assert_describe_table(sql: &str) { - let stmt = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}) - .unwrap() - .pop() - .unwrap(); - assert!(matches!(stmt, Statement::DescribeTable(_))) - } - - #[test] - fn test_parse_describe_table() { - assert_describe_table("desc table t;"); - assert_describe_table("describe table t;"); - assert_describe_table("desc t;"); - assert_describe_table("describe t;"); - } } diff --git a/src/sql/src/parsers.rs b/src/sql/src/parsers.rs index 960d5fe8c7e..1c62242b358 100644 --- a/src/sql/src/parsers.rs +++ b/src/sql/src/parsers.rs @@ -16,7 +16,11 @@ mod alter_parser; pub(crate) mod copy_parser; pub(crate) mod create_parser; pub(crate) mod delete_parser; +pub(crate) mod describe_parser; +pub(crate) mod drop_parser; +pub(crate) mod explain_parser; pub(crate) mod insert_parser; pub(crate) mod query_parser; +pub(crate) mod show_parser; pub(crate) mod tql_parser; pub(crate) mod truncate_parser; diff --git a/src/sql/src/parsers/describe_parser.rs b/src/sql/src/parsers/describe_parser.rs new file mode 100644 index 00000000000..a7c6b5c21e8 --- /dev/null +++ b/src/sql/src/parsers/describe_parser.rs @@ -0,0 +1,80 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use snafu::{ensure, ResultExt}; +use sqlparser::keywords::Keyword; + +use crate::error::{self, InvalidTableNameSnafu, Result}; +use crate::parser::ParserContext; +use crate::statements::describe::DescribeTable; +use crate::statements::statement::Statement; + +/// DESCRIBE statement parser implementation +impl<'a> ParserContext<'a> { + pub(crate) fn parse_describe(&mut self) -> Result { + if self.matches_keyword(Keyword::TABLE) { + let _ = self.parser.next_token(); + } + self.parse_describe_table() + } + + fn parse_describe_table(&mut self) -> Result { + let table_idents = + self.parser + .parse_object_name() + .with_context(|_| error::UnexpectedSnafu { + sql: self.sql, + expected: "a table name", + actual: self.peek_token_as_string(), + })?; + ensure!( + !table_idents.0.is_empty(), + InvalidTableNameSnafu { + name: table_idents.to_string(), + } + ); + Ok(Statement::DescribeTable(DescribeTable::new(table_idents))) + } +} + +#[cfg(test)] +mod tests { + use sqlparser::ast::Expr; + + use super::*; + use crate::dialect::GreptimeDbDialect; + + #[test] + fn test_parse_function() { + let expr = + ParserContext::parse_function("current_timestamp()", &GreptimeDbDialect {}).unwrap(); + assert!(matches!(expr, Expr::Function(_))); + } + + fn assert_describe_table(sql: &str) { + let stmt = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}) + .unwrap() + .pop() + .unwrap(); + assert!(matches!(stmt, Statement::DescribeTable(_))) + } + + #[test] + fn test_parse_describe_table() { + assert_describe_table("desc table t;"); + assert_describe_table("describe table t;"); + assert_describe_table("desc t;"); + assert_describe_table("describe t;"); + } +} diff --git a/src/sql/src/parsers/drop_parser.rs b/src/sql/src/parsers/drop_parser.rs new file mode 100644 index 00000000000..add3810385b --- /dev/null +++ b/src/sql/src/parsers/drop_parser.rs @@ -0,0 +1,91 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use snafu::{ensure, ResultExt}; +use sqlparser::keywords::Keyword; + +use crate::error::{self, InvalidTableNameSnafu, Result}; +use crate::parser::ParserContext; +use crate::statements::drop::DropTable; +use crate::statements::statement::Statement; + +/// DROP statement parser implementation +impl<'a> ParserContext<'a> { + pub(crate) fn parse_drop(&mut self) -> Result { + let _ = self.parser.next_token(); + if !self.matches_keyword(Keyword::TABLE) { + return self.unsupported(self.peek_token_as_string()); + } + let _ = self.parser.next_token(); + + let table_ident = + self.parser + .parse_object_name() + .with_context(|_| error::UnexpectedSnafu { + sql: self.sql, + expected: "a table name", + actual: self.peek_token_as_string(), + })?; + ensure!( + !table_ident.0.is_empty(), + InvalidTableNameSnafu { + name: table_ident.to_string() + } + ); + + Ok(Statement::DropTable(DropTable::new(table_ident))) + } +} + +#[cfg(test)] +mod tests { + use sqlparser::ast::{Ident, ObjectName}; + + use super::*; + use crate::dialect::GreptimeDbDialect; + + #[test] + pub fn test_drop_table() { + let sql = "DROP TABLE foo"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let mut stmts = result.unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::DropTable(DropTable::new(ObjectName(vec![Ident::new("foo")]))) + ); + + let sql = "DROP TABLE my_schema.foo"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let mut stmts = result.unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::DropTable(DropTable::new(ObjectName(vec![ + Ident::new("my_schema"), + Ident::new("foo") + ]))) + ); + + let sql = "DROP TABLE my_catalog.my_schema.foo"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let mut stmts = result.unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::DropTable(DropTable::new(ObjectName(vec![ + Ident::new("my_catalog"), + Ident::new("my_schema"), + Ident::new("foo") + ]))) + ) + } +} diff --git a/src/sql/src/parsers/explain_parser.rs b/src/sql/src/parsers/explain_parser.rs new file mode 100644 index 00000000000..701f137fffb --- /dev/null +++ b/src/sql/src/parsers/explain_parser.rs @@ -0,0 +1,100 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use snafu::ResultExt; + +use crate::error::{self, Result}; +use crate::parser::ParserContext; +use crate::statements::explain::Explain; +use crate::statements::statement::Statement; + +/// EXPLAIN statement parser implementation +impl<'a> ParserContext<'a> { + pub(crate) fn parse_explain(&mut self) -> Result { + let explain_statement = + self.parser + .parse_explain(false) + .with_context(|_| error::UnexpectedSnafu { + sql: self.sql, + expected: "a query statement", + actual: self.peek_token_as_string(), + })?; + + Ok(Statement::Explain(Explain::try_from(explain_statement)?)) + } +} + +#[cfg(test)] +mod tests { + use sqlparser::ast::{Query as SpQuery, Statement as SpStatement, WildcardAdditionalOptions}; + + use super::*; + use crate::dialect::GreptimeDbDialect; + + #[test] + pub fn test_explain() { + let sql = "EXPLAIN select * from foo"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + let select = sqlparser::ast::Select { + distinct: None, + top: None, + projection: vec![sqlparser::ast::SelectItem::Wildcard( + WildcardAdditionalOptions::default(), + )], + into: None, + from: vec![sqlparser::ast::TableWithJoins { + relation: sqlparser::ast::TableFactor::Table { + name: sqlparser::ast::ObjectName(vec![sqlparser::ast::Ident::new("foo")]), + alias: None, + args: None, + with_hints: vec![], + }, + joins: vec![], + }], + lateral_views: vec![], + selection: None, + group_by: vec![], + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + qualify: None, + named_window: vec![], + }; + + let sp_statement = SpStatement::Query(Box::new(SpQuery { + with: None, + body: Box::new(sqlparser::ast::SetExpr::Select(Box::new(select))), + order_by: vec![], + limit: None, + offset: None, + fetch: None, + locks: vec![], + })); + + let explain = Explain::try_from(SpStatement::Explain { + describe_alias: false, + analyze: false, + verbose: false, + statement: Box::new(sp_statement), + format: None, + }) + .unwrap(); + + assert_eq!(stmts[0], Statement::Explain(explain)) + } +} diff --git a/src/sql/src/parsers/show_parser.rs b/src/sql/src/parsers/show_parser.rs new file mode 100644 index 00000000000..90baef8ff6f --- /dev/null +++ b/src/sql/src/parsers/show_parser.rs @@ -0,0 +1,304 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use snafu::{ensure, ResultExt}; +use sqlparser::keywords::Keyword; +use sqlparser::tokenizer::Token; + +use crate::error::{self, InvalidDatabaseNameSnafu, InvalidTableNameSnafu, Result}; +use crate::parser::ParserContext; +use crate::statements::show::{ShowCreateTable, ShowDatabases, ShowKind, ShowTables}; +use crate::statements::statement::Statement; + +/// SHOW statement parser implementation +impl<'a> ParserContext<'a> { + /// Parses SHOW statements + /// todo(hl) support `show settings`/`show create`/`show users` etc. + pub(crate) fn parse_show(&mut self) -> Result { + 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.parse_show_tables() + } else if self.consume_token("CREATE") { + if self.consume_token("TABLE") { + self.parse_show_create_table() + } else { + self.unsupported(self.peek_token_as_string()) + } + } else { + self.unsupported(self.peek_token_as_string()) + } + } + + /// Parse SHOW CREATE TABLE statement + fn parse_show_create_table(&mut self) -> Result { + let table_name = + self.parser + .parse_object_name() + .with_context(|_| error::UnexpectedSnafu { + sql: self.sql, + expected: "a table name", + actual: self.peek_token_as_string(), + })?; + ensure!( + !table_name.0.is_empty(), + InvalidTableNameSnafu { + name: table_name.to_string(), + } + ); + Ok(Statement::ShowCreateTable(ShowCreateTable { table_name })) + } + + fn parse_show_tables(&mut self) -> Result { + let database = match self.parser.peek_token().token { + Token::EOF | Token::SemiColon => { + return Ok(Statement::ShowTables(ShowTables { + kind: ShowKind::All, + database: None, + })); + } + + // SHOW TABLES [in | FROM] [DATABASE] + Token::Word(w) => match w.keyword { + Keyword::IN | Keyword::FROM => { + let _ = self.parser.next_token(); + let db_name = self.parser.parse_object_name().with_context(|_| { + error::UnexpectedSnafu { + sql: self.sql, + expected: "a database name", + actual: self.peek_token_as_string(), + } + })?; + + ensure!( + db_name.0.len() == 1, + InvalidDatabaseNameSnafu { + name: db_name.to_string(), + } + ); + + Some(db_name.to_string()) + } + + _ => None, + }, + _ => 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.parser.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()), + }; + + Ok(Statement::ShowTables(ShowTables { kind, database })) + } + + /// Parses `SHOW DATABASES` statement. + pub fn parse_show_databases(&mut self) -> Result { + let tok = self.parser.next_token().token; + match &tok { + Token::EOF | Token::SemiColon => { + Ok(Statement::ShowDatabases(ShowDatabases::new(ShowKind::All))) + } + Token::Word(w) => match w.keyword { + Keyword::LIKE => Ok(Statement::ShowDatabases(ShowDatabases::new( + ShowKind::Like(self.parser.parse_identifier().with_context(|_| { + error::UnexpectedSnafu { + sql: self.sql, + expected: "LIKE", + actual: tok.to_string(), + } + })?), + ))), + Keyword::WHERE => Ok(Statement::ShowDatabases(ShowDatabases::new( + ShowKind::Where(self.parser.parse_expr().with_context(|_| { + error::UnexpectedSnafu { + sql: self.sql, + expected: "some valid expression", + actual: self.peek_token_as_string(), + } + })?), + ))), + _ => self.unsupported(self.peek_token_as_string()), + }, + _ => self.unsupported(self.peek_token_as_string()), + } + } +} + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use super::*; + use crate::dialect::GreptimeDbDialect; + use crate::statements::show::ShowDatabases; + + #[test] + pub fn test_show_database_all() { + let sql = "SHOW DATABASES"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowDatabases(ShowDatabases { + kind: ShowKind::All + }) + ); + } + + #[test] + pub fn test_show_database_like() { + let sql = "SHOW DATABASES LIKE test_database"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowDatabases(ShowDatabases { + kind: ShowKind::Like(sqlparser::ast::Ident { + value: _, + quote_style: None, + }) + }) + ); + } + + #[test] + pub fn test_show_database_where() { + let sql = "SHOW DATABASES WHERE Database LIKE '%whatever1%' OR Database LIKE '%whatever2%'"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowDatabases(ShowDatabases { + kind: ShowKind::Where(sqlparser::ast::Expr::BinaryOp { + left: _, + right: _, + op: sqlparser::ast::BinaryOperator::Or, + }) + }) + ); + } + + #[test] + pub fn test_show_tables_all() { + let sql = "SHOW TABLES"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowTables(ShowTables { + kind: ShowKind::All, + database: None, + }) + ); + } + + #[test] + pub fn test_show_tables_like() { + let sql = "SHOW TABLES LIKE test_table"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowTables(ShowTables { + kind: ShowKind::Like(sqlparser::ast::Ident { + value: _, + quote_style: None, + }), + database: None, + }) + ); + + let sql = "SHOW TABLES in test_db LIKE test_table"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowTables(ShowTables { + kind: ShowKind::Like(sqlparser::ast::Ident { + value: _, + quote_style: None, + }), + database: Some(_), + }) + ); + } + + #[test] + pub fn test_show_tables_where() { + let sql = "SHOW TABLES where name like test_table"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowTables(ShowTables { + kind: ShowKind::Where(sqlparser::ast::Expr::Like { .. }), + database: None, + }) + ); + + let sql = "SHOW TABLES in test_db where name LIKE test_table"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + assert_matches!( + &stmts[0], + Statement::ShowTables(ShowTables { + kind: ShowKind::Where(sqlparser::ast::Expr::Like { .. }), + database: Some(_), + }) + ); + } +}