Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,48 @@ 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.
*/
public interface InputFieldsUsed extends Metadata {
MetadataDef<InputFieldsUsed> DEF =
MetadataDef.of(InputFieldsUsed.class, InputFieldsUsed.Handler.class,
BuiltInMethod.INPUT_FIELDS_USED.method);

/**
* Returns, for each input of this relational expression, a bit set of the
* referenced field ordinals.
*
* @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
*/
ImmutableList<ImmutableBitSet> getInputFieldsUsed();

/** Handler API. */
@FunctionalInterface
interface Handler extends MetadataHandler<InputFieldsUsed> {
ImmutableList<ImmutableBitSet> getInputFieldsUsed(RelNode r,
RelMetadataQuery mq);

@Override default MetadataDef<InputFieldsUsed> getDef() {
return DEF;
}
}
}

/** Metadata about which combinations of columns are unique identifiers. */
public interface UniqueKeys extends Metadata {
MetadataDef<UniqueKeys> DEF =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ protected DefaultRelMetadataProvider() {
RelMdSelectivity.SOURCE,
RelMdExplainVisibility.SOURCE,
RelMdPredicates.SOURCE,
RelMdInputFieldsUsed.SOURCE,
RelMdAllPredicates.SOURCE,
RelMdCollation.SOURCE,
RelMdFunctionalDependency.SOURCE));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.rel.metadata;

import org.apache.calcite.plan.RelOptUtil;
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.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.TableScan;
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.
*/
public class RelMdInputFieldsUsed
implements MetadataHandler<BuiltInMetadata.InputFieldsUsed> {
public static final RelMetadataProvider SOURCE =
ReflectiveRelMetadataProvider.reflectiveSource(
new RelMdInputFieldsUsed(), BuiltInMetadata.InputFieldsUsed.Handler.class);

@Override public MetadataDef<BuiltInMetadata.InputFieldsUsed> getDef() {
return BuiltInMetadata.InputFieldsUsed.DEF;
}

public ImmutableList<ImmutableBitSet> getInputFieldsUsed(RelNode rel,

Check warning on line 51 in core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove these unused method parameters "rel", "mq".

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZrPwRgv4fu7CA6CYdB5&open=AZrPwRgv4fu7CA6CYdB5&pullRequest=4652
RelMetadataQuery mq) {
return ImmutableList.of();
}

public ImmutableList<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));
}

public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Project project,
RelMetadataQuery mq) {

Check warning on line 68 in core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "mq".

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZrPwRgv4fu7CA6CYdB6&open=AZrPwRgv4fu7CA6CYdB6&pullRequest=4652
final ImmutableBitSet bits = RelOptUtil.InputFinder.bits(project.getProjects(), null);
return ImmutableList.of(bits);
}

public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Filter filter,
RelMetadataQuery mq) {
return mq.getInputFieldsUsed(filter.getInput());
}

public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Calc calc,
RelMetadataQuery mq) {

Check warning on line 79 in core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "mq".

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZrPwRgv4fu7CA6CYdB7&open=AZrPwRgv4fu7CA6CYdB7&pullRequest=4652
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;

ImmutableBitSet rightUsedBits = rightInputFieldsUsed.get(0);
if (join.getJoinType() == JoinRelType.SEMI
|| join.getJoinType() == JoinRelType.ANTI) {
rightUsedBits = ImmutableBitSet.of();
}

return ImmutableList.of(leftInputFieldsUsed.get(0), rightUsedBits);
}

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));
}
return builder.build();
}

public ImmutableList<ImmutableBitSet> getInputFieldsUsed(Aggregate agg,
RelMetadataQuery mq) {

Check warning on line 116 in core/src/main/java/org/apache/calcite/rel/metadata/RelMdInputFieldsUsed.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "mq".

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZrPwRgv4fu7CA6CYdB8&open=AZrPwRgv4fu7CA6CYdB8&pullRequest=4652
Set<Integer> fields = RelOptUtil.getAllFields(agg);
return ImmutableList.of(ImmutableBitSet.of(fields));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public class RelMetadataQuery extends RelMetadataQueryBase {
private BuiltInMetadata.UniqueKeys.Handler uniqueKeysHandler;
private BuiltInMetadata.LowerBoundCost.Handler lowerBoundCostHandler;
private BuiltInMetadata.FunctionalDependency.Handler functionalDependencyHandler;
private BuiltInMetadata.InputFieldsUsed.Handler inputFieldsUsedHandler;

/**
* Creates the instance with {@link JaninoRelMetadataProvider} instance
Expand Down Expand Up @@ -158,6 +159,7 @@ public RelMetadataQuery(MetadataHandlerProvider provider) {
this.lowerBoundCostHandler = provider.handler(BuiltInMetadata.LowerBoundCost.Handler.class);
this.functionalDependencyHandler =
provider.handler(BuiltInMetadata.FunctionalDependency.Handler.class);
this.inputFieldsUsedHandler = provider.handler(BuiltInMetadata.InputFieldsUsed.Handler.class);
}

/** Creates and initializes the instance that will serve as a prototype for
Expand Down Expand Up @@ -193,6 +195,7 @@ private RelMetadataQuery(@SuppressWarnings("unused") boolean dummy) {
this.lowerBoundCostHandler = initialHandler(BuiltInMetadata.LowerBoundCost.Handler.class);
this.functionalDependencyHandler =
initialHandler(BuiltInMetadata.FunctionalDependency.Handler.class);
this.inputFieldsUsedHandler = initialHandler(BuiltInMetadata.InputFieldsUsed.Handler.class);
}

private RelMetadataQuery(
Expand Down Expand Up @@ -225,6 +228,7 @@ private RelMetadataQuery(
this.uniqueKeysHandler = prototype.uniqueKeysHandler;
this.lowerBoundCostHandler = prototype.lowerBoundCostHandler;
this.functionalDependencyHandler = prototype.functionalDependencyHandler;
this.inputFieldsUsedHandler = prototype.inputFieldsUsedHandler;
}

//~ Methods ----------------------------------------------------------------
Expand Down Expand Up @@ -1058,4 +1062,17 @@ public ArrowSet getFDs(RelNode rel) {
}
}
}

/**
* Returns the input fields are used by a RelNode.
*/
public ImmutableList<ImmutableBitSet> getInputFieldsUsed(RelNode rel) {
for (;;) {
try {
return inputFieldsUsedHandler.getInputFieldsUsed(rel, this);
} catch (MetadataHandlerProvider.NoHandler e) {
inputFieldsUsedHandler = revise(BuiltInMetadata.InputFieldsUsed.Handler.class);
}
}
}
}
2 changes: 2 additions & 0 deletions core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.apache.calcite.rel.metadata.BuiltInMetadata.ExplainVisibility;
import org.apache.calcite.rel.metadata.BuiltInMetadata.ExpressionLineage;
import org.apache.calcite.rel.metadata.BuiltInMetadata.FunctionalDependency;
import org.apache.calcite.rel.metadata.BuiltInMetadata.InputFieldsUsed;
import org.apache.calcite.rel.metadata.BuiltInMetadata.LowerBoundCost;
import org.apache.calcite.rel.metadata.BuiltInMetadata.MaxRowCount;
import org.apache.calcite.rel.metadata.BuiltInMetadata.Measure;
Expand Down Expand Up @@ -896,6 +897,7 @@ public enum BuiltInMethod {
STR_TO_MAP(SqlFunctions.class, "strToMap", String.class, String.class, String.class),
SUBSTRING_INDEX(SqlFunctions.class, "substringIndex", String.class, String.class, int.class),
SELECTIVITY(Selectivity.class, "getSelectivity", RexNode.class),
INPUT_FIELDS_USED(InputFieldsUsed.class, "getInputFieldsUsed"),
UNIQUE_KEYS(UniqueKeys.class, "getUniqueKeys", boolean.class),
AVERAGE_ROW_SIZE(Size.class, "averageRowSize"),
AVERAGE_COLUMN_SIZES(Size.class, "averageColumnSizes"),
Expand Down
86 changes: 86 additions & 0 deletions core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.Calc;
import org.apache.calcite.rel.core.Correlate;
import org.apache.calcite.rel.core.Exchange;
import org.apache.calcite.rel.core.Filter;
Expand All @@ -52,6 +53,7 @@
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;
Expand Down Expand Up @@ -931,6 +933,90 @@ final RelMetadataFixture sql(String sql) {
assertThat(fd2, sameInstance(fd1));
}

// ----------------------------------------------------------------------
// Tests for InputFieldsUsed metadata in RelMdInputFieldsUsed
// ----------------------------------------------------------------------

@Test void testInputFieldsUsedSemiJoin() {
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);

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

@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 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);

assertThat(inputFields, hasSize(1));
assertThat(inputFields.get(0), 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 int fieldCount = filter.getInput(0).getRowType().getFieldCount();
assertThat(inputFields, hasSize(1));
assertThat(inputFields.get(0), equalTo(ImmutableBitSet.range(fieldCount)));
}

@Test void testInputFieldsUsedCalc() {
final RelBuilder builder = RelBuilderTest.createBuilder();
final RelNode proj = builder
.scan("EMP")
.project(builder.field(0), builder.field(2))
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(CoreRules.PROJECT_TO_CALC)
.build();
final HepPlanner planner = new HepPlanner(program);
planner.setRoot(proj);
final RelNode calc = planner.findBestExp();
assertThat(calc, instanceOf(Calc.class));

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

// ----------------------------------------------------------------------
// Tests for getColumnOrigins
// ----------------------------------------------------------------------
Expand Down
Loading