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

siddteotia 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 d49d1173f9 Support filtering on bool/scalar fields without evaluator 
(#8518)
d49d1173f9 is described below

commit d49d1173f9b4b7f1e4e834db593dc6def20a9217
Author: Vivek Iyer Vaidyanathan <[email protected]>
AuthorDate: Mon May 2 16:58:46 2022 -0700

    Support filtering on bool/scalar fields without evaluator (#8518)
    
    * Support filtering on bool/scalar fields without evaluator
    
    Fixes the bugs in #8444 and #8487.
    Added unit tests.
    
    * Address review comments and refactor code
    
    * Address review comments 2
    
    * RequestContextUtil changes
    
    * Review comments in RequestContextUtils
    
    * Refactor RequestContextUtils
    
    * Refactor ComparisonPredicateRewriter
    
    Co-authored-by: Vivek Iyer Vaidyanathan <[email protected]>
---
 .../request/context/RequestContextUtils.java       |  69 ++++-
 .../rewriter/PredicateComparisonRewriter.java      |  89 +++++-
 .../pinot/sql/parsers/CalciteSqlCompilerTest.java  | 326 +++++++++++++++------
 .../core/query/request/context/QueryContext.java   |   3 -
 .../apache/pinot/queries/BooleanQueriesTest.java   |  15 +
 .../pinot/queries/ExplainPlanQueriesTest.java      |  69 ++++-
 .../pinot/queries/FilteredAggregationsTest.java    |  22 ++
 7 files changed, 465 insertions(+), 128 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 2fb53b0a6f..3dbb3fd046 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
@@ -21,6 +21,7 @@ package org.apache.pinot.common.request.context;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import org.apache.commons.lang3.EnumUtils;
 import org.apache.pinot.common.request.Expression;
 import org.apache.pinot.common.request.ExpressionType;
 import org.apache.pinot.common.request.FilterOperator;
@@ -37,6 +38,7 @@ import 
org.apache.pinot.common.request.context.predicate.RegexpLikePredicate;
 import org.apache.pinot.common.request.context.predicate.TextMatchPredicate;
 import org.apache.pinot.common.utils.RegexpPatternConverterUtils;
 import org.apache.pinot.common.utils.request.FilterQueryTree;
+import org.apache.pinot.common.utils.request.RequestUtils;
 import org.apache.pinot.pql.parsers.Pql2Compiler;
 import org.apache.pinot.pql.parsers.pql2.ast.AstNode;
 import org.apache.pinot.pql.parsers.pql2.ast.FilterKind;
@@ -163,10 +165,37 @@ public class RequestContextUtils {
   /**
    * Converts the given Thrift {@link Expression} into a {@link FilterContext}.
    * <p>NOTE: Currently the query engine only accepts string literals as the 
right-hand side of the predicate, so we
-   *          always convert the right-hand side expressions into strings.
+   *          always convert the right-hand side expressions into strings. We 
also update boolean predicates that are
+   *          missing an EQUALS filter operator.
    */
   public static FilterContext getFilter(Expression thriftExpression) {
-    Function thriftFunction = thriftExpression.getFunctionCall();
+    ExpressionType type = thriftExpression.getType();
+    switch (type) {
+      case FUNCTION:
+        Function thriftFunction = thriftExpression.getFunctionCall();
+        return getFilter(thriftFunction);
+      case IDENTIFIER:
+        // Convert "WHERE a" to "WHERE a = true"
+        return new FilterContext(FilterContext.Type.PREDICATE, null,
+            new EqPredicate(getExpression(thriftExpression), 
getStringValue(RequestUtils.getLiteralExpression(true))));
+      case LITERAL:
+        // TODO: Handle literals.
+        throw new IllegalStateException();
+      default:
+        throw new IllegalStateException();
+    }
+  }
+
+  public static FilterContext getFilter(Function thriftFunction) {
+    String functionOperator = thriftFunction.getOperator();
+
+    // convert "WHERE startsWith(col, 'str')" to "WHERE startsWith(col, 'str') 
= true"
+    if (!EnumUtils.isValidEnum(FilterKind.class, functionOperator)) {
+      return new FilterContext(FilterContext.Type.PREDICATE, null,
+          new 
EqPredicate(ExpressionContext.forFunction(getFunction(thriftFunction)),
+              getStringValue(RequestUtils.getLiteralExpression(true))));
+    }
+
     FilterKind filterKind = 
FilterKind.valueOf(thriftFunction.getOperator().toUpperCase());
     List<Expression> operands = thriftFunction.getOperands();
     int numOperands = operands.size();
@@ -185,7 +214,8 @@ public class RequestContextUtils {
         return new FilterContext(FilterContext.Type.OR, children, null);
       case NOT:
         assert numOperands == 1;
-        return new FilterContext(FilterContext.Type.NOT, new 
ArrayList<>(Collections.singletonList(getFilter(operands.get(0)))), null);
+        return new FilterContext(FilterContext.Type.NOT,
+            new 
ArrayList<>(Collections.singletonList(getFilter(operands.get(0)))), null);
       case EQUALS:
         return new FilterContext(FilterContext.Type.PREDICATE, null,
             new EqPredicate(getExpression(operands.get(0)), 
getStringValue(operands.get(1))));
@@ -264,10 +294,36 @@ public class RequestContextUtils {
   /**
    * Converts the given filter {@link ExpressionContext} into a {@link 
FilterContext}.
    * <p>NOTE: Currently the query engine only accepts string literals as the 
right-hand side of the predicate, so we
-   *          always convert the right-hand side expressions into strings.
+   *          always convert the right-hand side expressions into strings. We 
also update boolean predicates that are
+   *          missing an EQUALS filter operator.
    */
   public static FilterContext getFilter(ExpressionContext filterExpression) {
-    FunctionContext filterFunction = filterExpression.getFunction();
+    ExpressionContext.Type type = filterExpression.getType();
+    switch (type) {
+      case FUNCTION:
+        FunctionContext filterFunction = filterExpression.getFunction();
+        return getFilter(filterFunction);
+      case IDENTIFIER:
+        return new FilterContext(FilterContext.Type.PREDICATE, null,
+            new EqPredicate(filterExpression, 
getStringValue(RequestUtils.getLiteralExpression(true))));
+      case LITERAL:
+        // TODO: Handle literals
+        throw new IllegalStateException();
+      default:
+        throw new IllegalStateException();
+    }
+  }
+
+  public static FilterContext getFilter(FunctionContext filterFunction) {
+    String functionOperator = filterFunction.getFunctionName().toUpperCase();
+
+    // convert "WHERE startsWith(col, 'str')" to "WHERE startsWith(col, 'str') 
= true"
+    if (!EnumUtils.isValidEnum(FilterKind.class, functionOperator)) {
+      return new FilterContext(FilterContext.Type.PREDICATE, null,
+          new EqPredicate(ExpressionContext.forFunction(filterFunction),
+              getStringValue(RequestUtils.getLiteralExpression(true))));
+    }
+
     FilterKind filterKind = 
FilterKind.valueOf(filterFunction.getFunctionName().toUpperCase());
     List<ExpressionContext> operands = filterFunction.getArguments();
     int numOperands = operands.size();
@@ -286,7 +342,8 @@ public class RequestContextUtils {
         return new FilterContext(FilterContext.Type.OR, children, null);
       case NOT:
         assert numOperands == 1;
-        return new FilterContext(FilterContext.Type.NOT, new 
ArrayList<>(Collections.singletonList(getFilter(operands.get(0)))), null);
+        return new FilterContext(FilterContext.Type.NOT,
+            new 
ArrayList<>(Collections.singletonList(getFilter(operands.get(0)))), null);
       case EQUALS:
         return new FilterContext(FilterContext.Type.PREDICATE, null,
             new EqPredicate(operands.get(0), getStringValue(operands.get(1))));
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/PredicateComparisonRewriter.java
 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/PredicateComparisonRewriter.java
index ae5c8ae7a6..d24945baf0 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/PredicateComparisonRewriter.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/rewriter/PredicateComparisonRewriter.java
@@ -18,9 +18,12 @@
  */
 package org.apache.pinot.sql.parsers.rewriter;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import org.apache.commons.lang3.EnumUtils;
 import org.apache.pinot.common.request.Expression;
+import org.apache.pinot.common.request.ExpressionType;
 import org.apache.pinot.common.request.Function;
 import org.apache.pinot.common.request.PinotQuery;
 import org.apache.pinot.common.utils.request.RequestUtils;
@@ -33,34 +36,69 @@ public class PredicateComparisonRewriter implements 
QueryRewriter {
   public PinotQuery rewrite(PinotQuery pinotQuery) {
     Expression filterExpression = pinotQuery.getFilterExpression();
     if (filterExpression != null) {
-      
pinotQuery.setFilterExpression(updateComparisonPredicate(filterExpression));
+      pinotQuery.setFilterExpression(updatePredicate(filterExpression));
     }
     Expression havingExpression = pinotQuery.getHavingExpression();
     if (havingExpression != null) {
-      
pinotQuery.setHavingExpression(updateComparisonPredicate(havingExpression));
+      pinotQuery.setHavingExpression(updatePredicate(havingExpression));
     }
     return pinotQuery;
   }
 
-  // This method converts a predicate expression to the what Pinot could 
evaluate.
-  // For comparison expression, left operand could be any expression, but 
right operand only
-  // supports literal.
-  // E.g. 'WHERE a > b' will be updated to 'WHERE a - b > 0'
-  private static Expression updateComparisonPredicate(Expression expression) {
+  /**
+   * This method converts an expression to what Pinot could evaluate.
+   * 1. For comparison expression, left operand could be any expression, but 
right operand only
+   *    supports literal. E.g. 'WHERE a > b' will be converted to 'WHERE a - b 
> 0'
+   * 2. Updates boolean predicates (literals and scalar functions) that are 
missing an EQUALS filter.
+   *    E.g. 1:  'WHERE a' will be updated to 'WHERE a = true'
+   *    E.g. 2: "WHERE startsWith(col, 'str')" will be updated to "WHERE 
startsWith(col, 'str') = true"
+   *
+   * @param expression current expression in the expression tree
+   * @return re-written expression.
+   */
+  private static Expression updatePredicate(Expression expression) {
+    ExpressionType type = expression.getType();
+
+    switch (type) {
+      case FUNCTION:
+        return updateFunctionExpression(expression);
+      case IDENTIFIER:
+        return convertPredicateToEqualsBooleanExpression(expression);
+      case LITERAL:
+        // TODO: Convert literals to boolean expressions
+        return expression;
+      default:
+        throw new IllegalStateException();
+    }
+  }
+
+  /**
+   * Rewrites a function expression.
+   *
+   * @param expression
+   * @return re-written expression
+   */
+  private static Expression updateFunctionExpression(Expression expression) {
     Function function = expression.getFunctionCall();
-    if (function != null) {
-      FilterKind filterKind;
-      try {
-        filterKind = FilterKind.valueOf(function.getOperator());
-      } catch (Exception e) {
-        throw new SqlCompilationException("Unsupported filter kind: " + 
function.getOperator());
-      }
+    String functionOperator = function.getOperator();
+
+    if (!EnumUtils.isValidEnum(FilterKind.class, functionOperator)) {
+      // If the function is not of FilterKind, we have to rewrite the function.
+      // Example: A query like "select col1 from table where startsWith(col1, 
'myStr') AND col2 > 10;" should be
+      //          rewritten to "select col1 from table where startsWith(col1, 
'myStr') = true AND col2 > 10;".
+      expression = convertPredicateToEqualsBooleanExpression(expression);
+      return expression;
+    } else {
+      FilterKind filterKind = FilterKind.valueOf(function.getOperator());
       List<Expression> operands = function.getOperands();
       switch (filterKind) {
         case AND:
         case OR:
         case NOT:
-          
operands.replaceAll(PredicateComparisonRewriter::updateComparisonPredicate);
+          for (int i = 0; i < operands.size(); i++) {
+            Expression operand = operands.get(i);
+            operands.set(i, updatePredicate(operand));
+          }
           break;
         case EQUALS:
         case NOT_EQUALS:
@@ -102,9 +140,30 @@ public class PredicateComparisonRewriter implements 
QueryRewriter {
           break;
       }
     }
+
     return expression;
   }
 
+  /**
+   * Rewrite predicates to boolean expressions with EQUALS operator
+   *     Example1: "select * from table where col1" converts to
+   *                "select * from table where col1 = true"
+   *     Example2: "select * from table where startsWith(col1, 'str')" 
converts to
+   *               "select * from table where startsWith(col1, 'str') = true"
+   * @param expression Expression
+   * @return Rewritten expression
+   */
+  private static Expression 
convertPredicateToEqualsBooleanExpression(Expression expression) {
+      Expression newExpression;
+      newExpression = 
RequestUtils.getFunctionExpression(FilterKind.EQUALS.name());
+      List<Expression> operands = new ArrayList<>();
+      operands.add(expression);
+      operands.add(RequestUtils.getLiteralExpression(true));
+      newExpression.getFunctionCall().setOperands(operands);
+
+      return newExpression;
+  }
+
   /**
    * The purpose of this method is to convert expression "0 < columnA" to 
"columnA > 0".
    * The conversion would be:
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
index 0bb6035986..b206951c00 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
@@ -194,45 +194,148 @@ public class CalciteSqlCompilerTest {
 
   @Test
   public void testFilterClauses() {
-    PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetables where a > 1.5");
-    Function func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), FilterKind.GREATER_THAN.name());
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"a");
-    
Assert.assertEquals(func.getOperands().get(1).getLiteral().getDoubleValue(), 
1.5);
-    pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where b < 100");
-    func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), FilterKind.LESS_THAN.name());
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"b");
-    Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
100L);
-    pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where c >= 10");
-    func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), 
FilterKind.GREATER_THAN_OR_EQUAL.name());
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"c");
-    Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
10L);
-    pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where d <= 50");
-    func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), 
FilterKind.LESS_THAN_OR_EQUAL.name());
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"d");
-    Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
50L);
-    pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where e BETWEEN 70 AND 80");
-    func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), FilterKind.BETWEEN.name());
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"e");
-    Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
70L);
-    Assert.assertEquals(func.getOperands().get(2).getLiteral().getLongValue(), 
80L);
-    pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where regexp_like(E, '^U.*')");
-    func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), "REGEXP_LIKE");
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"E");
-    
Assert.assertEquals(func.getOperands().get(1).getLiteral().getStringValue(), 
"^U.*");
-    pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where g IN (12, 13, 15.2, 17)");
-    func = pinotQuery.getFilterExpression().getFunctionCall();
-    Assert.assertEquals(func.getOperator(), FilterKind.IN.name());
-    Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"g");
-    Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
12L);
-    Assert.assertEquals(func.getOperands().get(2).getLiteral().getLongValue(), 
13L);
-    
Assert.assertEquals(func.getOperands().get(3).getLiteral().getDoubleValue(), 
15.2);
-    Assert.assertEquals(func.getOperands().get(4).getLiteral().getLongValue(), 
17L);
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetables where a > 1.5");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.GREATER_THAN.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"a");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getDoubleValue(), 
1.5);
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetables where b < 100");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.LESS_THAN.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"b");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
100L);
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetables where c >= 10");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), 
FilterKind.GREATER_THAN_OR_EQUAL.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"c");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 10L);
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetables where d <= 50");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), 
FilterKind.LESS_THAN_OR_EQUAL.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"d");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 50L);
+    }
+
+    {
+      PinotQuery pinotQuery =
+          CalciteSqlParser.compileToPinotQuery("select * from vegetables where 
e BETWEEN 70 AND 80");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.BETWEEN.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"e");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 70L);
+      
Assert.assertEquals(func.getOperands().get(2).getLiteral().getLongValue(), 80L);
+    }
+
+    {
+      PinotQuery pinotQuery =
+          CalciteSqlParser.compileToPinotQuery("select * from vegetables where 
regexp_like(E, '^U.*')");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), "REGEXP_LIKE");
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"E");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getStringValue(), 
"^U.*");
+    }
+
+    {
+      PinotQuery pinotQuery =
+          CalciteSqlParser.compileToPinotQuery("select * from vegetables where 
g IN (12, 13, 15.2, 17)");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.IN.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"g");
+      
Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 12L);
+      
Assert.assertEquals(func.getOperands().get(2).getLiteral().getLongValue(), 13L);
+      
Assert.assertEquals(func.getOperands().get(3).getLiteral().getDoubleValue(), 
15.2);
+      
Assert.assertEquals(func.getOperands().get(4).getLiteral().getLongValue(), 17L);
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetable where g");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.EQUALS.name());
+      Assert.assertEquals(func.getOperands().get(0).getIdentifier().getName(), 
"g");
+      Assert.assertEquals(func.getOperands().get(1).getLiteral(), 
Literal.boolValue(true));
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetable where g or f = true");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.OR.name());
+      List<Expression> operands = func.getOperands();
+      Assert.assertEquals(operands.size(), 2);
+      Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      List<Expression> eqOperands = 
operands.get(0).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier().getName(), "g");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
+      eqOperands = operands.get(1).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier().getName(), "f");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.stringValue("true"));
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetable where startsWith(g, "
+          + "'str')");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.EQUALS.name());
+      
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"startswith");
+      Assert.assertEquals(func.getOperands().get(1).getLiteral(), 
Literal.boolValue(true));
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetable where startsWith(g, "
+          + "'str')=true and startsWith(f, 'str')");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.AND.name());
+      List<Expression> operands = func.getOperands();
+      Assert.assertEquals(operands.size(), 2);
+
+      Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      List<Expression> eqOperands = 
operands.get(0).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getFunctionCall().getOperator(), 
"startswith");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.stringValue("true"));
+
+      Assert.assertEquals(operands.get(1).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      eqOperands = operands.get(1).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getFunctionCall().getOperator(), 
"startswith");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
+    }
+
+    {
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("select * 
from vegetable where (startsWith(g, "
+          + "'str')=true and startsWith(f, 'str')) AND (e and d=true)");
+      Function func = pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(func.getOperator(), FilterKind.AND.name());
+      List<Expression> operands = func.getOperands();
+      Assert.assertEquals(operands.size(), 4);
+
+      Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      List<Expression> eqOperands = 
operands.get(0).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getFunctionCall().getOperator(), 
"startswith");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.stringValue("true"));
+
+      Assert.assertEquals(operands.get(1).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      eqOperands = operands.get(1).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getFunctionCall().getOperator(), 
"startswith");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
+
+      Assert.assertEquals(operands.get(2).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      eqOperands = operands.get(2).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier().getName(), "e");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
+
+      Assert.assertEquals(operands.get(3).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      eqOperands = operands.get(3).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier().getName(), "d");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.stringValue("true"));
+    }
   }
 
   @Test
@@ -241,29 +344,30 @@ public class CalciteSqlCompilerTest {
     Function func = pinotQuery.getFilterExpression().getFunctionCall();
     Assert.assertEquals(func.getOperator(), FilterKind.GREATER_THAN.name());
     
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"minus");
-    
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
-        "a");
-    
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
-        "b");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "a");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
 "b");
     Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
0L);
     pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where 0 < a-b");
     func = pinotQuery.getFilterExpression().getFunctionCall();
     Assert.assertEquals(func.getOperator(), FilterKind.GREATER_THAN.name());
     
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"minus");
-    
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
-        "a");
-    
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
-        "b");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "a");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
 "b");
     Assert.assertEquals(func.getOperands().get(1).getLiteral().getLongValue(), 
0L);
 
     pinotQuery = CalciteSqlParser.compileToPinotQuery("select * from 
vegetables where b < 100 + c");
     func = pinotQuery.getFilterExpression().getFunctionCall();
     Assert.assertEquals(func.getOperator(), FilterKind.LESS_THAN.name());
     
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"minus");
-    
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
-        "b");
-    Assert.assertEquals(
-        
func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
 "plus");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "b");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
+            "plus");
     Assert.assertEquals(
         
func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperands().get(0)
             .getLiteral().getLongValue(), 100L);
@@ -275,10 +379,11 @@ public class CalciteSqlCompilerTest {
     func = pinotQuery.getFilterExpression().getFunctionCall();
     Assert.assertEquals(func.getOperator(), FilterKind.LESS_THAN.name());
     
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"minus");
-    
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
-        "b");
-    Assert.assertEquals(
-        
func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
 "plus");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "b");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
+            "plus");
     Assert.assertEquals(
         
func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperands().get(0)
             .getLiteral().getLongValue(), 100L);
@@ -292,10 +397,12 @@ public class CalciteSqlCompilerTest {
     func = pinotQuery.getFilterExpression().getFunctionCall();
     Assert.assertEquals(func.getOperator(), 
FilterKind.LESS_THAN_OR_EQUAL.name());
     
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"minus");
-    Assert.assertEquals(
-        
func.getOperands().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
 "foo1");
-    Assert.assertEquals(
-        
func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
 "foo2");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+            "foo1");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
+            "foo2");
     Assert.assertEquals(
         
func.getOperands().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
             .getFunctionCall().getOperator(), "bar1");
@@ -330,10 +437,12 @@ public class CalciteSqlCompilerTest {
     func = pinotQuery.getFilterExpression().getFunctionCall();
     Assert.assertEquals(func.getOperator(), 
FilterKind.LESS_THAN_OR_EQUAL.name());
     
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"minus");
-    Assert.assertEquals(
-        
func.getOperands().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
 "foo1");
-    Assert.assertEquals(
-        
func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
 "foo2");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+            "foo1");
+    Assert
+        
.assertEquals(func.getOperands().get(0).getFunctionCall().getOperands().get(1).getFunctionCall().getOperator(),
+            "foo2");
     Assert.assertEquals(
         
func.getOperands().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
             .getFunctionCall().getOperator(), "bar1");
@@ -676,8 +785,8 @@ public class CalciteSqlCompilerTest {
     } catch (SqlCompilationException e) {
       // Expected
       Assert.assertTrue(e.getCause().getMessage().contains("at line 1, column 
31."),
-          "Compilation exception should contain line and character for error 
message. Error message is "
-              + e.getMessage());
+          "Compilation exception should contain line and character for error 
message. Error message is " + e
+              .getMessage());
       return;
     }
 
@@ -701,8 +810,8 @@ public class CalciteSqlCompilerTest {
     Assert.assertEquals(pinotQuery.getQueryOptionsSize(), 0);
     Assert.assertNull(pinotQuery.getQueryOptions());
 
-    pinotQuery = CalciteSqlParser.compileToPinotQuery(
-        "select * from vegetables where name <> 'Brussels sprouts' OPTION 
(delicious=yes)");
+    pinotQuery = CalciteSqlParser
+        .compileToPinotQuery("select * from vegetables where name <> 'Brussels 
sprouts' OPTION (delicious=yes)");
     Assert.assertEquals(pinotQuery.getQueryOptionsSize(), 1);
     Assert.assertTrue(pinotQuery.getQueryOptions().containsKey("delicious"));
     Assert.assertEquals(pinotQuery.getQueryOptions().get("delicious"), "yes");
@@ -777,8 +886,8 @@ public class CalciteSqlCompilerTest {
 
   @Test
   public void testIdentifierQuoteCharacter() {
-    PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(
-        "select avg(attributes.age) as avg_age from person group by 
attributes.address_city");
+    PinotQuery pinotQuery = CalciteSqlParser
+        .compileToPinotQuery("select avg(attributes.age) as avg_age from 
person group by attributes.address_city");
     Assert.assertEquals(
         
pinotQuery.getSelectList().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
             .getIdentifier().getName(), "attributes.age");
@@ -805,14 +914,15 @@ public class CalciteSqlCompilerTest {
     Assert.assertEquals(groupbyList.get(1).getIdentifier().getName(), "bar");
 
     // For UDF, string literal won't be treated as column but as LITERAL
-    pinotQuery = CalciteSqlParser.compileToPinotQuery(
-        "SELECT SUM(ADD(foo, 'bar')) FROM myTable GROUP BY sub(foo, bar), 
SUB(BAR, FOO)");
+    pinotQuery = CalciteSqlParser
+        .compileToPinotQuery("SELECT SUM(ADD(foo, 'bar')) FROM myTable GROUP 
BY sub(foo, bar), SUB(BAR, FOO)");
     selectFunctionList = pinotQuery.getSelectList();
     Assert.assertEquals(selectFunctionList.size(), 1);
     
Assert.assertEquals(selectFunctionList.get(0).getFunctionCall().getOperator(), 
"sum");
     
Assert.assertEquals(selectFunctionList.get(0).getFunctionCall().getOperands().size(),
 1);
-    Assert.assertEquals(
-        
selectFunctionList.get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
 "add");
+    Assert
+        
.assertEquals(selectFunctionList.get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+            "add");
     Assert.assertEquals(
         
selectFunctionList.get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().size(),
 2);
     Assert.assertEquals(
@@ -878,8 +988,8 @@ public class CalciteSqlCompilerTest {
 
   @Test
   public void testSelectionTransformFunction() {
-    PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(
-        "  select mapKey(mapField,k1) from baseballStats where 
mapKey(mapField,k1) = 'v1'");
+    PinotQuery pinotQuery = CalciteSqlParser
+        .compileToPinotQuery("  select mapKey(mapField,k1) from baseballStats 
where mapKey(mapField,k1) = 'v1'");
     
Assert.assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(),
 "mapkey");
     Assert.assertEquals(
         
pinotQuery.getSelectList().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "mapField");
@@ -1525,8 +1635,9 @@ public class CalciteSqlCompilerTest {
     Assert.assertEquals(
         
pinotQuery.getSelectList().get(4).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
 "avg");
     
Assert.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperator(),
 FilterKind.EQUALS.name());
-    Assert.assertEquals(
-        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "groups");
+    Assert
+        
.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
+            "groups");
     Assert.assertEquals(
         
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(1).getLiteral().getStringValue(),
 "foo");
 
@@ -1946,8 +2057,8 @@ public class CalciteSqlCompilerTest {
     expression = pinotQuery.getFilterExpression();
     Assert.assertNotNull(expression.getFunctionCall());
     Assert.assertEquals(expression.getFunctionCall().getOperator(), 
"todatetime");
-    
Assert.assertEquals(expression.getFunctionCall().getOperands().get(0).getIdentifier().getName(),
-        "millisSinceEpoch");
+    Assert
+        
.assertEquals(expression.getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "millisSinceEpoch");
 
     expression = CalciteSqlParser.compileToExpression("encodeUrl('key1=value 
1&key2=value@!$2&key3=value%3')");
     Assert.assertNotNull(expression.getFunctionCall());
@@ -2047,8 +2158,9 @@ public class CalciteSqlCompilerTest {
     pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
     brokerRequest = converter.convert(pinotQuery);
     
Assert.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperator(),
 "IS_NOT_NULL");
-    Assert.assertEquals(
-        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col");
+    Assert
+        
.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
+            "col");
     Assert.assertEquals(brokerRequest.getFilterQuery().getOperator(), 
FilterOperator.IS_NOT_NULL);
     Assert.assertEquals(brokerRequest.getFilterQuery().getColumn(), "col");
 
@@ -2056,8 +2168,9 @@ public class CalciteSqlCompilerTest {
     pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
     brokerRequest = converter.convert(pinotQuery);
     
Assert.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperator(),
 "IS_NOT_NULL");
-    Assert.assertEquals(
-        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col");
+    Assert
+        
.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
+            "col");
     Assert.assertEquals(brokerRequest.getFilterQuery().getOperator(), 
FilterOperator.IS_NOT_NULL);
     Assert.assertEquals(brokerRequest.getFilterQuery().getColumn(), "col");
 
@@ -2065,8 +2178,9 @@ public class CalciteSqlCompilerTest {
     pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
     brokerRequest = converter.convert(pinotQuery);
     
Assert.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperator(),
 "IS_NULL");
-    Assert.assertEquals(
-        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col");
+    Assert
+        
.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
+            "col");
     Assert.assertEquals(brokerRequest.getFilterQuery().getOperator(), 
FilterOperator.IS_NULL);
     Assert.assertEquals(brokerRequest.getFilterQuery().getColumn(), "col");
 
@@ -2074,8 +2188,9 @@ public class CalciteSqlCompilerTest {
     pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
     brokerRequest = converter.convert(pinotQuery);
     
Assert.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperator(),
 "IS_NULL");
-    Assert.assertEquals(
-        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col");
+    Assert
+        
.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getIdentifier().getName(),
+            "col");
     Assert.assertEquals(brokerRequest.getFilterQuery().getOperator(), 
FilterOperator.IS_NULL);
     Assert.assertEquals(brokerRequest.getFilterQuery().getColumn(), "col");
   }
@@ -2206,17 +2321,41 @@ public class CalciteSqlCompilerTest {
     }
 
     {
-      String query = "SELECT * FROM foo WHERE col1 > 0 AND (col2 AND col3 > 0) 
AND col4";
+      String query = "SELECT * FROM foo WHERE col1 > 0 AND (col2 AND col3 > 0) 
AND startsWith(col4, 'myStr')";
       PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
       Function functionCall = 
pinotQuery.getFilterExpression().getFunctionCall();
       Assert.assertEquals(functionCall.getOperator(), FilterKind.AND.name());
       List<Expression> operands = functionCall.getOperands();
       Assert.assertEquals(operands.size(), 4);
       Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), 
FilterKind.GREATER_THAN.name());
-      Assert.assertEquals(operands.get(1).getIdentifier().getName(), "col2");
+      Assert.assertEquals(operands.get(1).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      List<Expression> eqOperands = 
operands.get(1).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier(), new 
Identifier("col2"));
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
       Assert.assertEquals(operands.get(2).getFunctionCall().getOperator(), 
FilterKind.GREATER_THAN.name());
-      Assert.assertEquals(operands.get(3).getIdentifier().getName(), "col4");
+      Assert.assertEquals(operands.get(3).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      eqOperands = operands.get(3).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getFunctionCall().getOperator(), 
"startswith");
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
+    }
 
+    {
+      String query = "SELECT * FROM foo WHERE col1 > 0 AND (col2 AND col3 > 0) 
AND col4 = true";
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
+      Function functionCall = 
pinotQuery.getFilterExpression().getFunctionCall();
+      Assert.assertEquals(functionCall.getOperator(), FilterKind.AND.name());
+      List<Expression> operands = functionCall.getOperands();
+      Assert.assertEquals(operands.size(), 4);
+      Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), 
FilterKind.GREATER_THAN.name());
+      Assert.assertEquals(operands.get(1).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      List<Expression> eqOperands = 
operands.get(1).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier(), new 
Identifier("col2"));
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
+      Assert.assertEquals(operands.get(2).getFunctionCall().getOperator(), 
FilterKind.GREATER_THAN.name());
+      Assert.assertEquals(operands.get(3).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      eqOperands = operands.get(3).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier(), new 
Identifier("col4"));
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.stringValue("true"));
       // NOTE: PQL does not support logical identifier
     }
 
@@ -2249,9 +2388,12 @@ public class CalciteSqlCompilerTest {
       List<Expression> operands = functionCall.getOperands();
       Assert.assertEquals(operands.size(), 4);
       Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), 
FilterKind.LESS_THAN_OR_EQUAL.name());
-      Assert.assertEquals(operands.get(1).getIdentifier().getName(), "col2");
+      Assert.assertEquals(operands.get(1).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
       Assert.assertEquals(operands.get(2).getFunctionCall().getOperator(), 
FilterKind.LESS_THAN_OR_EQUAL.name());
-      Assert.assertEquals(operands.get(3).getIdentifier().getName(), "col4");
+      Assert.assertEquals(operands.get(3).getFunctionCall().getOperator(), 
FilterKind.EQUALS.name());
+      List<Expression> eqOperands = 
operands.get(3).getFunctionCall().getOperands();
+      Assert.assertEquals(eqOperands.get(0).getIdentifier(), new 
Identifier("col4"));
+      Assert.assertEquals(eqOperands.get(1).getLiteral(), 
Literal.boolValue(true));
 
       // NOTE: PQL does not support logical identifier
     }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java
index 3121fe2186..1076a8504f 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java
@@ -582,9 +582,6 @@ public class QueryContext {
           Preconditions.checkState(aggregation != null && 
aggregation.getType() == FunctionContext.Type.AGGREGATION,
               "First argument of FILTER must be an aggregation function");
           ExpressionContext filterExpression = arguments.get(1);
-          Preconditions.checkState(filterExpression.getFunction() != null
-                  && filterExpression.getFunction().getType() == 
FunctionContext.Type.TRANSFORM,
-              "Second argument of FILTER must be a filter expression");
           FilterContext filter = 
RequestContextUtils.getFilter(filterExpression);
           filteredAggregations.add(Pair.of(aggregation, filter));
         } else {
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanQueriesTest.java 
b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanQueriesTest.java
index dad1ac1455..384a8989ed 100644
--- a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanQueriesTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanQueriesTest.java
@@ -162,6 +162,21 @@ public class BooleanQueriesTest extends BaseQueriesTest {
         assertEquals(row[0], false);
       }
     }
+    {
+      String query = "SELECT booleanColumn FROM testTable WHERE booleanColumn";
+      BrokerResponseNative brokerResponse = getBrokerResponse(query);
+      ResultTable resultTable = brokerResponse.getResultTable();
+      DataSchema dataSchema = resultTable.getDataSchema();
+      assertEquals(dataSchema,
+          new DataSchema(new String[]{"booleanColumn"}, new 
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+      List<Object[]> rows = resultTable.getRows();
+      assertEquals(rows.size(), 10);
+      for (int i = 0; i < 10; i++) {
+        Object[] row = rows.get(i);
+        assertEquals(row.length, 1);
+        assertEquals(row[0], true);
+      }
+    }
     {
       String query = "SELECT * FROM testTable ORDER BY booleanColumn DESC 
LIMIT 20";
       BrokerResponseNative brokerResponse = getBrokerResponse(query);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java 
b/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java
index 9c2fe3d940..7e5dc8e9aa 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java
@@ -55,6 +55,7 @@ public class ExplainPlanQueriesTest extends BaseQueriesTest {
   private final static String COL1_NO_INDEX = "noIndexCol1";
   private final static String COL2_NO_INDEX = "noIndexCol2";
   private final static String COL3_NO_INDEX = "noIndexCol3";
+  private final static String COL4_NO_INDEX = "noIndexCol4";
   private final static String COL1_INVERTED_INDEX = "invertedIndexCol1";
   private final static String COL2_INVERTED_INDEX = "invertedIndexCol2";
   private final static String COL3_INVERTED_INDEX = "invertedIndexCol3";
@@ -69,6 +70,7 @@ public class ExplainPlanQueriesTest extends BaseQueriesTest {
       .addSingleValueDimension(COL1_NO_INDEX, FieldSpec.DataType.INT)
       .addSingleValueDimension(COL2_NO_INDEX, FieldSpec.DataType.INT)
       .addSingleValueDimension(COL3_NO_INDEX, FieldSpec.DataType.INT)
+      .addSingleValueDimension(COL4_NO_INDEX, FieldSpec.DataType.BOOLEAN)
       .addSingleValueDimension(COL1_INVERTED_INDEX, FieldSpec.DataType.DOUBLE)
       .addSingleValueDimension(COL2_INVERTED_INDEX, FieldSpec.DataType.INT)
       .addSingleValueDimension(COL3_INVERTED_INDEX, FieldSpec.DataType.STRING)
@@ -104,14 +106,16 @@ public class ExplainPlanQueriesTest extends 
BaseQueriesTest {
     return _indexSegments;
   }
 
-  GenericRow createMockRecord(int noIndexCol1, int noIndexCol2, int 
noIndexCol3, double invertedIndexCol1,
-      int invertedIndexCol2, String intervedIndexCol3, double rangeIndexCol1, 
int rangeIndexCol2, int rangeIndexCol3,
-      double sortedIndexCol1, String jsonIndexCol1, String textIndexCol1) {
+  GenericRow createMockRecord(int noIndexCol1, int noIndexCol2, int 
noIndexCol3,
+      boolean noIndexCol4, double invertedIndexCol1, int invertedIndexCol2, 
String intervedIndexCol3,
+      double rangeIndexCol1, int rangeIndexCol2, int rangeIndexCol3, double 
sortedIndexCol1, String jsonIndexCol1,
+      String textIndexCol1) {
 
     GenericRow record = new GenericRow();
     record.putValue(COL1_NO_INDEX, noIndexCol1);
     record.putValue(COL2_NO_INDEX, noIndexCol2);
     record.putValue(COL3_NO_INDEX, noIndexCol3);
+    record.putValue(COL4_NO_INDEX, noIndexCol4);
 
     record.putValue(COL1_INVERTED_INDEX, invertedIndexCol1);
     record.putValue(COL2_INVERTED_INDEX, invertedIndexCol2);
@@ -135,11 +139,11 @@ public class ExplainPlanQueriesTest extends 
BaseQueriesTest {
     FileUtils.deleteDirectory(INDEX_DIR);
 
     List<GenericRow> records = new ArrayList<>(NUM_RECORDS);
-    records.add(createMockRecord(1, 2, 3, 1.1, 2, "daffy", 10.1, 20, 30, 100.1,
+    records.add(createMockRecord(1, 2, 3, true, 1.1, 2, "daffy", 10.1, 20, 30, 
100.1,
         "{\"first\": \"daffy\", \"last\": " + "\"duck\"}", "daffy"));
-    records.add(createMockRecord(0, 1, 2, 0.1, 1, "mickey", 0.1, 10, 20, 100.2,
+    records.add(createMockRecord(0, 1, 2, false, 0.1, 1, "mickey", 0.1, 10, 
20, 100.2,
         "{\"first\": \"mickey\", \"last\": " + "\"mouse\"}", "mickey"));
-    records.add(createMockRecord(3, 4, 5, 2.1, 3, "mickey", 20.1, 30, 40, 
100.3,
+    records.add(createMockRecord(3, 4, 5, true, 2.1, 3, "mickey", 20.1, 30, 
40, 100.3,
         "{\"first\": \"mickey\", \"last\": " + "\"mouse\"}", "mickey"));
 
     IndexingConfig indexingConfig = TABLE_CONFIG.getIndexingConfig();
@@ -191,14 +195,14 @@ public class ExplainPlanQueriesTest extends 
BaseQueriesTest {
     result1.add(new Object[]{"COMBINE_SELECT", 1, 0});
     result1.add(new Object[]{
         "SELECT(selectList:invertedIndexCol1, invertedIndexCol2, 
invertedIndexCol3, jsonIndexCol1, "
-            + "noIndexCol1, noIndexCol2, noIndexCol3, rangeIndexCol1, 
rangeIndexCol2, rangeIndexCol3, "
+            + "noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, 
rangeIndexCol1, rangeIndexCol2, rangeIndexCol3, "
             + "sortedIndexCol1, textIndexCol1)", 2, 1});
     result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, 
invertedIndexCol2, invertedIndexCol3, "
-        + "jsonIndexCol1, noIndexCol1, noIndexCol2, noIndexCol3, 
rangeIndexCol1, rangeIndexCol2, rangeIndexCol3, "
-        + "sortedIndexCol1, textIndexCol1)", 3, 2});
-    result1.add(new Object[]{"PROJECT(sortedIndexCol1, noIndexCol3, 
rangeIndexCol1, rangeIndexCol2, jsonIndexCol1, "
-        + "invertedIndexCol1, noIndexCol2, invertedIndexCol2, noIndexCol1, 
invertedIndexCol3, rangeIndexCol3, "
-        + "textIndexCol1)", 4, 3});
+        + "jsonIndexCol1, noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, 
rangeIndexCol1, rangeIndexCol2, "
+        + "rangeIndexCol3, sortedIndexCol1, textIndexCol1)", 3, 2});
+    result1.add(new Object[]{"PROJECT(noIndexCol4, sortedIndexCol1, 
noIndexCol3, rangeIndexCol1, rangeIndexCol2, "
+        + "invertedIndexCol1, noIndexCol2, invertedIndexCol2, noIndexCol1, 
rangeIndexCol3, textIndexCol1, "
+        + "jsonIndexCol1, invertedIndexCol3)", 4, 3});
     result1.add(new Object[]{"DOC_ID_SET", 5, 4});
     result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5});
     check(query1, new ResultTable(DATA_SCHEMA, result1));
@@ -337,6 +341,47 @@ public class ExplainPlanQueriesTest extends 
BaseQueriesTest {
     result3.add(new 
Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 7, 6});
     result3.add(new 
Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND 
'101')", 8, 6});
     check(query3, new ResultTable(DATA_SCHEMA, result3));
+
+    String query4 = "EXPLAIN PLAN FOR SELECT invertedIndexCol1, noIndexCol1 
FROM testTable WHERE noIndexCol1 > 1 OR "
+        + "contains(textIndexCol1, 'daff') OR noIndexCol2 BETWEEN 2 AND 101 
LIMIT 100";
+    List<Object[]> result4 = new ArrayList<>();
+    result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 0, -1});
+    result4.add(new Object[]{"COMBINE_SELECT", 1, 0});
+    result4.add(new Object[]{"SELECT(selectList:invertedIndexCol1, 
noIndexCol1)", 2, 1});
+    result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, 
noIndexCol1)", 3, 2});
+    result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3});
+    result4.add(new Object[]{"DOC_ID_SET", 5, 4});
+    result4.add(new Object[]{"FILTER_OR", 6, 5});
+    result4.add(new 
Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 7, 6});
+    result4.add(new 
Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:contains(textIndexCol1,'daff')
 = 'true')", 8, 6});
+    result4.add(new 
Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND 
'101')", 9, 6});
+    check(query4, new ResultTable(DATA_SCHEMA, result4));
+
+    String query5 = "EXPLAIN PLAN FOR SELECT invertedIndexCol1, noIndexCol1 
FROM testTable WHERE noIndexCol4 LIMIT 100";
+    List<Object[]> result5 = new ArrayList<>();
+    result5.add(new Object[]{"BROKER_REDUCE(limit:100)", 0, -1});
+    result5.add(new Object[]{"COMBINE_SELECT", 1, 0});
+    result5.add(new Object[]{"SELECT(selectList:invertedIndexCol1, 
noIndexCol1)", 2, 1});
+    result5.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, 
noIndexCol1)", 3, 2});
+    result5.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3});
+    result5.add(new Object[]{"DOC_ID_SET", 5, 4});
+    result5.add(new 
Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 6, 5});
+    check(query5, new ResultTable(DATA_SCHEMA, result5));
+
+    String query6 = "EXPLAIN PLAN FOR SELECT invertedIndexCol1, noIndexCol1 
FROM testTable WHERE startsWith "
+        + "(textIndexCol1, 'daff') AND noIndexCol4";
+    List<Object[]> result6 = new ArrayList<>();
+    result6.add(new Object[]{"BROKER_REDUCE(limit:10)", 0, -1});
+    result6.add(new Object[]{"COMBINE_SELECT", 1, 0});
+    result6.add(new Object[]{"SELECT(selectList:invertedIndexCol1, 
noIndexCol1)", 2, 1});
+    result6.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, 
noIndexCol1)", 3, 2});
+    result6.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3});
+    result6.add(new Object[]{"DOC_ID_SET", 5, 4});
+    result6.add(new Object[]{"FILTER_AND", 6, 5});
+    result6.add(new 
Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 7, 6});
+    result6.add(new 
Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:startswith(textIndexCol1,'daff')
 = 'true')", 8,
+        6});
+    check(query6, new ResultTable(DATA_SCHEMA, result6));
   }
 
   /** Test case for SQL statements with filter that involves inverted or 
sorted index access. */
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java
index 5b3a1bc153..2fc9ad1fa6 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java
@@ -26,6 +26,8 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.math.RandomUtils;
 import org.apache.pinot.common.response.broker.ResultTable;
 import 
org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader;
 import 
org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl;
@@ -57,6 +59,8 @@ public class FilteredAggregationsTest extends BaseQueriesTest 
{
   private static final String INT_COL_NAME = "INT_COL";
   private static final String NO_INDEX_INT_COL_NAME = "NO_INDEX_COL";
   private static final String STATIC_INT_COL_NAME = "STATIC_INT_COL";
+  private static final String BOOLEAN_COL_NAME = "BOOLEAN_COL";
+  private static final String STRING_COL_NAME = "STRING_COL";
   private static final Integer NUM_ROWS = 30000;
 
   private IndexSegment _indexSegment;
@@ -111,6 +115,8 @@ public class FilteredAggregationsTest extends 
BaseQueriesTest {
       row.putValue(INT_COL_NAME, i);
       row.putValue(NO_INDEX_INT_COL_NAME, i);
       row.putValue(STATIC_INT_COL_NAME, 10);
+      row.putValue(BOOLEAN_COL_NAME, RandomUtils.nextBoolean());
+      row.putValue(STRING_COL_NAME, RandomStringUtils.randomAlphabetic(4));
       rows.add(row);
     }
     return rows;
@@ -126,6 +132,8 @@ public class FilteredAggregationsTest extends 
BaseQueriesTest {
     Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
         .addSingleValueDimension(NO_INDEX_INT_COL_NAME, FieldSpec.DataType.INT)
         .addSingleValueDimension(STATIC_INT_COL_NAME, FieldSpec.DataType.INT)
+        .addSingleValueDimension(BOOLEAN_COL_NAME, FieldSpec.DataType.BOOLEAN)
+        .addSingleValueDimension(STRING_COL_NAME, FieldSpec.DataType.STRING)
         .addMetric(INT_COL_NAME, FieldSpec.DataType.INT).build();
     SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, 
schema);
     config.setOutDir(INDEX_DIR.getPath());
@@ -185,6 +193,20 @@ public class FilteredAggregationsTest extends 
BaseQueriesTest {
         + "FROM MyTable";
     nonFilterQuery = "SELECT MIN(INT_COL), MAX(INT_COL) FROM MyTable WHERE 
INT_COL > 29990";
     testQuery(filterQuery, nonFilterQuery);
+
+    filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL) FROM MyTable";
+    nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE BOOLEAN_COL=true";
+    testQuery(filterQuery, nonFilterQuery);
+
+    filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL AND 
STARTSWITH(STRING_COL, 'abc')) FROM MyTable";
+    nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE BOOLEAN_COL=true 
AND STARTSWITH(STRING_COL, 'abc')";
+    testQuery(filterQuery, nonFilterQuery);
+
+    filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL AND 
STARTSWITH(REVERSE(STRING_COL), 'abc')) FROM "
+        + "MyTable";
+    nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE BOOLEAN_COL=true 
AND STARTSWITH(REVERSE(STRING_COL), "
+        + "'abc')";
+    testQuery(filterQuery, nonFilterQuery);
   }
 
   @Test


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

Reply via email to