66package org .opensearch .sql .calcite .validate ;
77
88import java .nio .charset .Charset ;
9+ import java .util .List ;
910import lombok .experimental .UtilityClass ;
1011import org .apache .calcite .rel .type .RelDataType ;
1112import org .apache .calcite .rel .type .RelDataTypeFactory ;
@@ -86,9 +87,11 @@ public static RelDataType createUDTWithAttributes(
8687 /**
8788 * Special handling for nested window functions that fail validation due to a Calcite bug.
8889 *
89- * <p>This method provides a workaround for a known issue in Calcite v1.41 where nested window
90- * functions within CASE expressions fail validation incorrectly. Only {@code
91- * CalcitePPLEventstatsIT#testMultipleEventstatsWithNullBucket} should be caught by this check.
90+ * <p>This method provides a workaround for 2 issues in Calcite v1.41
91+ *
92+ * <p>1. where nested window functions within CASE expressions fail validation incorrectly. Only
93+ * {@code CalcitePPLEventstatsIT#testMultipleEventstatsWithNullBucket} should be caught by this
94+ * case.
9295 *
9396 * <p><b>Calcite Bug (v1.41):</b> The {@code SqlImplementor.Result#containsOver()} method at
9497 * SqlImplementor.java:L2145 only checks {@code SqlBasicCall} nodes for window functions, missing
@@ -109,14 +112,54 @@ public static RelDataType createUDTWithAttributes(
109112 * SUM(CASE WHEN ... THEN (SUM(age) OVER (...)) END) OVER (...)
110113 * </pre>
111114 *
112- * <p><b>TODO:</b> Remove this workaround when upgrading to a Calcite version that fixes the bug.
115+ * 2. Projections containing OVER as function operands are not moved down to subqueries. This
116+ * should catch test case {@code CalciteExplainIT.noPushDownForAggOnWindow}
117+ *
118+ * <p>The check {@code needNewSubquery} at {@link
119+ * org.apache.calcite.rel.rel2sql.SqlImplementor}#L1930 should return true for the following plan
120+ * as it contains window functions nested inside a function call, which should be in an inner
121+ * query if further aggregation is performed on top of it.
122+ *
123+ * <pre>
124+ * LogicalProject(age_str=[WIDTH_BUCKET(SAFE_CAST($8), 3, -(MAX(SAFE_CAST($8)) OVER (), MIN(SAFE_CAST($8)) OVER ()), MAX(SAFE_CAST($8)) OVER ())])
125+ * CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_account]])
126+ * </pre>
127+ *
128+ * As a result, it creates a SQL where window functions are in group-by:
129+ *
130+ * <pre>
131+ * GROUP BY WIDTH_BUCKET(...MAX(...) OVER ()...)
132+ * </pre>
133+ *
134+ * Ideally, it should have created a SQL like the following for test case noPushDownForAggOnWindow
135+ *
136+ * <pre>
137+ * SELECT COUNT(*) AS `count()`, `age_str`
138+ * FROM (
139+ * SELECT WIDTH_BUCKET(
140+ * SAFE_CAST(`age` AS STRING),
141+ * 3,
142+ * (MAX(SAFE_CAST(`age` AS STRING)) OVER (...)) - (MIN(SAFE_CAST(`age` AS STRING)) OVER (...)),
143+ * MAX(SAFE_CAST(`age` AS STRING)) OVER (...)
144+ * ) AS `age_str`
145+ * FROM `OpenSearch`.`opensearch-sql_test_index_account`
146+ * ) subquery
147+ * GROUP BY `age_str`
148+ * ORDER BY 2
149+ * </pre>
150+ *
151+ * <p><b>TODO:</b> Remove this workaround when upgrading to a Calcite version that fixes the bugs.
113152 *
114153 * @param e the exception to check
115154 * @return {@code true} if the exception should be tolerated as a known Calcite bug, {@code false}
116155 * otherwise
117156 */
118157 public static boolean tolerantValidationException (Exception e ) {
158+ List <String > acceptableErrorMessages =
159+ List .of (
160+ "Aggregate expressions cannot be nested" ,
161+ "Windowed aggregate expression is illegal in GROUP BY clause" );
119162 return e .getMessage () != null
120- && e .getMessage (). contains ( "Aggregate expressions cannot be nested" );
163+ && acceptableErrorMessages . stream (). anyMatch ( e .getMessage ():: contains );
121164 }
122165}
0 commit comments