mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-18 05:50:41 +00:00
feat: impl show index and show columns (#3577)
* feat: impl show index and show columns * fix: show index from database * fix: canonicalize table name * refactor: show parsers
This commit is contained in:
@@ -84,6 +84,18 @@ pub enum Error {
|
||||
#[snafu(display("Invalid SQL, error: {}", msg))]
|
||||
InvalidSql { msg: String },
|
||||
|
||||
#[snafu(display(
|
||||
"Unexpected token while parsing SQL statement: {}, expected: '{}', found: {}",
|
||||
sql,
|
||||
expected,
|
||||
actual,
|
||||
))]
|
||||
UnexpectedToken {
|
||||
sql: String,
|
||||
expected: String,
|
||||
actual: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid column option, column name: {}, error: {}", name, msg))]
|
||||
InvalidColumnOption { name: String, msg: String },
|
||||
|
||||
@@ -185,6 +197,7 @@ impl ErrorExt for Error {
|
||||
| InvalidSql { .. }
|
||||
| ParseSqlValue { .. }
|
||||
| SqlTypeNotSupported { .. }
|
||||
| UnexpectedToken { .. }
|
||||
| InvalidDefault { .. } => StatusCode::InvalidSyntax,
|
||||
|
||||
InvalidColumnOption { .. }
|
||||
|
||||
@@ -19,7 +19,7 @@ use sqlparser::tokenizer::Token;
|
||||
use crate::error::{self, InvalidDatabaseNameSnafu, InvalidTableNameSnafu, Result};
|
||||
use crate::parser::ParserContext;
|
||||
use crate::statements::show::{
|
||||
ShowCreateTable, ShowDatabases, ShowKind, ShowTables, ShowVariables,
|
||||
ShowColumns, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowTables, ShowVariables,
|
||||
};
|
||||
use crate::statements::statement::Statement;
|
||||
|
||||
@@ -33,6 +33,16 @@ impl<'a> ParserContext<'a> {
|
||||
} else if self.matches_keyword(Keyword::TABLES) {
|
||||
let _ = self.parser.next_token();
|
||||
self.parse_show_tables(false)
|
||||
} else if self.matches_keyword(Keyword::COLUMNS) || self.matches_keyword(Keyword::FIELDS) {
|
||||
// SHOW {COLUMNS | FIELDS}
|
||||
let _ = self.parser.next_token();
|
||||
self.parse_show_columns(false)
|
||||
} else if self.consume_token("INDEX")
|
||||
|| self.consume_token("INDEXES")
|
||||
|| self.consume_token("KEYS")
|
||||
{
|
||||
// SHOW {INDEX | INDEXES | KEYS}
|
||||
self.parse_show_index()
|
||||
} else if self.consume_token("CREATE") {
|
||||
if self.consume_token("TABLE") {
|
||||
self.parse_show_create_table()
|
||||
@@ -42,6 +52,9 @@ impl<'a> ParserContext<'a> {
|
||||
} else if self.consume_token("FULL") {
|
||||
if self.consume_token("TABLES") {
|
||||
self.parse_show_tables(true)
|
||||
} else if self.consume_token("COLUMNS") || self.consume_token("FIELDS") {
|
||||
// SHOW {COLUMNS | FIELDS}
|
||||
self.parse_show_columns(true)
|
||||
} else {
|
||||
self.unsupported(self.peek_token_as_string())
|
||||
}
|
||||
@@ -80,6 +93,186 @@ impl<'a> ParserContext<'a> {
|
||||
Ok(Statement::ShowCreateTable(ShowCreateTable { table_name }))
|
||||
}
|
||||
|
||||
fn parse_show_table_name(&mut self) -> Result<String> {
|
||||
let _ = self.parser.next_token();
|
||||
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.len() == 1,
|
||||
InvalidDatabaseNameSnafu {
|
||||
name: table_name.to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
// Safety: already checked above
|
||||
Ok(Self::canonicalize_object_name(table_name).0[0]
|
||||
.value
|
||||
.clone())
|
||||
}
|
||||
|
||||
fn parse_db_name(&mut self) -> Result<Option<String>> {
|
||||
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(),
|
||||
}
|
||||
);
|
||||
|
||||
// Safety: already checked above
|
||||
Ok(Some(
|
||||
Self::canonicalize_object_name(db_name).0[0].value.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_show_columns(&mut self, full: bool) -> Result<Statement> {
|
||||
let table = match self.parser.peek_token().token {
|
||||
// SHOW columns {in | FROM} TABLE
|
||||
Token::Word(w) if matches!(w.keyword, Keyword::IN | Keyword::FROM) => {
|
||||
self.parse_show_table_name()?
|
||||
}
|
||||
_ => {
|
||||
return error::UnexpectedTokenSnafu {
|
||||
sql: self.sql,
|
||||
expected: "{FROM | IN} table",
|
||||
actual: self.peek_token_as_string(),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
};
|
||||
|
||||
let database = match self.parser.peek_token().token {
|
||||
Token::EOF | Token::SemiColon => {
|
||||
return Ok(Statement::ShowColumns(ShowColumns {
|
||||
kind: ShowKind::All,
|
||||
table,
|
||||
database: None,
|
||||
full,
|
||||
}));
|
||||
}
|
||||
|
||||
// SHOW columns {In | FROM} TABLE {In | FROM} DATABASE
|
||||
Token::Word(w) => match w.keyword {
|
||||
Keyword::IN | Keyword::FROM => self.parse_db_name()?,
|
||||
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let kind = match self.parser.peek_token().token {
|
||||
Token::EOF | Token::SemiColon => ShowKind::All,
|
||||
// SHOW COLUMNS [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::ShowColumns(ShowColumns {
|
||||
kind,
|
||||
database,
|
||||
table,
|
||||
full,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_show_index(&mut self) -> Result<Statement> {
|
||||
let table = match self.parser.peek_token().token {
|
||||
// SHOW INDEX {in | FROM} TABLE
|
||||
Token::Word(w) if matches!(w.keyword, Keyword::IN | Keyword::FROM) => {
|
||||
self.parse_show_table_name()?
|
||||
}
|
||||
_ => {
|
||||
return error::UnexpectedTokenSnafu {
|
||||
sql: self.sql,
|
||||
expected: "{FROM | IN} table",
|
||||
actual: self.peek_token_as_string(),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
};
|
||||
|
||||
let database = match self.parser.peek_token().token {
|
||||
Token::EOF | Token::SemiColon => {
|
||||
return Ok(Statement::ShowIndex(ShowIndex {
|
||||
kind: ShowKind::All,
|
||||
table,
|
||||
database: None,
|
||||
}));
|
||||
}
|
||||
|
||||
// SHOW INDEX {In | FROM} TABLE {In | FROM} DATABASE
|
||||
Token::Word(w) => match w.keyword {
|
||||
Keyword::IN | Keyword::FROM => self.parse_db_name()?,
|
||||
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let kind = match self.parser.peek_token().token {
|
||||
Token::EOF | Token::SemiColon => ShowKind::All,
|
||||
// SHOW INDEX [WHERE] [EXPR]
|
||||
Token::Word(w) => match w.keyword {
|
||||
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::ShowIndex(ShowIndex {
|
||||
kind,
|
||||
database,
|
||||
table,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_show_tables(&mut self, full: bool) -> Result<Statement> {
|
||||
let database = match self.parser.peek_token().token {
|
||||
Token::EOF | Token::SemiColon => {
|
||||
@@ -92,25 +285,7 @@ impl<'a> ParserContext<'a> {
|
||||
|
||||
// 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())
|
||||
}
|
||||
Keyword::IN | Keyword::FROM => self.parse_db_name()?,
|
||||
|
||||
_ => None,
|
||||
},
|
||||
@@ -431,4 +606,120 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_columns() {
|
||||
let sql = "SHOW COLUMNS";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let error = result.unwrap_err();
|
||||
assert_eq!("Unexpected token while parsing SQL statement: SHOW COLUMNS, expected: '{FROM | IN} table', found: EOF", error.to_string());
|
||||
|
||||
let sql = "SHOW COLUMNS from test";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowColumns(ShowColumns {
|
||||
table,
|
||||
database,
|
||||
full,
|
||||
..
|
||||
|
||||
}) if table == "test" && database.is_none() && !full));
|
||||
|
||||
let sql = "SHOW FULL COLUMNS from test from public";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowColumns(ShowColumns {
|
||||
table,
|
||||
database: Some(database),
|
||||
full,
|
||||
..
|
||||
}) if table == "test" && database == "public" && *full));
|
||||
|
||||
let sql = "SHOW COLUMNS from test like 'disk%'";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowColumns(ShowColumns {
|
||||
table,
|
||||
kind: ShowKind::Like(ident),
|
||||
..
|
||||
}) if table == "test" && ident.to_string() == "'disk%'"));
|
||||
|
||||
let sql = "SHOW COLUMNS from test where Field = 'disk'";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowColumns(ShowColumns {
|
||||
table,
|
||||
kind: ShowKind::Where(expr),
|
||||
..
|
||||
}) if table == "test" && expr.to_string() == "Field = 'disk'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_index() {
|
||||
let sql = "SHOW INDEX";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let error = result.unwrap_err();
|
||||
assert_eq!("Unexpected token while parsing SQL statement: SHOW INDEX, expected: '{FROM | IN} table', found: EOF", error.to_string());
|
||||
|
||||
let sql = "SHOW INDEX from test";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowIndex(ShowIndex {
|
||||
table,
|
||||
database,
|
||||
..
|
||||
|
||||
}) if table == "test" && database.is_none()));
|
||||
|
||||
let sql = "SHOW INDEX from test from public";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowIndex(ShowIndex {
|
||||
table,
|
||||
database: Some(database),
|
||||
..
|
||||
}) if table == "test" && database == "public"));
|
||||
|
||||
// SHOW INDEX deosn't support like
|
||||
let sql = "SHOW INDEX from test like 'disk%'";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let error = result.unwrap_err();
|
||||
assert_eq!(
|
||||
"SQL statement is not supported: SHOW INDEX from test like 'disk%', keyword: like",
|
||||
error.to_string()
|
||||
);
|
||||
|
||||
let sql = "SHOW INDEX from test where Field = 'disk'";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
let stmts = result.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert!(matches!(&stmts[0],
|
||||
Statement::ShowIndex(ShowIndex {
|
||||
table,
|
||||
kind: ShowKind::Where(expr),
|
||||
..
|
||||
}) if table == "test" && expr.to_string() == "Field = 'disk'"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,23 @@ pub struct ShowDatabases {
|
||||
pub kind: ShowKind,
|
||||
}
|
||||
|
||||
/// The SQL `SHOW COLUMNS` statement
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
|
||||
pub struct ShowColumns {
|
||||
pub kind: ShowKind,
|
||||
pub table: String,
|
||||
pub database: Option<String>,
|
||||
pub full: bool,
|
||||
}
|
||||
|
||||
/// The SQL `SHOW INDEX` statement
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
|
||||
pub struct ShowIndex {
|
||||
pub kind: ShowKind,
|
||||
pub table: String,
|
||||
pub database: Option<String>,
|
||||
}
|
||||
|
||||
impl ShowDatabases {
|
||||
/// Creates a statement for `SHOW DATABASES`
|
||||
pub fn new(kind: ShowKind) -> Self {
|
||||
|
||||
@@ -30,7 +30,7 @@ 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::{ShowCreateTable, ShowDatabases, ShowTables};
|
||||
use crate::statements::show::{ShowColumns, ShowCreateTable, ShowDatabases, ShowIndex, ShowTables};
|
||||
use crate::statements::tql::Tql;
|
||||
use crate::statements::truncate::TruncateTable;
|
||||
|
||||
@@ -62,6 +62,10 @@ pub enum Statement {
|
||||
ShowDatabases(ShowDatabases),
|
||||
// SHOW TABLES
|
||||
ShowTables(ShowTables),
|
||||
// SHOW COLUMNS
|
||||
ShowColumns(ShowColumns),
|
||||
// SHOW INDEX
|
||||
ShowIndex(ShowIndex),
|
||||
// SHOW CREATE TABLE
|
||||
ShowCreateTable(ShowCreateTable),
|
||||
// DESCRIBE TABLE
|
||||
|
||||
Reference in New Issue
Block a user