feat: show databases and show tables (#276)

* feat: ensure time index column can't be included in primary key

* feat: sql parser supports show tables statement

* feat: impl show databases and show tables, #183

* feat: impl like expression for show databases/tables and add tests

* fix: typo

* fix: address CR problems
This commit is contained in:
dennis zhuang
2022-09-26 14:05:49 +08:00
committed by GitHub
parent 5f322ba16e
commit 0fa68ab7a5
14 changed files with 681 additions and 96 deletions

View File

@@ -79,6 +79,9 @@ pub enum Error {
expect: ConcreteDataType,
actual: ConcreteDataType,
},
#[snafu(display("Invalid database name: {}", name))]
InvalidDatabaseName { name: String, backtrace: Backtrace },
}
impl ErrorExt for Error {
@@ -95,7 +98,7 @@ impl ErrorExt for Error {
| ParseSqlValue { .. }
| SqlTypeNotSupported { .. } => StatusCode::InvalidSyntax,
ColumnTypeMismatch { .. } => StatusCode::InvalidArguments,
InvalidDatabaseName { .. } | ColumnTypeMismatch { .. } => StatusCode::InvalidArguments,
}
}

View File

@@ -1,13 +1,12 @@
use snafu::ResultExt;
use snafu::{ensure, ResultExt};
use sqlparser::dialect::Dialect;
use sqlparser::keywords::Keyword;
use sqlparser::parser::Parser;
use sqlparser::parser::ParserError;
use sqlparser::tokenizer::{Token, Tokenizer};
use crate::error::{self, Result, SyntaxSnafu, TokenizerSnafu};
use crate::statements::show_database::SqlShowDatabase;
use crate::statements::show_kind::ShowKind;
use crate::error::{self, InvalidDatabaseNameSnafu, Result, SyntaxSnafu, TokenizerSnafu};
use crate::statements::show::{ShowDatabases, ShowKind, ShowTables};
use crate::statements::statement::Statement;
/// GrepTime SQL parser context, a simple wrapper for Datafusion SQL parser.
@@ -96,15 +95,86 @@ impl<'a> ParserContext<'a> {
}
/// Parses SHOW statements
/// todo(hl) support `show table`/`show settings`/`show create`/`show users` ect.
/// todo(hl) support `show settings`/`show create`/`show users` ect.
fn parse_show(&mut self) -> Result<Statement> {
if self.consume_token("DATABASES") || self.consume_token("SCHEMAS") {
Ok(self.parse_show_databases()?)
self.parse_show_databases()
} else if self.matches_keyword(Keyword::TABLES) {
self.parser.next_token();
self.parse_show_tables()
} else {
self.unsupported(self.peek_token_as_string())
}
}
fn parse_show_tables(&mut self) -> Result<Statement> {
let database = match self.parser.peek_token() {
Token::EOF | Token::SemiColon => {
return Ok(Statement::ShowTables(ShowTables {
kind: ShowKind::All,
database: None,
}));
}
// SHOW TABLES [in | FROM] [DATABSE]
Token::Word(w) => match w.keyword {
Keyword::IN | Keyword::FROM => {
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::EOF | Token::SemiColon => ShowKind::All,
// SHOW TABLES [WHERE | LIKE] [EXPR]
Token::Word(w) => match w.keyword {
Keyword::LIKE => {
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 => {
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 }))
}
fn parse_explain(&mut self) -> Result<Statement> {
todo!()
}
@@ -118,6 +188,13 @@ impl<'a> ParserContext<'a> {
.context(SyntaxSnafu { sql: self.sql })
}
pub fn matches_keyword(&mut self, expected: Keyword) -> bool {
match self.parser.peek_token() {
Token::Word(w) => w.keyword == expected,
_ => false,
}
}
pub fn consume_token(&mut self, expected: &str) -> bool {
if self.peek_token_as_string().to_uppercase() == *expected.to_uppercase() {
self.parser.next_token();
@@ -136,11 +213,11 @@ impl<'a> ParserContext<'a> {
pub fn parse_show_databases(&mut self) -> Result<Statement> {
let tok = self.parser.next_token();
match &tok {
Token::EOF | Token::SemiColon => Ok(Statement::ShowDatabases(SqlShowDatabase::new(
ShowKind::All,
))),
Token::EOF | Token::SemiColon => {
Ok(Statement::ShowDatabases(ShowDatabases::new(ShowKind::All)))
}
Token::Word(w) => match w.keyword {
Keyword::LIKE => Ok(Statement::ShowDatabases(SqlShowDatabase::new(
Keyword::LIKE => Ok(Statement::ShowDatabases(ShowDatabases::new(
ShowKind::Like(self.parser.parse_identifier().with_context(|_| {
error::UnexpectedSnafu {
sql: self.sql,
@@ -149,7 +226,7 @@ impl<'a> ParserContext<'a> {
}
})?),
))),
Keyword::WHERE => Ok(Statement::ShowDatabases(SqlShowDatabase::new(
Keyword::WHERE => Ok(Statement::ShowDatabases(ShowDatabases::new(
ShowKind::Where(self.parser.parse_expr().with_context(|_| {
error::UnexpectedSnafu {
sql: self.sql,
@@ -182,7 +259,7 @@ mod tests {
assert_matches!(
&stmts[0],
Statement::ShowDatabases(SqlShowDatabase {
Statement::ShowDatabases(ShowDatabases {
kind: ShowKind::All
})
);
@@ -197,7 +274,7 @@ mod tests {
assert_matches!(
&stmts[0],
Statement::ShowDatabases(SqlShowDatabase {
Statement::ShowDatabases(ShowDatabases {
kind: ShowKind::Like(sqlparser::ast::Ident {
value: _,
quote_style: None,
@@ -215,7 +292,7 @@ mod tests {
assert_matches!(
&stmts[0],
Statement::ShowDatabases(SqlShowDatabase {
Statement::ShowDatabases(ShowDatabases {
kind: ShowKind::Where(sqlparser::ast::Expr::BinaryOp {
left: _,
right: _,
@@ -224,4 +301,92 @@ mod tests {
})
);
}
#[test]
pub fn test_show_tables_all() {
let sql = "SHOW TABLES";
let result = ParserContext::create_with_dialect(sql, &GenericDialect {});
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, &GenericDialect {});
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, &GenericDialect {});
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, &GenericDialect {});
let stmts = result.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(
&stmts[0],
Statement::ShowTables(ShowTables {
kind: ShowKind::Where(sqlparser::ast::Expr::BinaryOp {
left: _,
right: _,
op: sqlparser::ast::BinaryOperator::Like,
}),
database: None,
})
);
let sql = "SHOW TABLES in test_db where name LIKE test_table";
let result = ParserContext::create_with_dialect(sql, &GenericDialect {});
let stmts = result.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(
&stmts[0],
Statement::ShowTables(ShowTables {
kind: ShowKind::Where(sqlparser::ast::Expr::BinaryOp {
left: _,
right: _,
op: sqlparser::ast::BinaryOperator::Like,
}),
database: Some(_),
})
);
}
}

View File

@@ -2,8 +2,7 @@ pub mod alter;
pub mod create_table;
pub mod insert;
pub mod query;
pub mod show_database;
pub mod show_kind;
pub mod show;
pub mod statement;
use std::str::FromStr;

View File

@@ -0,0 +1,97 @@
use std::fmt;
use crate::ast::{Expr, Ident};
/// Show kind for SQL expressions like `SHOW DATABASE` or `SHOW TABLE`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShowKind {
All,
Like(Ident),
Where(Expr),
}
impl fmt::Display for ShowKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ShowKind::All => write!(f, "ALL"),
ShowKind::Like(ident) => write!(f, "LIKE {}", ident),
ShowKind::Where(expr) => write!(f, "WHERE {}", expr),
}
}
}
/// SQL structure for `SHOW DATABASES`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShowDatabases {
pub kind: ShowKind,
}
impl ShowDatabases {
/// Creates a statement for `SHOW DATABASES`
pub fn new(kind: ShowKind) -> Self {
ShowDatabases { kind }
}
}
/// SQL structure for `SHOW TABLES`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShowTables {
pub kind: ShowKind,
pub database: Option<String>,
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
use sqlparser::ast::UnaryOperator;
use sqlparser::dialect::GenericDialect;
use super::*;
use crate::parser::ParserContext;
use crate::statements::statement::Statement;
#[test]
fn test_kind_display() {
assert_eq!("ALL", format!("{}", ShowKind::All));
assert_eq!(
"LIKE test",
format!(
"{}",
ShowKind::Like(Ident {
value: "test".to_string(),
quote_style: None,
})
)
);
assert_eq!(
"WHERE NOT a",
format!(
"{}",
ShowKind::Where(Expr::UnaryOp {
op: UnaryOperator::Not,
expr: Box::new(Expr::Identifier(Ident {
value: "a".to_string(),
quote_style: None,
})),
})
)
);
}
#[test]
pub fn test_show_database() {
let sql = "SHOW DATABASES";
let stmts = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::ShowDatabases { .. });
match &stmts[0] {
Statement::ShowDatabases(show) => {
assert_eq!(ShowKind::All, show.kind);
}
_ => {
unreachable!();
}
}
}
}

View File

@@ -1,14 +0,0 @@
use crate::statements::show_kind::ShowKind;
/// SQL structure for `SHOW DATABASES`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SqlShowDatabase {
pub kind: ShowKind,
}
impl SqlShowDatabase {
/// Creates a statement for `SHOW DATABASES`
pub fn new(kind: ShowKind) -> Self {
SqlShowDatabase { kind }
}
}

View File

@@ -1,37 +0,0 @@
use sqlparser::ast::Expr;
use sqlparser::ast::Ident;
/// Show kind for SQL expressions like `SHOW DATABASE` or `SHOW TABLE`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShowKind {
All,
Like(Ident),
Where(Expr),
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
use sqlparser::dialect::GenericDialect;
use crate::parser::ParserContext;
use crate::statements::show_kind::ShowKind::All;
use crate::statements::statement::Statement;
#[test]
pub fn test_show_database() {
let sql = "SHOW DATABASES";
let stmts = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::ShowDatabases { .. });
match &stmts[0] {
Statement::ShowDatabases(show) => {
assert_eq!(All, show.kind);
}
_ => {
unreachable!();
}
}
}
}

View File

@@ -5,25 +5,23 @@ use crate::statements::alter::AlterTable;
use crate::statements::create_table::CreateTable;
use crate::statements::insert::Insert;
use crate::statements::query::Query;
use crate::statements::show_database::SqlShowDatabase;
use crate::statements::show::{ShowDatabases, ShowTables};
/// Tokens parsed by `DFParser` are converted into these values.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
// Databases.
ShowDatabases(SqlShowDatabase),
// Query
Query(Box<Query>),
// Insert
Insert(Box<Insert>),
/// CREATE TABLE
Create(CreateTable),
/// ALTER TABLE
Alter(AlterTable),
// Databases.
ShowDatabases(ShowDatabases),
// SHOW TABLES
ShowTables(ShowTables),
}
/// Converts Statement to sqlparser statement
@@ -35,6 +33,9 @@ impl TryFrom<Statement> for SpStatement {
Statement::ShowDatabases(_) => Err(ParserError::ParserError(
"sqlparser does not support SHOW DATABASE query.".to_string(),
)),
Statement::ShowTables(_) => Err(ParserError::ParserError(
"sqlparser does not support SHOW TABLES query.".to_string(),
)),
Statement::Query(s) => Ok(SpStatement::Query(Box::new(s.inner))),
Statement::Insert(i) => Ok(i.inner),
Statement::Create(_) | Statement::Alter(_) => unimplemented!(),