You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/09/24 19:41:56 UTC
(datafusion-sqlparser-rs) 01/01: Snowflake: preserve reluctant MATCH_RECOGNIZE quantifiers (#2510)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-2510-f45d254ea8dcc1ad538ab7cf1504c0deea1c8cba
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 0fd4fa892c91e2f5ee6ffc0bcf614219a921b5e3
Author: Artem Osipov <59...@users.noreply.github.com>
AuthorDate: Thu Sep 24 19:41:30 2026 +0000
Snowflake: preserve reluctant MATCH_RECOGNIZE quantifiers (#2510)
---
src/ast/query.rs | 3 ++
src/parser/mod.rs | 99 ++++++++++++++++++++++++-----------------------
tests/sqlparser_common.rs | 46 ++++++++++++++++------
3 files changed, 89 insertions(+), 59 deletions(-)
diff --git a/src/ast/query.rs b/src/ast/query.rs
index 98a9c84ae..95f980181 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -2193,6 +2193,8 @@ pub enum RepetitionQuantifier {
AtMost(u32),
/// `{n,m}
Range(u32, u32),
+ /// A reluctant (non-greedy) quantifier, for example `*?` or `{n,m}?`.
+ Reluctant(Box<RepetitionQuantifier>),
}
impl fmt::Display for RepetitionQuantifier {
@@ -2206,6 +2208,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 bc64f63b7..994073e77 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -17327,58 +17327,61 @@ impl<'a> Parser<'a> {
}
fn parse_repetition_pattern(&mut self) -> Result<MatchRecognizePattern, ParserError> {
- 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<MatchRecognizePattern, ParserError> {
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 78473dbdd..63639b916 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -13959,18 +13959,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}",
@@ -14011,11 +14038,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![
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org