diff --git a/src/ast/query.rs b/src/ast/query.rs index 296e4e8ca..ecd95e527 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2194,6 +2194,8 @@ pub enum RepetitionQuantifier { AtMost(u32), /// `{n,m} Range(u32, u32), + /// A reluctant (non-greedy) quantifier, for example `*?` or `{n,m}?`. + Reluctant(Box), } impl fmt::Display for RepetitionQuantifier { @@ -2207,6 +2209,7 @@ impl fmt::Display for RepetitionQuantifier { AtLeast(n) => write!(f, "{{{n},}}"), AtMost(n) => write!(f, "{{,{n}}}"), Range(n, m) => write!(f, "{{{n},{m}}}"), + Reluctant(quantifier) => write!(f, "{quantifier}?"), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c23b27053..cfa0a6807 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -17322,58 +17322,61 @@ impl<'a> Parser<'a> { } fn parse_repetition_pattern(&mut self) -> Result { - let mut pattern = self.parse_base_pattern()?; - loop { - let token = self.next_token(); - let quantifier = match token.token { - Token::Mul => RepetitionQuantifier::ZeroOrMore, - Token::Plus => RepetitionQuantifier::OneOrMore, - Token::Placeholder(s) if s == "?" => RepetitionQuantifier::AtMostOne, - Token::LBrace => { - // quantifier is a range like {n} or {n,} or {,m} or {n,m} - let token = self.next_token(); - match token.token { - Token::Comma => { - let next_token = self.next_token(); - let Token::Number(n, _) = next_token.token else { - return self.expected("literal number", next_token); - }; - self.expect_token(&Token::RBrace)?; - RepetitionQuantifier::AtMost(Self::parse(n, token.span.start)?) - } - Token::Number(n, _) if self.consume_token(&Token::Comma) => { - let next_token = self.next_token(); - match next_token.token { - Token::Number(m, _) => { - self.expect_token(&Token::RBrace)?; - RepetitionQuantifier::Range( - Self::parse(n, token.span.start)?, - Self::parse(m, token.span.start)?, - ) - } - Token::RBrace => { - RepetitionQuantifier::AtLeast(Self::parse(n, token.span.start)?) - } - _ => { - return self.expected("} or upper bound", next_token); - } + let pattern = self.parse_base_pattern()?; + let token = self.next_token(); + let quantifier = match token.token { + Token::Mul => RepetitionQuantifier::ZeroOrMore, + Token::Plus => RepetitionQuantifier::OneOrMore, + Token::Placeholder(s) if s == "?" => RepetitionQuantifier::AtMostOne, + Token::LBrace => { + // quantifier is a range like {n} or {n,} or {,m} or {n,m} + let token = self.next_token(); + match token.token { + Token::Comma => { + let next_token = self.next_token(); + let Token::Number(n, _) = next_token.token else { + return self.expected("literal number", next_token); + }; + self.expect_token(&Token::RBrace)?; + RepetitionQuantifier::AtMost(Self::parse(n, token.span.start)?) + } + Token::Number(n, _) if self.consume_token(&Token::Comma) => { + let next_token = self.next_token(); + match next_token.token { + Token::Number(m, _) => { + self.expect_token(&Token::RBrace)?; + RepetitionQuantifier::Range( + Self::parse(n, token.span.start)?, + Self::parse(m, token.span.start)?, + ) } + Token::RBrace => { + RepetitionQuantifier::AtLeast(Self::parse(n, token.span.start)?) + } + _ => return self.expected("} or upper bound", next_token), } - Token::Number(n, _) => { - self.expect_token(&Token::RBrace)?; - RepetitionQuantifier::Exactly(Self::parse(n, token.span.start)?) - } - _ => return self.expected("quantifier range", token), } + Token::Number(n, _) => { + self.expect_token(&Token::RBrace)?; + RepetitionQuantifier::Exactly(Self::parse(n, token.span.start)?) + } + _ => return self.expected("quantifier range", token), } - _ => { - self.prev_token(); - break; - } - }; - pattern = MatchRecognizePattern::Repetition(Box::new(pattern), quantifier); - } - Ok(pattern) + } + _ => { + self.prev_token(); + return Ok(pattern); + } + }; + let quantifier = if self.consume_token(&Token::Placeholder("?".into())) { + RepetitionQuantifier::Reluctant(Box::new(quantifier)) + } else { + quantifier + }; + Ok(MatchRecognizePattern::Repetition( + Box::new(pattern), + quantifier, + )) } fn parse_concat_pattern(&mut self) -> Result { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 29b060a82..54604e105 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -13948,18 +13948,45 @@ fn test_match_recognize_patterns() { ]), ); - // double repetition + // reluctant repetition check( "S2*?", Repetition( - Box::new(Repetition( - Box::new(Symbol(Named(Ident::new("S2")))), - ZeroOrMore, - )), - AtMostOne, + Box::new(Symbol(Named(Ident::new("S2")))), + Reluctant(Box::new(ZeroOrMore)), ), ); + check( + "S1+? S2?? S3{2,4}?", + Concat(vec![ + Repetition( + Box::new(Symbol(Named(Ident::new("S1")))), + Reluctant(Box::new(OneOrMore)), + ), + Repetition( + Box::new(Symbol(Named(Ident::new("S2")))), + Reluctant(Box::new(AtMostOne)), + ), + Repetition( + Box::new(Symbol(Named(Ident::new("S3")))), + Reluctant(Box::new(Range(2, 4))), + ), + ]), + ); + + for pattern in ["S1**", "S1+++", "S1???", "S1{2,4}+"] { + let sql = format!( + "SELECT * FROM my_table MATCH_RECOGNIZE(PATTERN ({pattern}) DEFINE DUMMY AS 1 = 1)" + ); + assert!( + all_dialects_where(|d| d.supports_match_recognize()) + .parse_sql_statements(&sql) + .is_err(), + "stacked quantifier should fail: {pattern}" + ); + } + // range quantifiers in an alternation check( "S1{1} | S2{2,3} | S3{4,} | S4{,5}", @@ -14000,11 +14027,8 @@ fn test_match_recognize_patterns() { Symbol(Start), Symbol(Named(Ident::new("S1"))), Repetition( - Box::new(Repetition( - Box::new(Symbol(Named(Ident::new("S2")))), - ZeroOrMore, - )), - AtMostOne, + Box::new(Symbol(Named(Ident::new("S2")))), + Reluctant(Box::new(ZeroOrMore)), ), Repetition( Box::new(Group(Box::new(Concat(vec![