Skip to content

Commit 6a5c598

Browse files
committed
Stabilize PPL ITs on the analytics-engine route (array/map-path/datatype/basic)
Analytics-engine route parity for four PPL IT classes; test-only. Uses the @RequiresCapability annotation + Capability registry (opensearch-project#5560) plus matching excludeTestsMatching entries. CalciteArrayFunctionIT: - Skip the array-index load on the AE route (multi-value 'numbers' field the parquet store rejects); no test queries it (all build arrays inline). - 16 higher-order lambda functions (transform/mvmap, reduce, filter, exists, forall) -> new ARRAY_HIGHER_ORDER_FUNC (no DataFusion lambda execution). CalcitePPLMapPathIT: - mvcombine lowers to ARRAY_AGG, unregistered on the analytics backend -> new MVCOMBINE_ARRAY_AGG. - addtotals crashes the DataFusion backend with a join panic -> new ADDTOTALS_JOIN_PANIC. CalciteDataTypeIT (guards on base DataTypeIT; build.gradle globs broadened to '*' so they cover the Calcite subclass): - test_nonnumeric_data_types / test_alias_data_type: nested/object/geo/alias types stripped (NESTED_FIELDS). - test_numeric_data_types: scaled_float reported as bigint not double -> new SCALED_FLOAT_TYPE. - testNumericFieldFromString: empty-string -> numeric coerces to null not 0 -> new STRING_TO_NUMERIC_COERCION. - testBooleanFieldFromNumberAcrossWildcardIndices: cross-index incompatible field types rejected -> new CROSS_INDEX_INCOMPATIBLE_TYPES. - testBooleanFieldFromString: seeds+deletes a doc; DELETE unsupported (DOC_MUTATION). CalcitePPLBasicIT.testRegexpFilter: REGEXP filter throws a backend NullPointerException on the AE route -> new REGEXP_FILTER. v2/Calcite route unchanged (all run, 0 skips). Signed-off-by: Kai Huang <ahkcs@amazon.com>
1 parent cb80047 commit 6a5c598

6 files changed

Lines changed: 143 additions & 5 deletions

File tree

integ-test/build.gradle

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,10 +1075,17 @@ task integTestRemote(type: RestIntegTestTask) {
10751075
// GeoPointFormatsIT: EVERY test reads a geo_point field — whole class doomed.
10761076
excludeTestsMatching 'org.opensearch.sql.ppl.GeoPointFormatsIT'
10771077
// datatypes_index_mapping: nested_value=nested, geo_point_value=geo_point.
1078-
excludeTestsMatching 'org.opensearch.sql.ppl.DataTypeIT.test_nonnumeric_data_types'
1079-
excludeTestsMatching 'org.opensearch.sql.ppl.SystemFunctionIT.typeof_opensearch_types'
1078+
// Glob matches the Calcite subclasses too (CalciteDataTypeIT, CalciteSystemFunctionIT).
1079+
excludeTestsMatching '*DataTypeIT.test_nonnumeric_data_types'
1080+
excludeTestsMatching '*SystemFunctionIT.typeof_opensearch_types'
10801081
// alias_index_mapping: alias_col is type=alias; query is `where alias_col > 1`.
1081-
excludeTestsMatching 'org.opensearch.sql.ppl.DataTypeIT.test_alias_data_type'
1082+
excludeTestsMatching '*DataTypeIT.test_alias_data_type'
1083+
// scaled_float reports bigint (not double); string->numeric coerces to null (not 0);
1084+
// cross-index incompatible field types are rejected; boolean-from-string test DELETEs.
1085+
excludeTestsMatching '*DataTypeIT.test_numeric_data_types'
1086+
excludeTestsMatching '*DataTypeIT.testNumericFieldFromString'
1087+
excludeTestsMatching '*DataTypeIT.testBooleanFieldFromNumberAcrossWildcardIndices'
1088+
excludeTestsMatching '*DataTypeIT.testBooleanFieldFromString'
10821089
// CalciteAliasFieldAggregationIT: raw-PUT alias index can't be created on the AE route
10831090
// and every test queries alias fields directly — whole class doomed.
10841091
excludeTestsMatching 'org.opensearch.sql.calcite.remote.CalciteAliasFieldAggregationIT'
@@ -1176,6 +1183,35 @@ task integTestRemote(type: RestIntegTestTask) {
11761183
// - max() over int operands reports bigint on the AE route (DataFusion widens integers
11771184
// to Int64) where the v2/Calcite path reports int.
11781185
excludeTestsMatching '*CalcitePPLEvalMaxMinFunctionIT.testEvalMaxNumeric'
1186+
1187+
// === Excludes: CalciteArrayFunctionIT route divergences ===
1188+
// Higher-order array functions (transform/mvmap, reduce, filter, exists, forall) take a
1189+
// PPL lambda the AE backends can't execute ('No backend supports scalar function [...]').
1190+
excludeTestsMatching '*CalciteArrayFunctionIT.testForAll'
1191+
excludeTestsMatching '*CalciteArrayFunctionIT.testExists'
1192+
excludeTestsMatching '*CalciteArrayFunctionIT.testFilter'
1193+
excludeTestsMatching '*CalciteArrayFunctionIT.testTransform'
1194+
excludeTestsMatching '*CalciteArrayFunctionIT.testTransformForTwoInput'
1195+
excludeTestsMatching '*CalciteArrayFunctionIT.testTransformForWithDouble'
1196+
excludeTestsMatching '*CalciteArrayFunctionIT.testTransformForWithUDF'
1197+
excludeTestsMatching '*CalciteArrayFunctionIT.testReduce'
1198+
excludeTestsMatching '*CalciteArrayFunctionIT.testReduce2'
1199+
excludeTestsMatching '*CalciteArrayFunctionIT.testReduce3'
1200+
excludeTestsMatching '*CalciteArrayFunctionIT.testReduceWithUDF'
1201+
excludeTestsMatching '*CalciteArrayFunctionIT.testMvmap'
1202+
excludeTestsMatching '*CalciteArrayFunctionIT.testMvmapWithAddition'
1203+
excludeTestsMatching '*CalciteArrayFunctionIT.testMvmapWithEvalFieldReference'
1204+
excludeTestsMatching '*CalciteArrayFunctionIT.testMvmapWithNestedFunction'
1205+
excludeTestsMatching '*CalciteArrayFunctionIT.testMvmapWithOtherFieldReference'
1206+
1207+
// === Excludes: CalcitePPLMapPathIT route divergences ===
1208+
// mvcombine -> ARRAY_AGG (no such AggregateFunction enum); addtotals -> DataFusion join panic.
1209+
excludeTestsMatching '*CalcitePPLMapPathIT.testMvcombineOnMapPath'
1210+
excludeTestsMatching '*CalcitePPLMapPathIT.testAddtotalsOnMapPath'
1211+
1212+
// === Excludes: CalcitePPLBasicIT route divergence ===
1213+
// REGEXP filter throws a backend NullPointerException on the AE route.
1214+
excludeTestsMatching '*CalcitePPLBasicIT.testRegexpFilter'
11791215
}
11801216
}
11811217

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteArrayFunctionIT.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.opensearch.sql.calcite.remote;
77

88
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK;
9+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.ARRAY_HIGHER_ORDER_FUNC;
910
import static org.opensearch.sql.util.MatcherUtils.*;
1011

1112
import java.io.IOException;
@@ -22,7 +23,11 @@ public void init() throws Exception {
2223
super.init();
2324
enableCalcite();
2425
loadIndex(Index.BANK);
25-
loadIndex(Index.ARRAY);
26+
// No test queries the array index (all build arrays inline via array()); its multi-value
27+
// numbers field can't be bulk-loaded into the parquet store, so skip it on the AE route.
28+
if (!isAnalyticsParquetIndicesEnabled()) {
29+
loadIndex(Index.ARRAY);
30+
}
2631
}
2732

2833
@Test
@@ -86,6 +91,7 @@ public void testArrayLength() throws IOException {
8691

8792
@Test
8893
public void testForAll() throws IOException {
94+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
8995
JSONObject actual =
9096
executeQuery(
9197
String.format(
@@ -100,6 +106,7 @@ public void testForAll() throws IOException {
100106

101107
@Test
102108
public void testExists() throws IOException {
109+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
103110
JSONObject actual =
104111
executeQuery(
105112
String.format(
@@ -114,6 +121,7 @@ public void testExists() throws IOException {
114121

115122
@Test
116123
public void testFilter() throws IOException {
124+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
117125
JSONObject actual =
118126
executeQuery(
119127
String.format(
@@ -128,6 +136,7 @@ public void testFilter() throws IOException {
128136

129137
@Test
130138
public void testTransform() throws IOException {
139+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
131140
JSONObject actual =
132141
executeQuery(
133142
String.format(
@@ -142,6 +151,7 @@ public void testTransform() throws IOException {
142151

143152
@Test
144153
public void testTransformForTwoInput() throws IOException {
154+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
145155
JSONObject actual =
146156
executeQuery(
147157
String.format(
@@ -156,6 +166,7 @@ public void testTransformForTwoInput() throws IOException {
156166

157167
@Test
158168
public void testTransformForWithDouble() throws IOException {
169+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
159170
JSONObject actual =
160171
executeQuery(
161172
String.format(
@@ -170,6 +181,7 @@ public void testTransformForWithDouble() throws IOException {
170181

171182
@Test
172183
public void testTransformForWithUDF() throws IOException {
184+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
173185
JSONObject actual =
174186
executeQuery(
175187
String.format(
@@ -186,6 +198,7 @@ public void testTransformForWithUDF() throws IOException {
186198

187199
@Test
188200
public void testReduce() throws IOException {
201+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
189202
JSONObject actual =
190203
executeQuery(
191204
String.format(
@@ -203,6 +216,7 @@ public void testReduce() throws IOException {
203216

204217
@Test
205218
public void testReduce2() throws IOException {
219+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
206220
JSONObject actual =
207221
executeQuery(
208222
String.format(
@@ -217,6 +231,7 @@ public void testReduce2() throws IOException {
217231

218232
@Test
219233
public void testReduce3() throws IOException {
234+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
220235
JSONObject actual =
221236
executeQuery(
222237
String.format(
@@ -232,6 +247,7 @@ public void testReduce3() throws IOException {
232247

233248
@Test
234249
public void testReduceWithUDF() throws IOException {
250+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
235251
JSONObject actual =
236252
executeQuery(
237253
String.format(
@@ -797,6 +813,7 @@ public void testSplitWithEmptyDelimiter() throws IOException {
797813

798814
@Test
799815
public void testMvmap() throws IOException {
816+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
800817
JSONObject actual =
801818
executeQuery(
802819
String.format(
@@ -810,6 +827,7 @@ public void testMvmap() throws IOException {
810827

811828
@Test
812829
public void testMvmapWithAddition() throws IOException {
830+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
813831
JSONObject actual =
814832
executeQuery(
815833
String.format(
@@ -823,6 +841,7 @@ public void testMvmapWithAddition() throws IOException {
823841

824842
@Test
825843
public void testMvmapWithNestedFunction() throws IOException {
844+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
826845
// Test mvmap with mvindex as first argument - extracts field name from nested function
827846
// Equivalent to Splunk: mvmap(mvindex(arr, 1, 3), arr * 10)
828847
// The lambda binds 'arr' and iterates over mvindex output (values at indices 1-3)
@@ -840,6 +859,7 @@ public void testMvmapWithNestedFunction() throws IOException {
840859

841860
@Test
842861
public void testMvmapWithOtherFieldReference() throws IOException {
862+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
843863
// Test mvmap with reference to another field in the expression
844864
// The first record in bank has age=32, so array(1,2,3) * 32 = [32, 64, 96]
845865
JSONObject actual =
@@ -855,6 +875,7 @@ public void testMvmapWithOtherFieldReference() throws IOException {
855875

856876
@Test
857877
public void testMvmapWithEvalFieldReference() throws IOException {
878+
assumeNotAnalytics(ARRAY_HIGHER_ORDER_FUNC);
858879
// Test mvmap with reference to another field created by eval
859880
// array(1,2,3) * 10 = [10, 20, 30]
860881
JSONObject actual =

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLBasicIT.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static org.junit.Assume.assumeFalse;
99
import static org.opensearch.sql.legacy.TestUtils.isIndexExist;
1010
import static org.opensearch.sql.legacy.TestsConstants.*;
11+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.REGEXP_FILTER;
1112
import static org.opensearch.sql.util.MatcherUtils.rows;
1213
import static org.opensearch.sql.util.MatcherUtils.schema;
1314
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
@@ -155,6 +156,7 @@ public void testFilterQuery4() throws IOException {
155156

156157
@Test
157158
public void testRegexpFilter() throws IOException {
159+
assumeNotAnalytics(REGEXP_FILTER);
158160
JSONObject actual = executeQuery("source=test | where name REGEXP 'he.*' | fields name, age");
159161
verifySchema(actual, schema("name", "string"), schema("age", "bigint"));
160162
verifyDataRows(actual, rows("hello", 20));

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLMapPathIT.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package org.opensearch.sql.calcite.remote;
77

8+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.ADDTOTALS_JOIN_PANIC;
9+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.MVCOMBINE_ARRAY_AGG;
810
import static org.opensearch.sql.util.MatcherUtils.rows;
911
import static org.opensearch.sql.util.MatcherUtils.schema;
1012
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
@@ -142,6 +144,7 @@ public void testFieldsExclusionOnMapPath() throws IOException {
142144

143145
@Test
144146
public void testAddtotalsOnMapPath() throws IOException {
147+
assumeNotAnalytics(ADDTOTALS_JOIN_PANIC);
145148
JSONObject result =
146149
ppl(
147150
"""
@@ -164,6 +167,7 @@ public void testAddtotalsOnMapPath() throws IOException {
164167

165168
@Test
166169
public void testMvcombineOnMapPath() throws IOException {
170+
assumeNotAnalytics(MVCOMBINE_ARRAY_AGG);
167171
JSONObject result =
168172
ppl(
169173
"""

integ-test/src/test/java/org/opensearch/sql/ppl/DataTypeIT.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ALIAS;
1212
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DATATYPE_NONNUMERIC;
1313
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DATATYPE_NUMERIC;
14+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.CROSS_INDEX_INCOMPATIBLE_TYPES;
15+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.DOC_MUTATION;
16+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.NESTED_FIELDS;
17+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.SCALED_FLOAT_TYPE;
18+
import static org.opensearch.sql.util.AnalyticsRouteLimitation.STRING_TO_NUMERIC_COERCION;
1419
import static org.opensearch.sql.util.MatcherUtils.rows;
1520
import static org.opensearch.sql.util.MatcherUtils.schema;
1621
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
@@ -36,6 +41,7 @@ public void init() throws Exception {
3641

3742
@Test
3843
public void test_numeric_data_types() throws IOException {
44+
assumeNotAnalytics(SCALED_FLOAT_TYPE);
3945
JSONObject result = executeQuery(String.format("source=%s", TEST_INDEX_DATATYPE_NUMERIC));
4046
verifySchema(
4147
result,
@@ -51,6 +57,8 @@ public void test_numeric_data_types() throws IOException {
5157

5258
@Test
5359
public void test_nonnumeric_data_types() throws IOException {
60+
// nested_value/object_value/geo_point_value: stripped/unsupported on the AE route.
61+
assumeNotAnalytics(NESTED_FIELDS);
5462
JSONObject result = executeQuery(String.format("source=%s", TEST_INDEX_DATATYPE_NONNUMERIC));
5563
verifySchemaInOrder(
5664
result,
@@ -103,6 +111,8 @@ public void test_long_integer_data_type() throws IOException {
103111

104112
@Test
105113
public void test_alias_data_type() throws IOException {
114+
// alias_col is type=alias, stripped from the mapping on the AE route.
115+
assumeNotAnalytics(NESTED_FIELDS);
106116
JSONObject result =
107117
executeQuery(
108118
String.format(
@@ -114,6 +124,8 @@ public void test_alias_data_type() throws IOException {
114124

115125
@Test
116126
public void testNumericFieldFromString() throws Exception {
127+
// Empty-string -> numeric coerces to 0 on v2/Calcite but null on the AE route.
128+
assumeNotAnalytics(STRING_TO_NUMERIC_COERCION);
117129
final int docId = 2;
118130
Request insertRequest =
119131
new Request(
@@ -149,6 +161,8 @@ public void testNumericFieldFromString() throws Exception {
149161
public void testBooleanFieldFromNumberAcrossWildcardIndices() throws Exception {
150162
// Reproduce issue #5269: querying across indices where same field has conflicting types
151163
// (boolean vs text) and the text-typed index stores a numeric value like 0.
164+
// The AE route rejects the incompatible cross-index field types instead of coercing.
165+
assumeNotAnalytics(CROSS_INDEX_INCOMPATIBLE_TYPES);
152166
String indexBool = "repro_bool_test_bb";
153167
String indexText = "repro_bool_test_aa";
154168

@@ -188,6 +202,8 @@ public void testBooleanFieldFromNumberAcrossWildcardIndices() throws Exception {
188202

189203
@Test
190204
public void testBooleanFieldFromString() throws Exception {
205+
// Seeds + deletes a doc; the AE store doesn't support DELETE.
206+
assumeNotAnalytics(DOC_MUTATION);
191207
final int docId = 2;
192208
Request insertRequest =
193209
new Request(

integ-test/src/test/java/org/opensearch/sql/util/Capability.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,66 @@ public enum Capability {
192192
EVAL_MAX_MIN_INT_WIDENING(
193193
"eval max()/min() over integer operands reports the result column as bigint on the"
194194
+ " analytics-engine route (DataFusion widens integers to Int64), whereas the v2/Calcite"
195-
+ " path reports int.");
195+
+ " path reports int."),
196+
197+
/**
198+
* Higher-order array functions that take a lambda ({@code transform}/{@code mvmap}, {@code
199+
* reduce}, {@code filter}, {@code exists}, {@code forall}) are unsupported on the
200+
* analytics-engine route: the capability registry rejects them ({@code No backend supports scalar
201+
* function [...] among [lucene, datafusion]}) since the backends can't execute a PPL lambda.
202+
*/
203+
ARRAY_HIGHER_ORDER_FUNC(
204+
"Higher-order array functions (transform/mvmap, reduce, filter, exists, forall) are"
205+
+ " unsupported on the analytics-engine route: the backends can't execute a PPL lambda."),
206+
207+
/**
208+
* {@code scaled_float} fields are reported as {@code bigint} on the analytics-engine route
209+
* (DataFusion stores the underlying scaled long) rather than {@code double} as on v2/Calcite.
210+
*/
211+
SCALED_FLOAT_TYPE(
212+
"scaled_float is reported as bigint on the analytics-engine route (DataFusion stores the"
213+
+ " scaled long), whereas the v2/Calcite path reports double."),
214+
215+
/**
216+
* Coercing an empty string to a numeric field yields {@code null} on the analytics-engine route,
217+
* whereas the v2/Calcite path coerces it to {@code 0}.
218+
*/
219+
STRING_TO_NUMERIC_COERCION(
220+
"Coercing an empty/invalid string to a numeric field yields null on the analytics-engine"
221+
+ " route, whereas the v2/Calcite path coerces it to 0."),
222+
223+
/**
224+
* A wildcard/alias source whose member indices map the same field to incompatible types (e.g.
225+
* {@code text} in one, {@code boolean} in another) is rejected on the analytics-engine route
226+
* ({@code resolves to indices with incompatible field types}); the v2/Calcite path coerces.
227+
*/
228+
CROSS_INDEX_INCOMPATIBLE_TYPES(
229+
"A wildcard/alias source with incompatible field types across member indices is rejected on"
230+
+ " the analytics-engine route, whereas the v2/Calcite path coerces."),
231+
232+
/**
233+
* The {@code REGEXP} filter operator throws a backend NullPointerException on the
234+
* analytics-engine route.
235+
*/
236+
REGEXP_FILTER(
237+
"The REGEXP filter operator throws a backend NullPointerException on the analytics-engine"
238+
+ " route."),
239+
240+
/**
241+
* {@code mvcombine} lowers to an {@code ARRAY_AGG} aggregate the analytics-engine backend doesn't
242+
* register ({@code No enum constant ... AggregateFunction.ARRAY_AGG}).
243+
*/
244+
MVCOMBINE_ARRAY_AGG(
245+
"mvcombine lowers to ARRAY_AGG, which the analytics-engine backend does not support (no"
246+
+ " AggregateFunction.ARRAY_AGG enum constant)."),
247+
248+
/**
249+
* {@code addtotals} crashes the DataFusion backend with a join panic (out-of-range slice index)
250+
* on the analytics-engine route.
251+
*/
252+
ADDTOTALS_JOIN_PANIC(
253+
"addtotals crashes the DataFusion backend with a join panic (out-of-range slice index) on the"
254+
+ " analytics-engine route.");
196255

197256
private final String reason;
198257

0 commit comments

Comments
 (0)