You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2014/08/30 00:04:27 UTC

git commit: [OPTIQ-393] If no fields are projected from a table, field trimmer should project a dummy expression

Repository: incubator-optiq
Updated Branches:
  refs/heads/master acee9632f -> e60fa76e2


[OPTIQ-393] If no fields are projected from a table, field trimmer should project a dummy expression

Be careful not to create a dummy on top of a dummy.

Close apache/incubator-optiq#8 which was the original attempt to solve the problem of zero-field relational expressions.


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/e60fa76e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/e60fa76e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/e60fa76e

Branch: refs/heads/master
Commit: e60fa76e207f7a9d8c7bdaf0a5dea951f9b561a5
Parents: acee963
Author: Ashutosh Chauhan <ha...@apache.org>
Authored: Wed Aug 27 14:24:23 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Aug 29 14:32:40 2014 -0700

----------------------------------------------------------------------
 .../org/eigenbase/sql2rel/RelFieldTrimmer.java  | 73 ++++++++++++++------
 .../net/hydromatic/optiq/test/JdbcTest.java     | 16 ++++-
 .../net/hydromatic/optiq/test/LatticeTest.java  |  3 +-
 core/src/test/resources/sql/misc.oq             | 37 ++++++++++
 4 files changed, 104 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/e60fa76e/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java b/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
index 75c04b1..14ae9be 100644
--- a/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/eigenbase/sql2rel/RelFieldTrimmer.java
@@ -33,6 +33,8 @@ import net.hydromatic.linq4j.Ord;
 
 import net.hydromatic.optiq.util.BitSets;
 
+import com.google.common.collect.ImmutableList;
+
 /**
  * Transformer that walks over a tree of relational expressions, replacing each
  * {@link RelNode} with a 'slimmed down' relational expression that projects
@@ -277,26 +279,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     // Some parts of the system can't handle rows with zero fields, so
     // pretend that one field is used.
     if (fieldsUsed.cardinality() == 0) {
-      final Mapping mapping =
-          Mappings.create(
-              MappingType.INVERSE_SURJECTION,
-              fieldCount,
-              1);
-      final RexLiteral expr =
-          project.getCluster().getRexBuilder().makeExactLiteral(
-              BigDecimal.ZERO);
-      RelDataType newRowType =
-          project.getCluster().getTypeFactory().createStructType(
-              Collections.singletonList(expr.getType()),
-              Collections.singletonList("DUMMY"));
-      ProjectRel newProject = new ProjectRel(
-          project.getCluster(),
-          project.getCluster().traitSetOf(RelCollationImpl.EMPTY),
-          newInput,
-          Collections.<RexNode>singletonList(expr),
-          newRowType,
-          project.getFlags());
-      return new TrimResult(newProject, mapping);
+      return dummyProject(fieldCount, newInput);
     }
 
     // Build new project expressions, and populate the mapping.
@@ -349,6 +332,34 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     return new TrimResult(newProject, mapping);
   }
 
+  /** Creates a project with a dummy column, to protect the parts of the system
+   * that cannot handle a relational expression with no columns.
+   *
+   * @param fieldCount Number of fields in the original relational expression
+   * @param input Trimmed input
+   * @return Dummy project, or null if no dummy is required
+   */
+  private TrimResult dummyProject(int fieldCount, RelNode input) {
+    final RelOptCluster cluster = input.getCluster();
+    final Mapping mapping =
+        Mappings.create(MappingType.INVERSE_SURJECTION, fieldCount, 1);
+    if (input.getRowType().getFieldCount() == 1) {
+      // Input already has one field (and may in fact be a dummy project we
+      // created for the child). We can't do better.
+      return new TrimResult(input, mapping);
+    }
+    final RexLiteral expr =
+        cluster.getRexBuilder().makeExactLiteral(
+            BigDecimal.ZERO);
+    RelDataType newRowType =
+        cluster.getTypeFactory().builder().add("DUMMY", expr.getType()).build();
+    ProjectRel newProject = new ProjectRel(cluster,
+        cluster.traitSetOf(RelCollationImpl.EMPTY), input,
+        ImmutableList.<RexNode>of(expr), newRowType,
+        ProjectRelBase.Flags.BOXED);
+    return new TrimResult(newProject, mapping);
+  }
+
   /**
    * Variant of {@link #trimFields(RelNode, BitSet, Set)} for
    * {@link FilterRel}.
@@ -930,13 +941,31 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
       BitSet fieldsUsed,
       Set<RelDataTypeField> extraFields) {
     final int fieldCount = tableAccessRel.getRowType().getFieldCount();
-    if (fieldsUsed.equals(BitSets.range(fieldCount))
-        && extraFields.isEmpty()) {
+    if (fieldsUsed.equals(BitSets.range(fieldCount)) && extraFields.isEmpty()) {
+      // if there is nothing to project or if we are projecting everything
+      // then no need to introduce another RelNode
       return trimFields(
           (RelNode) tableAccessRel, fieldsUsed, extraFields);
     }
     final RelNode newTableAccessRel =
         tableAccessRel.project(fieldsUsed, extraFields);
+
+    // Some parts of the system can't handle rows with zero fields, so
+    // pretend that one field is used.
+    if (fieldsUsed.cardinality() == 0) {
+      RelNode input = newTableAccessRel;
+      if (input instanceof ProjectRelBase) {
+        // The table has implemented the project in the obvious way - by
+        // creating project with 0 fields. Strip it away, and create our own
+        // project with one field.
+        ProjectRelBase project = (ProjectRelBase) input;
+        if (project.getRowType().getFieldCount() == 0) {
+          input = project.getChild();
+        }
+      }
+      return dummyProject(fieldCount, input);
+    }
+
     final Mapping mapping = createMapping(fieldsUsed, fieldCount);
     return new TrimResult(newTableAccessRel, mapping);
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/e60fa76e/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
index 53e5cdc..9c624d5 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
@@ -2962,6 +2962,20 @@ public class JdbcTest {
         .returnsCount(0);
   }
 
+  /** Query that reads no columns from either underlying table. */
+  @Test public void testCountStar() {
+    OptiqAssert.that()
+        .with(OptiqAssert.Config.REGULAR)
+        .query("select count(*) c from \"hr\".\"emps\", \"hr\".\"depts\"")
+        .convertContains("AggregateRel(group=[{}], C=[COUNT()])\n"
+            + "  ProjectRel(DUMMY=[0])\n"
+            + "    JoinRel(condition=[true], joinType=[inner])\n"
+            + "      ProjectRel(DUMMY=[0])\n"
+            + "        EnumerableTableAccessRel(table=[[hr, emps]])\n"
+            + "      ProjectRel(DUMMY=[0])\n"
+            + "        EnumerableTableAccessRel(table=[[hr, depts]])");
+  }
+
   /** Same result (and plan) as {@link #testSelectDistinct}. */
   @Test public void testCountUnionAll() {
     OptiqAssert.that()
@@ -3009,7 +3023,7 @@ public class JdbcTest {
         .explainContains(
             "PLAN=EnumerableCalcRel(expr#0=[{inputs}], CS=[$t0], CS2=[$t0])\n"
             + "  EnumerableAggregateRel(group=[{}], CS=[COUNT()])\n"
-            + "    EnumerableCalcRel(expr#0..4=[{inputs}], expr#5=[0], expr#6=[<($t1, $t5)], DUMMY=[$t5], $condition=[$t6])\n"
+            + "    EnumerableCalcRel(expr#0..4=[{inputs}], expr#5=[0], expr#6=[<($t1, $t5)], deptno=[$t1], $condition=[$t6])\n"
             + "      EnumerableTableAccessRel(table=[[hr, emps]])\n")
         .returns("CS=0; CS2=0\n");
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/e60fa76e/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
index 80b4cde..2c473c9 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
@@ -146,8 +146,7 @@ public class LatticeTest {
           .convertContains(
               "AggregateRel(group=[{}], EXPR$0=[COUNT()])\n"
               + "  ProjectRel(DUMMY=[0])\n"
-              + "    ProjectRel\n"
-              + "      StarTableScan(table=[[adhoc, star]])\n");
+              + "    StarTableScan(table=[[adhoc, star]])\n");
     } catch (RuntimeException e) {
       assertThat(Util.getStackTrace(e), containsString("CannotPlanException"));
     }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/e60fa76e/core/src/test/resources/sql/misc.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/misc.oq b/core/src/test/resources/sql/misc.oq
index fb635a5..f5b7e1c 100644
--- a/core/src/test/resources/sql/misc.oq
+++ b/core/src/test/resources/sql/misc.oq
@@ -324,6 +324,43 @@ where e."deptno" >= 10 and e."name" = 'Sebastian';
 
 !ok
 
+# [OPTIQ-393] If no fields are projected from a table, field trimmer should
+# project a dummy expression
+select 1 from "hr"."emps";
++--------+
+| EXPR$0 |
++--------+
+|      1 |
+|      1 |
+|      1 |
+|      1 |
++--------+
+(4 rows)
+
+!ok
+EnumerableCalcRel(expr#0..4=[{inputs}], expr#5=[1], EXPR$0=[$t5])
+  EnumerableTableAccessRel(table=[[hr, emps]])
+!plan
+
+# [OPTIQ-393] for table scan under join
+select count(*) as c from "hr"."emps", "hr"."depts";
++----+
+| C  |
++----+
+| 12 |
++----+
+(1 row)
+
+!ok
+EnumerableAggregateRel(group=[{}], C=[COUNT()])
+  EnumerableCalcRel(expr#0..1=[{inputs}], expr#2=[0], DUMMY=[$t2])
+    EnumerableJoinRel(condition=[true], joinType=[inner])
+      EnumerableCalcRel(expr#0..4=[{inputs}], expr#5=[0], DUMMY=[$t5])
+        EnumerableTableAccessRel(table=[[hr, emps]])
+      EnumerableCalcRel(expr#0..2=[{inputs}], expr#3=[0], DUMMY=[$t3])
+        EnumerableTableAccessRel(table=[[hr, depts]])
+!plan
+
 # [OPTIQ-345] AssertionError in RexToLixTranslator comparing to date literal
 !use catchall
 select count(*) as c from "everyTypes" where "sqlDate" = DATE '1970-01-01';