From 915c769d8b3211c210b886c2562925364b353fe3 Mon Sep 17 00:00:00 2001 From: Sergey Nuyanzin Date: Tue, 21 Apr 2026 00:21:17 +0200 Subject: [PATCH 1/2] [CALCITE-7465] Make `MATCH_RECOGNIZE` tolerant to `FINAL` and `RUNNING` non function `MEASURES` --- core/src/main/codegen/templates/Parser.jj | 6 +-- .../apache/calcite/test/SqlValidatorTest.java | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj index cd1fbdc38560..f549446ea082 100644 --- a/core/src/main/codegen/templates/Parser.jj +++ b/core/src/main/codegen/templates/Parser.jj @@ -7505,7 +7505,7 @@ SqlCall MatchRecognizeCallWithModifier() : { final Span s; final SqlOperator runningOp; - final SqlNode func; + final SqlNode e; } { ( @@ -7514,8 +7514,8 @@ SqlCall MatchRecognizeCallWithModifier() : { runningOp = SqlStdOperatorTable.FINAL; } ) { s = span(); } - func = NamedFunctionCall() { - return runningOp.createCall(s.end(func), func); + e = Expression3(ExprContext.ACCEPT_NON_QUERY) { + return runningOp.createCall(s.end(e), e); } } diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java index 361f12eb34a5..bb79a517ce99 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -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() { From bda5286d5a2d5dbf505b062b48f537b511bc7b3f Mon Sep 17 00:00:00 2001 From: Sergey Nuyanzin Date: Thu, 23 Apr 2026 16:11:49 +0200 Subject: [PATCH 2/2] [CALCITE-7486] Operators in `MATCH_RECOGNIZE` don't support `SqlLiterals` --- .../sql/validate/SqlValidatorImpl.java | 21 ++++++++++++---- .../apache/calcite/test/SqlValidatorTest.java | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index 6195ffd90448..320ba721533b 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -8225,17 +8225,21 @@ private class PatternValidator extends SqlBasicVisitor<@Nullable Set> { int firstLastCount; int prevNextCount; int aggregateCount; + int argIndex; + int argCount; PatternValidator(boolean isMeasure) { - this(isMeasure, 0, 0, 0); + this(isMeasure, 0, 0, 0, 0, 0); } PatternValidator(boolean isMeasure, int firstLastCount, int prevNextCount, - int aggregateCount) { + int aggregateCount, int index, int argCount) { this.isMeasure = isMeasure; this.firstLastCount = firstLastCount; this.prevNextCount = prevNextCount; this.aggregateCount = aggregateCount; + this.argIndex = index; + this.argCount = argCount; } @Override public Set visit(SqlCall call) { @@ -8281,13 +8285,14 @@ private class PatternValidator extends SqlBasicVisitor<@Nullable Set> { Static.RESOURCE.patternRunningFunctionInDefine(call.toString())); } - for (SqlNode node : operands) { + for (int i = 0; i < operands.size(); i++) { + SqlNode node = operands.get(i); if (node != null) { vars.addAll( requireNonNull( node.accept( new PatternValidator(isMeasure, firstLastCount, prevNextCount, - aggregateCount)), + aggregateCount, i, operands.size())), () -> "node.accept(PatternValidator) for node " + node)); } } @@ -8329,7 +8334,13 @@ private class PatternValidator extends SqlBasicVisitor<@Nullable Set> { } @Override public Set visit(SqlLiteral literal) { - return ImmutableSet.of(); + if ((this.argCount == 1 || this.argIndex < this.argCount - 1) + && (this.firstLastCount > 0 || this.prevNextCount > 0) + && !SqlUtil.isNull(literal)) { + return ImmutableSet.of(requireNonNull(literal.toValue())); + } else { + return ImmutableSet.of(); + } } @Override public Set visit(SqlIntervalQualifier qualifier) { diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java index bb79a517ce99..3d7eda0edf55 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -2702,6 +2702,31 @@ void testLikeAndSimilarFails() { .rewritesTo(expected10); sql(expected10) .withParserConfig(c -> c.withQuoting(Quoting.BACK_TICK)).ok(); + + // Test cases for [CALCITE-7486] https://issues.apache.org/jira/browse/CALCITE-7486 + // Operators in MATCH_RECOGNIZE don't support SqlLiterals + // Accepted by Snowflake + final String sql11 = "SELECT *\n" + + "FROM emp\n" + + "MATCH_RECOGNIZE (\n" + + " MEASURES\n" + + " FIRST(DOWN.empno + DOWN.deptno + 1) AS bottom_total" + + " PATTERN (DOWN{2,})\n" + + " DEFINE\n" + + " DOWN AS PREV(EMP.EMPNO + 2, 0) < 1" + + ")"; + + final String expected11 = "SELECT `EXPR$0`.`BOTTOM_TOTAL`\n" + + "FROM `CATALOG`.`SALES`.`EMP` AS `EMP` MATCH_RECOGNIZE(\n" + + "MEASURES FINAL (FIRST(`DOWN`.`EMPNO` + `DOWN`.`DEPTNO`, 0) + FIRST(1, 0)) AS `BOTTOM_TOTAL`\n" + + "PATTERN (`DOWN` { 2, })\n" + + "DEFINE `DOWN` AS LAST(`EMP`.`EMPNO`, 0) + PREV(2, 0) < 1) AS `EXPR$0`"; + + sql(sql11) + .withValidatorConfig(c -> c.withIdentifierExpansion(true)) + .rewritesTo(expected11); + sql(expected11) + .withParserConfig(c -> c.withQuoting(Quoting.BACK_TICK)).ok(); } @Test void testIntervalTimeUnitEnumeration() {