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]


Reply via email to