mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-16 02:02:56 +00:00
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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(_),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
97
src/sql/src/statements/show.rs
Normal file
97
src/sql/src/statements/show.rs
Normal 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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!(),
|
||||
|
||||
Reference in New Issue
Block a user