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

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 555da953fe758a7d310aeb3aed463f3f2f3cdc3b
Author: Julian Hyde <[email protected]>
AuthorDate: Wed Feb 5 17:02:22 2020 -0800

    [CALCITE-3774] In RelBuilder and ProjectMergeRule, prevent merges when it 
would increase expression complexity
    
    Add an option RelBuilder.Config.bloat(), default 100.
    Set it, using RelBuilder.Config.withBloat(int),
    to -1 to never merge,
    0 to merge only if complexity does not increase,
    b to merge if complexity increases by no more than b.
    
    Deprecate RelBuilder.shouldMergeProject().
    
    Cache the nodeCount value in RexCall and RexWindow. Compute nodeCount
    each time for RexOver (a sub-class of RexCall with an extra window),
    because caching it would increase the complexity of RexCall's
    constructor.
---
 .../java/org/apache/calcite/plan/RelOptUtil.java   | 23 +++++++
 .../rel/rules/AbstractMaterializedViewRule.java    |  4 +-
 .../apache/calcite/rel/rules/ProjectMergeRule.java | 24 ++++++-
 .../main/java/org/apache/calcite/rex/RexCall.java  |  6 ++
 .../main/java/org/apache/calcite/rex/RexNode.java  | 12 ++++
 .../main/java/org/apache/calcite/rex/RexOver.java  |  4 ++
 .../main/java/org/apache/calcite/rex/RexUtil.java  | 15 ++++
 .../java/org/apache/calcite/rex/RexWindow.java     | 18 +++--
 .../org/apache/calcite/rex/RexWindowBound.java     | 13 ++++
 .../java/org/apache/calcite/tools/RelBuilder.java  | 56 ++++++++++++++-
 .../org/apache/calcite/rex/RexProgramTest.java     |  2 +-
 .../org/apache/calcite/rex/RexProgramTestBase.java | 11 ---
 .../org/apache/calcite/test/RelBuilderTest.java    | 80 ++++++++++++++++++++++
 .../java/org/apache/calcite/tools/PlannerTest.java |  2 +-
 .../org/apache/calcite/piglet/PigRelBuilder.java   | 17 +++--
 15 files changed, 259 insertions(+), 28 deletions(-)

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 99469d6..9b73fd1 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -135,6 +135,7 @@ import java.util.TreeSet;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 /**
  * <code>RelOptUtil</code> defines static utility methods for use in optimizing
@@ -2967,6 +2968,28 @@ public abstract class RelOptUtil {
     return list;
   }
 
+  /** As {@link #pushPastProject}, but returns null if the resulting 
expressions
+   * are significantly more complex.
+   *
+   * @param bloat Maximum allowable increase in complexity */
+  public static @Nullable List<RexNode> pushPastProjectUnlessBloat(
+      List<? extends RexNode> nodes, Project project, int bloat) {
+    if (bloat < 0) {
+      // If bloat is negative never merge.
+      return null;
+    }
+    final List<RexNode> list = pushPastProject(nodes, project);
+    final int bottomCount = RexUtil.nodeCount(project.getProjects());
+    final int topCount = RexUtil.nodeCount(nodes);
+    final int mergedCount = RexUtil.nodeCount(list);
+    if (mergedCount > bottomCount + topCount + bloat) {
+      // The merged expression is more complex than the input expressions.
+      // Do not merge.
+      return null;
+    }
+    return list;
+  }
+
   private static RexShuttle pushShuttle(final Project project) {
     return new RexShuttle() {
       @Override public RexNode visitInputRef(RexInputRef ref) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
index 80eb1ff..d1338a9 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
@@ -960,7 +960,9 @@ public abstract class AbstractMaterializedViewRule extends 
RelOptRule {
           Filter.class, relBuilderFactory, Aggregate.class);
       this.aggregateProjectPullUpConstantsRule = new 
AggregateProjectPullUpConstantsRule(
           Aggregate.class, Filter.class, relBuilderFactory, 
"AggFilterPullUpConstants");
-      this.projectMergeRule = new ProjectMergeRule(true, relBuilderFactory);
+      this.projectMergeRule =
+          new ProjectMergeRule(true, ProjectMergeRule.DEFAULT_BLOAT,
+              relBuilderFactory);
     }
 
     @Override protected boolean isValidPlan(Project topProject, RelNode node,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
index 2d550f9..818f7b5 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
@@ -37,14 +37,20 @@ import java.util.List;
  * provided the projects aren't projecting identical sets of input references.
  */
 public class ProjectMergeRule extends RelOptRule {
+  /** Default amount by which complexity is allowed to increase. */
+  public static final int DEFAULT_BLOAT = 100;
+
   public static final ProjectMergeRule INSTANCE =
-      new ProjectMergeRule(true, RelFactories.LOGICAL_BUILDER);
+      new ProjectMergeRule(true, DEFAULT_BLOAT, RelFactories.LOGICAL_BUILDER);
 
   //~ Instance fields --------------------------------------------------------
 
   /** Whether to always merge projects. */
   private final boolean force;
 
+  /** Limit how much complexity can increase during merging. */
+  private final int bloat;
+
   //~ Constructors -----------------------------------------------------------
 
   /**
@@ -52,13 +58,20 @@ public class ProjectMergeRule extends RelOptRule {
    *
    * @param force Whether to always merge projects
    */
-  public ProjectMergeRule(boolean force, RelBuilderFactory relBuilderFactory) {
+  public ProjectMergeRule(boolean force, int bloat,
+      RelBuilderFactory relBuilderFactory) {
     super(
         operand(Project.class,
             operand(Project.class, any())),
         relBuilderFactory,
         "ProjectMergeRule" + (force ? ":force_mode" : ""));
     this.force = force;
+    this.bloat = bloat;
+  }
+
+  @Deprecated // to be removed before 2.0
+  public ProjectMergeRule(boolean force, RelBuilderFactory relBuilderFactory) {
+    this(force, DEFAULT_BLOAT, relBuilderFactory);
   }
 
   @Deprecated // to be removed before 2.0
@@ -106,7 +119,12 @@ public class ProjectMergeRule extends RelOptRule {
     }
 
     final List<RexNode> newProjects =
-        RelOptUtil.pushPastProject(topProject.getProjects(), bottomProject);
+        RelOptUtil.pushPastProjectUnlessBloat(topProject.getProjects(),
+            bottomProject, bloat);
+    if (newProjects == null) {
+      // Merged projects are significantly more complex. Do not merge.
+      return;
+    }
     final RelNode input = bottomProject.getInput();
     if (RexUtil.isIdentity(newProjects, input.getRowType())) {
       if (force
diff --git a/core/src/main/java/org/apache/calcite/rex/RexCall.java 
b/core/src/main/java/org/apache/calcite/rex/RexCall.java
index 8c38a7f..d5c2cea 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexCall.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexCall.java
@@ -65,6 +65,7 @@ public class RexCall extends RexNode {
   public final SqlOperator op;
   public final ImmutableList<RexNode> operands;
   public final RelDataType type;
+  public final int nodeCount;
 
   /**
    * Simple binary operators are those operators which expects operands from 
the same Domain.
@@ -91,6 +92,7 @@ public class RexCall extends RexNode {
     this.type = Objects.requireNonNull(type, "type");
     this.op = Objects.requireNonNull(op, "operator");
     this.operands = ImmutableList.copyOf(operands);
+    this.nodeCount = RexUtil.nodeCount(1, this.operands);
     assert op.getKind() != null : op;
     assert op.validRexOperands(operands.size(), Litmus.THROW) : this;
   }
@@ -342,6 +344,10 @@ public class RexCall extends RexNode {
     return op;
   }
 
+  @Override public int nodeCount() {
+    return nodeCount;
+  }
+
   /**
    * Creates a new call to the same operator with different operands.
    *
diff --git a/core/src/main/java/org/apache/calcite/rex/RexNode.java 
b/core/src/main/java/org/apache/calcite/rex/RexNode.java
index d9f51c3..7a8f058 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexNode.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexNode.java
@@ -158,6 +158,18 @@ public abstract class RexNode {
     }
   }
 
+  /** Returns the number of nodes in this expression.
+   *
+   * <p>Leaf nodes, such as {@link RexInputRef} or {@link RexLiteral}, have
+   * a count of 1. Calls have a count of 1 plus the sum of their operands.
+   *
+   * <p>Node count is a measure of expression complexity that is used by some
+   * planner rules to prevent deeply nested expressions.
+   */
+  public int nodeCount() {
+    return 1;
+  }
+
   /**
    * Accepts a visitor, dispatching to the right overloaded
    * {@link RexVisitor#visitInputRef visitXxx} method.
diff --git a/core/src/main/java/org/apache/calcite/rex/RexOver.java 
b/core/src/main/java/org/apache/calcite/rex/RexOver.java
index 9e6e931..d197cc2 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexOver.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexOver.java
@@ -125,6 +125,10 @@ public class RexOver extends RexCall {
     return visitor.visitOver(this, arg);
   }
 
+  @Override public int nodeCount() {
+    return super.nodeCount() + window.nodeCount;
+  }
+
   /**
    * Returns whether an expression contains an OVER clause.
    */
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 27b2b22..7dd6a9d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -469,6 +469,21 @@ public class RexUtil {
     return false;
   }
 
+  /** Returns the number of nodes (including leaves) in a list of
+   * expressions.
+   *
+   * @see RexNode#nodeCount() */
+  public static int nodeCount(List<? extends RexNode> nodes) {
+    return nodeCount(0, nodes);
+  }
+
+  static int nodeCount(int n, List<? extends RexNode> nodes) {
+    for (RexNode operand : nodes) {
+      n += operand.nodeCount();
+    }
+    return n;
+  }
+
   /**
    * Walks over an expression and determines whether it is constant.
    */
diff --git a/core/src/main/java/org/apache/calcite/rex/RexWindow.java 
b/core/src/main/java/org/apache/calcite/rex/RexWindow.java
index da9a33f..4e47f68 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexWindow.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexWindow.java
@@ -16,11 +16,14 @@
  */
 package org.apache.calcite.rex;
 
+import org.apache.calcite.util.Pair;
+
 import com.google.common.collect.ImmutableList;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
+import javax.annotation.Nullable;
 
 /**
  * Specification of the window of rows over which a {@link RexOver} windowed
@@ -37,6 +40,7 @@ public class RexWindow {
   private final RexWindowBound upperBound;
   private final boolean isRows;
   private final String digest;
+  public final int nodeCount;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -49,16 +53,15 @@ public class RexWindow {
   RexWindow(
       List<RexNode> partitionKeys,
       List<RexFieldCollation> orderKeys,
-      RexWindowBound lowerBound,
-      RexWindowBound upperBound,
+      @Nullable RexWindowBound lowerBound,
+      @Nullable RexWindowBound upperBound,
       boolean isRows) {
-    assert partitionKeys != null;
-    assert orderKeys != null;
     this.partitionKeys = ImmutableList.copyOf(partitionKeys);
     this.orderKeys = ImmutableList.copyOf(orderKeys);
     this.lowerBound = lowerBound;
     this.upperBound = upperBound;
     this.isRows = isRows;
+    this.nodeCount = computeCodeCount();
     this.digest = computeDigest();
   }
 
@@ -149,4 +152,11 @@ public class RexWindow {
   public boolean isRows() {
     return isRows;
   }
+
+  private int computeCodeCount() {
+    return RexUtil.nodeCount(partitionKeys)
+        + RexUtil.nodeCount(Pair.left(orderKeys))
+        + (lowerBound == null ? 0 : lowerBound.nodeCount())
+        + (upperBound == null ? 0 : upperBound.nodeCount());
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java 
b/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java
index bfcf92d..547bec6 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java
@@ -106,6 +106,15 @@ public abstract class RexWindowBound {
   }
 
   /**
+   * Returns the number of nodes in this bound.
+   *
+   * @see RexNode#nodeCount()
+   */
+  public int nodeCount() {
+    return 1;
+  }
+
+  /**
    * Implements UNBOUNDED PRECEDING/FOLLOWING bound.
    */
   private static class RexWindowBoundUnbounded extends RexWindowBound {
@@ -217,6 +226,10 @@ public abstract class RexWindowBound {
       return offset;
     }
 
+    @Override public int nodeCount() {
+      return super.nodeCount() + offset.nodeCount();
+    }
+
     @Override public <R> RexWindowBound accept(RexVisitor<R> visitor) {
       R r = offset.accept(visitor);
       if (r instanceof RexNode && r != offset) {
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java 
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 800f6d9..eec1af1 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -1324,8 +1324,9 @@ public class RelBuilder {
       fieldNameList.add(null);
     }
 
+    bloat:
     if (frame.rel instanceof Project
-        && shouldMergeProject()) {
+        && config.bloat() >= 0) {
       final Project project = (Project) frame.rel;
       // Populate field names. If the upper expression is an input ref and does
       // not have a recommended name, use the name of the underlying field.
@@ -1340,7 +1341,13 @@ public class RelBuilder {
         }
       }
       final List<RexNode> newNodes =
-          RelOptUtil.pushPastProject(nodeList, project);
+          RelOptUtil.pushPastProjectUnlessBloat(nodeList, project,
+              config.bloat());
+      if (newNodes == null) {
+        // The merged expression is more complex than the input expressions.
+        // Do not merge.
+        break bloat;
+      }
 
       // Carefully build a list of fields, so that table aliases from the input
       // can be seen for fields that are based on a RexInputRef.
@@ -1445,6 +1452,7 @@ public class RelBuilder {
    * <p>The default implementation returns {@code true};
    * sub-classes may disable merge by overriding to return {@code false}. */
   @Experimental
+  @Deprecated
   protected boolean shouldMergeProject() {
     return true;
   }
@@ -3053,6 +3061,50 @@ public class RelBuilder {
       return new ConfigBuilder(this);
     }
 
+    /** Controls whether to merge two {@link Project} operators when inlining
+     * expressions causes complexity to increase.
+     *
+     * <p>Usually merging projects is beneficial, but occasionally the
+     * result is more complex than the original projects. Consider:
+     *
+     * <pre>
+     * P: Project(a+b+c AS x, d+e+f AS y, g+h+i AS z)  # complexity 15
+     * Q: Project(x*y*z AS p, x-y-z AS q)              # complexity 10
+     * R: Project((a+b+c)*(d+e+f)*(g+h+i) AS s,
+     *            (a+b+c)-(d+e+f)-(g+h+i) AS t)        # complexity 34
+     * </pre>
+     *
+     * The complexity of an expression is the number of nodes (leaves and
+     * operators). For example, {@code a+b+c} has complexity 5 (3 field
+     * references and 2 calls):
+     *
+     * <pre>
+     *       +
+     *      /  \
+     *     +    c
+     *    / \
+     *   a   b
+     * </pre>
+     *
+     * <p>A negative value never allows merges.
+     *
+     * <p>A zero or positive value, {@code bloat}, allows a merge if complexity
+     * of the result is less than or equal to the sum of the complexity of the
+     * originals plus {@code bloat}.
+     *
+     * <p>The default value, 100, allows a moderate increase in complexity but
+     * prevents cases where complexity would run away into the millions and run
+     * out of memory. Moderate complexity is OK; the implementation, say via
+     * {@link org.apache.calcite.adapter.enumerable.EnumerableCalc}, will often
+     * gather common sub-expressions and compute them only once.
+     */
+    @ImmutableBeans.Property
+    @ImmutableBeans.IntDefault(100)
+    int bloat();
+
+    /** Sets {@link #bloat}. */
+    Config withBloat(int bloat);
+
     /** Whether {@link RelBuilder#aggregate} should eliminate duplicate
      * aggregate calls; default true. */
     @ImmutableBeans.Property
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java 
b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index 6f4f77e..09989c1 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -738,7 +738,7 @@ public class RexProgramTest extends RexProgramTestBase {
               rexBuilder.makeFieldAccess(range3, i * 2 + 1)));
     }
     final RexNode cnf = RexUtil.toCnf(rexBuilder, or(list));
-    final int nodeCount = nodeCount(cnf);
+    final int nodeCount = cnf.nodeCount();
     assertThat((n + 1) * (int) Math.pow(2, n) + 1, equalTo(nodeCount));
     if (n == 3) {
       assertThat(cnf.toStringRaw(),
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTestBase.java 
b/core/src/test/java/org/apache/calcite/rex/RexProgramTestBase.java
index dc4ae80..9a542ed 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTestBase.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTestBase.java
@@ -171,17 +171,6 @@ public class RexProgramTestBase extends 
RexProgramBuilderBase {
         is(expected ? "true" : "false"));
   }
 
-  /** Returns the number of nodes (including leaves) in a Rex tree. */
-  protected static int nodeCount(RexNode node) {
-    int n = 1;
-    if (node instanceof RexCall) {
-      for (RexNode operand : ((RexCall) node).getOperands()) {
-        n += nodeCount(operand);
-      }
-    }
-    return n;
-  }
-
   protected Comparable eval(RexNode e) {
     return RexInterpreter.evaluate(e, ImmutableMap.of());
   }
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java 
b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 75cde59..aa9e09d 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -75,6 +75,7 @@ import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -757,6 +758,85 @@ public class RelBuilderTest {
             + "  LogicalValues(tuples=[[]])\n");
   }
 
+  @Test public void testProjectBloat() {
+    final Function<RelBuilder, RelNode> f = b ->
+        b.scan("EMP")
+            .project(
+                b.alias(
+                    caseCall(b, b.field("DEPTNO"),
+                        b.literal(0), b.literal("zero"),
+                        b.literal(1), b.literal("one"),
+                        b.literal(2), b.literal("two"),
+                        b.literal("other")),
+                    "v"))
+            .project(
+                b.call(SqlStdOperatorTable.PLUS, b.field("v"), b.field("v")))
+        .build();
+    // Complexity of bottom is 14; top is 3; merged is 29; difference is -12.
+    // So, we merge if bloat is 20 or 100 (the default),
+    // but not if it is -1, 0 or 10.
+    final String expected = "LogicalProject($f0=[+"
+        + "(CASE(=($7, 0), 'zero', =($7, 1), 'one', =($7, 2), 'two', 'other'),"
+        + " CASE(=($7, 0), 'zero', =($7, 1), 'one', =($7, 2), 'two', 
'other'))])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    final String expectedNeg = "LogicalProject($f0=[+($0, $0)])\n"
+        + "  LogicalProject(v=[CASE(=($7, 0), 'zero', =($7, 1), "
+        + "'one', =($7, 2), 'two', 'other')])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(f.apply(createBuilder(c -> c)), hasTree(expected));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(0))),
+        hasTree(expectedNeg));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(-1))),
+        hasTree(expectedNeg));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(10))),
+        hasTree(expectedNeg));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(20))),
+        hasTree(expected));
+  }
+
+  @Test public void testProjectBloat2() {
+    final Function<RelBuilder, RelNode> f = b ->
+        b.scan("EMP")
+            .project(
+                b.field("DEPTNO"),
+                b.field("SAL"),
+                b.alias(
+                    b.call(SqlStdOperatorTable.PLUS, b.field("DEPTNO"),
+                        b.field("EMPNO")), "PLUS"))
+            .project(
+                b.call(SqlStdOperatorTable.MULTIPLY, b.field("SAL"),
+                    b.field("PLUS")),
+                b.field("SAL"))
+        .build();
+    // Complexity of bottom is 5; top is 4; merged is 6; difference is 3.
+    // So, we merge except when bloat is -1.
+    final String expected = "LogicalProject($f0=[*($5, +($7, $0))], 
SAL=[$5])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    final String expectedNeg = "LogicalProject($f0=[*($1, $2)], SAL=[$1])\n"
+        + "  LogicalProject(DEPTNO=[$7], SAL=[$5], PLUS=[+($7, $0)])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(f.apply(createBuilder(c -> c)), hasTree(expected));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(0))),
+        hasTree(expected));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(-1))),
+        hasTree(expectedNeg));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(10))),
+        hasTree(expected));
+    assertThat(f.apply(createBuilder(c -> c.withBloat(20))),
+        hasTree(expected));
+  }
+
+  private RexNode caseCall(RelBuilder b, RexNode ref, RexNode... nodes) {
+    final List<RexNode> list = new ArrayList<>();
+    for (int i = 0; i + 1 < nodes.length; i += 2) {
+      list.add(b.equals(ref, nodes[i]));
+      list.add(nodes[i + 1]);
+    }
+    list.add(nodes.length % 2 == 1 ? nodes[nodes.length - 1]
+        : b.literal(null));
+    return b.call(SqlStdOperatorTable.CASE, list);
+  }
+
   @Test public void testRename() {
     final RelBuilder builder = RelBuilder.create(config().build());
 
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java 
b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index c310594..f78d61e 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -1377,7 +1377,7 @@ public class PlannerTest {
   @Test public void testMergeProjectForceMode() throws Exception {
     RuleSet ruleSet =
         RuleSets.ofList(
-            new ProjectMergeRule(true,
+            new ProjectMergeRule(true, ProjectMergeRule.DEFAULT_BLOAT,
                 RelBuilder.proto(RelFactories.DEFAULT_PROJECT_FACTORY)));
     Planner planner = getPlanner(null, Programs.of(ruleSet));
     planner.close();
diff --git a/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java 
b/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java
index 96150af..b8337eb 100644
--- a/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java
+++ b/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.piglet;
 
 import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptSchema;
@@ -56,6 +57,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.UnaryOperator;
 
 /**
  * Extension to {@link RelBuilder} for Pig logical operators.
@@ -79,10 +81,19 @@ public class PigRelBuilder extends RelBuilder {
   public static PigRelBuilder create(FrameworkConfig config) {
     final RelBuilder relBuilder = RelBuilder.create(config);
     Hook.REL_BUILDER_SIMPLIFY.addThread(Hook.propertyJ(false));
-    return new PigRelBuilder(config.getContext(), relBuilder.getCluster(),
+    return new PigRelBuilder(
+        transform(config.getContext(), c -> c.withBloat(-1)),
+        relBuilder.getCluster(),
         relBuilder.getRelOptSchema());
   }
 
+  private static Context transform(Context context,
+      UnaryOperator<RelBuilder.Config> transform) {
+    final Config config =
+        Util.first(context.unwrap(Config.class), Config.DEFAULT);
+    return Contexts.of(transform.apply(config), context);
+  }
+
   public RelNode getRel(String alias) {
     return aliasMap.get(alias);
   }
@@ -108,10 +119,6 @@ public class PigRelBuilder extends RelBuilder {
     return new CorrelationId(nextCorrelId++);
   }
 
-  @Override protected boolean shouldMergeProject() {
-    return false;
-  }
-
   public String getAlias() {
     final RelNode input = peek();
     if (reverseAliasMap.containsKey(input)) {

Reply via email to