Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -7505,7 +7505,7 @@ SqlCall MatchRecognizeCallWithModifier() :
{
final Span s;
final SqlOperator runningOp;
final SqlNode func;
final SqlNode e;
}
{
(
Expand All @@ -7514,8 +7514,8 @@ SqlCall MatchRecognizeCallWithModifier() :
<FINAL> { runningOp = SqlStdOperatorTable.FINAL; }
)
{ s = span(); }
func = NamedFunctionCall() {
return runningOp.createCall(s.end(func), func);
e = Expression3(ExprContext.ACCEPT_NON_QUERY) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a spec of the grammar for this construct somewhere?
Ideally we would follow that.
I don't know enough about this construct to make a choice myself.

Copy link
Copy Markdown
Contributor Author

@snuyanzin snuyanzin Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get if your question is about Expression3 then https://github.com/snuyanzin/calcite/blob/dd71d5b3aa0bd004f4d5369986e50301b628b0ad/core/src/main/codegen/templates/Parser.jj#L4045-L4049

if your question is about MATCH_RECOGNIZE then I have only links to other vendors
like Snowflake https://docs.snowflake.com/en/sql-reference/constructs/match_recognize#syntax

    [ MEASURES <expr> [AS] <alias> [, ... ] ]

and below they have explanation for FINAL, RUNNING (Snowlake doesn't allow FINAL, RUNNING for non function, however Calcite always sets either FINAL or RUNNING and unparses it like that)

expr ::= ... [ { RUNNING | FINAL } ] windowFunction ...

BigQuery seems doesn't have FINAL, RUNNING at all https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#match_recognize_clause

Oracle doesn't use these keyword and determines FINAL or RUNNING implicitly https://docs.oracle.com/cd/E29542_01/apirefs.1111/e12048/pattern_recog.htm#BEJBFEGJ

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so there isn't a universally agreed standard for MATCH RECOGNIZE.
It's fine for the parser to be more permissive and the validator to reject some things later (e.g., if you want to emulate restrictions of a specific dialect).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is Expression3 too permissive?
Can you make up some expressions that would be accepted which are not legal?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise this approach is nice, because it is minimally invasive.

Copy link
Copy Markdown
Contributor Author

@snuyanzin snuyanzin Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spotted another case where this PR will help #4907
I tried other case where expression under FIRST, LAST, NEXT , PREV might be expanded however parser after that not able to parse it.
After changing to expression parser started to parse such unparsed queries, example in linked PR and related jira

return runningOp.createCall(s.end(e), e);
}
}

Expand Down
47 changes: 47 additions & 0 deletions core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2655,6 +2655,53 @@ void testLikeAndSimilarFails() {
.rewritesTo(expected8);
sql(expected8)
.withParserConfig(c -> c.withQuoting(Quoting.BACK_TICK)).ok();

// Test cases for [CALCITE-7465] https://issues.apache.org/jira/browse/CALCITE-7465
// Unparse of MATCH_RECOGNIZE MEASURES might produce unparsable sql
// Accepted by Snowflake (it doesn't accept FINAL or RUNNING before non function measure)
final String sql9 = "SELECT *\n"
+ "FROM emp\n"
+ "MATCH_RECOGNIZE (\n"
+ " MEASURES\n"
+ " A.deptno AS deptno\n"
+ " PATTERN (A B)\n"
+ " DEFINE\n"
+ " A AS A.empno = 123\n"
+ ")";
final String expected9 = "SELECT `EXPR$0`.`DEPTNO`\n"
+ "FROM `CATALOG`.`SALES`.`EMP` AS `EMP` MATCH_RECOGNIZE(\n"
+ "MEASURES FINAL `A`.`DEPTNO` AS `DEPTNO`\n"
+ "PATTERN (`A` `B`)\n"
+ "DEFINE `A` AS PREV(`A`.`EMPNO`, 0) = 123) AS `EXPR$0`";

sql(sql9)
.withValidatorConfig(c -> c.withIdentifierExpansion(true))
.rewritesTo(expected9);
sql(expected9)
.withParserConfig(c -> c.withQuoting(Quoting.BACK_TICK)).ok();

final String sql10 = "SELECT deptno\n"
+ "FROM emp\n"
+ "MATCH_RECOGNIZE (\n"
+ " MEASURES\n"
+ " A.deptno AS deptno\n"
+ "ALL ROWS PER MATCH\n"
+ " PATTERN (A B)\n"
+ " DEFINE\n"
+ " A AS A.empno = 123\n"
+ ")";
final String expected10 = "SELECT `EXPR$0`.`DEPTNO`\n"
+ "FROM `CATALOG`.`SALES`.`EMP` AS `EMP` MATCH_RECOGNIZE(\n"
+ "MEASURES RUNNING `A`.`DEPTNO` AS `DEPTNO`\n"
+ "ALL ROWS PER MATCH\n"
+ "PATTERN (`A` `B`)\n"
+ "DEFINE `A` AS PREV(`A`.`EMPNO`, 0) = 123) AS `EXPR$0`";

sql(sql10)
.withValidatorConfig(c -> c.withIdentifierExpansion(true))
.rewritesTo(expected10);
sql(expected10)
.withParserConfig(c -> c.withQuoting(Quoting.BACK_TICK)).ok();
}

@Test void testIntervalTimeUnitEnumeration() {
Expand Down
Loading