From c16fc0cc00b2cc2df0ce05116d9a4db44875d161 Mon Sep 17 00:00:00 2001 From: Radhakrishnan Pachyappan Date: Tue, 30 Jun 2026 21:20:44 +0530 Subject: [PATCH 1/3] Improve error message for unsupported window functions in eventstats/streamstats Window functions outside WINDOW_FUNC_MAPPING (e.g. rank, dense_rank, nth_value) throw a generic "Unexpected window function: X" from CalciteRexNodeVisitor#visitWindowFunction. These functions require ORDER BY semantics that eventstats/streamstats don't have (they only support partition-by), so they are intentionally unsupported, not a bug -- but the error message gave users no indication of what to use instead. Replace the message with one that names the function and lists the functions eventstats/streamstats do support, so users get actionable guidance instead of a bare "unexpected" error. Fixes #5168 Signed-off-by: Radhakrishnan Pachyappan --- .../sql/api/UnifiedQueryPlannerTest.java | 2 +- .../sql/calcite/CalciteRexNodeVisitor.java | 9 ++++++++- .../calcite/remote/CalcitePPLEventstatsIT.java | 17 ++++++++++++++++- .../remote/CalciteStreamstatsCommandIT.java | 18 +++++++++++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java index 008121e8377..7c1c68b1fff 100644 --- a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java +++ b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java @@ -168,7 +168,7 @@ public void unsupportedWindowFunctionIsRethrownAsSemanticCheckException() { givenInvalidQuery("source = catalog.employees | eventstats rank()") .assertErrorType(SemanticCheckException.class) .assertCauseType(CalciteUnsupportedException.class) - .assertErrorMessageContains("Unexpected window function: rank"); + .assertErrorMessageContains("Window function 'rank' is not supported in eventstats/streamstats"); } @Test diff --git a/core/src/main/java/org/opensearch/sql/calcite/CalciteRexNodeVisitor.java b/core/src/main/java/org/opensearch/sql/calcite/CalciteRexNodeVisitor.java index 849b615f970..929bc977d23 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/CalciteRexNodeVisitor.java +++ b/core/src/main/java/org/opensearch/sql/calcite/CalciteRexNodeVisitor.java @@ -705,7 +705,14 @@ public RexNode visitWindowFunction(WindowFunction node, CalcitePlanContext conte node.getWindowFrame()); }) .orElseThrow( - () -> new CalciteUnsupportedException("Unexpected window function: " + funcName)); + () -> + new CalciteUnsupportedException( + "Window function '" + + funcName + + "' is not supported in eventstats/streamstats." + + " Supported functions: avg, count, dc, distinct_count, earliest," + + " latest, max, min, row_number, stddev_pop, stddev_samp, sum," + + " var_pop, var_samp.")); } private List translateOrderKeys( diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java index a2d1e4af086..36f870f4a4e 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java @@ -329,7 +329,22 @@ public void testUnsupportedWindowFunctions() { executeQuery( String.format( "source=%s | eventstats %s(age)", TEST_INDEX_STATE_COUNTRY, u))); - verifyErrorMessageContains(e, "Unexpected window function: " + u); + verifyErrorMessageContains(e, "is not supported in eventstats/streamstats"); + } + } + + @Test + public void testRankingWindowFunctionsUnsupportedInEventstats() { + for (String func : List.of("rank", "dense_rank")) { + Throwable e = + assertThrowsWithReplace( + UnsupportedOperationException.class, + () -> + executeQuery( + String.format( + "source=%s | eventstats %s() by state", TEST_INDEX_STATE_COUNTRY, func))); + verifyErrorMessageContains( + e, "Window function '" + func + "' is not supported in eventstats/streamstats"); } } diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java index e70812e3c3b..26407b08dfc 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java @@ -846,7 +846,23 @@ public void testUnsupportedWindowFunctions() { executeQuery( String.format( "source=%s | streamstats %s(age)", TEST_INDEX_STATE_COUNTRY, u))); - verifyErrorMessageContains(e, "Unexpected window function: " + u); + verifyErrorMessageContains(e, "is not supported in eventstats/streamstats"); + } + } + + @Test + public void testRankingWindowFunctionsUnsupportedInStreamstats() { + for (String func : List.of("rank", "dense_rank")) { + Throwable e = + assertThrowsWithReplace( + UnsupportedOperationException.class, + () -> + executeQuery( + String.format( + "source=%s | streamstats %s() by state", + TEST_INDEX_STATE_COUNTRY, func))); + verifyErrorMessageContains( + e, "Window function '" + func + "' is not supported in eventstats/streamstats"); } } From 99c53f5523f0752eb69e5ec3f8a63a1139344e95 Mon Sep 17 00:00:00 2001 From: Radhakrishnan Pachyappan Date: Sun, 5 Jul 2026 16:21:14 +0530 Subject: [PATCH 2/3] spotless: fix formatting in UnifiedQueryPlannerTest Signed-off-by: Radhakrishnan Pachyappan --- .../java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java index 7c1c68b1fff..426493a02ca 100644 --- a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java +++ b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java @@ -168,7 +168,8 @@ public void unsupportedWindowFunctionIsRethrownAsSemanticCheckException() { givenInvalidQuery("source = catalog.employees | eventstats rank()") .assertErrorType(SemanticCheckException.class) .assertCauseType(CalciteUnsupportedException.class) - .assertErrorMessageContains("Window function 'rank' is not supported in eventstats/streamstats"); + .assertErrorMessageContains( + "Window function 'rank' is not supported in eventstats/streamstats"); } @Test From da13116812bfdfa65bbd9929a48ad68c3458afc1 Mon Sep 17 00:00:00 2001 From: Radhakrishnan P Date: Sat, 29 Aug 2026 00:33:48 +0530 Subject: [PATCH 3/3] Reject rank/dense_rank in PPL eventstats/streamstats at parse time WINDOW_FUNC_MAPPING (used by eventstats/streamstats) never supported rank/dense_rank, but PPL's scalarWindowFunctionName grammar rule still accepted the tokens, so `eventstats rank()` reached CalciteRexNodeVisitor#visitWindowFunction and failed there with the "not supported" message this PR improves. Meanwhile SQL's grammar already accepts RANK()/DENSE_RANK() OVER (...), and #5720 is adding real support for them on the SQL side via the same shared visitor. Remove RANK/DENSE_RANK from scalarWindowFunctionName so PPL rejects them at parse time instead of falling through to the shared SQL/PPL visitor - this keeps the language separation at the parser rather than relying on a WINDOW_FUNC_MAPPING check in shared planner code, and avoids PPL silently gaining rank/dense_rank as a side effect of #5720 registering them for SQL. Updates the eventstats/streamstats tests added earlier in this PR to expect a SyntaxCheckException (parse-time) instead of the semantic "not supported" error, and switches the unrelated visitWindowFunction-rejection unit test from rank() to percent_rank(), which remains unsupported and still exercises that code path. Signed-off-by: Radhakrishnan P --- .../org/opensearch/sql/api/UnifiedQueryPlannerTest.java | 4 ++-- .../sql/calcite/remote/CalcitePPLEventstatsIT.java | 8 +++++--- .../sql/calcite/remote/CalciteStreamstatsCommandIT.java | 8 +++++--- ppl/src/main/antlr/OpenSearchPPLParser.g4 | 2 -- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java index 426493a02ca..4a87bbbbb2f 100644 --- a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java +++ b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java @@ -165,11 +165,11 @@ public void unsupportedWindowFunctionIsRethrownAsSemanticCheckException() { // CalciteRexNodeVisitor#visitWindowFunction's // orElseThrow. The throw site emits CalciteUnsupportedException so this path normalizes to a // 4xx SemanticCheckException rather than escaping as a 500. - givenInvalidQuery("source = catalog.employees | eventstats rank()") + givenInvalidQuery("source = catalog.employees | eventstats percent_rank()") .assertErrorType(SemanticCheckException.class) .assertCauseType(CalciteUnsupportedException.class) .assertErrorMessageContains( - "Window function 'rank' is not supported in eventstats/streamstats"); + "Window function 'percent_rank' is not supported in eventstats/streamstats"); } @Test diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java index 36f870f4a4e..9c0bd5422c5 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEventstatsIT.java @@ -12,6 +12,7 @@ import java.util.List; import org.json.JSONObject; import org.junit.jupiter.api.Test; +import org.opensearch.sql.common.antlr.SyntaxCheckException; import org.opensearch.sql.ppl.PPLIntegTestCase; public class CalcitePPLEventstatsIT extends PPLIntegTestCase { @@ -335,16 +336,17 @@ public void testUnsupportedWindowFunctions() { @Test public void testRankingWindowFunctionsUnsupportedInEventstats() { + // rank/dense_rank aren't in eventstats/streamstats' windowFunctionName grammar rule, so this + // is now a parse-time rejection rather than a semantic one. for (String func : List.of("rank", "dense_rank")) { Throwable e = assertThrowsWithReplace( - UnsupportedOperationException.class, + SyntaxCheckException.class, () -> executeQuery( String.format( "source=%s | eventstats %s() by state", TEST_INDEX_STATE_COUNTRY, func))); - verifyErrorMessageContains( - e, "Window function '" + func + "' is not supported in eventstats/streamstats"); + verifyErrorMessageContains(e, "is not a valid term at this part of the query"); } } diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java index 26407b08dfc..81c653b0742 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteStreamstatsCommandIT.java @@ -16,6 +16,7 @@ import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.opensearch.client.Request; +import org.opensearch.sql.common.antlr.SyntaxCheckException; import org.opensearch.sql.ppl.PPLIntegTestCase; import org.opensearch.sql.util.RequiresCapability; @@ -852,17 +853,18 @@ public void testUnsupportedWindowFunctions() { @Test public void testRankingWindowFunctionsUnsupportedInStreamstats() { + // rank/dense_rank aren't in eventstats/streamstats' windowFunctionName grammar rule, so this + // is now a parse-time rejection rather than a semantic one. for (String func : List.of("rank", "dense_rank")) { Throwable e = assertThrowsWithReplace( - UnsupportedOperationException.class, + SyntaxCheckException.class, () -> executeQuery( String.format( "source=%s | streamstats %s() by state", TEST_INDEX_STATE_COUNTRY, func))); - verifyErrorMessageContains( - e, "Window function '" + func + "' is not supported in eventstats/streamstats"); + verifyErrorMessageContains(e, "is not a valid term at this part of the query"); } } diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index 98f85b08282..9d81688e6a5 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -832,8 +832,6 @@ windowFunctionName scalarWindowFunctionName : ROW_NUMBER - | RANK - | DENSE_RANK | PERCENT_RANK | CUME_DIST | FIRST