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

xiangfu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 622e00e  Support alias replacement in PinotQuery (#5016)
622e00e is described below

commit 622e00e2c31c7e509fb4f9352cb9c7e57d773ddb
Author: Xiang Fu <[email protected]>
AuthorDate: Tue Jan 28 01:48:27 2020 -0800

    Support alias replacement in PinotQuery (#5016)
    
    * Make toExpression by default converts SqlNode to function
    
    * Support alias replacement in PinotQuery
    
    * Adding validation for selection alias
---
 .../pinot/common/utils/request/RequestUtils.java   |   2 +-
 .../apache/pinot/sql/parsers/CalciteSqlParser.java | 123 ++++++++++++++++-----
 .../pinot/sql/parsers/CalciteSqlCompilerTest.java  | 120 ++++++++++++++++++++
 3 files changed, 216 insertions(+), 29 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
index 0a3a71d..a82eb6a 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
@@ -338,12 +338,12 @@ public class RequestUtils {
       String res = expression.getFunctionCall().getOperator() + "(";
       boolean isFirstParam = true;
       for (Expression operand : expression.getFunctionCall().getOperands()) {
-        res += prettyPrint(operand);
         if (!isFirstParam) {
           res += ", ";
         } else {
           isFirstParam = false;
         }
+        res += prettyPrint(operand);
       }
       res += ")";
       return res;
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
index cec5752..a907dc2 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
@@ -47,6 +47,7 @@ import org.apache.pinot.common.request.DataSource;
 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.Identifier;
 import org.apache.pinot.common.request.PinotQuery;
 import org.apache.pinot.common.utils.request.RequestUtils;
 import org.apache.pinot.pql.parsers.Pql2Compiler;
@@ -94,11 +95,43 @@ public class CalciteSqlParser {
     return pinotQuery;
   }
 
-  static void validate(PinotQuery pinotQuery)
+  static void validate(Map<Identifier, Expression> aliasMap, PinotQuery 
pinotQuery)
       throws SqlCompilationException {
+    validateSelectionClause(aliasMap, pinotQuery);
     validateGroupByClause(pinotQuery);
   }
 
+  private static void validateSelectionClause(Map<Identifier, Expression> 
aliasMap, PinotQuery pinotQuery)
+      throws SqlCompilationException {
+    // Sanity check on selection expression shouldn't use alias reference.
+    Set<String> aliasKeys = new HashSet<>();
+    for (Identifier identifier : aliasMap.keySet()) {
+      aliasKeys.add(identifier.getName().toLowerCase());
+    }
+    for (Expression selectExpr : pinotQuery.getSelectList()) {
+      matchIdentifierInAliasMap(selectExpr, aliasKeys);
+    }
+  }
+
+  private static void matchIdentifierInAliasMap(Expression selectExpr, 
Set<String> aliasKeys)
+      throws SqlCompilationException {
+    if (selectExpr.getFunctionCall() != null) {
+      if (selectExpr.getFunctionCall().getOperator().equalsIgnoreCase("AS")) {
+        
matchIdentifierInAliasMap(selectExpr.getFunctionCall().getOperands().get(0), 
aliasKeys);
+      } else {
+        for (Expression operand : selectExpr.getFunctionCall().getOperands()) {
+          matchIdentifierInAliasMap(operand, aliasKeys);
+        }
+      }
+    }
+    if (selectExpr.getIdentifier() != null) {
+      if 
(aliasKeys.contains(selectExpr.getIdentifier().getName().toLowerCase())) {
+        throw new SqlCompilationException(
+            "Alias " + selectExpr.getIdentifier().getName() + " cannot be 
referred in SELECT Clause");
+      }
+    }
+  }
+
   private static void validateGroupByClause(PinotQuery pinotQuery)
       throws SqlCompilationException {
     if (pinotQuery.getGroupByList() == null) {
@@ -108,14 +141,21 @@ public class CalciteSqlParser {
     for (Expression selectExpression : pinotQuery.getSelectList()) {
       if (!isAggregateExpression(selectExpression)) {
         boolean foundInGroupByClause = false;
+        Expression selectionToCheck;
+        if (selectExpression.getFunctionCall() != null && 
selectExpression.getFunctionCall().getOperator()
+            .equalsIgnoreCase("AS")) {
+          selectionToCheck = 
selectExpression.getFunctionCall().getOperands().get(0);
+        } else {
+          selectionToCheck = selectExpression;
+        }
         for (Expression groupByExpression : pinotQuery.getGroupByList()) {
-          if (groupByExpression.equals(selectExpression)) {
+          if (groupByExpression.equals(selectionToCheck)) {
             foundInGroupByClause = true;
           }
         }
         if (!foundInGroupByClause) {
           throw new SqlCompilationException(
-              "'" + RequestUtils.prettyPrint(selectExpression) + "' should 
appear in GROUP BY clause.");
+              "'" + RequestUtils.prettyPrint(selectionToCheck) + "' should 
appear in GROUP BY clause.");
         }
       }
     }
@@ -239,10 +279,60 @@ public class CalciteSqlParser {
         throw new RuntimeException(
             "Unable to convert SqlNode: " + sqlNode + " to PinotQuery. Unknown 
node type: " + sqlNode.getKind());
     }
-    validate(pinotQuery);
+    Map<Identifier, Expression> aliasMap = 
extractAlias(pinotQuery.getSelectList());
+    applyAlias(aliasMap, pinotQuery);
+    validate(aliasMap, pinotQuery);
     return pinotQuery;
   }
 
+  private static void applyAlias(Map<Identifier, Expression> aliasMap, 
PinotQuery pinotQuery) {
+    if (pinotQuery.isSetFilterExpression()) {
+      applyAlias(aliasMap, pinotQuery.getFilterExpression());
+    }
+    if (pinotQuery.isSetGroupByList()) {
+      for (Expression groupByExpr : pinotQuery.getGroupByList()) {
+        applyAlias(aliasMap, groupByExpr);
+      }
+    }
+    if (pinotQuery.isSetOrderByList()) {
+      for (Expression orderByExpr : pinotQuery.getOrderByList()) {
+        applyAlias(aliasMap, orderByExpr);
+      }
+    }
+  }
+
+  private static void applyAlias(Map<Identifier, Expression> aliasMap, 
Expression expression) {
+    if (expression == null) {
+      return;
+    }
+    Identifier identifierKey = expression.getIdentifier();
+    if ((identifierKey != null) && (aliasMap.containsKey(identifierKey))) {
+      Expression aliasExpression = aliasMap.get(identifierKey);
+      
expression.setType(aliasExpression.getType()).setIdentifier(aliasExpression.getIdentifier())
+          
.setFunctionCall(aliasExpression.getFunctionCall()).setLiteral(aliasExpression.getLiteral());
+    }
+    if (expression.getFunctionCall() != null) {
+      for (Expression operand : expression.getFunctionCall().getOperands()) {
+        applyAlias(aliasMap, operand);
+      }
+    }
+  }
+
+  private static Map<Identifier, Expression> extractAlias(List<Expression> 
expressions) {
+    Map<Identifier, Expression> aliasMap = new HashMap<>();
+    for (Expression expression : expressions) {
+      Function functionCall = expression.getFunctionCall();
+      if (functionCall == null) {
+        continue;
+      }
+      if (functionCall.getOperator().equalsIgnoreCase("AS")) {
+        Expression identifierExpr = functionCall.getOperands().get(1);
+        aliasMap.put(identifierExpr.getIdentifier(), 
functionCall.getOperands().get(0));
+      }
+    }
+    return aliasMap;
+  }
+
   private static List<String> extractOptionsFromSql(String sql) {
     List<String> results = new ArrayList<>();
     Matcher matcher = OPTIONS_REGEX_PATTEN.matcher(sql);
@@ -350,28 +440,7 @@ public class CalciteSqlParser {
         return RequestUtils.getIdentifierExpression(node.toString());
       case LITERAL:
         return RequestUtils.getLiteralExpression((SqlLiteral) node);
-      case AS:
-        // Aggregation
-      case COUNT:
-      case SUM:
-      case AVG:
-      case MAX:
-      case MIN:
-      case HOP:
-      case OTHER_FUNCTION:
-        // Filtering
-      case OR:
-      case AND:
-      case EQUALS:
-      case NOT_EQUALS:
-      case BETWEEN:
-      case GREATER_THAN:
-      case GREATER_THAN_OR_EQUAL:
-      case LESS_THAN:
-      case LESS_THAN_OR_EQUAL:
-      case IN:
-      case NOT_IN:
-      case LIKE:
+      default:
         SqlBasicCall funcSqlNode = (SqlBasicCall) node;
         String funcName = funcSqlNode.getOperator().getKind().name();
         if (funcSqlNode.getOperator().getKind() == SqlKind.OTHER_FUNCTION) {
@@ -390,8 +459,6 @@ public class CalciteSqlParser {
           }
         }
         return funcExpr;
-      default:
-        throw new RuntimeException("Unknown node type: " + node.getKind());
     }
   }
 }
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 6ae54e3..2957d81 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
@@ -898,4 +898,124 @@ public class CalciteSqlCompilerTest {
       Assert.assertTrue(e.getMessage().contains("is not allowed in GROUP BY 
clause."));
     }
   }
+
+  @Test
+  public void testAliasQuery() {
+    String sql;
+    PinotQuery pinotQuery;
+    // Valid alias in query.
+    sql =
+        "select secondsSinceEpoch, sum(rsvp_count) as sum_rsvp_count, count(*) 
as cnt from meetupRsvp group by secondsSinceEpoch order by cnt, sum_rsvp_count 
DESC limit 50";
+    pinotQuery = CalciteSqlParser.compileToPinotQuery(sql);
+    Assert.assertEquals(pinotQuery.getSelectListSize(), 3);
+    Assert.assertEquals(pinotQuery.getGroupByListSize(), 1);
+    Assert.assertEquals(pinotQuery.getOrderByListSize(), 2);
+    
Assert.assertEquals(pinotQuery.getOrderByList().get(0).getFunctionCall().getOperator(),
 "ASC");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+        "COUNT");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
+            .getIdentifier().getName(), "*");
+    
Assert.assertEquals(pinotQuery.getOrderByList().get(1).getFunctionCall().getOperator(),
 "DESC");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(1).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+        "SUM");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(1).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
+            .getIdentifier().getName(), "rsvp_count");
+
+    // Valid mixed alias expressions in query.
+    sql =
+        "select secondsSinceEpoch, sum(rsvp_count), count(*) as cnt from 
meetupRsvp group by secondsSinceEpoch order by cnt, sum(rsvp_count) DESC limit 
50";
+    pinotQuery = CalciteSqlParser.compileToPinotQuery(sql);
+    Assert.assertEquals(pinotQuery.getSelectListSize(), 3);
+    Assert.assertEquals(pinotQuery.getGroupByListSize(), 1);
+    Assert.assertEquals(pinotQuery.getOrderByListSize(), 2);
+    
Assert.assertEquals(pinotQuery.getOrderByList().get(0).getFunctionCall().getOperator(),
 "ASC");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+        "COUNT");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
+            .getIdentifier().getName(), "*");
+    
Assert.assertEquals(pinotQuery.getOrderByList().get(1).getFunctionCall().getOperator(),
 "DESC");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(1).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+        "SUM");
+    Assert.assertEquals(
+        
pinotQuery.getOrderByList().get(1).getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
+            .getIdentifier().getName(), "rsvp_count");
+
+    sql =
+        "select secondsSinceEpoch/86400 AS daysSinceEpoch, sum(rsvp_count) as 
sum_rsvp_count, count(*) as cnt from meetupRsvp where daysSinceEpoch = 18523 
group by daysSinceEpoch order by cnt, sum_rsvp_count DESC limit 50";
+    pinotQuery = CalciteSqlParser.compileToPinotQuery(sql);
+    Assert.assertEquals(pinotQuery.getSelectListSize(), 3);
+    
Assert.assertEquals(pinotQuery.getFilterExpression().getFunctionCall().getOperator(),
 "EQUALS");
+    Assert.assertEquals(
+        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
+        "DIVIDE");
+    Assert.assertEquals(
+        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(0)
+            .getIdentifier().getName(), "secondsSinceEpoch");
+    Assert.assertEquals(
+        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(0).getFunctionCall().getOperands().get(1)
+            .getLiteral().getLongValue(), 86400);
+    Assert.assertEquals(
+        
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(1).getLiteral().getLongValue(),
 18523);
+    Assert.assertEquals(pinotQuery.getGroupByListSize(), 1);
+    
Assert.assertEquals(pinotQuery.getGroupByList().get(0).getFunctionCall().getOperator(),
 "DIVIDE");
+    Assert.assertEquals(
+        
pinotQuery.getGroupByList().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
+        "secondsSinceEpoch");
+    Assert.assertEquals(
+        
pinotQuery.getGroupByList().get(0).getFunctionCall().getOperands().get(1).getLiteral().getLongValue(),
 86400);
+    Assert.assertEquals(pinotQuery.getOrderByListSize(), 2);
+
+    // Invalid groupBy clause shouldn't contain aggregate expression, like 
sum(rsvp_count), count(*).
+    try {
+      sql = "select  sum(rsvp_count), count(*) as cnt from meetupRsvp group by 
group_country, cnt limit 50";
+      CalciteSqlParser.compileToPinotQuery(sql);
+      Assert.fail("Query should have failed compilation");
+    } catch (Exception e) {
+      Assert.assertTrue(e instanceof SqlCompilationException);
+      Assert.assertTrue(e.getMessage().contains("is not allowed in GROUP BY 
clause."));
+    }
+  }
+
+  @Test
+  public void testAliasInSelection() {
+    String sql;
+    PinotQuery pinotQuery;
+    sql = "SELECT C1 AS ALIAS_C1, C2 AS ALIAS_C2, ADD(C1, C2) FROM Foo";
+    pinotQuery = CalciteSqlParser.compileToPinotQuery(sql);
+    Assert.assertEquals(pinotQuery.getSelectListSize(), 3);
+    
Assert.assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(),
 "AS");
+    Assert.assertEquals(
+        
pinotQuery.getSelectList().get(0).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "C1");
+    Assert.assertEquals(
+        
pinotQuery.getSelectList().get(0).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
 "ALIAS_C1");
+
+    
Assert.assertEquals(pinotQuery.getSelectList().get(1).getFunctionCall().getOperator(),
 "AS");
+    Assert.assertEquals(
+        
pinotQuery.getSelectList().get(1).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "C2");
+    Assert.assertEquals(
+        
pinotQuery.getSelectList().get(1).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
 "ALIAS_C2");
+
+    
Assert.assertEquals(pinotQuery.getSelectList().get(2).getFunctionCall().getOperator(),
 "ADD");
+    Assert.assertEquals(
+        
pinotQuery.getSelectList().get(2).getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "C1");
+    Assert.assertEquals(
+        
pinotQuery.getSelectList().get(2).getFunctionCall().getOperands().get(1).getIdentifier().getName(),
 "C2");
+
+    // Invalid groupBy clause shouldn't contain aggregate expression, like 
sum(rsvp_count), count(*).
+    try {
+      sql = "SELECT C1 AS ALIAS_C1, C2 AS ALIAS_C2, ADD(alias_c1, alias_c2) 
FROM Foo";
+      CalciteSqlParser.compileToPinotQuery(sql);
+      Assert.fail("Query should have failed compilation");
+    } catch (Exception e) {
+      Assert.assertTrue(e instanceof SqlCompilationException);
+      Assert.assertTrue(e.getMessage().contains("cannot be referred in SELECT 
Clause"));
+    }
+  }
 }


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

Reply via email to