This is an automated email from the ASF dual-hosted git repository.
yashmayya pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 54f29a9d0f1 Fix MSE handle search null handling issue by adding an or
clause with a null check when required (#18729)
54f29a9d0f1 is described below
commit 54f29a9d0f11960827b93ec7f5abbd12229a36d1
Author: Cristian Pop <[email protected]>
AuthorDate: Mon Jun 22 17:56:23 2026 +0300
Fix MSE handle search null handling issue by adding an or clause with a
null check when required (#18729)
---
.../tests/NullHandlingIntegrationTest.java | 30 ++
.../query/planner/logical/RexExpressionUtils.java | 73 ++-
.../planner/logical/RexExpressionUtilsTest.java | 570 +++++++++++++++++++++
3 files changed, 663 insertions(+), 10 deletions(-)
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/NullHandlingIntegrationTest.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/NullHandlingIntegrationTest.java
index f0375a74ca0..c77573621e3 100644
---
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/NullHandlingIntegrationTest.java
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/NullHandlingIntegrationTest.java
@@ -167,6 +167,36 @@ public class NullHandlingIntegrationTest extends
BaseClusterIntegrationTestSet
testQuery(query);
}
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testCountWithGivenOrNullSalary(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+
+ String query = "SELECT COUNT(*) FROM " + getTableName() + " WHERE salary =
4398214 OR salary IS NULL";
+
+ JsonNode response = postQuery(query);
+ assertTrue(response.get("exceptions").isEmpty());
+ JsonNode rows = response.get("resultTable").get("rows");
+ assertEquals(rows.size(), 1);
+ JsonNode count = rows.get(0).get(0);
+ assertEquals(count.asInt(), 57);
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testCountWithDifferentOrNullSalary(boolean
useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+
+ String query = "SELECT COUNT(*) FROM " + getTableName() + " WHERE salary
!= 46314 OR salary IS NULL";
+
+ JsonNode response = postQuery(query);
+ assertTrue(response.get("exceptions").isEmpty());
+ JsonNode rows = response.get("resultTable").get("rows");
+ assertEquals(rows.size(), 1);
+ JsonNode count = rows.get(0).get(0);
+ assertEquals(count.asInt(), 99);
+ }
+
@Test(dataProvider = "useBothQueryEngines")
public void testCaseWithNullSalary(boolean useMultiStageQueryEngine)
throws Exception {
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 9122cb6a36e..185b1ee74e8 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
@@ -36,6 +36,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.SqlAggFunction;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
@@ -348,26 +349,33 @@ public class RexExpressionUtils {
assert sarg != null;
if (sarg.isPoints()) {
if (leftOperand instanceof RexLiteral) {
- return evaluateLiteralIn((RexLiteral) leftOperand,
sarg.rangeSet.asRanges());
+ return evaluateLiteralIn((RexLiteral) leftOperand,
sarg.rangeSet.asRanges(), sarg.nullAs);
}
- return new RexExpression.FunctionCall(ColumnDataType.BOOLEAN,
SqlKind.IN.name(),
+ RexExpression inExpr = new
RexExpression.FunctionCall(ColumnDataType.BOOLEAN, SqlKind.IN.name(),
toSearchFunctionOperands(leftOperand, sarg.rangeSet.asRanges(),
dataType));
+ return addNullCheckIfRequired(leftOperand, sarg.nullAs, inExpr);
} else if (sarg.isComplementedPoints()) {
if (leftOperand instanceof RexLiteral) {
- return evaluateLiteralNotIn((RexLiteral) leftOperand,
sarg.rangeSet.complement().asRanges());
+ return evaluateLiteralNotIn((RexLiteral) leftOperand,
sarg.rangeSet.complement().asRanges(), sarg.nullAs);
}
- return new RexExpression.FunctionCall(ColumnDataType.BOOLEAN,
SqlKind.NOT_IN.name(),
+ RexExpression notInExpr = new
RexExpression.FunctionCall(ColumnDataType.BOOLEAN, SqlKind.NOT_IN.name(),
toSearchFunctionOperands(leftOperand,
sarg.rangeSet.complement().asRanges(), dataType));
+ return addNullCheckIfRequired(leftOperand, sarg.nullAs, notInExpr);
} else {
if (leftOperand instanceof RexLiteral) {
- return evaluateLiteralOrRanges((RexLiteral) leftOperand,
sarg.rangeSet.asRanges());
+ return evaluateLiteralOrRanges((RexLiteral) leftOperand,
sarg.rangeSet.asRanges(), sarg.nullAs);
}
- Set<Range> ranges = sarg.rangeSet.asRanges();
- return convertRangesToOr(dataType, leftOperand, ranges);
+ RexExpression orExpr = convertRangesToOr(dataType, leftOperand,
sarg.rangeSet.asRanges());
+ return addNullCheckIfRequired(leftOperand, sarg.nullAs, orExpr);
}
}
- private static RexExpression evaluateLiteralIn(RexLiteral leftOperand,
Set<Range> ranges) {
+ private static RexExpression evaluateLiteralIn(RexLiteral leftOperand,
Set<Range> ranges, RexUnknownAs nullAs) {
+ // No need to do normal evaluation if the literal is a null literal and
nulls need to be included/excluded, so we
+ // can return early. Otherwise, continue with normal evaluation
+ if (leftOperand.isNull() && nullAs != RexUnknownAs.UNKNOWN) {
+ return fromRexUnknownAs(nullAs);
+ }
Comparable leftVal = leftOperand.getValue();
for (Range range : ranges) {
if (range.lowerEndpoint().equals(leftVal)) {
@@ -377,7 +385,12 @@ public class RexExpressionUtils {
return RexExpression.Literal.FALSE;
}
- private static RexExpression evaluateLiteralNotIn(RexLiteral leftOperand,
Set<Range> ranges) {
+ private static RexExpression evaluateLiteralNotIn(RexLiteral leftOperand,
Set<Range> ranges, RexUnknownAs nullAs) {
+ // No need to do normal evaluation if the literal is a null literal and
nulls need to be included/excluded, so we
+ // can return early. Otherwise, continue with normal evaluation
+ if (leftOperand.isNull() && nullAs != RexUnknownAs.UNKNOWN) {
+ return fromRexUnknownAs(nullAs);
+ }
Comparable leftVal = leftOperand.getValue();
for (Range range : ranges) {
if (range.lowerEndpoint().equals(leftVal)) {
@@ -387,7 +400,17 @@ public class RexExpressionUtils {
return RexExpression.Literal.TRUE;
}
- private static RexExpression evaluateLiteralOrRanges(RexLiteral leftOperand,
Set<Range> ranges) {
+ private static RexExpression evaluateLiteralOrRanges(RexLiteral leftOperand,
Set<Range> ranges, RexUnknownAs nullAs) {
+ // No need to do normal evaluation if the literal is a null literal and
nulls need to be included/excluded, so we
+ // can return early. If the literal is a null literal but nulls should be
treated as unknown, we cannot continue
+ // with normal evaluation because it fails for null values and we cannot
evaluate an unknown value anyway, so we
+ // return false instead
+ if (leftOperand.isNull()) {
+ if (nullAs != RexUnknownAs.UNKNOWN) {
+ return fromRexUnknownAs(nullAs);
+ }
+ return RexExpression.Literal.FALSE;
+ }
Comparable leftVal = leftOperand.getValue();
for (Range range : ranges) {
if (range.contains(leftVal)) {
@@ -397,6 +420,36 @@ public class RexExpressionUtils {
return RexExpression.Literal.FALSE;
}
+ private static RexExpression fromRexUnknownAs(RexUnknownAs nullAs) {
+ switch (nullAs) {
+ case TRUE:
+ return RexExpression.Literal.TRUE;
+
+ case FALSE:
+ return RexExpression.Literal.FALSE;
+
+ default:
+ throw new IllegalArgumentException("Unsupported RexUnknownAs: " +
nullAs);
+ }
+ }
+
+ private static RexExpression addNullCheckIfRequired(RexNode leftOperand,
RexUnknownAs nullAs, RexExpression expr) {
+ switch (nullAs) {
+ case TRUE:
+ RexExpression isNullExpr = new
RexExpression.FunctionCall(ColumnDataType.BOOLEAN, SqlKind.IS_NULL.name(),
+ List.of(fromRexNode(leftOperand)));
+ return new RexExpression.FunctionCall(ColumnDataType.BOOLEAN,
SqlKind.OR.name(), List.of(expr, isNullExpr));
+
+ case FALSE:
+ RexExpression isNotNullExpr = new
RexExpression.FunctionCall(ColumnDataType.BOOLEAN, SqlKind.IS_NOT_NULL.name(),
+ List.of(fromRexNode(leftOperand)));
+ return new RexExpression.FunctionCall(ColumnDataType.BOOLEAN,
SqlKind.AND.name(), List.of(expr, isNotNullExpr));
+
+ default:
+ return expr;
+ }
+ }
+
private static RexExpression convertRangesToOr(ColumnDataType dataType,
RexNode leftOperand, Set<Range> ranges) {
int numRanges = ranges.size();
if (numRanges == 0) {
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
new file mode 100644
index 00000000000..4c2ae2c57a7
--- /dev/null
+++
b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/logical/RexExpressionUtilsTest.java
@@ -0,0 +1,570 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.query.planner.logical;
+
+import com.google.common.collect.ImmutableRangeSet;
+import com.google.common.collect.Range;
+import java.math.BigDecimal;
+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;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexUnknownAs;
+import org.apache.calcite.sql.SqlCollation;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Sarg;
+import org.apache.pinot.query.type.TypeFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+/**
+ * Tests for RexExpressionUtils, focusing on the handleSearch method and null
handling.
+ */
+public class RexExpressionUtilsTest {
+ private RexBuilder _rexBuilder;
+ private RelDataTypeFactory _typeFactory;
+
+ @BeforeClass
+ public void setup() {
+ _typeFactory = new TypeFactory();
+ _rexBuilder = new RexBuilder(_typeFactory);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralInWithNullAsUnknown() {
+ // Test: NULL IN (1, 2, 3) (when nullAs = UNKNOWN)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.UNKNOWN,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be FALSE
+ Assert.assertEquals(result, RexExpression.Literal.FALSE);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralInWithNullAsTrue() {
+ // Test: NULL IN (1, 2, 3) (when nullAs = TRUE)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be TRUE
+ Assert.assertEquals(result, RexExpression.Literal.TRUE);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralInWithNullAsFalse() {
+ // Test: NULL IN (1, 2, 3) (when nullAs = FALSE)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.FALSE,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be FALSE
+ Assert.assertEquals(result, RexExpression.Literal.FALSE);
+ }
+
+ @Test
+ public void testHandleSearchInWithNullAsUnknown() {
+ // Test: col IN (1, 2, 3) (when nullAs = UNKNOWN)
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.UNKNOWN,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be a simple IN expression without null check
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.IN.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 4); // col + 3
values
+ }
+
+ @Test
+ public void testHandleSearchInWithNullAsTrue() {
+ // Test: col IN (1, 2, 3) OR col IS NULL (when nullAs = TRUE)
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col IN (1, 2, 3)) OR (col IS NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.OR.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the IN expression
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.IN.name());
+
+ // Second operand should be IS NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchInWithNullAsFalse() {
+ // Test: col IN (1, 2) AND col IS NOT NULL (when nullAs = FALSE)
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.FALSE,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col IN (1, 2)) AND (col IS NOT NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.AND.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the IN expression
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.IN.name());
+
+ // Second operand should be IS NOT NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NOT_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralNotInWithNullAsUnknown() {
+ // Test: NULL NOT IN (1, 2, 3) (when nullAs = UNKNOWN)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.UNKNOWN,
rangeSetBuilder.build()).negate();
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // This should be UNKNOWN, not TRUE, since we cannot evaluate an unknown
value, but this tests the current
+ // behavior. It shouldn't matter though, because Calcite folds these away
before reaching this code
+ Assert.assertEquals(result, RexExpression.Literal.TRUE);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralNotInWithNullAsTrue() {
+ // Test: NULL NOT IN (1, 2, 3) (when nullAs = TRUE)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.FALSE,
rangeSetBuilder.build()).negate();
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be TRUE
+ Assert.assertEquals(result, RexExpression.Literal.TRUE);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralNotInWithNullAsFalse() {
+ // Test: NULL NOT IN (1, 2, 3) (when nullAs = FALSE)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE,
rangeSetBuilder.build()).negate();
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be FALSE
+ Assert.assertEquals(result, RexExpression.Literal.FALSE);
+ }
+
+ @Test
+ public void testHandleSearchNotInWithNullAsTrue() {
+ // Test: col NOT IN (1, 2) OR col IS NULL
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.FALSE,
rangeSetBuilder.build()).negate();
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col NOT IN (1, 2)) OR (col IS NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.OR.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the NOT IN expression
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.NOT_IN.name());
+
+ // Second operand should be IS NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchNotInWithNullAsFalse() {
+ // Test: col NOT IN (1, 2) AND col IS NOT NULL
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE,
rangeSetBuilder.build()).negate();
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col NOT IN (1, 2)) AND (col IS NOT NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.AND.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the NOT IN expression
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.NOT_IN.name());
+
+ // Second operand should be IS NOT NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NOT_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralRangeWithNullAsUnknown() {
+ // Test: NULL > 10 with RexUnknownAs.UNKNOWN
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ Range<BigDecimal> range = Range.greaterThan(BigDecimal.valueOf(10));
+ ImmutableRangeSet<BigDecimal> rangeSet = ImmutableRangeSet.of(range);
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.UNKNOWN, rangeSet);
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be FALSE
+ Assert.assertEquals(result, RexExpression.Literal.FALSE);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralRangeWithNullAsTrue() {
+ // Test: NULL > 10 (when nullAs = TRUE)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ Range<BigDecimal> range = Range.greaterThan(BigDecimal.valueOf(10));
+ ImmutableRangeSet<BigDecimal> rangeSet = ImmutableRangeSet.of(range);
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE, rangeSet);
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be TRUE
+ Assert.assertEquals(result, RexExpression.Literal.TRUE);
+ }
+
+ @Test
+ public void testHandleSearchNullLiteralRangeWithNullAsFalse() {
+ // Test: NULL > 10 (when nullAs = FALSE)
+ RexLiteral literal =
_rexBuilder.makeNullLiteral(_typeFactory.createSqlType(SqlTypeName.INTEGER));
+
+ Range<BigDecimal> range = Range.greaterThan(BigDecimal.valueOf(10));
+ ImmutableRangeSet<BigDecimal> rangeSet = ImmutableRangeSet.of(range);
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.FALSE, rangeSet);
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, literal, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be FALSE
+ Assert.assertEquals(result, RexExpression.Literal.FALSE);
+ }
+
+ @Test
+ public void testHandleSearchRangeWithNullAsTrue() {
+ // Test: col > 10 OR col IS NULL (when nullAs = TRUE)
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ Range<BigDecimal> range = Range.greaterThan(BigDecimal.valueOf(10));
+ ImmutableRangeSet<BigDecimal> rangeSet = ImmutableRangeSet.of(range);
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE, rangeSet);
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col > 10) OR (col IS NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.OR.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the range expression (GREATER_THAN)
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.GREATER_THAN.name());
+
+ // Second operand should be IS NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchRangeWithNullAsFalse() {
+ // Test: col BETWEEN 10 AND 20 AND col IS NOT NULL (when nullAs = FALSE)
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ Range<BigDecimal> range = Range.closed(BigDecimal.valueOf(10),
BigDecimal.valueOf(20));
+ ImmutableRangeSet<BigDecimal> rangeSet = ImmutableRangeSet.of(range);
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.FALSE, rangeSet);
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col >= 10 AND col <= 20) AND (col IS NOT NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.AND.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the range expression (another AND with
GREATER_THAN_OR_EQUAL and LESS_THAN_OR_EQUAL)
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.AND.name());
+
+ // Second operand should be IS NOT NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NOT_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchMultipleRangesWithNullAsTrue() {
+ // Test: (col < 5 OR col > 20) OR col IS NULL (when nullAs = TRUE)
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.INTEGER), 0);
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.lessThan(BigDecimal.valueOf(5)));
+ rangeSetBuilder.add(Range.greaterThan(BigDecimal.valueOf(20)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.TRUE,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: ((col < 5) OR (col > 20)) OR (col IS NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.OR.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be an OR of the two ranges
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.OR.name());
+
+ // Second operand should be IS NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchWithStringType() {
+ // Test: col IN ('a', 'b', 'c') OR col IS NULL
+ RexInputRef inputRef =
_rexBuilder.makeInputRef(_typeFactory.createSqlType(SqlTypeName.VARCHAR), 0);
+
+ ImmutableRangeSet.Builder<NlsString> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(new NlsString("a", "UTF-8",
SqlCollation.COERCIBLE)));
+ rangeSetBuilder.add(Range.singleton(new NlsString("b", "UTF-8",
SqlCollation.COERCIBLE)));
+ rangeSetBuilder.add(Range.singleton(new NlsString("c", "UTF-8",
SqlCollation.COERCIBLE)));
+ Sarg<NlsString> sarg = Sarg.of(RexUnknownAs.TRUE, rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.VARCHAR));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, inputRef, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be: (col IN ('a', 'b', 'c')) OR (col IS NULL)
+ Assert.assertTrue(result instanceof RexExpression.FunctionCall);
+ RexExpression.FunctionCall funcCall = (RexExpression.FunctionCall) result;
+ Assert.assertEquals(funcCall.getFunctionName(), SqlKind.OR.name());
+ Assert.assertEquals(funcCall.getFunctionOperands().size(), 2);
+
+ // First operand should be the IN expression
+ RexExpression firstOperand = funcCall.getFunctionOperands().get(0);
+ Assert.assertTrue(firstOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
firstOperand).getFunctionName(), SqlKind.IN.name());
+
+ // Second operand should be IS NULL
+ RexExpression secondOperand = funcCall.getFunctionOperands().get(1);
+ Assert.assertTrue(secondOperand instanceof RexExpression.FunctionCall);
+ Assert.assertEquals(((RexExpression.FunctionCall)
secondOperand).getFunctionName(), SqlKind.IS_NULL.name());
+ }
+
+ @Test
+ public void testHandleSearchLiteralInEvaluation() {
+ // Test: 5 IN (1, 2, 3) should evaluate to FALSE
+ RexLiteral leftLiteral =
_rexBuilder.makeExactLiteral(BigDecimal.valueOf(5));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.UNKNOWN,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, leftLiteral, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be evaluated to FALSE literal
+ Assert.assertTrue(result instanceof RexExpression.Literal);
+ Assert.assertEquals(result, RexExpression.Literal.FALSE);
+ }
+
+ @Test
+ public void testHandleSearchLiteralInMatchEvaluation() {
+ // Test: 2 IN (1, 2, 3) should evaluate to TRUE
+ RexLiteral leftLiteral =
_rexBuilder.makeExactLiteral(BigDecimal.valueOf(2));
+
+ ImmutableRangeSet.Builder<BigDecimal> rangeSetBuilder =
ImmutableRangeSet.builder();
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(1)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(2)));
+ rangeSetBuilder.add(Range.singleton(BigDecimal.valueOf(3)));
+ Sarg<BigDecimal> sarg = Sarg.of(RexUnknownAs.UNKNOWN,
rangeSetBuilder.build());
+
+ RexLiteral searchLiteral = _rexBuilder.makeSearchArgumentLiteral(sarg,
+ _typeFactory.createSqlType(SqlTypeName.INTEGER));
+ RexCall searchCall = (RexCall)
_rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, leftLiteral, searchLiteral);
+
+ RexExpression result = RexExpressionUtils.fromRexCall(searchCall);
+
+ // Should be evaluated to TRUE literal
+ Assert.assertTrue(result instanceof RexExpression.Literal);
+ Assert.assertEquals(result, RexExpression.Literal.TRUE);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]