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:
Lei, Huang
2022-04-27 15:14:33 +08:00
committed by GitHub
parent 3a2f794f6c
commit 12eefc3cd0
9 changed files with 158 additions and 23 deletions

View File

@@ -8,4 +8,4 @@ edition = "2021"
[dependencies]
query = { path = "../query" }
snafu = "0.7.0"
sqlparser = "0.16.0"
sqlparser = "0.16.0"

View File

@@ -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 { .. }
})
)
}
}

View File

@@ -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;

View File

@@ -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
View File

@@ -0,0 +1 @@
pub(crate) mod query_parser;

View 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(_) => {}
}
}
}

View File

@@ -1,3 +1,4 @@
pub mod show_kind;
pub mod statement;
pub mod statement_query;
pub mod statement_show_database;

View File

@@ -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.

View 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)
}
}