Repository: calcite
Updated Branches:
  refs/heads/master cf5ff72b7 -> ecc1623fb


[CALCITE-1714] Do not push query with group by on druid metrics (Slim Bouguerra)

Close apache/calcite#409


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/0209b16c
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/0209b16c
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/0209b16c

Branch: refs/heads/master
Commit: 0209b16c0adca468c3da06ef036e3d06432ea17b
Parents: cf5ff72
Author: Slim Bouguerra <[email protected]>
Authored: Mon Mar 27 11:05:42 2017 +0100
Committer: Jesus Camacho Rodriguez <[email protected]>
Committed: Mon Mar 27 19:53:35 2017 +0100

----------------------------------------------------------------------
 .../calcite/adapter/druid/DruidQuery.java       |  3 +-
 .../calcite/adapter/druid/DruidRules.java       | 34 +++++++++
 .../org/apache/calcite/test/DruidAdapterIT.java | 76 ++++++++++++++++++++
 3 files changed, 111 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/0209b16c/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
index 061c52c..9378e47 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
@@ -509,7 +509,7 @@ public class DruidQuery extends AbstractRelNode implements 
BindableRel {
           final String s = fieldNames.get(groupKey);
           final RexNode project = projects.get(groupKey);
           if (project instanceof RexInputRef) {
-            // Reference, it could be to the timestamp column or any other 
dimension
+            // Reference could be to the timestamp or druid dimension but no 
druid metric
             final RexInputRef ref = (RexInputRef) project;
             final String origin = 
druidTable.getRowType(getCluster().getTypeFactory())
                 .getFieldList().get(ref.getIndex()).getName();
@@ -563,7 +563,6 @@ public class DruidQuery extends AbstractRelNode implements 
BindableRel {
       }
 
       fieldNames = builder.build();
-
       ImmutableList<JsonCollation> collations = null;
       boolean sortsMetric = false;
       if (collationIndexes != null) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/0209b16c/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index 2054c11..a417698 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -364,6 +364,9 @@ public class DruidRules {
       for (AggregateCall aggCall : aggregate.getAggCallList()) {
         builder.addAll(aggCall.getArgList());
       }
+      if (checkAggregateOnMetric(aggregate.getGroupSet(), aggregate, query)) {
+        return false;
+      }
       return !checkTimestampRefOnQuery(builder.build(), query.getTopNode(), 
query);
     }
   }
@@ -398,6 +401,10 @@ public class DruidRules {
         return;
       }
 
+      if (checkAggregateOnMetric(aggregate.getGroupSet(), project, query)) {
+        return;
+      }
+
       final RelNode newProject = project.copy(project.getTraitSet(),
               ImmutableList.of(Util.last(query.rels)));
       final DruidQuery projectDruidQuery = DruidQuery.extendQuery(query, 
newProject);
@@ -600,6 +607,33 @@ public class DruidRules {
     return false;
   }
 
+  /** Checks whether any of the references leads to a metric column. */
+  private static boolean checkAggregateOnMetric(ImmutableBitSet set, RelNode 
topProject,
+      DruidQuery query) {
+    if (topProject instanceof Project) {
+      ImmutableBitSet.Builder newSet = ImmutableBitSet.builder();
+      final Project project = (Project) topProject;
+      for (int index : set) {
+        RexNode node = project.getProjects().get(index);
+        if (node instanceof RexInputRef) {
+          newSet.set(((RexInputRef) node).getIndex());
+        } else if (node instanceof RexCall) {
+          RexCall call = (RexCall) node;
+          newSet.set(((RexInputRef) call.getOperands().get(0)).getIndex());
+        }
+      }
+      set = newSet.build();
+    }
+    for (int index : set) {
+      if (query.druidTable.metricFieldNames
+              
.contains(query.getTopNode().getRowType().getFieldNames().get(index))) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   /**
    * Rule to push a {@link org.apache.calcite.rel.core.Project}
    * past a {@link org.apache.calcite.rel.core.Filter}

http://git-wip-us.apache.org/repos/asf/calcite/blob/0209b16c/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
----------------------------------------------------------------------
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java 
b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
index 4ee32a5..312ff63 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -411,6 +411,60 @@ public class DruidAdapterIT {
         .explainContains(explain);
   }
 
+  @Test public void testGroupbyMetric() {
+    final String sql = "select  \"store_sales\" ,\"product_id\" from 
\"foodmart\" "
+            + "where \"product_id\" = 1020" + "group by \"store_sales\" 
,\"product_id\" ";
+    final String plan = "PLAN=EnumerableInterpreter\n  
BindableAggregate(group=[{0, 1}])\n"
+            + "    DruidQuery(table=[[foodmart, foodmart]], "
+            + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], 
filter=[=($1, 1020)],"
+            + " projects=[[$90, $1]])\n";
+    sql(sql).explainContains(plan).
+    queryContains(
+            
druidChecker("{\"queryType\":\"select\",\"dataSource\":\"foodmart\","
+                    + "\"descending\":false,"
+                    + 
"\"intervals\":[\"1900-01-09T00:00:00.000/2992-01-10T00:00:00.000"
+                    + 
"\"],\"filter\":{\"type\":\"selector\",\"dimension\":\"product_id\","
+                    + "\"value\":\"1020\"},\"dimensions\":[\"product_id\"],"
+                    + "\"metrics\":[\"store_sales\"],\"granularity\":\"all\","
+                    + "\"pagingSpec\":{\"threshold\":16384,\"fromNext\":true},"
+                    + "\"context\":{\"druid.query.fetch\":false}}")
+    )
+    .returnsUnordered("store_sales=0.5099999904632568; product_id=1020",
+                    "store_sales=1.0199999809265137; product_id=1020",
+                    "store_sales=1.5299999713897705; product_id=1020",
+                    "store_sales=2.0399999618530273; product_id=1020",
+                    "store_sales=2.549999952316284; product_id=1020"
+    );
+  }
+
+  @Test public void testPushSimpleGroupBy() {
+    final String sql = "select \"product_id\" from \"foodmart\" where "
+            + "\"product_id\" = 1020 group by \"product_id\"";
+    final String druidQuery = 
"{\"queryType\":\"groupBy\",\"dataSource\":\"foodmart\","
+            + "\"granularity\":\"all\",\"dimensions\":[\"product_id\"],"
+            + 
"\"limitSpec\":{\"type\":\"default\"},\"filter\":{\"type\":\"selector\","
+            + "\"dimension\":\"product_id\",\"value\":\"1020\"},"
+            + "\"aggregations\":[{\"type\":\"longSum\",\"name\":\"dummy_agg\","
+            + "\"fieldName\":\"dummy_agg\"}],"
+            + 
"\"intervals\":[\"1900-01-09T00:00:00.000/2992-01-10T00:00:00.000\"]}";
+    
sql(sql).queryContains(druidChecker(druidQuery)).returnsUnordered("product_id=1020");
+  }
+
+  @Test public void testComplexPushGroupBy() {
+    final String innerQuery = "select \"product_id\" as \"id\" from 
\"foodmart\" where "
+            + "\"product_id\" = 1020";
+    final String sql = "select \"id\" from (" + innerQuery + ") group by 
\"id\"";
+
+    sql(sql).returnsUnordered("id=1020")
+            .queryContains(
+                    druidChecker("{\"queryType\":\"groupBy"
+            + "\",\"dataSource\":\"foodmart\",\"granularity\":\"all\","
+            + 
"\"dimensions\":[\"product_id\"],\"limitSpec\":{\"type\":\"default\"},"
+            + "\"filter\":{\"type\":\"selector\",\"dimension\":\"product_id\","
+            + "\"value\":\"1020\"},\"aggregations\":[{\"type\":\"longSum\","
+            + "\"name\":\"dummy_agg\",\"fieldName\":\"dummy_agg\"}],"
+            + 
"\"intervals\":[\"1900-01-09T00:00:00.000/2992-01-10T00:00:00.000\"]}"));
+  }
   /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1281";>[CALCITE-1281]
    * Druid adapter wrongly returns all numeric values as int or float</a>. */
@@ -928,6 +982,20 @@ public class DruidAdapterIT {
         .queryContains(druidChecker("'queryType':'groupBy'"));
   }
 
+  @Test public void testGroupByTimeAndOneMetricNotProjected() {
+    final String sql =
+            "select count(*) as \"c\", floor(\"timestamp\" to MONTH) as 
\"month\", floor"
+                    + "(\"store_sales\") as sales\n"
+                    + "from \"foodmart\"\n"
+                    + "group by floor(\"timestamp\" to MONTH), 
\"state_province\", floor"
+                    + "(\"store_sales\")\n"
+                    + "order by \"c\" desc limit 3";
+    sql(sql).returnsOrdered("c=494; month=1997-11-01 00:00:00; SALES=5.0",
+            "c=475; month=1997-12-01 00:00:00; SALES=5.0",
+            "c=468; month=1997-03-01 00:00:00; SALES=5.0"
+    ).queryContains(druidChecker("'queryType':'select'"));
+  }
+
   @Test public void testGroupByTimeAndOneColumnNotProjected() {
     final String sql = "select count(*) as \"c\",\n"
         + "  floor(\"timestamp\" to MONTH) as \"month\"\n"
@@ -1420,6 +1488,14 @@ public class DruidAdapterIT {
     sql(sql, WIKI).queryContains(druidChecker(druidQuery));
   }
 
+  @Test public void testGroupByMetricAndExtractTime() {
+    final String sql = "SELECT count(*), floor(\"timestamp\" to DAY), 
\"store_sales\" "
+            + "FROM \"foodmart\"\n"
+            + "GROUP BY \"store_sales\", floor(\"timestamp\" to DAY)\n ORDER 
BY \"store_sales\" DESC\n"
+            + "LIMIT 10\n";
+    sql(sql).queryContains(druidChecker("{\"queryType\":\"select\""));
+  }
+
 }
 
 // End DruidAdapterIT.java

Reply via email to