mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-03 11:52:54 +00:00
feat: implement SELECT query parser. (#12)
* feat: Add SQL parser definition and SHOW DATABASE implementation * chores: Eliminate clippy warnings and errors. * chores: remove Gt prefix in some structs; rename some mod; remove print(s) in unit tests; refactor crate layout; feat: wrap sqlparser error; * chores: reorder cargo dependency * chores: fix code style * chores: add #[cfg(test)] to unit tests * style: fix test mod style * feat: implement select query parser * chores: remove unused dependency * feat: implement TryInto<sqlparser::ast::Statement> for Statement * chore: fix style issues * refactor: wrap sqlparser Query inside Query statement variant to reduce complexity * refactor: replace TryInto to TryFrom * refactor: use [Rust 2018 mod convention](https://doc.rust-lang.org/edition-guide/rust-2018/path-changes.html#no-more-modrs)
This commit is contained in:
@@ -8,4 +8,4 @@ edition = "2021"
|
||||
[dependencies]
|
||||
query = { path = "../query" }
|
||||
snafu = "0.7.0"
|
||||
sqlparser = "0.16.0"
|
||||
sqlparser = "0.16.0"
|
||||
|
||||
@@ -3,13 +3,13 @@ use sqlparser::parser::ParserError as SpParserError;
|
||||
|
||||
/// SQL parser errors.
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub(crate)))]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum ParserError {
|
||||
#[snafu(display("SQL statement is not supported: {sql}"))]
|
||||
Unsupported { sql: String },
|
||||
#[snafu(display("SQL statement is not supported: {sql}, keyword: {keyword}"))]
|
||||
Unsupported { sql: String, keyword: String },
|
||||
|
||||
#[snafu(display(
|
||||
"Unexpected token while parsing SQL statement: {sql}, expected: {expected}, found: {actual}"
|
||||
"Unexpected token while parsing SQL statement: {sql}, expected: {expected}, found: {actual}, source: {source}"
|
||||
))]
|
||||
Unexpected {
|
||||
sql: String,
|
||||
@@ -17,4 +17,36 @@ pub enum ParserError {
|
||||
actual: String,
|
||||
source: SpParserError,
|
||||
},
|
||||
|
||||
#[snafu(display("SQL syntax error: {msg}"))]
|
||||
SyntaxError { msg: String },
|
||||
|
||||
#[snafu(display("Unknown inner parser error, sql: {sql}, source: {source}"))]
|
||||
InnerError { sql: String, source: SpParserError },
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use snafu::ResultExt;
|
||||
|
||||
#[test]
|
||||
pub fn test_error_conversion() {
|
||||
pub fn raise_error() -> Result<(), sqlparser::parser::ParserError> {
|
||||
Err(sqlparser::parser::ParserError::ParserError(
|
||||
"parser error".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
assert_matches!(
|
||||
raise_error().context(crate::errors::InnerSnafu {
|
||||
sql: "".to_string(),
|
||||
}),
|
||||
Err(super::ParserError::InnerError {
|
||||
sql: _,
|
||||
source: sqlparser::parser::ParserError::ParserError { .. }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
extern crate core;
|
||||
|
||||
mod ast;
|
||||
mod dialect;
|
||||
mod errors;
|
||||
pub mod ast;
|
||||
pub mod dialect;
|
||||
pub mod errors;
|
||||
pub mod parser;
|
||||
mod planner;
|
||||
mod statements;
|
||||
pub mod parsers;
|
||||
pub mod planner;
|
||||
pub mod statements;
|
||||
|
||||
@@ -42,7 +42,7 @@ impl<'a> ParserContext<'a> {
|
||||
break;
|
||||
}
|
||||
if expecting_statement_delimiter {
|
||||
return parser_ctx.unsupported();
|
||||
return parser_ctx.unsupported(parser_ctx.peek_token_as_string());
|
||||
}
|
||||
|
||||
let statement = parser_ctx.parse_statement()?;
|
||||
@@ -78,18 +78,19 @@ impl<'a> ParserContext<'a> {
|
||||
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => self.parse_query(),
|
||||
|
||||
// todo(hl) support more statements.
|
||||
_ => self.unsupported(),
|
||||
_ => self.unsupported(self.peek_token_as_string()),
|
||||
}
|
||||
}
|
||||
Token::LParen => self.parse_query(),
|
||||
_ => self.unsupported(),
|
||||
unexpected => self.unsupported(unexpected.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Raises an "unsupported statement" error.
|
||||
pub fn unsupported<T>(&self) -> Result<T, errors::ParserError> {
|
||||
pub fn unsupported<T>(&self, keyword: String) -> Result<T, errors::ParserError> {
|
||||
Err(errors::ParserError::Unsupported {
|
||||
sql: self.sql.to_string(),
|
||||
keyword,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ impl<'a> ParserContext<'a> {
|
||||
if self.consume_token("DATABASES") || self.consume_token("SCHEMAS") {
|
||||
Ok(self.parse_show_databases()?)
|
||||
} else {
|
||||
self.unsupported()
|
||||
self.unsupported(self.peek_token_as_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,16 +112,12 @@ impl<'a> ParserContext<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn parse_query(&mut self) -> Result<Statement, errors::ParserError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn parse_create(&mut self) -> Result<Statement, errors::ParserError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn consume_token(&mut self, expected: &str) -> bool {
|
||||
if self.parser.peek_token().to_string().to_uppercase() == *expected.to_uppercase() {
|
||||
if self.peek_token_as_string().to_uppercase() == *expected.to_uppercase() {
|
||||
self.parser.next_token();
|
||||
true
|
||||
} else {
|
||||
@@ -128,6 +125,11 @@ impl<'a> ParserContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<Statement, errors::ParserError> {
|
||||
let tok = self.parser.next_token();
|
||||
@@ -152,12 +154,12 @@ impl<'a> ParserContext<'a> {
|
||||
ShowKind::Where(self.parser.parse_expr().context(errors::UnexpectedSnafu {
|
||||
sql: self.sql.to_string(),
|
||||
expected: "some valid expression".to_string(),
|
||||
actual: self.parser.peek_token().to_string(),
|
||||
actual: self.peek_token_as_string(),
|
||||
})?),
|
||||
))),
|
||||
_ => self.unsupported(),
|
||||
_ => self.unsupported(self.peek_token_as_string()),
|
||||
},
|
||||
_ => self.unsupported(),
|
||||
_ => self.unsupported(self.peek_token_as_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/sql/src/parsers.rs
Normal file
1
src/sql/src/parsers.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub(crate) mod query_parser;
|
||||
51
src/sql/src/parsers/query_parser.rs
Normal file
51
src/sql/src/parsers/query_parser.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use errors::ParserError;
|
||||
use snafu::prelude::*;
|
||||
|
||||
use crate::errors;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::statements::statement::Statement;
|
||||
use crate::statements::statement_query::Query;
|
||||
|
||||
impl<'a> ParserContext<'a> {
|
||||
/// Parses select and it's variants.
|
||||
pub(crate) fn parse_query(&mut self) -> Result<Statement, ParserError> {
|
||||
let spquery = self
|
||||
.parser
|
||||
.parse_query()
|
||||
.context(errors::InnerSnafu { sql: self.sql })?;
|
||||
|
||||
Ok(Statement::Query(Box::new(Query::try_from(spquery)?)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use sqlparser::dialect::GenericDialect;
|
||||
|
||||
use super::*;
|
||||
use crate::parser::ParserContext;
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_query() {
|
||||
let sql = "SELECT a, b, 123, myfunc(b) \
|
||||
FROM table_1 \
|
||||
WHERE a > b AND b < 100 \
|
||||
ORDER BY a DESC, b";
|
||||
|
||||
let _ = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parser_invalid_query() {
|
||||
// sql without selection
|
||||
let sql = "SELECT FROM table_1";
|
||||
|
||||
let parser = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
|
||||
match &parser[0] {
|
||||
Statement::ShowDatabases(_) => {
|
||||
panic!("Not expected to be a show database statement")
|
||||
}
|
||||
Statement::Query(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod show_kind;
|
||||
pub mod statement;
|
||||
pub mod statement_query;
|
||||
pub mod statement_show_database;
|
||||
@@ -1,3 +1,7 @@
|
||||
use sqlparser::ast::Statement as SpStatement;
|
||||
use sqlparser::parser::ParserError::ParserError;
|
||||
|
||||
use crate::statements::statement_query::Query;
|
||||
use crate::statements::statement_show_database::SqlShowDatabase;
|
||||
|
||||
/// Tokens parsed by `DFParser` are converted into these values.
|
||||
@@ -5,6 +9,23 @@ use crate::statements::statement_show_database::SqlShowDatabase;
|
||||
pub enum Statement {
|
||||
// Databases.
|
||||
ShowDatabases(SqlShowDatabase),
|
||||
|
||||
// Query
|
||||
Query(Box<Query>),
|
||||
}
|
||||
|
||||
/// Converts Statement to sqlparser statement
|
||||
impl TryFrom<Statement> for SpStatement {
|
||||
type Error = sqlparser::parser::ParserError;
|
||||
|
||||
fn try_from(value: Statement) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Statement::ShowDatabases(_) => Err(ParserError(
|
||||
"sqlparser does not support SHOW DATABASE query.".to_string(),
|
||||
)),
|
||||
Statement::Query(s) => Ok(SpStatement::Query(Box::new(s.inner))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Comment hints from SQL.
|
||||
|
||||
26
src/sql/src/statements/statement_query.rs
Normal file
26
src/sql/src/statements/statement_query.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use sqlparser::ast::Query as SpQuery;
|
||||
|
||||
use crate::errors::ParserError;
|
||||
|
||||
/// Query statement instance.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Query {
|
||||
pub inner: SpQuery,
|
||||
}
|
||||
|
||||
/// Automatically converts from sqlparser Query instance to SqlQuery.
|
||||
impl TryFrom<SpQuery> for Query {
|
||||
type Error = ParserError;
|
||||
|
||||
fn try_from(q: SpQuery) -> Result<Self, Self::Error> {
|
||||
Ok(Query { inner: q })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Query> for SpQuery {
|
||||
type Error = ParserError;
|
||||
|
||||
fn try_from(value: Query) -> Result<Self, Self::Error> {
|
||||
Ok(value.inner)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user