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 operands = thriftFunction.getOperands();
int numOperands = operands.size();
@@ -185,7 +214,8 @@ public static FilterContext getFilter(Expression thriftExpression) {
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 @@ private static String getStringValue(Expression thriftExpression) {
/**
* Converts the given filter {@link ExpressionContext} into a {@link FilterContext}.
*
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 operands = filterFunction.getArguments();
int numOperands = operands.size();
@@ -286,7 +342,8 @@ public static FilterContext getFilter(ExpressionContext filterExpression) {
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 ae5c8ae7a6b..d24945baf0d 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 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 @@ private static Expression updateComparisonPredicate(Expression expression) {
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 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 0bb6035986e..b206951c001 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 void testQuotedStrings() {
@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 operands = func.getOperands();
+ Assert.assertEquals(operands.size(), 2);
+ Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), FilterKind.EQUALS.name());
+ List 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 operands = func.getOperands();
+ Assert.assertEquals(operands.size(), 2);
+
+ Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), FilterKind.EQUALS.name());
+ List 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 operands = func.getOperands();
+ Assert.assertEquals(operands.size(), 4);
+
+ Assert.assertEquals(operands.get(0).getFunctionCall().getOperator(), FilterKind.EQUALS.name());
+ List 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 void testFilterClausesWithRightExpression() {
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 void testFilterClausesWithRightExpression() {
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 void testFilterClausesWithRightExpression() {
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 void testFilterClausesWithRightExpression() {
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 void testParseExceptionHasCharacterPosition() {
} 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 void testQueryOptions() {
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 @@ private void testRemoveComments(String sqlWithComments, String expectedSqlWithou
@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 void testStringLiteral() {
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 void testFilterUdf() {
@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 void testReservedKeywords() {
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 void testCompileTimeExpression() {
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 void testCaseInsensitiveFilter() {
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 void testCaseInsensitiveFilter() {
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 void testCaseInsensitiveFilter() {
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 void testCaseInsensitiveFilter() {
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 void testFlattenAndOr() {
}
{
- 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 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 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 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 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 void testFlattenAndOr() {
List 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 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 3121fe21862..1076a8504f0 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 @@ private static void getAggregations(ExpressionContext expression,
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 dad1ac14558..384a8989eda 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 void testQueries() {
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