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

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


The following commit(s) were added to refs/heads/main by this push:
     new 4253102ac6 [CALCITE-5787] The RelMdInputFieldsUsed is introduced to 
track the usage of input fields
4253102ac6 is described below

commit 4253102ac6f9e51e69702b224db314c439cf6c6e
Author: Zhen Chen <[email protected]>
AuthorDate: Thu Jan 29 08:53:20 2026 +0800

    [CALCITE-5787] The RelMdInputFieldsUsed is introduced to track the usage of 
input fields
---
 .../calcite/rel/metadata/BuiltInMetadata.java      |  28 ++--
 .../calcite/rel/metadata/RelMdInputFieldsUsed.java | 144 ++++++++++++++------
 .../calcite/rel/metadata/RelMetadataQuery.java     |   4 +-
 .../org/apache/calcite/rel/rules/SemiJoinRule.java |   2 +-
 .../org/apache/calcite/test/RelMetadataTest.java   | 151 ++++++++++++++-------
 5 files changed, 212 insertions(+), 117 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
index 0f297a95a5..9c571dfddc 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
@@ -77,19 +77,8 @@ interface Handler extends MetadataHandler<Selectivity> {
   }
 
   /**
-   * Metadata that identifies, per input, which fields of each
-   * input are referenced by a relational expression ({@link RelNode}).
-   * Here, "referenced" means the input field is used by the parent
-   * RelNode. Operators such as Filter, while not inherently consuming
-   * all input fields, must preserve them since parent RelNodes may depend on
-   * these fields. Thus, Filter is regarded as utilizing all fields.
-   *
-   * <p>For a relational expression with N inputs, this returns an
-   * {@link ImmutableList} of length N. Each element is an
-   * {@link ImmutableBitSet} with bits set for zero-based field ordinals of
-   * that input which are referenced by the expression.
-   *
-   * <p>Returns empty {@link ImmutableList} if information cannot be 
determined.
+   * Metadata that identifies which columns of its inputs are referenced by a
+   * relational expression.
    */
   public interface InputFieldsUsed extends Metadata {
     MetadataDef<InputFieldsUsed> DEF =
@@ -97,19 +86,18 @@ public interface InputFieldsUsed extends Metadata {
             BuiltInMethod.INPUT_FIELDS_USED.method);
 
     /**
-     * Returns, for each input of this relational expression, a bit set of the
-     * referenced field ordinals.
+     * Returns which columns of its inputs are referenced by this relational
+     * expression.
      *
-     * @return an {@link ImmutableList} of {@link ImmutableBitSet} of length N
-     *         where N is the number of inputs, or empty {@link ImmutableList}
-     *         if the information is not available
+     * @return an {@link ImmutableBitSet} where bits correspond to input column
+     *         ordinals from the first input to the last
      */
-    ImmutableList<ImmutableBitSet> getInputFieldsUsed();
+    ImmutableBitSet getInputFieldsUsed();
 
     /** Handler API. */
     @FunctionalInterface
     interface Handler extends MetadataHandler<InputFieldsUsed> {
-      ImmutableList<ImmutableBitSet> getInputFieldsUsed(RelNode r,
+      ImmutableBitSet getInputFieldsUsed(RelNode r,
           RelMetadataQuery mq);
 
       @Override default MetadataDef<InputFieldsUsed> getDef() {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java
index 771ed7e5fc..2d2d7d6a83 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java
@@ -20,23 +20,41 @@
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Calc;
+import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.SetOp;
+import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.Window;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.util.ImmutableBitSet;
 
-import com.google.common.collect.ImmutableList;
-
 import java.util.List;
 import java.util.Set;
 
 /**
  * Metadata provider to determine which input fields are used by a RelNode.
+ *
+ * <p>A field is considered "used" if it is referenced by the relational
+ * expression. The result is an {@link ImmutableBitSet} where bits correspond 
to
+ * input column ordinals.
+ *
+ * <p>Examples:
+ * <ul>
+ *   <li>For an {@link Aggregate}, "used" fields are those in the group set or
+ *   referenced in aggregate functions. see {@link 
RelOptUtil#getAllFields}</li>
+ *   <li>For a {@link Join}, it is the union of "used" fields from both inputs
+ *   (shifted appropriately for the right input). For SEMI and ANTI joins, 
fields
+ *   from the right input are not considered "used" as they are not projected 
to
+ *   the output</li>
+ * </ul>
+ *
+ * @see BuiltInMetadata.InputFieldsUsed
+ * @see RelMetadataQuery#getInputFieldsUsed(RelNode)
  */
 public class RelMdInputFieldsUsed
     implements MetadataHandler<BuiltInMetadata.InputFieldsUsed> {
@@ -48,77 +66,115 @@ public class RelMdInputFieldsUsed
     return BuiltInMetadata.InputFieldsUsed.DEF;
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(RelNode rel,
-      RelMetadataQuery mq) {
-    ImmutableList.Builder<ImmutableBitSet> builder = ImmutableList.builder();
-    rel.getInputs().forEach(input -> {
-      builder.addAll(mq.getInputFieldsUsed(input));
-    });
-    return builder.build();
+  /** Catch-all implementation for
+   * {@link BuiltInMetadata.InputFieldsUsed#getInputFieldsUsed()},
+   * invoked using reflection.
+   *
+   * @see 
org.apache.calcite.rel.metadata.RelMetadataQuery#getInputFieldsUsed(RelNode)
+   */
+  public ImmutableBitSet getInputFieldsUsed(RelNode rel, RelMetadataQuery mq) {
+    // By default, a RelNode uses all of its input fields.
+    return getAllInputFieldsUsed(rel);
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(TableScan scan,
-      RelMetadataQuery mq) {
+  public ImmutableBitSet getInputFieldsUsed(TableScan scan, RelMetadataQuery 
mq) {
     final BuiltInMetadata.InputFieldsUsed.Handler handler =
         scan.getTable().unwrap(BuiltInMetadata.InputFieldsUsed.Handler.class);
     if (handler != null) {
       return handler.getInputFieldsUsed(scan, mq);
     }
     final int fieldCount = scan.getRowType().getFieldCount();
-    return ImmutableList.of(ImmutableBitSet.range(fieldCount));
+    return ImmutableBitSet.range(fieldCount);
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Project project,
-      RelMetadataQuery mq) {
-    final ImmutableBitSet bits = 
RelOptUtil.InputFinder.bits(project.getProjects(), null);
-    return ImmutableList.of(bits);
+  public ImmutableBitSet getInputFieldsUsed(Project project, RelMetadataQuery 
mq) {
+    // Project involves column trimming, returning only the columns that are 
used.
+    return RelOptUtil.InputFinder.bits(project.getProjects(), null);
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Filter filter,
-      RelMetadataQuery mq) {
-    return mq.getInputFieldsUsed(filter.getInput());
+  public ImmutableBitSet getInputFieldsUsed(Filter filter, RelMetadataQuery 
mq) {
+    return getAllFieldsUsed(filter);
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Calc calc,
-      RelMetadataQuery mq) {
+  public ImmutableBitSet getInputFieldsUsed(Sort sort, RelMetadataQuery mq) {
+    return getAllFieldsUsed(sort);
+  }
+
+  public ImmutableBitSet getInputFieldsUsed(Window window, RelMetadataQuery 
mq) {
+    return getAllFieldsUsed(window);
+  }
+
+  public ImmutableBitSet getInputFieldsUsed(Calc calc, RelMetadataQuery mq) {
     final RexProgram program = calc.getProgram();
     final List<RexNode> expandedProjects = 
program.expandList(program.getProjectList());
     final RexNode cond = program.getCondition() == null
         ? null
         : program.expandLocalRef(program.getCondition());
-    final ImmutableBitSet bits = RelOptUtil.InputFinder.bits(expandedProjects, 
cond);
-    return ImmutableList.of(bits);
-  }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Join join,
-      RelMetadataQuery mq) {
-    List<ImmutableBitSet> leftInputFieldsUsed = 
mq.getInputFieldsUsed(join.getLeft());
-    List<ImmutableBitSet> rightInputFieldsUsed = 
mq.getInputFieldsUsed(join.getRight());
-    assert leftInputFieldsUsed.size() == 1 && rightInputFieldsUsed.size() == 1;
+    // Same as Project.
+    return RelOptUtil.InputFinder.bits(expandedProjects, cond);
+  }
 
-    ImmutableBitSet rightUsedBits = rightInputFieldsUsed.get(0);
+  public ImmutableBitSet getInputFieldsUsed(Join join, RelMetadataQuery mq) {
+    // Computes the union of fields used by both inputs. For SEMI and ANTI 
joins,
+    // fields from the right input are excluded as they are not projected to 
the output.
+    final ImmutableBitSet leftInputFieldsUsed = 
getAllFieldsUsed(join.getLeft());
     if (join.getJoinType() == JoinRelType.SEMI
-        ||  join.getJoinType() == JoinRelType.ANTI) {
-      rightUsedBits = ImmutableBitSet.of();
+        || join.getJoinType() == JoinRelType.ANTI) {
+      return leftInputFieldsUsed;
     }
 
-    return ImmutableList.of(leftInputFieldsUsed.get(0), rightUsedBits);
+    final ImmutableBitSet rightInputFieldsUsedShifted =
+        getAllFieldsUsed(join.getRight(),
+            join.getLeft().getRowType().getFieldCount());
+    return leftInputFieldsUsed.union(rightInputFieldsUsedShifted);
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(SetOp setOp,
-      RelMetadataQuery mq) {
-    final ImmutableList.Builder<ImmutableBitSet> builder = 
ImmutableList.builder();
-    for (RelNode input : setOp.getInputs()) {
-      ImmutableList<ImmutableBitSet> inputFieldsBits = 
mq.getInputFieldsUsed(input);
-      assert inputFieldsBits.size() == 1;
-      builder.add(inputFieldsBits.get(0));
+  public ImmutableBitSet getInputFieldsUsed(SetOp setOp, RelMetadataQuery mq) {
+    return getAllInputFieldsUsed(setOp);
+  }
+
+  public ImmutableBitSet getInputFieldsUsed(Aggregate agg, RelMetadataQuery 
mq) {
+    Set<Integer> fields = RelOptUtil.getAllFields(agg);
+    return ImmutableBitSet.of(fields);
+  }
+
+  public ImmutableBitSet getInputFieldsUsed(Correlate correlate, 
RelMetadataQuery mq) {
+    // Computes the union of fields referenced by both inputs. For SEMI and 
ANTI
+    // correlates, fields from the right input are excluded from the 
projection.
+    final ImmutableBitSet leftInputFieldsUsed = 
getAllFieldsUsed(correlate.getLeft());
+    if (correlate.getJoinType() == JoinRelType.SEMI
+        || correlate.getJoinType() == JoinRelType.ANTI) {
+      return leftInputFieldsUsed;
+    }
+
+    final ImmutableBitSet rightInputFieldsUsedShifted =
+        getAllFieldsUsed(correlate.getRight(),
+            correlate.getLeft().getRowType().getFieldCount());
+    return leftInputFieldsUsed.union(rightInputFieldsUsedShifted);
+  }
+
+  // ~ Private helper methods ------------------------------------------------
+
+  /**
+   * Returns a bitset of all fields used by all inputs of a {@link RelNode},
+   * shifted by the cumulative field count of preceding inputs.
+   */
+  private static ImmutableBitSet getAllInputFieldsUsed(RelNode rel) {
+    ImmutableBitSet.Builder builder = ImmutableBitSet.builder();
+    int offset = 0;
+    for (RelNode input : rel.getInputs()) {
+      builder.addAll(getAllFieldsUsed(input, offset));
+      offset += input.getRowType().getFieldCount();
     }
     return builder.build();
   }
 
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Aggregate agg,
-      RelMetadataQuery mq) {
-    Set<Integer> fields =  RelOptUtil.getAllFields(agg);
-    return ImmutableList.of(ImmutableBitSet.of(fields));
+  private static ImmutableBitSet getAllFieldsUsed(RelNode rel, int offset) {
+    return 
ImmutableBitSet.range(rel.getRowType().getFieldCount()).shift(offset);
+  }
+
+  private static ImmutableBitSet getAllFieldsUsed(RelNode rel) {
+    return getAllFieldsUsed(rel, 0);
   }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
index f86f1de868..aeeefa9763 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
@@ -1064,9 +1064,9 @@ public ArrowSet getFDs(RelNode rel) {
   }
 
   /**
-   * Returns the input fields are used by a RelNode.
+   * Returns which columns of its inputs are referenced by a relational 
expression.
    */
-  public ImmutableList<ImmutableBitSet> getInputFieldsUsed(RelNode rel) {
+  public ImmutableBitSet getInputFieldsUsed(RelNode rel) {
     for (;;) {
       try {
         return inputFieldsUsedHandler.getInputFieldsUsed(rel, this);
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
index 427ea979bd..8b011ed08b 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
@@ -143,7 +143,7 @@ protected void perform(RelOptRuleCall call, @Nullable 
RelNode topRel,
   /** Returns a bit set of the input fields used by a relational expression. */
   private static ImmutableBitSet getUsedFields(RelNode rel) {
     final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
-    return ImmutableBitSet.union(mq.getInputFieldsUsed(rel));
+    return mq.getInputFieldsUsed(rel);
   }
 
   /** SemiJoinRule that matches a Aggregate on top of a Join with an Aggregate
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java 
b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 110c0d0f51..f438a0ad94 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -53,7 +53,6 @@
 import org.apache.calcite.rel.core.Minus;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.Sample;
-import org.apache.calcite.rel.core.SetOp;
 import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.core.TableScan;
@@ -941,80 +940,132 @@ final RelMetadataFixture sql(String sql) {
     final RelBuilder relBuilder = RelBuilderTest.createBuilder();
     relBuilder.scan("EMP");
     relBuilder.scan("DEPT");
-    // Build semi-join on DEPTNO
     relBuilder.semiJoin(
         relBuilder.equals(relBuilder.field(2, 0, "DEPTNO"),
-        relBuilder.field(2, 1, "DEPTNO")));
-    final Join join = (Join) relBuilder.build();
-    final RelMetadataQuery mq = join.getCluster().getMetadataQuery();
-    final List<ImmutableBitSet> inputFields = mq.getInputFieldsUsed(join);
+            relBuilder.field(2, 1, "DEPTNO")));
+    final RelNode rel = relBuilder.build();
+    assertThat(Util.toLinux(RelOptUtil.toString(rel)),
+        is(""
+            + "LogicalJoin(condition=[=($7, $8)], joinType=[semi])\n"
+            + "  LogicalTableScan(table=[[scott, EMP]])\n"
+            + "  LogicalTableScan(table=[[scott, DEPT]])\n"));
+
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(rel);
 
     // For SEMI join expect left input fields to be all columns of left input
-    // and right input fields to be empty (semi-join does not require right 
output).
-    final int leftCount = join.getLeft().getRowType().getFieldCount();
-    assertThat(inputFields, hasSize(2));
-    assertThat(inputFields.get(0), equalTo(ImmutableBitSet.range(leftCount)));
-    assertThat(inputFields.get(1).isEmpty(), is(true));
+    assertThat(inputFields, equalTo(ImmutableBitSet.range(8)));
   }
 
-  @Test void testInputFieldsUsedUnionSetOp() {
-    final RelBuilder builder = RelBuilderTest.createBuilder();
-    builder.scan("DEPT").project(builder.field(1)); // name
-    builder.scan("EMP").project(builder.field(2)); // job
-    builder.union(true);
-    final SetOp setOp = (SetOp) builder.build();
-    final RelMetadataQuery mq = setOp.getCluster().getMetadataQuery();
-    final List<ImmutableBitSet> inputFields = mq.getInputFieldsUsed(setOp);
-    assertThat(
-        inputFields, equalTo(
-        ImmutableList.of(ImmutableBitSet.of(1), ImmutableBitSet.of(2))));
+  @Test void testInputFieldsUsedJoin() {
+    final RelBuilder relBuilder = RelBuilderTest.createBuilder();
+    final RelNode rel = relBuilder
+        .scan("EMP")
+        .project(relBuilder.field(0), relBuilder.field(7))
+        .scan("DEPT")
+        .project(relBuilder.field(0),  relBuilder.field(1))
+        .join(JoinRelType.INNER,
+            relBuilder.equals(relBuilder.field(2, 0, "DEPTNO"),
+                relBuilder.field(2, 1, "DEPTNO")))
+        .build();
+
+    assertThat(Util.toLinux(RelOptUtil.toString(rel)),
+        is("LogicalJoin(condition=[=($1, $2)], joinType=[inner])\n"
+            + "  LogicalProject(EMPNO=[$0], DEPTNO=[$7])\n"
+            + "    LogicalTableScan(table=[[scott, EMP]])\n"
+            + "  LogicalProject(DEPTNO=[$0], DNAME=[$1])\n"
+            + "    LogicalTableScan(table=[[scott, DEPT]])\n"));
+
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(rel);
+
+    // For normal join expect all columns of both inputs to be used.
+    assertThat(inputFields, equalTo(ImmutableBitSet.range(4)));
+  }
+
+  @Test void testInputFieldsUsedUnion() {
+    final String sql = "select deptno from dept union all select deptno from 
emp";
+    final RelNode rel = sql(sql).toRel();
+    assertThat(Util.toLinux(RelOptUtil.toString(rel)),
+        is(""
+            + "LogicalUnion(all=[true])\n"
+            + "  LogicalProject(DEPTNO=[$0])\n"
+            + "    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])\n"
+            + "  LogicalProject(DEPTNO=[$7])\n"
+            + "    LogicalTableScan(table=[[CATALOG, SALES, EMP]])\n"));
+
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(rel);
+
+    // Expected result columns: [0, 1]
+    // this representing the sole field from input 0 and the sole field from 
input 1.
+    assertThat(inputFields, equalTo(ImmutableBitSet.of(0, 1)));
   }
 
   @Test void testInputFieldsUsedProject() {
-    final RelBuilder builder = RelBuilderTest.createBuilder();
-    final RelNode project = builder
-        .scan("EMP")
-        .project(builder.field(0), builder.field(2))
-        .build();
-    final RelMetadataQuery mq = project.getCluster().getMetadataQuery();
-    final java.util.List<ImmutableBitSet> inputFields = 
mq.getInputFieldsUsed(project);
+    final String sql = "select empno, job from emp";
+    final RelNode rel = sql(sql).toRel();
+    assertThat(Util.toLinux(RelOptUtil.toString(rel)),
+        is(""
+            + "LogicalProject(EMPNO=[$0], JOB=[$2])\n"
+            + "  LogicalTableScan(table=[[CATALOG, SALES, EMP]])\n"));
+
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(rel);
 
-    assertThat(inputFields, hasSize(1));
-    assertThat(inputFields.get(0), equalTo(ImmutableBitSet.of(0, 2)));
+    assertThat(inputFields, equalTo(ImmutableBitSet.of(0, 2)));
   }
 
   @Test void testInputFieldsUsedFilter() {
-    final RelBuilder builder = RelBuilderTest.createBuilder();
-    final RelNode filter = builder
-        .scan("EMP")
-        .filter(builder.equals(builder.field(2), builder.literal(10)))
-        .build();
-    final RelMetadataQuery mq = filter.getCluster().getMetadataQuery();
-    final List<ImmutableBitSet> inputFields = mq.getInputFieldsUsed(filter);
+    final String sql = "select * from emp where sal > 1000";
+    final RelNode rel = sql(sql).toRel();
+    assertThat(Util.toLinux(RelOptUtil.toString(rel)),
+        is(""
+            + "LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])\n"
+            + "  LogicalFilter(condition=[>($5, 1000)])\n"
+            + "    LogicalTableScan(table=[[CATALOG, SALES, EMP]])\n"));
 
-    final int fieldCount = filter.getInput(0).getRowType().getFieldCount();
-    assertThat(inputFields, hasSize(1));
-    assertThat(inputFields.get(0), equalTo(ImmutableBitSet.range(fieldCount)));
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    final RelNode filter = rel.getInput(0);
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(filter);
+
+    assertThat(inputFields, equalTo(ImmutableBitSet.range(9)));
   }
 
   @Test void testInputFieldsUsedCalc() {
-    final RelBuilder builder = RelBuilderTest.createBuilder();
-    final RelNode proj = builder
-        .scan("EMP")
-        .project(builder.field(0), builder.field(2))
-        .build();
+    final String sql = "select empno, job from emp";
+    final RelNode rel = sql(sql).toRel();
     final HepProgram program = new HepProgramBuilder()
         .addRuleInstance(CoreRules.PROJECT_TO_CALC)
         .build();
     final HepPlanner planner = new HepPlanner(program);
-    planner.setRoot(proj);
+    planner.setRoot(rel);
     final RelNode calc = planner.findBestExp();
     assertThat(calc, instanceOf(Calc.class));
+    assertThat(Util.toLinux(RelOptUtil.toString(calc)),
+        is(""
+            + "LogicalCalc(expr#0..8=[{inputs}], EMPNO=[$t0], JOB=[$t2])\n"
+            + "  LogicalTableScan(table=[[CATALOG, SALES, EMP]])\n"));
 
     final RelMetadataQuery mq = calc.getCluster().getMetadataQuery();
-    final List<ImmutableBitSet> inputFields = mq.getInputFieldsUsed(calc);
-    assertThat(inputFields, hasSize(1));
-    assertThat(inputFields.get(0), equalTo(ImmutableBitSet.of(0, 2)));
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(calc);
+
+    assertThat(inputFields, equalTo(ImmutableBitSet.of(0, 2)));
+  }
+
+  @Test void testInputFieldsUsedAggregate() {
+    final String sql = "select deptno, sum(sal) from emp group by deptno";
+    final RelNode rel = sql(sql).toRel();
+    assertThat(Util.toLinux(RelOptUtil.toString(rel)),
+        is(""
+            + "LogicalAggregate(group=[{0}], EXPR$1=[SUM($1)])\n"
+            + "  LogicalProject(DEPTNO=[$7], SAL=[$5])\n"
+            + "    LogicalTableScan(table=[[CATALOG, SALES, EMP]])\n"));
+
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    final ImmutableBitSet inputFields = mq.getInputFieldsUsed(rel);
+
+    assertThat(inputFields, equalTo(ImmutableBitSet.of(0, 1)));
   }
 
   // ----------------------------------------------------------------------

Reply via email to