From 32f355ba10c8870922d46ece6c03c9d4a2b9ab3a Mon Sep 17 00:00:00 2001 From: Yash Mayya Date: Tue, 6 Oct 2026 11:06:47 -0700 Subject: [PATCH 1/2] Fix CAST type in multi-stage EXPLAIN when asking servers --- .../query/planner/logical/RexExpressionUtils.java | 10 ++++++++++ .../planner/logical/RexExpressionUtilsTest.java | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java index 278e34ac0741..8bcf2bf6b634 100644 --- a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java +++ b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java @@ -34,6 +34,8 @@ import org.apache.calcite.avatica.util.ByteString; import org.apache.calcite.rel.core.AggregateCall; import org.apache.calcite.rel.core.Window; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; @@ -106,6 +108,14 @@ private static RexNode toRexCall(RelBuilder builder, RexExpression.FunctionCall // Calcite creates IN as SEARCH return builder.in(operands.get(0), operands.subList(1, operands.size())); } + if (operator.kind == SqlKind.CAST) { + // The second operand only holds the target type name (see handleCast). Calcite would use its type as the result. + RexNode operand = operands.get(0); + RelDataTypeFactory typeFactory = builder.getTypeFactory(); + RelDataType type = typeFactory.createTypeWithNullability(rexExpression.getDataType().toType(typeFactory), + operand.getType().isNullable()); + return builder.getRexBuilder().makeCast(type, operand); + } return builder.call(operator, operands); } diff --git a/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java index 0d5355c36271..fe39ce509053 100644 --- a/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java +++ b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java @@ -28,6 +28,7 @@ import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexNode; import org.apache.calcite.rex.RexUnknownAs; import org.apache.calcite.sql.SqlCollation; import org.apache.calcite.sql.SqlKind; @@ -92,6 +93,18 @@ public void testSqlKindFunctionNames() { assertSame(RexExpressionUtils.getAggFunction(sum0), SqlStdOperatorTable.SUM0); } + @Test + public void testCastRoundTrip() { + // Plan nodes hold the target type of CAST as a STRING literal operand. Field "a" is a nullable INTEGER. + RelBuilder relBuilder = + RelBuilder.create(Frameworks.newConfigBuilder().build()).values(new String[]{"a"}, 1, null); + RexExpression castCall = new RexExpression.FunctionCall(ColumnDataType.DOUBLE, SqlKind.CAST.name(), + List.of(new RexExpression.InputRef(0), new RexExpression.Literal(ColumnDataType.STRING, "DOUBLE"))); + RexNode rexNode = RexExpressionUtils.toRexNode(relBuilder, castCall); + Assert.assertTrue(rexNode.getType().isNullable()); + Assert.assertEquals(RexExpressionUtils.fromRexNode(rexNode), castCall); + } + @Test public void testHandleSearchNullLiteralInWithNullAsUnknown() { // Test: NULL IN (1, 2, 3) (when nullAs = UNKNOWN) From 81a977956876d3ee6043854d80e639583e4d7f3e Mon Sep 17 00:00:00 2001 From: Yash Mayya Date: Tue, 6 Oct 2026 17:08:48 -0700 Subject: [PATCH 2/2] Use static assert imports in testCastRoundTrip --- .../pinot/query/planner/logical/RexExpressionUtilsTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java index fe39ce509053..536b2ecb9483 100644 --- a/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java +++ b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java @@ -48,6 +48,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; /// Tests for RexExpressionUtils, focusing on the handleSearch method and null handling. @@ -101,8 +102,8 @@ public void testCastRoundTrip() { RexExpression castCall = new RexExpression.FunctionCall(ColumnDataType.DOUBLE, SqlKind.CAST.name(), List.of(new RexExpression.InputRef(0), new RexExpression.Literal(ColumnDataType.STRING, "DOUBLE"))); RexNode rexNode = RexExpressionUtils.toRexNode(relBuilder, castCall); - Assert.assertTrue(rexNode.getType().isNullable()); - Assert.assertEquals(RexExpressionUtils.fromRexNode(rexNode), castCall); + assertTrue(rexNode.getType().isNullable()); + assertEquals(RexExpressionUtils.fromRexNode(rexNode), castCall); } @Test