Skip to content

Fix ClassCastException in PPL multisearch on indexes with @timestamp alias field#5577

Merged
ahkcs merged 1 commit into
opensearch-project:mainfrom
gingeekrishna:fix/5533-multisearch-alias-timestamp-classcastexception-v2
Jun 25, 2026
Merged

Fix ClassCastException in PPL multisearch on indexes with @timestamp alias field#5577
ahkcs merged 1 commit into
opensearch-project:mainfrom
gingeekrishna:fix/5533-multisearch-alias-timestamp-classcastexception-v2

Conversation

@gingeekrishna

Copy link
Copy Markdown
Contributor

Summary

Fixes #5533.

When @timestamp is defined as a field-type alias in the index mapping (e.g. "@timestamp": {"type": "alias", "path": "timestamp"}), running a | multisearch command on that index throws:

java.lang.ClassCastException: class org.apache.calcite.rel.RelCompositeTrait
    cannot be cast to class org.apache.calcite.rel.RelCollation

Root cause

reIndexCollations() (CalciteLogicalIndexScan) and pushDownSort() (AbstractCalciteIndexScan) both called RelTraitSet.plus() to set the collation trait on a scan node. plus() composes traits — when the trait set already contains a RelCollation, it merges the old and new collations into a RelCompositeTrait. Calcite's RelTraitSet.getCollation() then does an unchecked cast (RelCollation) getTrait(...) which fails at runtime for RelCompositeTrait.

The @timestamp alias path specifically triggers this because wrapProjectForAliasFields() adds a LogicalProject on top of each sub-scan during multisearch plan building. Calcite's push-down rules later push this project back into the scan via pushDownProject()reIndexCollations(), which uses plus() to remap an existing sort collation — producing the bad composite trait.

Fix

Use RelTraitSet.replace() in both locations instead of plus(). replace() substitutes the collation trait in-place regardless of what was already there, which is the correct semantics for "this scan is sorted by these columns".

Files changed (3):

  • opensearch/.../AbstractCalciteIndexScan.javapushDownSort(): plusreplace
  • opensearch/.../CalciteLogicalIndexScan.javareIndexCollations(): plusreplace
  • integ-test/.../CalciteMultisearchCommandIT.java — regression IT against TEST_INDEX_ALIAS (which maps @timestamp as a field-type alias to original_date)

Test plan

  • Added testMultisearchWithTimestampAliasFieldDoesNotThrow IT — runs a multisearch against an index where @timestamp is a field-type alias; this would have crashed with ClassCastException before this fix
  • All existing CalciteMultisearchCommandIT tests pass
  • OpenSearchIndexScanOptimizationTest and related unit tests pass (.replace() is correct semantics here — a scan's collation is always a full replacement, never a union of multiple sort orders)

Checklist

  • New functionality includes testing
  • New functionality has been documented
  • Commits are signed and DCO verification will pass

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Calcite planning/runtime ClassCastException triggered by | multisearch on indices where @timestamp is a field-type alias. The fix corrects collation trait handling on scan nodes so the collation trait is replaced (not composed) during sort/project pushdown.

Changes:

  • Replace RelTraitSet.plus(RelCollations.of(...)) with RelTraitSet.replace(RelCollations.of(...)) in sort pushdown to avoid creating RelCompositeTrait.
  • Replace RelTraitSet.plus(...) with replace(...) when reindexing collations after project pushdown.
  • Add an integration regression test covering multisearch against an index with an alias-mapped @timestamp.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/CalciteLogicalIndexScan.java Uses RelTraitSet.replace() when reindexing collations after project pushdown.
opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/AbstractCalciteIndexScan.java Uses RelTraitSet.replace() when setting collations during sort pushdown.
integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java Adds regression IT for multisearch on an index where @timestamp is an alias field.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mengweieric

Copy link
Copy Markdown
Collaborator

@gingeekrishna thanks for taking care of this issue, could you please check CI failures?

…alias field

Fixes opensearch-project#5533.

When `@timestamp` is defined as a field-type alias in the index mapping,
multisearch queries threw:

  ClassCastException: RelCompositeTrait cannot be cast to RelCollation

Root cause: `reIndexCollations()` in `CalciteLogicalIndexScan` and
`pushDownSort()` in `AbstractCalciteIndexScan` both called
`RelTraitSet.plus()` to update the collation trait on a scan node.
`plus()` *composes* traits — if the trait set already contains a
`RelCollation`, it merges the old and new collations into a
`RelCompositeTrait`.  Calcite's `RelTraitSet.getCollation()` then does
an unchecked cast `(RelCollation) getTrait(...)` which fails at runtime
for `RelCompositeTrait`.

The `@timestamp` alias path specifically triggers this because
`wrapProjectForAliasFields()` adds a project on top of each sub-scan
which is later pushed back down via `pushDownProject()`.
`pushDownProject()` calls `reIndexCollations()` to remap field indices
inside an existing collation — but re-using `plus()` here composes the
existing sort collation with the re-indexed one, producing the bad
composite.

Fix: use `RelTraitSet.replace()` in both locations.  `replace()`
substitutes the collation trait in-place regardless of what was there
before, which is the correct semantics for "this scan is now sorted by
these columns".

Added a regression IT (`testMultisearchWithTimestampAliasFieldDoesNotThrow`)
that runs a multisearch against `TEST_INDEX_ALIAS`, whose mapping defines
`@timestamp` as an alias for `original_date`.

Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
@gingeekrishna gingeekrishna force-pushed the fix/5533-multisearch-alias-timestamp-classcastexception-v2 branch from d87f33f to 7fb0d69 Compare June 22, 2026 04:43
@ahkcs ahkcs merged commit 0a4d40e into opensearch-project:main Jun 25, 2026
27 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants