You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by vl...@apache.org on 2015/01/06 14:25:32 UTC

incubator-calcite git commit: [CALCITE-92] Optimize away Project that merely renames fields

Repository: incubator-calcite
Updated Branches:
  refs/heads/master dd05f6cd9 -> 3c508b5c4


[CALCITE-92] Optimize away Project that merely renames fields

Fixes #32


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

Branch: refs/heads/master
Commit: 3c508b5c4fa3aa3eff3837df77a06f3361103dee
Parents: dd05f6c
Author: Vladimir Sitnikov <si...@gmail.com>
Authored: Tue Jan 6 16:25:34 2015 +0300
Committer: Vladimir Sitnikov <si...@gmail.com>
Committed: Tue Jan 6 16:25:34 2015 +0300

----------------------------------------------------------------------
 .../org/apache/calcite/plan/RelOptUtil.java     |  9 +++-
 .../calcite/plan/SubstitutionVisitor.java       | 11 +---
 .../calcite/rel/rules/ProjectRemoveRule.java    | 46 +++++-----------
 .../java/org/apache/calcite/rex/RexUtil.java    |  2 +-
 .../apache/calcite/sql2rel/RelFieldTrimmer.java |  1 -
 .../java/org/apache/calcite/test/JdbcTest.java  | 57 ++++++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml | 31 +++++------
 .../calcite/test/SqlToRelConverterTest.xml      |  5 +-
 core/src/test/resources/sql/join.oq             |  6 +--
 core/src/test/resources/sql/outer.oq            |  3 +-
 10 files changed, 96 insertions(+), 75 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 9cc2401..c0a83ae 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -2709,7 +2709,14 @@ public abstract class RelOptUtil {
                 : SqlValidatorUtil.uniquify(
                     fieldNames, SqlValidatorUtil.F_SUGGESTER));
     if (optimize
-        && ProjectRemoveRule.isIdentity(exprs, rowType, child.getRowType())) {
+        && ProjectRemoveRule.isIdentity(exprs, child.getRowType())) {
+      if (child instanceof Project && fieldNames != null) {
+        // Rename columns of child projection if desired field names are given.
+        Project childProject = (Project) child;
+        child = childProject.copy(childProject.getTraitSet(),
+            childProject.getInput(), childProject.getProjects(),
+            rowType);
+      }
       return child;
     }
     return new LogicalProject(cluster,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
index e0889dc..54fe7b8 100644
--- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
+++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
@@ -1886,16 +1886,7 @@ public class SubstitutionVisitor {
     public static boolean isTrivial(MutableProject project) {
       MutableRel child = project.getInput();
       final RelDataType childRowType = child.getRowType();
-      if (!childRowType.isStruct()) {
-        return false;
-      }
-      if (!ProjectRemoveRule.isIdentity(
-          project.getProjects(),
-          project.getRowType(),
-          childRowType)) {
-        return false;
-      }
-      return true;
+      return ProjectRemoveRule.isIdentity(project.getProjects(), childRowType);
     }
 
     /** Equivalent to

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java b/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
index b74f532..5e39853 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectRemoveRule.java
@@ -21,9 +21,8 @@ import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
 
 import com.google.common.base.Predicate;
 
@@ -66,6 +65,13 @@ public class ProjectRemoveRule extends RelOptRule {
     Project project = call.rel(0);
     assert isTrivial(project);
     RelNode stripped = project.getInput();
+    if (stripped instanceof Project) {
+      // Rename columns of child projection if desired field names are given.
+      Project childProject = (Project) stripped;
+      stripped = childProject.copy(childProject.getTraitSet(),
+          childProject.getInput(), childProject.getProjects(),
+          project.getRowType());
+    }
     RelNode child = call.getPlanner().register(stripped, project);
     call.transformTo(child);
   }
@@ -81,41 +87,13 @@ public class ProjectRemoveRule extends RelOptRule {
   public static boolean isTrivial(Project project) {
     RelNode child = project.getInput();
     final RelDataType childRowType = child.getRowType();
-    if (!childRowType.isStruct()) {
-      return false;
-    }
-    if (!project.isBoxed()) {
-      return false;
-    }
-    if (!isIdentity(project.getProjects(), project.getRowType(),
-        childRowType)) {
-      return false;
-    }
-    return true;
+    return isIdentity(project.getProjects(), childRowType);
   }
 
   public static boolean isIdentity(List<? extends RexNode> exps,
-      RelDataType rowType, RelDataType childRowType) {
-    List<RelDataTypeField> fields = rowType.getFieldList();
-    List<RelDataTypeField> childFields = childRowType.getFieldList();
-    int fieldCount = childFields.size();
-    if (exps.size() != fieldCount) {
-      return false;
-    }
-    for (int i = 0; i < exps.size(); i++) {
-      RexNode exp = exps.get(i);
-      if (!(exp instanceof RexInputRef)) {
-        return false;
-      }
-      RexInputRef var = (RexInputRef) exp;
-      if (var.getIndex() != i) {
-        return false;
-      }
-      if (!fields.get(i).getName().equals(childFields.get(i).getName())) {
-        return false;
-      }
-    }
-    return true;
+      RelDataType childRowType) {
+    return childRowType.getFieldCount() == exps.size()
+        && RexUtil.containIdentity(exps, childRowType, false);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/main/java/org/apache/calcite/rex/RexUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index c5efd29..ef430d8 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -583,7 +583,7 @@ public class RexUtil {
    * underlying datatype.
    */
   public static boolean containIdentity(
-      List<RexNode> exprs,
+      List<? extends RexNode> exprs,
       RelDataType rowType,
       boolean fail) {
     final List<RelDataTypeField> fields = rowType.getFieldList();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
index 4bfe35b..a41606e 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -380,7 +380,6 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     final RelNode newProject;
     if (ProjectRemoveRule.isIdentity(
         newProjectExprList,
-        newRowType,
         newInput.getRowType())) {
       // The new project would be the identity. It is equivalent to return
       // its child.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index b7b1d39..3493dcb 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -2486,6 +2486,58 @@ public class JdbcTest {
             + "EXPR$0=2; EXPR$1=abc\n");
   }
 
+  /**
+   * Tests that even though trivial "rename columns" projection is removed,
+   * the query still returns proper column names.
+   */
+  @Test public void testValuesCompositeRenamed() {
+    CalciteAssert.that()
+        .query("select EXPR$0 q, EXPR$1 w from (values (1, 'a'), (2, 'abc'))")
+        .explainContains(
+            "PLAN=EnumerableValues(tuples=[[{ 1, 'a  ' }, { 2, 'abc' }]])\n")
+        .returns("Q=1; W=a  \n"
+            + "Q=2; W=abc\n");
+  }
+
+  /**
+   * Tests that even though trivial "rename columns" projection is removed,
+   * the query still returns proper column names.
+   */
+  @Test public void testValuesCompositeRenamedSameNames() {
+    CalciteAssert.that()
+        .query("select EXPR$0 q, EXPR$1 q from (values (1, 'a'), (2, 'abc'))")
+        .explainContains(
+            "PLAN=EnumerableValues(tuples=[[{ 1, 'a  ' }, { 2, 'abc' }]])\n")
+        .returnsUnordered(
+            "Q=1; Q=a  ",
+            "Q=2; Q=abc");
+  }
+
+  /**
+   * Tests that even though trivial "rename columns" projection is removed,
+   * the query still returns proper column names.
+   */
+  @Test public void testUnionWithSameColumnNames() {
+    CalciteAssert.that()
+        .with(CalciteAssert.Config.REGULAR)
+        .query(
+            "select \"deptno\", \"deptno\" from \"hr\".\"depts\" union select \"deptno\", \"empid\" from \"hr\".\"emps\"")
+        .explainContains(""
+            + "PLAN=EnumerableUnion(all=[false])\n"
+            + "  EnumerableCalc(expr#0..3=[{inputs}], deptno=[$t0], deptno0=[$t0])\n"
+            + "    EnumerableTableScan(table=[[hr, depts]])\n"
+            + "  EnumerableCalc(expr#0..4=[{inputs}], deptno=[$t1], empid=[$t0])\n"
+            + "    EnumerableTableScan(table=[[hr, emps]])\n")
+        .returnsUnordered(
+            "deptno=10; deptno=110",
+            "deptno=10; deptno=10",
+            "deptno=20; deptno=200",
+            "deptno=10; deptno=100",
+            "deptno=10; deptno=150",
+            "deptno=30; deptno=30",
+            "deptno=40; deptno=40");
+  }
+
   /** Tests inner join to an inline table ({@code VALUES} clause). */
   @Test public void testInnerJoinValues() {
     CalciteAssert.that()
@@ -2493,6 +2545,11 @@ public class JdbcTest {
         .query("select empno, desc from sales.emps,\n"
             + "  (SELECT * FROM (VALUES (10, 'SameName')) AS t (id, desc)) as sn\n"
             + "where emps.deptno = sn.id and sn.desc = 'SameName' group by empno, desc")
+        .explainContains("EnumerableAggregate(group=[{0, 1}])\n"
+            + "  EnumerableCalc(expr#0..3=[{inputs}], expr#4=[CAST($t3):INTEGER NOT NULL], expr#5=[=($t4, $t0)], expr#6=['SameName'], expr#7=[=($t1, $t6)], expr#8=[AND($t5, $t7)], EMPNO=[$t2], DESC=[$t1], $condition=[$t8])\n"
+            + "    EnumerableJoin(condition=[true], joinType=[inner])\n"
+            + "      EnumerableValues(tuples=[[{ 10, 'SameName' }]])\n"
+            + "      EnumerableTableScan(table=[[SALES, EMPS]])\n")
         .returns("EMPNO=1; DESC=SameName\n");
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 8603a5f..7e1e00b 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -296,8 +296,7 @@ LogicalProject(DDEPTNO=[$0], DNAME=[$1], C=[$2])
   LogicalProject(DDEPTNO=[CASE($2, null, $0)], DNAME=[CASE($3, null, $1)], C=[$4])
     LogicalFilter(condition=[=(CASE($3, null, $1), 'Charlie')])
       LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {}]], indicator=[true], C=[COUNT()])
-        LogicalProject(DDEPTNO=[$0], DNAME=[$1])
-          LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
         <Resource name="planAfter">
@@ -306,8 +305,7 @@ LogicalProject(DDEPTNO=[$0], DNAME=[$1], C=[$2])
   LogicalProject(DDEPTNO=[CASE($2, null, $0)], DNAME=[CASE($3, null, $1)], C=[$4])
     LogicalFilter(condition=[=(CASE($3, null, $1), 'Charlie')])
       LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {}]], indicator=[true], C=[COUNT()])
-        LogicalProject(DDEPTNO=[$0], DNAME=[$1])
-          LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
     </TestCase>
@@ -2764,9 +2762,8 @@ LogicalProject(DEPTNO=[$0], NAME=[$1])
                 LogicalProject(SAL=[$5], DEPTNO=[$7])
                   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
                 LogicalAggregate(group=[{0}])
-                  LogicalProject($f0=[$0])
-                    LogicalProject(DEPTNO=[$0])
-                      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+                  LogicalProject(DEPTNO=[$0])
+                    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
         <Resource name="planAfter">
@@ -2783,9 +2780,8 @@ LogicalProject(DEPTNO=[$0], NAME=[$1])
                 LogicalProject(SAL=[$5], DEPTNO=[$7])
                   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
               LogicalAggregate(group=[{0}])
-                LogicalProject($f0=[$0])
-                  LogicalProject(DEPTNO=[$0])
-                    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+                LogicalProject(DEPTNO=[$0])
+                  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
     </TestCase>
@@ -2808,9 +2804,8 @@ LogicalProject(DEPTNO=[$0], NAME=[$1])
             LogicalProject(SAL=[$5], DEPTNO=[$7])
               LogicalTableScan(table=[[CATALOG, SALES, EMP]])
           LogicalAggregate(group=[{0}])
-            LogicalProject($f0=[$0])
-              LogicalProject(DEPTNO=[$0])
-                LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+            LogicalProject(DEPTNO=[$0])
+              LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
         <Resource name="planAfter">
@@ -2823,9 +2818,8 @@ SemiJoin(condition=[=($0, $2)], joinType=[inner])
         LogicalProject(SAL=[$5], DEPTNO=[$7])
           LogicalTableScan(table=[[CATALOG, SALES, EMP]])
       LogicalAggregate(group=[{0}])
-        LogicalProject($f0=[$0])
-          LogicalProject(DEPTNO=[$0])
-            LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+        LogicalProject(DEPTNO=[$0])
+          LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
     </TestCase>
@@ -2865,9 +2859,8 @@ LogicalProject(DEPTNO=[$0])
             LogicalProject(SAL=[$5], DEPTNO=[$7])
               LogicalTableScan(table=[[CATALOG, SALES, EMP]])
           LogicalAggregate(group=[{0}])
-            LogicalProject($f0=[$0])
-              LogicalProject(DEPTNO=[$0])
-                LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+            LogicalProject(DEPTNO=[$0])
+              LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
     LogicalProject(ACCTNO=[$0])
       LogicalTableScan(table=[[CATALOG, CUSTOMER, ACCOUNT]])
 ]]>

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index b2990d2..db79eb8 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2226,9 +2226,8 @@ group by rollup(a, b)]]>
 LogicalProject(A=[$0], B=[$1], C=[$4])
   LogicalProject(A=[CASE($2, null, $0)], B=[CASE($3, null, $1)], i$A=[$2], i$B=[$3], C=[$4])
     LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {}]], indicator=[true], C=[COUNT()])
-      LogicalProject(A=[$0], B=[$1])
-        LogicalProject(EXPR$0=[null], EXPR$1=[2])
-          LogicalValues(tuples=[[{ 0 }]])
+      LogicalProject(A=[null], B=[2])
+        LogicalValues(tuples=[[{ 0 }]])
 ]]>
         </Resource>
     </TestCase>

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/test/resources/sql/join.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/join.oq b/core/src/test/resources/sql/join.oq
index 28031ac..1bbb48c 100644
--- a/core/src/test/resources/sql/join.oq
+++ b/core/src/test/resources/sql/join.oq
@@ -39,8 +39,7 @@ on emp.deptno = dept.deptno or emp.ename = dept.dname;
 # As an INNER join, it can be executed as an equi-join followed by a filter
 EnumerableCalc(expr#0..5=[{inputs}], expr#6=[=($t3, $t0)], expr#7=[=($t5, $t1)], expr#8=[OR($t6, $t7)], ENAME=[$t2], DEPTNO=[$t3], GENDER=[$t4], DEPTNO0=[$t0], DNAME=[$t1], $condition=[$t8])
   EnumerableJoin(condition=[true], joinType=[inner])
-    EnumerableCalc(expr#0..1=[{inputs}], proj#0..1=[{exprs}])
-      EnumerableValues(tuples=[[{ 10, 'Sales      ' }, { 20, 'Marketing  ' }, { 30, 'Engineering' }, { 40, 'Empty      ' }]])
+    EnumerableValues(tuples=[[{ 10, 'Sales      ' }, { 20, 'Marketing  ' }, { 30, 'Engineering' }, { 40, 'Empty      ' }]])
     EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t0):CHAR(11) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary" NOT NULL], proj#0..3=[{exprs}])
       EnumerableUnion(all=[true])
         EnumerableCalc(expr#0=[{inputs}], expr#1=['Jane'], expr#2=[10], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
@@ -108,8 +107,7 @@ EnumerableCalc(expr#0..5=[{inputs}], proj#0..2=[{exprs}], DEPTNO0=[$t4], DNAME=[
           EnumerableValues(tuples=[[{ 0 }]])
         EnumerableCalc(expr#0=[{inputs}], expr#1=['Wilma'], expr#2=[null], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
           EnumerableValues(tuples=[[{ 0 }]])
-    EnumerableCalc(expr#0..1=[{inputs}], proj#0..1=[{exprs}])
-      EnumerableValues(tuples=[[{ 10, 'Sales      ' }, { 20, 'Marketing  ' }, { 30, 'Engineering' }, { 40, 'Empty      ' }]])
+    EnumerableValues(tuples=[[{ 10, 'Sales      ' }, { 20, 'Marketing  ' }, { 30, 'Engineering' }, { 40, 'Empty      ' }]])
 !plan
 
 # End join.oq

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/3c508b5c/core/src/test/resources/sql/outer.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/outer.oq b/core/src/test/resources/sql/outer.oq
index 92071b1..fe94a70 100644
--- a/core/src/test/resources/sql/outer.oq
+++ b/core/src/test/resources/sql/outer.oq
@@ -266,8 +266,7 @@ EnumerableThetaJoin(condition=[=(-($1, $3), 0)], joinType=[full])
         EnumerableValues(tuples=[[{ 0 }]])
       EnumerableCalc(expr#0=[{inputs}], expr#1=['Wilma'], expr#2=[null], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
         EnumerableValues(tuples=[[{ 0 }]])
-  EnumerableCalc(expr#0..1=[{inputs}], proj#0..1=[{exprs}])
-    EnumerableValues(tuples=[[{ 10, 'Sales      ' }, { 20, 'Marketing  ' }, { 30, 'Engineering' }, { 40, 'Empty      ' }]])
+  EnumerableValues(tuples=[[{ 10, 'Sales      ' }, { 20, 'Marketing  ' }, { 30, 'Engineering' }, { 40, 'Empty      ' }]])
 !plan