diff --git a/src/sql/Cargo.toml b/src/sql/Cargo.toml index 70bd188d09..94bf7505d0 100644 --- a/src/sql/Cargo.toml +++ b/src/sql/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] query = { path = "../query" } snafu = "0.7.0" -sqlparser = "0.16.0" \ No newline at end of file +sqlparser = "0.16.0" diff --git a/src/sql/src/errors.rs b/src/sql/src/errors.rs index 621f9efdc5..155e4fd55d 100644 --- a/src/sql/src/errors.rs +++ b/src/sql/src/errors.rs @@ -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 { .. } + }) + ) + } } diff --git a/src/sql/src/lib.rs b/src/sql/src/lib.rs index 8d9671e9e2..3081a847dc 100644 --- a/src/sql/src/lib.rs +++ b/src/sql/src/lib.rs @@ -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; diff --git a/src/sql/src/parser.rs b/src/sql/src/parser.rs index 963e21d3c0..4f61c600c0 100644 --- a/src/sql/src/parser.rs +++ b/src/sql/src/parser.rs @@ -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(&self) -> Result { + pub fn unsupported(&self, keyword: String) -> Result { 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 { - todo!() - } - fn parse_create(&mut self) -> Result { 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 { 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()), } } } diff --git a/src/sql/src/parsers.rs b/src/sql/src/parsers.rs new file mode 100644 index 0000000000..9d5260780c --- /dev/null +++ b/src/sql/src/parsers.rs @@ -0,0 +1 @@ +pub(crate) mod query_parser; diff --git a/src/sql/src/parsers/query_parser.rs b/src/sql/src/parsers/query_parser.rs new file mode 100644 index 0000000000..38d6887519 --- /dev/null +++ b/src/sql/src/parsers/query_parser.rs @@ -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 { + 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(_) => {} + } + } +} diff --git a/src/sql/src/statements/mod.rs b/src/sql/src/statements.rs similarity index 73% rename from src/sql/src/statements/mod.rs rename to src/sql/src/statements.rs index 2b2ac6579a..895ca9bfee 100644 --- a/src/sql/src/statements/mod.rs +++ b/src/sql/src/statements.rs @@ -1,3 +1,4 @@ pub mod show_kind; pub mod statement; +pub mod statement_query; pub mod statement_show_database; diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index 4227e6f626..9b62092200 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -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), +} + +/// Converts Statement to sqlparser statement +impl TryFrom for SpStatement { + type Error = sqlparser::parser::ParserError; + + fn try_from(value: Statement) -> Result { + 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. diff --git a/src/sql/src/statements/statement_query.rs b/src/sql/src/statements/statement_query.rs new file mode 100644 index 0000000000..ec4315b91a --- /dev/null +++ b/src/sql/src/statements/statement_query.rs @@ -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 for Query { + type Error = ParserError; + + fn try_from(q: SpQuery) -> Result { + Ok(Query { inner: q }) + } +} + +impl TryFrom for SpQuery { + type Error = ParserError; + + fn try_from(value: Query) -> Result { + Ok(value.inner) + } +}