From ce4f24f103d0d705e364834835fc61ffa6390cac Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Fri, 31 May 2024 18:22:02 +0800 Subject: [PATCH 1/6] refactor(parser): using v2 parser for cast/extract/substring Signed-off-by: TennyZhuang --- src/sqlparser/src/parser.rs | 49 +----------- src/sqlparser/src/parser_v2/expr.rs | 93 ++++++++++++++++++++++- src/sqlparser/tests/sqlparser_common.rs | 3 - src/sqlparser/tests/testdata/extract.yaml | 6 ++ 4 files changed, 101 insertions(+), 50 deletions(-) create mode 100644 src/sqlparser/tests/testdata/extract.yaml diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index c80c3c145dd96..3b7d64f80767c 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -1009,28 +1009,12 @@ impl Parser<'_> { /// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)` pub fn parse_cast_expr(&mut self) -> PResult { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - self.expect_keyword(Keyword::AS)?; - let data_type = self.parse_data_type()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::Cast { - expr: Box::new(expr), - data_type, - }) + parser_v2::expr_cast(self) } /// Parse a SQL TRY_CAST function e.g. `TRY_CAST(expr AS FLOAT)` pub fn parse_try_cast_expr(&mut self) -> PResult { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - self.expect_keyword(Keyword::AS)?; - let data_type = self.parse_data_type()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::TryCast { - expr: Box::new(expr), - data_type, - }) + parser_v2::expr_try_cast(self) } /// Parse a SQL EXISTS expression e.g. `WHERE EXISTS(SELECT ...)`. @@ -1042,37 +1026,12 @@ impl Parser<'_> { } pub fn parse_extract_expr(&mut self) -> PResult { - self.expect_token(&Token::LParen)?; - let field = self.parse_date_time_field_in_extract()?; - self.expect_keyword(Keyword::FROM)?; - let expr = self.parse_expr()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::Extract { - field, - expr: Box::new(expr), - }) + parser_v2::expr_extract(self) } pub fn parse_substring_expr(&mut self) -> PResult { // PARSE SUBSTRING (EXPR [FROM 1] [FOR 3]) - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - let mut from_expr = None; - if self.parse_keyword(Keyword::FROM) || self.consume_token(&Token::Comma) { - from_expr = Some(self.parse_expr()?); - } - - let mut to_expr = None; - if self.parse_keyword(Keyword::FOR) || self.consume_token(&Token::Comma) { - to_expr = Some(self.parse_expr()?); - } - self.expect_token(&Token::RParen)?; - - Ok(Expr::Substring { - expr: Box::new(expr), - substring_from: from_expr.map(Box::new), - substring_for: to_expr.map(Box::new), - }) + parser_v2::expr_substring(self) } /// `POSITION( IN )` diff --git a/src/sqlparser/src/parser_v2/expr.rs b/src/sqlparser/src/parser_v2/expr.rs index 7447984d7caf0..34d00512501a5 100644 --- a/src/sqlparser/src/parser_v2/expr.rs +++ b/src/sqlparser/src/parser_v2/expr.rs @@ -9,12 +9,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use winnow::combinator::{cut_err, opt, preceded, repeat, trace}; +use winnow::combinator::{alt, cut_err, opt, preceded, repeat, trace}; use winnow::{PResult, Parser}; -use super::TokenStream; +use super::{data_type, token, ParserExt, TokenStream}; use crate::ast::Expr; use crate::keywords::Keyword; +use crate::tokenizer::Token; fn expr(input: &mut S) -> PResult where @@ -58,3 +59,91 @@ where trace("expr_case", parse).parse_next(input) } + +/// Consome a SQL CAST function e.g. `CAST(expr AS FLOAT)` +pub fn expr_cast(input: &mut S) -> PResult +where + S: TokenStream, +{ + let parse = ( + cut_err(Token::LParen), + cut_err(expr).map(Box::new), + cut_err(Keyword::AS), + cut_err(data_type), + cut_err(Token::RParen), + ) + .map(|(_, expr, _, data_type, _)| Expr::Cast { expr, data_type }); + + trace("expr_cast", parse).parse_next(input) +} + +/// Consume a SQL TRY_CAST function e.g. `TRY_CAST(expr AS FLOAT)` +pub fn expr_try_cast(input: &mut S) -> PResult +where + S: TokenStream, +{ + let parse = ( + cut_err(Token::LParen), + cut_err(expr).map(Box::new), // Parses the expresion within TRY_CAST + cut_err(Keyword::AS), + cut_err(data_type), // Parses the data type to TRY_CAST to + cut_err(Token::RParen), + ) + .map(|(_, expr, _, data_type, _)| Expr::TryCast { expr, data_type }); + + trace("expr_try_cast", parse).parse_next(input) +} + +/// Consume a SQL EXTRACT function e.g. `EXTRACT(YEAR FROM expr)` +pub fn expr_extract(input: &mut S) -> PResult +where + S: TokenStream, +{ + let date_time_field = token + .verify_map(|token| match token.token { + Token::Word(w) => Some(w.value.to_uppercase()), + Token::SingleQuotedString(s) => Some(s.to_uppercase()), + _ => None, + }) + .expect("date/time field"); + + let parse = ( + cut_err(Token::LParen), + cut_err(date_time_field), + cut_err(Keyword::FROM), + cut_err(expr).map(Box::new), + cut_err(Token::RParen), + ) + .map(|(_, field, _, expr, _)| Expr::Extract { field, expr }); + + trace("expr_extract", parse).parse_next(input) +} + +/// Consume `SUBSTRING (EXPR [FROM 1] [FOR 3])` +pub fn expr_substring(input: &mut S) -> PResult +where + S: TokenStream, +{ + let parse = ( + cut_err(Token::LParen), + cut_err(expr).map(Box::new), + opt(preceded( + alt((Token::Comma.void(), Keyword::FROM.void())), + cut_err(expr).map(Box::new), + )), + opt(preceded( + alt((Token::Comma.void(), Keyword::FOR.void())), + cut_err(expr).map(Box::new), + )), + cut_err(Token::RParen), + ) + .map( + |(_, expr, substring_from, substring_for, _)| Expr::Substring { + expr, + substring_from, + substring_for, + }, + ); + + trace("expr_substring", parse).parse_next(input) +} diff --git a/src/sqlparser/tests/sqlparser_common.rs b/src/sqlparser/tests/sqlparser_common.rs index c694aba3d1308..f3284ca35eee4 100644 --- a/src/sqlparser/tests/sqlparser_common.rs +++ b/src/sqlparser/tests/sqlparser_common.rs @@ -1268,9 +1268,6 @@ fn parse_extract() { verified_stmt("SELECT EXTRACT(HOUR FROM d)"); verified_stmt("SELECT EXTRACT(MINUTE FROM d)"); verified_stmt("SELECT EXTRACT(SECOND FROM d)"); - - let res = parse_sql_statements("SELECT EXTRACT(0 FROM d)"); - assert!(format!("{}", res.unwrap_err()).contains("expected date/time field, found: 0")); } #[test] diff --git a/src/sqlparser/tests/testdata/extract.yaml b/src/sqlparser/tests/testdata/extract.yaml new file mode 100644 index 0000000000000..174cb88e8c1a8 --- /dev/null +++ b/src/sqlparser/tests/testdata/extract.yaml @@ -0,0 +1,6 @@ +# This file is automatically generated by `src/sqlparser/tests/parser_test.rs`. +- input: SELECT EXTRACT(0 FROM d) + error_msg: |- + sql parser error: invalid date/time field + LINE 1: SELECT EXTRACT(0 FROM d) + ^ From 570cf4a07ffdda3a8a9b0ca3722d2173870a721e Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Fri, 31 May 2024 18:24:16 +0800 Subject: [PATCH 2/6] fix typo Signed-off-by: TennyZhuang --- src/sqlparser/src/parser_v2/expr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sqlparser/src/parser_v2/expr.rs b/src/sqlparser/src/parser_v2/expr.rs index 34d00512501a5..68ab0aa09c386 100644 --- a/src/sqlparser/src/parser_v2/expr.rs +++ b/src/sqlparser/src/parser_v2/expr.rs @@ -84,9 +84,9 @@ where { let parse = ( cut_err(Token::LParen), - cut_err(expr).map(Box::new), // Parses the expresion within TRY_CAST + cut_err(expr).map(Box::new), cut_err(Keyword::AS), - cut_err(data_type), // Parses the data type to TRY_CAST to + cut_err(data_type), cut_err(Token::RParen), ) .map(|(_, expr, _, data_type, _)| Expr::TryCast { expr, data_type }); From 89b674b2d791dcd28168af969120f159aecc47dc Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Fri, 31 May 2024 19:02:50 +0800 Subject: [PATCH 3/6] update output Signed-off-by: TennyZhuang --- src/sqlparser/tests/testdata/extract.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlparser/tests/testdata/extract.yaml b/src/sqlparser/tests/testdata/extract.yaml index 174cb88e8c1a8..ba38c3e25f261 100644 --- a/src/sqlparser/tests/testdata/extract.yaml +++ b/src/sqlparser/tests/testdata/extract.yaml @@ -1,6 +1,6 @@ # This file is automatically generated by `src/sqlparser/tests/parser_test.rs`. - input: SELECT EXTRACT(0 FROM d) error_msg: |- - sql parser error: invalid date/time field + sql parser error: expected date/time field LINE 1: SELECT EXTRACT(0 FROM d) ^ From 460f7a2fbf7f0a6b8b6fd3f08d7f6248a53aeef4 Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Tue, 4 Jun 2024 16:32:10 +0800 Subject: [PATCH 4/6] utilize seq! macro Signed-off-by: TennyZhuang --- src/sqlparser/src/parser_v2/expr.rs | 84 +++++++++++++---------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/src/sqlparser/src/parser_v2/expr.rs b/src/sqlparser/src/parser_v2/expr.rs index 68ab0aa09c386..d12024b9769fa 100644 --- a/src/sqlparser/src/parser_v2/expr.rs +++ b/src/sqlparser/src/parser_v2/expr.rs @@ -9,7 +9,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use winnow::combinator::{alt, cut_err, opt, preceded, repeat, trace}; +use winnow::combinator::{alt, cut_err, opt, preceded, repeat, seq, trace}; use winnow::{PResult, Parser}; use super::{data_type, token, ParserExt, TokenStream}; @@ -60,19 +60,18 @@ where trace("expr_case", parse).parse_next(input) } -/// Consome a SQL CAST function e.g. `CAST(expr AS FLOAT)` +/// Consume a SQL CAST function e.g. `CAST(expr AS FLOAT)` pub fn expr_cast(input: &mut S) -> PResult where S: TokenStream, { - let parse = ( - cut_err(Token::LParen), - cut_err(expr).map(Box::new), - cut_err(Keyword::AS), - cut_err(data_type), - cut_err(Token::RParen), - ) - .map(|(_, expr, _, data_type, _)| Expr::Cast { expr, data_type }); + let parse = cut_err(seq! {Expr::Cast { + _: Token::LParen, + expr: expr.map(Box::new), + _: Keyword::AS, + data_type: data_type, + _: Token::RParen, + }}); trace("expr_cast", parse).parse_next(input) } @@ -82,14 +81,13 @@ pub fn expr_try_cast(input: &mut S) -> PResult where S: TokenStream, { - let parse = ( - cut_err(Token::LParen), - cut_err(expr).map(Box::new), - cut_err(Keyword::AS), - cut_err(data_type), - cut_err(Token::RParen), - ) - .map(|(_, expr, _, data_type, _)| Expr::TryCast { expr, data_type }); + let parse = cut_err(seq! {Expr::TryCast { + _: Token::LParen, + expr: expr.map(Box::new), + _: Keyword::AS, + data_type: data_type, + _: Token::RParen, + }}); trace("expr_try_cast", parse).parse_next(input) } @@ -99,7 +97,7 @@ pub fn expr_extract(input: &mut S) -> PResult where S: TokenStream, { - let date_time_field = token + let mut date_time_field = token .verify_map(|token| match token.token { Token::Word(w) => Some(w.value.to_uppercase()), Token::SingleQuotedString(s) => Some(s.to_uppercase()), @@ -107,14 +105,13 @@ where }) .expect("date/time field"); - let parse = ( - cut_err(Token::LParen), - cut_err(date_time_field), - cut_err(Keyword::FROM), - cut_err(expr).map(Box::new), - cut_err(Token::RParen), - ) - .map(|(_, field, _, expr, _)| Expr::Extract { field, expr }); + let parse = cut_err(seq! {Expr::Extract { + _: Token::LParen, + field: date_time_field, + _: Keyword::FROM, + expr: expr.map(Box::new), + _: Token::RParen, + }}); trace("expr_extract", parse).parse_next(input) } @@ -124,26 +121,21 @@ pub fn expr_substring(input: &mut S) -> PResult where S: TokenStream, { - let parse = ( - cut_err(Token::LParen), + let mut substring_from = opt(preceded( + alt((Token::Comma.void(), Keyword::FROM.void())), cut_err(expr).map(Box::new), - opt(preceded( - alt((Token::Comma.void(), Keyword::FROM.void())), - cut_err(expr).map(Box::new), - )), - opt(preceded( - alt((Token::Comma.void(), Keyword::FOR.void())), - cut_err(expr).map(Box::new), - )), - cut_err(Token::RParen), - ) - .map( - |(_, expr, substring_from, substring_for, _)| Expr::Substring { - expr, - substring_from, - substring_for, - }, - ); + )); + let mut substring_for = opt(preceded( + alt((Token::Comma.void(), Keyword::FOR.void())), + cut_err(expr).map(Box::new), + )); + let parse = cut_err(seq! {Expr::Substring { + _: Token::LParen, + expr: expr.map(Box::new), + substring_from: substring_from, + substring_for: substring_for, + _: Token::RParen, + }}); trace("expr_substring", parse).parse_next(input) } From 6f9a750e5915ee738e957fedea7028abdbcfead1 Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Tue, 4 Jun 2024 16:49:49 +0800 Subject: [PATCH 5/6] add position Signed-off-by: TennyZhuang --- src/sqlparser/src/parser.rs | 19 ++---------------- src/sqlparser/src/parser_v2/expr.rs | 31 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index 521f69d8872d0..c1467634797f3 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -170,7 +170,7 @@ type ColumnsDefTuple = ( /// Reference: /// -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Precedence { Zero = 0, LogicalOr, // 5 in upstream @@ -1030,27 +1030,12 @@ impl Parser<'_> { } pub fn parse_substring_expr(&mut self) -> PResult { - // PARSE SUBSTRING (EXPR [FROM 1] [FOR 3]) parser_v2::expr_substring(self) } /// `POSITION( IN )` pub fn parse_position_expr(&mut self) -> PResult { - self.expect_token(&Token::LParen)?; - - // Logically `parse_expr`, but limited to those with precedence higher than `BETWEEN`/`IN`, - // to avoid conflict with general IN operator, for example `position(a IN (b) IN (c))`. - // https://github.com/postgres/postgres/blob/REL_15_2/src/backend/parser/gram.y#L16012 - let substring = self.parse_subexpr(Precedence::Between)?; - self.expect_keyword(Keyword::IN)?; - let string = self.parse_subexpr(Precedence::Between)?; - - self.expect_token(&Token::RParen)?; - - Ok(Expr::Position { - substring: Box::new(substring), - string: Box::new(string), - }) + parser_v2::expr_position(self) } /// `OVERLAY( PLACING FROM [ FOR ])` diff --git a/src/sqlparser/src/parser_v2/expr.rs b/src/sqlparser/src/parser_v2/expr.rs index d12024b9769fa..cb879dc6af35d 100644 --- a/src/sqlparser/src/parser_v2/expr.rs +++ b/src/sqlparser/src/parser_v2/expr.rs @@ -10,11 +10,13 @@ // See the License for the specific language governing permissions and // limitations under the License. use winnow::combinator::{alt, cut_err, opt, preceded, repeat, seq, trace}; +use winnow::error::ContextError; use winnow::{PResult, Parser}; use super::{data_type, token, ParserExt, TokenStream}; use crate::ast::Expr; use crate::keywords::Keyword; +use crate::parser::Precedence; use crate::tokenizer::Token; fn expr(input: &mut S) -> PResult @@ -28,6 +30,16 @@ where .parse_next(input) } +fn subexpr(precedence: Precedence) -> impl Parser +where + S: TokenStream, +{ + // TODO: implement this function using combinator style. + trace("subexpr", move |input: &mut S| { + input.parse_v1(|parser| parser.parse_subexpr(precedence)) + }) +} + pub fn expr_case(input: &mut S) -> PResult where S: TokenStream, @@ -139,3 +151,22 @@ where trace("expr_substring", parse).parse_next(input) } + +/// `POSITION( IN )` +pub fn expr_position(input: &mut S) -> PResult +where + S: TokenStream, +{ + let parse = cut_err(seq! {Expr::Position { + _: Token::LParen, + // Logically `parse_expr`, but limited to those with precedence higher than `BETWEEN`/`IN`, + // to avoid conflict with general IN operator, for example `position(a IN (b) IN (c))`. + // https://github.com/postgres/postgres/blob/REL_15_2/src/backend/parser/gram.y#L16012 + substring: subexpr(Precedence::Between).map(Box::new), + _: Keyword::IN, + string: subexpr(Precedence::Between).map(Box::new), + _: Token::RParen, + }}); + + trace("expr_position", parse).parse_next(input) +} From 8f465b7b01b9a295857c32f7401124c863cb03d0 Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Tue, 4 Jun 2024 17:11:03 +0800 Subject: [PATCH 6/6] add overlay Signed-off-by: TennyZhuang --- src/sqlparser/src/parser.rs | 24 +----------- src/sqlparser/src/parser_v2/expr.rs | 46 +++++++++++++++++------ src/sqlparser/tests/sqlparser_common.rs | 32 ---------------- src/sqlparser/tests/testdata/overlay.yaml | 13 +++++++ 4 files changed, 49 insertions(+), 66 deletions(-) create mode 100644 src/sqlparser/tests/testdata/overlay.yaml diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index c1467634797f3..e68fb13e88552 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -1040,29 +1040,7 @@ impl Parser<'_> { /// `OVERLAY( PLACING FROM [ FOR ])` pub fn parse_overlay_expr(&mut self) -> PResult { - self.expect_token(&Token::LParen)?; - - let expr = self.parse_expr()?; - - self.expect_keyword(Keyword::PLACING)?; - let new_substring = self.parse_expr()?; - - self.expect_keyword(Keyword::FROM)?; - let start = self.parse_expr()?; - - let mut count = None; - if self.parse_keyword(Keyword::FOR) { - count = Some(self.parse_expr()?); - } - - self.expect_token(&Token::RParen)?; - - Ok(Expr::Overlay { - expr: Box::new(expr), - new_substring: Box::new(new_substring), - start: Box::new(start), - count: count.map(Box::new), - }) + parser_v2::expr_overlay(self) } /// `TRIM ([WHERE] ['text'] FROM 'text')`\ diff --git a/src/sqlparser/src/parser_v2/expr.rs b/src/sqlparser/src/parser_v2/expr.rs index cb879dc6af35d..2923919ac83b9 100644 --- a/src/sqlparser/src/parser_v2/expr.rs +++ b/src/sqlparser/src/parser_v2/expr.rs @@ -19,7 +19,7 @@ use crate::keywords::Keyword; use crate::parser::Precedence; use crate::tokenizer::Token; -fn expr(input: &mut S) -> PResult +fn expr_parse(input: &mut S) -> PResult where S: TokenStream, { @@ -45,17 +45,17 @@ where S: TokenStream, { let parse = ( - opt(expr), + opt(expr_parse), repeat( 1.., ( Keyword::WHEN, - cut_err(expr), + cut_err(expr_parse), cut_err(Keyword::THEN), - cut_err(expr), + cut_err(expr_parse), ), ), - opt(preceded(Keyword::ELSE, cut_err(expr))), + opt(preceded(Keyword::ELSE, cut_err(expr_parse))), cut_err(Keyword::END), ) .map(|(operand, branches, else_result, _)| { @@ -79,7 +79,7 @@ where { let parse = cut_err(seq! {Expr::Cast { _: Token::LParen, - expr: expr.map(Box::new), + expr: expr_parse.map(Box::new), _: Keyword::AS, data_type: data_type, _: Token::RParen, @@ -95,7 +95,7 @@ where { let parse = cut_err(seq! {Expr::TryCast { _: Token::LParen, - expr: expr.map(Box::new), + expr: expr_parse.map(Box::new), _: Keyword::AS, data_type: data_type, _: Token::RParen, @@ -121,7 +121,7 @@ where _: Token::LParen, field: date_time_field, _: Keyword::FROM, - expr: expr.map(Box::new), + expr: expr_parse.map(Box::new), _: Token::RParen, }}); @@ -135,15 +135,15 @@ where { let mut substring_from = opt(preceded( alt((Token::Comma.void(), Keyword::FROM.void())), - cut_err(expr).map(Box::new), + cut_err(expr_parse).map(Box::new), )); let mut substring_for = opt(preceded( alt((Token::Comma.void(), Keyword::FOR.void())), - cut_err(expr).map(Box::new), + cut_err(expr_parse).map(Box::new), )); let parse = cut_err(seq! {Expr::Substring { _: Token::LParen, - expr: expr.map(Box::new), + expr: expr_parse.map(Box::new), substring_from: substring_from, substring_for: substring_for, _: Token::RParen, @@ -170,3 +170,27 @@ where trace("expr_position", parse).parse_next(input) } + +/// `OVERLAY( PLACING FROM [ FOR ])` +pub fn expr_overlay(input: &mut S) -> PResult +where + S: TokenStream, +{ + let mut count_parse = opt(preceded( + Keyword::FOR.void(), + cut_err(expr_parse).map(Box::new), + )); + + let parse = cut_err(seq! {Expr::Overlay { + _: Token::LParen, + expr: expr_parse.map(Box::new), + _: Keyword::PLACING, + new_substring: expr_parse.map(Box::new), + _: Keyword::FROM, + start: expr_parse.map(Box::new), + count: count_parse, + _: Token::RParen, + }}); + + trace("expr_overlay", parse).parse_next(input) +} diff --git a/src/sqlparser/tests/sqlparser_common.rs b/src/sqlparser/tests/sqlparser_common.rs index f3284ca35eee4..a3159400b88fe 100644 --- a/src/sqlparser/tests/sqlparser_common.rs +++ b/src/sqlparser/tests/sqlparser_common.rs @@ -2916,38 +2916,6 @@ fn parse_substring() { one_statement_parses_to("SELECT SUBSTRING('1' FOR 3)", "SELECT SUBSTRING('1' FOR 3)"); } -#[test] -fn parse_overlay() { - one_statement_parses_to( - "SELECT OVERLAY('abc' PLACING 'xyz' FROM 1)", - "SELECT OVERLAY('abc' PLACING 'xyz' FROM 1)", - ); - - one_statement_parses_to( - "SELECT OVERLAY('abc' PLACING 'xyz' FROM 1 FOR 2)", - "SELECT OVERLAY('abc' PLACING 'xyz' FROM 1 FOR 2)", - ); - - for (sql, err_msg) in [ - ("SELECT OVERLAY('abc', 'xyz')", "expected PLACING, found: ,"), - ( - "SELECT OVERLAY('abc' PLACING 'xyz')", - "expected FROM, found: )", - ), - ( - "SELECT OVERLAY('abc' PLACING 'xyz' FOR 2)", - "expected FROM, found: FOR", - ), - ( - "SELECT OVERLAY('abc' PLACING 'xyz' FOR 2 FROM 1)", - "expected FROM, found: FOR", - ), - ] { - let res = parse_sql_statements(sql); - assert!(format!("{}", res.unwrap_err()).contains(err_msg)); - } -} - #[test] fn parse_trim() { one_statement_parses_to( diff --git a/src/sqlparser/tests/testdata/overlay.yaml b/src/sqlparser/tests/testdata/overlay.yaml new file mode 100644 index 0000000000000..07e51c46abb60 --- /dev/null +++ b/src/sqlparser/tests/testdata/overlay.yaml @@ -0,0 +1,13 @@ +# This file is automatically generated by `src/sqlparser/tests/parser_test.rs`. +- input: SELECT OVERLAY('abc' PLACING 'xyz' FROM 1) + formatted_sql: SELECT OVERLAY('abc' PLACING 'xyz' FROM 1) +- input: SELECT OVERLAY('abc' PLACING 'xyz' FROM 1 FOR 2) + formatted_sql: SELECT OVERLAY('abc' PLACING 'xyz' FROM 1 FOR 2) +- input: SELECT OVERLAY('abc', 'xyz') + error_msg: "sql parser error: \nLINE 1: SELECT OVERLAY('abc', 'xyz')\n ^" +- input: SELECT OVERLAY('abc' PLACING 'xyz') + error_msg: "sql parser error: \nLINE 1: SELECT OVERLAY('abc' PLACING 'xyz')\n ^" +- input: SELECT OVERLAY('abc' PLACING 'xyz' FOR 2) + error_msg: "sql parser error: \nLINE 1: SELECT OVERLAY('abc' PLACING 'xyz' FOR 2)\n ^" +- input: SELECT OVERLAY('abc' PLACING 'xyz' FOR 2 FROM 1) + error_msg: "sql parser error: \nLINE 1: SELECT OVERLAY('abc' PLACING 'xyz' FOR 2 FROM 1)\n ^"