Skip to content

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

Open
gingeekrishna wants to merge 1 commit into
opensearch-project:mainfrom
gingeekrishna:fix/5533-multisearch-alias-timestamp-classcastexception-v2
Open

Fix ClassCastException in PPL multisearch on indexes with @timestamp alias field#5577
gingeekrishna wants to merge 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants