Skip to content

fix: [bug] PPL query with mvindex() fails when plugins.calcite.pushdown.enabled=true (#5660) - #5689

Open
AjimelecGonzalez wants to merge 1 commit into
opensearch-project:mainfrom
AjimelecGonzalez:fix/mvindex
Open

fix: [bug] PPL query with mvindex() fails when plugins.calcite.pushdown.enabled=true (#5660)#5689
AjimelecGonzalez wants to merge 1 commit into
opensearch-project:mainfrom
AjimelecGonzalez:fix/mvindex

Conversation

@AjimelecGonzalez

@AjimelecGonzalez AjimelecGonzalez commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

Fix Calcite pushdown CompileException where arithmetic in array index expressions (e.g., mvindex(entity, 1)) produces long instead of int at runtime.

RexStandardizer widens arithmetic operands to BIGINT for doc-value compatibility, but PLUS nodes do not have their type serialized in JSON (unlike CAST/MINUS). On deserialization the type is re-derived from BIGINT operands, breaking operators like ITEM that expect int.

Fix: Wrap arithmetic results in an explicit CAST(INTEGER) in RexStandardizer.visitCall() when the original type is narrower than BIGINT. CAST nodes always serialize their type, so INTEGER is preserved through serialization and deserialization.

Test Added:

  • testArithmeticInItemIndexPreservesIntegerType: PLUS(int, int) preserves INTEGER type through serialization round-trip (the main bug fix)
  • testArithmeticBigintNotWrappedInCast: PLUS(bigint, bigint) stays BIGINT without unnecessary CAST (no regression)
  • testArithmeticWithFieldPreservesIntegerType: field_ref + 1 where field is INTEGER preserves INTEGER (field-based index scenario)

Related Issues

Resolves #5660
See also: #5670 (alternative plan-layer fix that is ineffective because the serialization layer overrides it)

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • New functionality has javadoc added.
  • New functionality has a user manual doc added.
  • New PPL command checklist all confirmed.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff or -s.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit a114262)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Possible Issue

The condition checks !call.getType().getSqlTypeName().equals(SqlTypeName.BIGINT) but does not verify that the operands were actually widened to BIGINT. If operands remain at their original narrower type (e.g., due to future changes in widening logic), the cast will be applied unnecessarily or incorrectly. The fix assumes widening always occurs when allowNumericTypeWiden is true, but does not confirm operand types changed.

if (allowNumericTypeWiden
    && SqlTypeUtil.isExactNumeric(call.getType())
    && !call.getType().getSqlTypeName().equals(SqlTypeName.BIGINT)) {
  RelDataType targetType =
      OpenSearchTypeFactory.TYPE_FACTORY.createTypeWithNullability(
          call.getType(), call.getType().isNullable());
  result = helper.rexBuilder.makeCast(targetType, result);
}

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit ee33606

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Latest suggestions up to a114262

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Use result's nullability for cast

The targetType is created from call.getType() but should be created from
result.getType() after standardization. The standardized result may have a different
type (e.g., BIGINT) than the original call, and the cast should preserve the
original call type, not recreate it from itself.

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/RexStandardizer.java [89-96]

 if (allowNumericTypeWiden
     && SqlTypeUtil.isExactNumeric(call.getType())
     && !call.getType().getSqlTypeName().equals(SqlTypeName.BIGINT)) {
   RelDataType targetType =
       OpenSearchTypeFactory.TYPE_FACTORY.createTypeWithNullability(
-          call.getType(), call.getType().isNullable());
+          call.getType(), result.getType().isNullable());
   result = helper.rexBuilder.makeCast(targetType, result);
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that targetType should use result.getType().isNullable() instead of call.getType().isNullable() since the standardized result may have different nullability characteristics. This ensures the cast preserves the correct nullability from the standardized expression rather than the original call.

Medium

Previous suggestions

Suggestions up to commit 3cca658
CategorySuggestion                                                                                                                                    Impact
General
Remove redundant type creation

The targetType is created from call.getType() with the same nullability, making the
cast redundant. Consider using call.getType() directly in makeCast() to avoid
unnecessary type creation, or verify if a different type transformation is intended.

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/RexStandardizer.java [89-96]

 if (allowNumericTypeWiden
     && SqlTypeUtil.isExactNumeric(call.getType())
     && !call.getType().getSqlTypeName().equals(SqlTypeName.BIGINT)) {
-  RelDataType targetType =
-      OpenSearchTypeFactory.TYPE_FACTORY.createTypeWithNullability(
-          call.getType(), call.getType().isNullable());
-  result = helper.rexBuilder.makeCast(targetType, result);
+  result = helper.rexBuilder.makeCast(call.getType(), result);
 }
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies that targetType is created from call.getType() with the same nullability, making the intermediate variable potentially redundant. However, the cast itself serves a purpose in the logic (preserving integer types after widening), so this is a minor code simplification rather than a functional improvement.

Low
Suggestions up to commit ee33606
CategorySuggestion                                                                                                                                    Impact
Possible issue
Redundant cast to same type

The cast operation creates a targetType that is identical to call.getType() with the
same nullability, making the cast redundant. Consider either removing the cast
entirely or defining a different target type (e.g., BIGINT) if type widening is the
intended behavior.

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/RexStandardizer.java [89-96]

 if (allowNumericTypeWiden
     && SqlTypeUtil.isExactNumeric(call.getType())
     && !call.getType().getSqlTypeName().equals(SqlTypeName.BIGINT)) {
   RelDataType targetType =
-      OpenSearchTypeFactory.TYPE_FACTORY.createTypeWithNullability(
-          call.getType(), call.getType().isNullable());
+      OpenSearchTypeFactory.TYPE_FACTORY.createSqlType(SqlTypeName.BIGINT);
   result = helper.rexBuilder.makeCast(targetType, result);
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical logic error where targetType is created with the same type as call.getType(), making the cast operation redundant and defeating the purpose of type widening. The improved code properly widens to BIGINT type, which aligns with the condition checking that the type is not already BIGINT.

High

@ahkcs ahkcs added the bugFix label Aug 7, 2026

@dai-chen dai-chen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any unit or integration test to cover?

Pair<SqlOperator, List<RexNode>> normalized = RexNormalize.normalize(call.op, call.operands);
List<RexNode> standardizedOperands = visitList(normalized.right, helper, update);
return helper.rexBuilder.makeCall(call.getType(), normalized.left, standardizedOperands);
RexNode result =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

+1 to Chen's comment - lets add more testing coverage on the change

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 3cca658

…hdown.enabled=true` (opensearch-project#5660)

* Add Tests

Signed-off-by: Ajimelec Gonzalez <ajimelec@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit a114262

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] PPL query with mvindex() fails when plugins.calcite.pushdown.enabled=true

4 participants