This is an automated email from the ASF dual-hosted git repository.

jackie 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 a68a44b6c11 Trim of redundant IS_TRUE function when creation filter on 
leaf stage (#16966)
a68a44b6c11 is described below

commit a68a44b6c1183ef75bb05640a9425dae8da9ea90
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Wed Oct 8 14:47:47 2025 -0700

    Trim of redundant IS_TRUE function when creation filter on leaf stage 
(#16966)
---
 .../request/context/RequestContextUtils.java       | 40 +++++++++++++++------
 .../tests/NullHandlingIntegrationTest.java         | 41 ++++++++++++++++++++++
 2 files changed, 71 insertions(+), 10 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java
index 4517b1f43d0..fabaa72b100 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/request/context/RequestContextUtils.java
@@ -106,11 +106,21 @@ public class RequestContextUtils {
    *          missing an EQUALS filter operator.
    */
   public static FilterContext getFilter(Expression thriftExpression) {
+    Function function = thriftExpression.getFunctionCall();
+    // Trim off outer IS_TRUE function as it is redundant
+    if (function != null && function.getOperator().equals("istrue")) {
+      return getFilter(function.getOperands().get(0));
+    } else {
+      return getFilterInner(thriftExpression);
+    }
+  }
+
+  private static FilterContext getFilterInner(Expression thriftExpression) {
     ExpressionType type = thriftExpression.getType();
     switch (type) {
       case FUNCTION:
         Function thriftFunction = thriftExpression.getFunctionCall();
-        return getFilter(thriftFunction);
+        return getFilterInner(thriftFunction);
       case IDENTIFIER:
         // Convert "WHERE a" to "WHERE a = true"
         return FilterContext.forPredicate(new 
EqPredicate(getExpression(thriftExpression), "true"));
@@ -121,7 +131,7 @@ public class RequestContextUtils {
     }
   }
 
-  public static FilterContext getFilter(Function thriftFunction) {
+  private static FilterContext getFilterInner(Function thriftFunction) {
     String functionOperator = thriftFunction.getOperator();
 
     // convert "WHERE startsWith(col, 'str')" to "WHERE startsWith(col, 'str') 
= true"
@@ -137,7 +147,7 @@ public class RequestContextUtils {
       case AND: {
         List<FilterContext> children = new ArrayList<>(numOperands);
         for (Expression operand : operands) {
-          FilterContext filter = getFilter(operand);
+          FilterContext filter = getFilterInner(operand);
           if (!filter.isConstant()) {
             children.add(filter);
           } else {
@@ -158,7 +168,7 @@ public class RequestContextUtils {
       case OR: {
         List<FilterContext> children = new ArrayList<>(numOperands);
         for (Expression operand : operands) {
-          FilterContext filter = getFilter(operand);
+          FilterContext filter = getFilterInner(operand);
           if (!filter.isConstant()) {
             children.add(filter);
           } else {
@@ -178,7 +188,7 @@ public class RequestContextUtils {
       }
       case NOT: {
         assert numOperands == 1;
-        FilterContext filter = getFilter(operands.get(0));
+        FilterContext filter = getFilterInner(operands.get(0));
         if (!filter.isConstant()) {
           return FilterContext.forNot(filter);
         } else {
@@ -287,11 +297,21 @@ public class RequestContextUtils {
    *          missing an EQUALS filter operator.
    */
   public static FilterContext getFilter(ExpressionContext filterExpression) {
+    FunctionContext function = filterExpression.getFunction();
+    // Trim off outer IS_TRUE function as it is redundant
+    if (function != null && function.getFunctionName().equals("istrue")) {
+      return getFilter(function.getArguments().get(0));
+    } else {
+      return getFilterInner(filterExpression);
+    }
+  }
+
+  private static FilterContext getFilterInner(ExpressionContext 
filterExpression) {
     ExpressionContext.Type type = filterExpression.getType();
     switch (type) {
       case FUNCTION:
         FunctionContext filterFunction = filterExpression.getFunction();
-        return getFilter(filterFunction);
+        return getFilterInner(filterFunction);
       case IDENTIFIER:
         return FilterContext.forPredicate(
             new EqPredicate(filterExpression, 
getStringValue(RequestUtils.getLiteralExpression(true))));
@@ -302,7 +322,7 @@ public class RequestContextUtils {
     }
   }
 
-  public static FilterContext getFilter(FunctionContext filterFunction) {
+  private static FilterContext getFilterInner(FunctionContext filterFunction) {
     String functionOperator = filterFunction.getFunctionName().toUpperCase();
 
     // convert "WHERE startsWith(col, 'str')" to "WHERE startsWith(col, 'str') 
= true"
@@ -317,7 +337,7 @@ public class RequestContextUtils {
       case AND: {
         List<FilterContext> children = new ArrayList<>(numOperands);
         for (ExpressionContext operand : operands) {
-          FilterContext filter = getFilter(operand);
+          FilterContext filter = getFilterInner(operand);
           if (!filter.isConstant()) {
             children.add(filter);
           } else {
@@ -338,7 +358,7 @@ public class RequestContextUtils {
       case OR: {
         List<FilterContext> children = new ArrayList<>(numOperands);
         for (ExpressionContext operand : operands) {
-          FilterContext filter = getFilter(operand);
+          FilterContext filter = getFilterInner(operand);
           if (!filter.isConstant()) {
             children.add(filter);
           } else {
@@ -358,7 +378,7 @@ public class RequestContextUtils {
       }
       case NOT: {
         assert numOperands == 1;
-        FilterContext filter = getFilter(operands.get(0));
+        FilterContext filter = getFilterInner(operands.get(0));
         if (!filter.isConstant()) {
           return FilterContext.forNot(filter);
         } else {
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 9e31c21292b..c25af339add 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
@@ -459,4 +459,45 @@ public class NullHandlingIntegrationTest extends 
BaseClusterIntegrationTestSet
         {String.format("SELECT tan(null) FROM %s", getTableName()), "null"}
     };
   }
+
+  /// This test ensures IS_TRUE can be trimmed off on leaf stage
+  @Test(dataProvider = "useBothQueryEngines")
+  public void testFilteredAggregationNoScanInFilter(boolean 
useMultiStageQueryEngine)
+      throws Exception {
+    setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+
+    String query = "SELECT city, COUNT(*), COUNT(*) FILTER(WHERE description = 
'unknown') FROM mytable GROUP BY city";
+
+    if (useMultiStageQueryEngine) {
+      // MSE will insert IS_TRUE to the aggregate filter
+      explainLogical(query,
+          "Execution Plan\n"
+              + "PinotLogicalAggregate(group=[{0}], agg#0=[COUNT($1)], 
agg#1=[COUNT($2)], aggType=[FINAL])\n"
+              + "  PinotLogicalExchange(distribution=[hash[0]])\n"
+              + "    PinotLogicalAggregate(group=[{0}], agg#0=[COUNT()], 
agg#1=[COUNT() FILTER $1], aggType=[LEAF])\n"
+              + "      LogicalProject(city=[$5], $f1=[IS TRUE(=($7, 
_UTF-8'unknown'))])\n"
+              + "        PinotLogicalTableScan(table=[[default, mytable]])\n");
+      // IS_TRUE should be trimmed off, then the filter becomes always false 
in the server execution plan
+      explainAskingServers(query,
+          "Execution Plan\n"
+              + "PinotLogicalAggregate(group=[{0}], agg#0=[COUNT($1)], 
agg#1=[COUNT($2)], aggType=[FINAL])\n"
+              + "  PinotLogicalExchange(distribution=[hash[0]])\n"
+              + "    LeafStageCombineOperator(table=[mytable])\n"
+              + "      StreamingInstanceResponse\n"
+              + "        CombineGroupBy\n"
+              + "          GroupByFiltered(groupKeys=[[city]], 
aggregations=[[count(*), count(*)]])\n"
+              + "            Project(columns=[[city]])\n"
+              + "              DocIdSet(maxDocs=[20000])\n"
+              + "                FilterEmpty\n"
+              + "            Project(columns=[[city]])\n"
+              + "              DocIdSet(maxDocs=[20000])\n"
+              + "                FilterMatchEntireSegment(numDocs=[100])\n"
+      );
+    }
+
+    // There should be no scan in the filter since description = 'unknown' 
matches no value
+    JsonNode response = postQuery(query);
+    assertNoError(response);
+    assertEquals(response.get("numEntriesScannedInFilter").asLong(), 0L);
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to