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)));
}
// ----------------------------------------------------------------------