Skip to content

Commit cb80047

Browse files
authored
refactor(integ-test): capability-based analytics-engine test gating (#5560)
Rename AnalyticsRouteLimitation to Capability and add a declarative @RequiresCapability annotation (enforced by CapabilityRule) wired into SQLIntegTestCase, so SQL and PPL ITs gate by capability instead of naming a backend. Gate logic lives in BackendCapabilities. All assumeNotAnalytics(...) calls migrated to @RequiresCapability, with per-test divergence comments folded into the annotation note. Signed-off-by: Chen Dai <daichen@amazon.com>
1 parent 9ed9b1a commit cb80047

20 files changed

Lines changed: 232 additions & 109 deletions

integ-test/build.gradle

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,8 +1084,8 @@ task integTestRemote(type: RestIntegTestTask) {
10841084
excludeTestsMatching 'org.opensearch.sql.calcite.remote.CalciteAliasFieldAggregationIT'
10851085

10861086
// === Excludes: WhereCommandIT / CalciteWhereCommandIT route divergences (#5546) ===
1087-
// Each test also carries an in-test assumeNotAnalytics(...) recording the reason (see
1088-
// AnalyticsRouteLimitation); listed here so the AE-route skip set stays countable in one
1087+
// Each test also carries an in-test requireCapability(...) recording the reason (see
1088+
// Capability); listed here so the AE-route skip set stays countable in one
10891089
// place. Reasons:
10901090
// - Nested-field queries: the parquet/composite store has no nested-document support, so
10911091
// nested fields are stripped at load (#5541). Defined only in CalciteWhereCommandIT.
@@ -1104,8 +1104,8 @@ task integTestRemote(type: RestIntegTestTask) {
11041104
excludeTestsMatching '*WhereCommandIT.testDoubleEqualWithSpecialCharacters'
11051105

11061106
// === Excludes: CalciteMultisearchCommandIT route divergences ===
1107-
// Each test also carries an in-test assumeNotAnalytics(...) recording the reason (see
1108-
// AnalyticsRouteLimitation); listed here so the AE-route skip set stays countable.
1107+
// Each test also carries an in-test requireCapability(...) recording the reason (see
1108+
// Capability); listed here so the AE-route skip set stays countable.
11091109
// - Same-index subsearch conflation: when every subsearch reads the same index, the AE
11101110
// route applies the first subsearch's filter to all of them, so counts are wrong.
11111111
excludeTestsMatching '*CalciteMultisearchCommandIT.testMultisearchWithThreeSubsearches'
@@ -1119,15 +1119,15 @@ task integTestRemote(type: RestIntegTestTask) {
11191119
// bin on a time field then grouping by it: the AE route types the date-histogram bucket
11201120
// column as string (not timestamp) AND produces a different bucket set (auto-histogram
11211121
// span / empty-bucket filtering differ), so both schema and row counts diverge. Each
1122-
// test also carries an in-test assumeNotAnalytics(...) (see AnalyticsRouteLimitation).
1122+
// test also carries an in-test requireCapability(...) (see Capability).
11231123
excludeTestsMatching '*CalciteBinCommandIT.testStatsWithBinsOnTimeField_Count'
11241124
excludeTestsMatching '*CalciteBinCommandIT.testStatsWithBinsOnTimeField_Avg'
11251125
excludeTestsMatching '*CalciteBinCommandIT.testStatsWithBinsOnTimeAndTermField_Count'
11261126
excludeTestsMatching '*CalciteBinCommandIT.testStatsWithBinsOnTimeAndTermField_Avg'
11271127

11281128
// === Excludes: CalcitePPLEnhancedCoalesceIT route divergences ===
1129-
// Each test also carries an in-test assumeNotAnalytics(...) recording the reason (see
1130-
// AnalyticsRouteLimitation). The other ordering-sensitive coalesce tests were fixed in
1129+
// Each test also carries an in-test requireCapability(...) recording the reason (see
1130+
// Capability). The other ordering-sensitive coalesce tests were fixed in
11311131
// place with an explicit `sort` instead of being skipped.
11321132
// - COALESCE over all-untyped-null operands is rejected by the capability registry.
11331133
excludeTestsMatching '*CalcitePPLEnhancedCoalesceIT.testCoalesceWithAllNonExistentFields'
@@ -1137,8 +1137,8 @@ task integTestRemote(type: RestIntegTestTask) {
11371137
excludeTestsMatching '*CalcitePPLEnhancedCoalesceIT.testCoalesceBasic'
11381138

11391139
// === Excludes: CalcitePPLScalarSubqueryIT / CalcitePPLInSubqueryIT route divergences ===
1140-
// Each test also carries an in-test assumeNotAnalytics(...) recording the reason (see
1141-
// AnalyticsRouteLimitation); listed here so the AE-route skip set stays countable.
1140+
// Each test also carries an in-test requireCapability(...) recording the reason (see
1141+
// Capability); listed here so the AE-route skip set stays countable.
11421142
// - Exact equality on an explicitly text-mapped field (department/occupation = '...')
11431143
// in the subsearch returns no rows on the AE route (analyzed text, no .keyword).
11441144
excludeTestsMatching '*CalcitePPLScalarSubqueryIT.testTwoUncorrelatedScalarSubqueriesInOr'
@@ -1149,8 +1149,8 @@ task integTestRemote(type: RestIntegTestTask) {
11491149
excludeTestsMatching '*CalcitePPLInSubqueryIT.testSubsearchMaxOut'
11501150

11511151
// === Excludes: CalcitePPLConditionBuiltinFunctionIT route divergences ===
1152-
// Each test also carries an in-test assumeNotAnalytics(...) recording the reason (see
1153-
// AnalyticsRouteLimitation); listed here so the AE-route skip set stays countable.
1152+
// Each test also carries an in-test requireCapability(...) recording the reason (see
1153+
// Capability); listed here so the AE-route skip set stays countable.
11541154
// - isnull/isnotnull on the object/struct parent field big5.aws: objects are flattened
11551155
// to dotted leaf columns and the struct parent is not a queryable column.
11561156
excludeTestsMatching '*CalcitePPLConditionBuiltinFunctionIT.testIsNullWithStruct'
@@ -1168,7 +1168,7 @@ task integTestRemote(type: RestIntegTestTask) {
11681168
// === Excludes: CalcitePPLEvalMaxMinFunctionIT route divergences ===
11691169
// The AnalyticsExecutionEngine ANY-rescue (this PR) fixes the homogeneous max()/min()
11701170
// schema types, so most of the class now passes on the AE route. Three tests still
1171-
// diverge; each carries an in-test assumeNotAnalytics(...) (see AnalyticsRouteLimitation).
1171+
// diverge; each carries an in-test requireCapability(...) (see Capability).
11721172
// - Mixed numeric+string operands throw 'Cannot infer return type for GREATEST' (the
11731173
// DataFusion GREATEST/LEAST can't unify heterogeneous operand types).
11741174
excludeTestsMatching '*CalcitePPLEvalMaxMinFunctionIT.testEvalMaxNumericAndString'

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import static org.junit.Assert.assertTrue;
1010
import static org.junit.jupiter.api.Assertions.assertThrows;
1111
import static org.opensearch.sql.legacy.TestsConstants.*;
12-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.BIN_TIME_FIELD_BUCKETING;
12+
import static org.opensearch.sql.util.Capability.BIN_TIME_FIELD_BUCKETING;
1313
import static org.opensearch.sql.util.MatcherUtils.rows;
1414
import static org.opensearch.sql.util.MatcherUtils.schema;
1515
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
@@ -21,6 +21,7 @@
2121
import org.junit.jupiter.api.Test;
2222
import org.opensearch.client.ResponseException;
2323
import org.opensearch.sql.ppl.PPLIntegTestCase;
24+
import org.opensearch.sql.util.RequiresCapability;
2425

2526
public class CalciteBinCommandIT extends PPLIntegTestCase {
2627
@Override
@@ -870,10 +871,10 @@ public void testBinFloatingPointSpanWithStatsCount() throws IOException {
870871
}
871872

872873
@Test
874+
@RequiresCapability(BIN_TIME_FIELD_BUCKETING)
873875
public void testStatsWithBinsOnTimeField_Count() throws IOException {
874876
// TODO: Remove this after addressing https://github.com/opensearch-project/sql/issues/4317
875877
enabledOnlyWhenPushdownIsEnabled();
876-
assumeNotAnalytics(BIN_TIME_FIELD_BUCKETING);
877878

878879
JSONObject result =
879880
executeQuery("source=events_null | bin @timestamp bins=3 | stats count() by @timestamp");
@@ -909,10 +910,10 @@ public void testStatsWithBinsOnTimeField_Count() throws IOException {
909910
}
910911

911912
@Test
913+
@RequiresCapability(BIN_TIME_FIELD_BUCKETING)
912914
public void testStatsWithBinsOnTimeField_Avg() throws IOException {
913915
// TODO: Remove this after addressing https://github.com/opensearch-project/sql/issues/4317
914916
enabledOnlyWhenPushdownIsEnabled();
915-
assumeNotAnalytics(BIN_TIME_FIELD_BUCKETING);
916917

917918
JSONObject result =
918919
executeQuery(
@@ -951,10 +952,10 @@ public void testStatsWithBinsOnTimeField_Avg() throws IOException {
951952
}
952953

953954
@Test
955+
@RequiresCapability(BIN_TIME_FIELD_BUCKETING)
954956
public void testStatsWithBinsOnTimeAndTermField_Count() throws IOException {
955957
// TODO: Remove this after addressing https://github.com/opensearch-project/sql/issues/4317
956958
enabledOnlyWhenPushdownIsEnabled();
957-
assumeNotAnalytics(BIN_TIME_FIELD_BUCKETING);
958959

959960
JSONObject result =
960961
executeQuery(
@@ -975,10 +976,10 @@ public void testStatsWithBinsOnTimeAndTermField_Count() throws IOException {
975976
}
976977

977978
@Test
979+
@RequiresCapability(BIN_TIME_FIELD_BUCKETING)
978980
public void testStatsWithBinsOnTimeAndTermField_Avg() throws IOException {
979981
// TODO: Remove this after addressing https://github.com/opensearch-project/sql/issues/4317
980982
enabledOnlyWhenPushdownIsEnabled();
981-
assumeNotAnalytics(BIN_TIME_FIELD_BUCKETING);
982983

983984
JSONObject result =
984985
executeQuery(

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ARRAY;
99
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_NESTED_SIMPLE;
10-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.DOC_MUTATION;
10+
import static org.opensearch.sql.util.Capability.DOC_MUTATION;
1111
import static org.opensearch.sql.util.MatcherUtils.rows;
1212
import static org.opensearch.sql.util.MatcherUtils.schema;
1313
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
@@ -20,6 +20,7 @@
2020
import org.junit.jupiter.api.Test;
2121
import org.opensearch.client.Request;
2222
import org.opensearch.sql.ppl.PPLIntegTestCase;
23+
import org.opensearch.sql.util.RequiresCapability;
2324

2425
public class CalciteExpandCommandIT extends PPLIntegTestCase {
2526
@Override
@@ -295,8 +296,8 @@ public void testExpandWithEval() throws Exception {
295296
}
296297

297298
@Test
299+
@RequiresCapability(DOC_MUTATION)
298300
public void testExpandEmptyArray() throws Exception {
299-
assumeNotAnalytics(DOC_MUTATION);
300301
final int docId = 6;
301302
Request insertRequest =
302303
new Request(
@@ -325,8 +326,8 @@ public void testExpandEmptyArray() throws Exception {
325326
}
326327

327328
@Test
329+
@RequiresCapability(DOC_MUTATION)
328330
public void testExpandOnNullField() throws Exception {
329-
assumeNotAnalytics(DOC_MUTATION);
330331
final int docId = 6;
331332
Request insertRequest =
332333
new Request(

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

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

88
import static org.opensearch.sql.legacy.TestsConstants.*;
9-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.MULTISEARCH_COLUMN_ORDER;
10-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.MULTISEARCH_SAME_INDEX_CONFLATION;
9+
import static org.opensearch.sql.util.Capability.MULTISEARCH_COLUMN_ORDER;
10+
import static org.opensearch.sql.util.Capability.MULTISEARCH_SAME_INDEX_CONFLATION;
1111
import static org.opensearch.sql.util.MatcherUtils.rows;
1212
import static org.opensearch.sql.util.MatcherUtils.schema;
1313
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
@@ -18,6 +18,7 @@
1818
import org.junit.jupiter.api.Test;
1919
import org.opensearch.client.ResponseException;
2020
import org.opensearch.sql.ppl.PPLIntegTestCase;
21+
import org.opensearch.sql.util.RequiresCapability;
2122

2223
public class CalciteMultisearchCommandIT extends PPLIntegTestCase {
2324

@@ -66,8 +67,8 @@ public void testMultisearchSuccessRatePattern() throws IOException {
6667
}
6768

6869
@Test
70+
@RequiresCapability(MULTISEARCH_SAME_INDEX_CONFLATION)
6971
public void testMultisearchWithThreeSubsearches() throws IOException {
70-
assumeNotAnalytics(MULTISEARCH_SAME_INDEX_CONFLATION);
7172
JSONObject result =
7273
executeQuery(
7374
String.format(
@@ -83,8 +84,8 @@ public void testMultisearchWithThreeSubsearches() throws IOException {
8384
}
8485

8586
@Test
87+
@RequiresCapability(MULTISEARCH_SAME_INDEX_CONFLATION)
8688
public void testMultisearchWithComplexAggregation() throws IOException {
87-
assumeNotAnalytics(MULTISEARCH_SAME_INDEX_CONFLATION);
8889
JSONObject result =
8990
executeQuery(
9091
String.format(
@@ -146,8 +147,8 @@ public void testMultisearchWithFieldsProjection() throws IOException {
146147
}
147148

148149
@Test
150+
@RequiresCapability(MULTISEARCH_COLUMN_ORDER)
149151
public void testMultisearchWithTimestampInterleaving() throws IOException {
150-
assumeNotAnalytics(MULTISEARCH_COLUMN_ORDER);
151152
JSONObject result =
152153
executeQuery(
153154
"| multisearch [search"
@@ -357,8 +358,8 @@ public void testMultisearchCrossIndexFieldSelection() throws IOException {
357358

358359
/** Reproduce #5145: multisearch without further processing should return all rows. */
359360
@Test
361+
@RequiresCapability(MULTISEARCH_SAME_INDEX_CONFLATION)
360362
public void testMultisearchWithoutFurtherProcessing() throws IOException {
361-
assumeNotAnalytics(MULTISEARCH_SAME_INDEX_CONFLATION);
362363
JSONObject result =
363364
executeQuery(
364365
"| multisearch [search source=opensearch-sql_test_index_time_data | where category ="

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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
import static org.opensearch.sql.legacy.TestUtils.isIndexExist;
99
import static org.opensearch.sql.legacy.TestsConstants.*;
10-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.CONCAT_NULL_AS_EMPTY;
11-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.EARLIEST_LATEST_NOW_CLOCK;
12-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.NESTED_FIELDS;
13-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.STRUCT_PARENT_FIELD;
10+
import static org.opensearch.sql.util.Capability.CONCAT_NULL_AS_EMPTY;
11+
import static org.opensearch.sql.util.Capability.EARLIEST_LATEST_NOW_CLOCK;
12+
import static org.opensearch.sql.util.Capability.NESTED_FIELDS;
13+
import static org.opensearch.sql.util.Capability.STRUCT_PARENT_FIELD;
1414
import static org.opensearch.sql.util.MatcherUtils.*;
1515
import static org.opensearch.sql.util.MatcherUtils.rows;
1616

@@ -20,6 +20,7 @@
2020
import org.junit.jupiter.api.Test;
2121
import org.opensearch.client.Request;
2222
import org.opensearch.sql.ppl.PPLIntegTestCase;
23+
import org.opensearch.sql.util.RequiresCapability;
2324

2425
public class CalcitePPLConditionBuiltinFunctionIT extends PPLIntegTestCase {
2526
@Override
@@ -65,18 +66,20 @@ public void testIsNull() throws IOException {
6566
}
6667

6768
@Test
69+
@RequiresCapability(
70+
value = STRUCT_PARENT_FIELD,
71+
note = "Queries the object/struct parent field 'aws' directly.")
6872
public void testIsNullWithStruct() throws IOException {
69-
// Queries the object/struct parent field 'aws' directly.
70-
assumeNotAnalytics(STRUCT_PARENT_FIELD);
7173
JSONObject actual = executeQuery("source=big5 | where isnull(aws) | fields aws");
7274
verifySchema(actual, schema("aws", "struct"));
7375
verifyNumOfRows(actual, 0);
7476
}
7577

7678
@Test
79+
@RequiresCapability(
80+
value = NESTED_FIELDS,
81+
note = "Queries a nested field; the route strips nested fields at index creation.")
7782
public void testIsNullWithNested() throws IOException {
78-
// Queries a nested field; the route strips nested fields at index creation.
79-
assumeNotAnalytics(NESTED_FIELDS);
8083
JSONObject actual =
8184
executeQuery(
8285
String.format(
@@ -139,18 +142,20 @@ public void testIsNotNullWithSingleNotEquals() throws IOException {
139142
}
140143

141144
@Test
145+
@RequiresCapability(
146+
value = STRUCT_PARENT_FIELD,
147+
note = "Queries the object/struct parent field 'aws' directly.")
142148
public void testIsNotNullWithStruct() throws IOException {
143-
// Queries the object/struct parent field 'aws' directly.
144-
assumeNotAnalytics(STRUCT_PARENT_FIELD);
145149
JSONObject actual = executeQuery("source=big5 | where isnotnull(aws) | fields aws");
146150
verifySchema(actual, schema("aws", "struct"));
147151
verifyNumOfRows(actual, 3);
148152
}
149153

150154
@Test
155+
@RequiresCapability(
156+
value = NESTED_FIELDS,
157+
note = "Queries a nested field; the route strips nested fields at index creation.")
151158
public void testIsNotNullWithNested() throws IOException {
152-
// Queries a nested field; the route strips nested fields at index creation.
153-
assumeNotAnalytics(NESTED_FIELDS);
154159
JSONObject actual =
155160
executeQuery(
156161
String.format(
@@ -184,9 +189,11 @@ public void testNullIf() throws IOException {
184189
}
185190

186191
@Test
192+
@RequiresCapability(
193+
value = CONCAT_NULL_AS_EMPTY,
194+
note =
195+
"concat('H', name) over the null-name row diverges (NULL-as-empty vs NULL-propagating).")
187196
public void testNullIfWithExpression() throws IOException {
188-
// concat('H', name) over the null-name row diverges (NULL-as-empty vs NULL-propagating).
189-
assumeNotAnalytics(CONCAT_NULL_AS_EMPTY);
190197
JSONObject actual =
191198
executeQuery(
192199
String.format(
@@ -375,9 +382,12 @@ public void testLatest() throws IOException {
375382
}
376383

377384
@Test
385+
@RequiresCapability(
386+
value = EARLIEST_LATEST_NOW_CLOCK,
387+
note =
388+
"earliest('now', utc_timestamp()) resolves true on the route but false on v2 (clock"
389+
+ " source).")
378390
public void testEarliestWithEval() throws IOException {
379-
// earliest('now', utc_timestamp()) resolves true on the route but false on v2 (clock source).
380-
assumeNotAnalytics(EARLIEST_LATEST_NOW_CLOCK);
381391
JSONObject actual =
382392
executeQuery(
383393
String.format(

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
package org.opensearch.sql.calcite.remote;
77

88
import static org.opensearch.sql.legacy.TestsConstants.*;
9-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.COALESCE_ALL_NULL_OPERANDS;
10-
import static org.opensearch.sql.util.AnalyticsRouteLimitation.HEAD_WITHOUT_STABLE_SORT;
9+
import static org.opensearch.sql.util.Capability.COALESCE_ALL_NULL_OPERANDS;
10+
import static org.opensearch.sql.util.Capability.HEAD_WITHOUT_STABLE_SORT;
1111
import static org.opensearch.sql.util.MatcherUtils.*;
1212

1313
import java.io.IOException;
1414
import org.json.JSONObject;
1515
import org.junit.jupiter.api.Test;
1616
import org.opensearch.client.Request;
1717
import org.opensearch.sql.ppl.PPLIntegTestCase;
18+
import org.opensearch.sql.util.RequiresCapability;
1819

1920
public class CalcitePPLEnhancedCoalesceIT extends PPLIntegTestCase {
2021
@Override
@@ -38,8 +39,8 @@ public void init() throws Exception {
3839
}
3940

4041
@Test
42+
@RequiresCapability(HEAD_WITHOUT_STABLE_SORT)
4143
public void testCoalesceBasic() throws IOException {
42-
assumeNotAnalytics(HEAD_WITHOUT_STABLE_SORT);
4344
JSONObject actual =
4445
executeQuery(
4546
String.format(
@@ -54,8 +55,8 @@ public void testCoalesceBasic() throws IOException {
5455
}
5556

5657
@Test
58+
@RequiresCapability(HEAD_WITHOUT_STABLE_SORT)
5759
public void testCoalesceWithMixedTypes() throws IOException {
58-
assumeNotAnalytics(HEAD_WITHOUT_STABLE_SORT);
5960
JSONObject actual =
6061
executeQuery(
6162
String.format(
@@ -164,8 +165,8 @@ public void testCoalesceWithMultipleNonExistentFields() throws IOException {
164165
}
165166

166167
@Test
168+
@RequiresCapability(COALESCE_ALL_NULL_OPERANDS)
167169
public void testCoalesceWithAllNonExistentFields() throws IOException {
168-
assumeNotAnalytics(COALESCE_ALL_NULL_OPERANDS);
169170
JSONObject actual =
170171
executeQuery(
171172
String.format(

0 commit comments

Comments
 (0)