diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java index 10cc7ffd459..383ae5e400f 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java @@ -5,6 +5,7 @@ package org.opensearch.sql.calcite.remote; +import static org.junit.Assume.assumeFalse; import static org.opensearch.sql.legacy.TestsConstants.*; import static org.opensearch.sql.util.Capability.MULTISEARCH_COLUMN_ORDER; import static org.opensearch.sql.util.Capability.MULTISEARCH_SAME_INDEX_CONFLATION; @@ -12,6 +13,7 @@ import static org.opensearch.sql.util.MatcherUtils.schema; import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; import static org.opensearch.sql.util.MatcherUtils.verifySchema; +import static org.opensearch.sql.util.MatcherUtils.verifySchemaInOrder; import java.io.IOException; import org.json.JSONObject; @@ -31,6 +33,7 @@ public void init() throws Exception { loadIndex(Index.TIME_TEST_DATA); loadIndex(Index.TIME_TEST_DATA2); loadIndex(Index.LOCATIONS_TYPE_CONFLICT); + loadIndex(Index.DATA_TYPE_ALIAS); } @Test @@ -462,4 +465,42 @@ public void testMultisearchTypeConflictWithStats() { .getMessage() .contains("Unable to process column 'age' due to incompatible types:")); } + + /** + * Regression test for GitHub issue #5533. When {@code @timestamp} is defined as a field-type + * alias in the index mapping, multisearch used to throw: + * + *
ClassCastException: RelCompositeTrait cannot be cast to RelCollation+ * + *
Root cause: {@code reIndexCollations()} and {@code pushDownSort()} both used {@code
+ * RelTraitSet.plus()} which composes collation traits into a {@link
+ * org.apache.calcite.rel.RelCompositeTrait} when a collation is already present. Calcite's {@code
+ * RelTraitSet.getCollation()} then fails with a ClassCastException. Fixed by using {@code
+ * RelTraitSet.replace()} instead to always replace the collation trait.
+ */
+ @Test
+ public void testMultisearchWithTimestampAliasFieldDoesNotThrow() throws IOException {
+ // alias-typed fields are stripped when loading indices in analytics-engine parquet mode,
+ // so @timestamp does not exist in TEST_INDEX_ALIAS on that route.
+ assumeFalse(
+ "alias-typed fields are stripped in analytics-engine parquet mode;"
+ + " @timestamp won't exist in TEST_INDEX_ALIAS on that route.",
+ isAnalyticsParquetIndicesEnabled());
+ // TEST_INDEX_ALIAS has @timestamp defined as an alias field pointing to original_date.
+ // Running multisearch on such an index used to crash with ClassCastException.
+ JSONObject result =
+ executeQuery(
+ String.format(
+ "| multisearch "
+ + "[search source=%s | where original_col > 1 | fields original_col,"
+ + " @timestamp] "
+ + "[search source=%s | where original_col = 1 | fields original_col,"
+ + " @timestamp]",
+ TEST_INDEX_ALIAS, TEST_INDEX_ALIAS));
+
+ verifySchemaInOrder(
+ result, schema("original_col", null, "int"), schema("@timestamp", null, "timestamp"));
+ // 2 rows from original_col > 1, 1 row from original_col = 1
+ assertEquals(3, result.getInt("total"));
+ }
}
diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/AbstractCalciteIndexScan.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/AbstractCalciteIndexScan.java
index 609a5aaa92f..52d64fb5a73 100644
--- a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/AbstractCalciteIndexScan.java
+++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/AbstractCalciteIndexScan.java
@@ -323,7 +323,7 @@ && isAnyCollationNameInAggregators(collationNames)) {
// aggregators.
return null;
}
- RelTraitSet traitsWithCollations = getTraitSet().plus(RelCollations.of(collations));
+ RelTraitSet traitsWithCollations = getTraitSet().replace(RelCollations.of(collations));
PushDownContext pushDownContextWithoutSort = this.pushDownContext.cloneWithoutSort();
AbstractAction> action;
Object digest;
diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/CalciteLogicalIndexScan.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/CalciteLogicalIndexScan.java
index 740801ff418..2017437e7bd 100644
--- a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/CalciteLogicalIndexScan.java
+++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/CalciteLogicalIndexScan.java
@@ -321,7 +321,7 @@ private RelTraitSet reIndexCollations(List