blob: f7902cffd9515220cde7af212a22d81987697df4 [file] [log] [blame]
/*
* 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;
import org.apache.calcite.plan.Convention;
import org.apache.calcite.plan.ConventionTraitDef;
import org.apache.calcite.plan.RelDigest;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.hint.Hintable;
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.metadata.Metadata;
import org.apache.calcite.rel.metadata.MetadataFactory;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableSet;
import org.apiguardian.api.API;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.qual.Pure;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Objects.requireNonNull;
/**
* Base class for every relational expression ({@link RelNode}).
*/
public abstract class AbstractRelNode implements RelNode {
//~ Static fields/initializers ---------------------------------------------
/** Generator for {@link #id} values. */
private static final AtomicInteger NEXT_ID = new AtomicInteger(0);
//~ Instance fields --------------------------------------------------------
/**
* Cached type of this relational expression.
*/
protected @MonotonicNonNull RelDataType rowType;
/**
* The digest that uniquely identifies the node.
*/
@API(since = "1.24", status = API.Status.INTERNAL)
protected RelDigest digest;
private final RelOptCluster cluster;
/** Unique id of this object, for debugging. */
protected final int id;
/** RelTraitSet that describes the traits of this RelNode. */
protected RelTraitSet traitSet;
//~ Constructors -----------------------------------------------------------
/**
* Creates an <code>AbstractRelNode</code>.
*/
protected AbstractRelNode(RelOptCluster cluster, RelTraitSet traitSet) {
super();
assert cluster != null;
this.cluster = cluster;
this.traitSet = traitSet;
this.id = NEXT_ID.getAndIncrement();
this.digest = new InnerRelDigest();
}
//~ Methods ----------------------------------------------------------------
@Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
// Note that empty set equals empty set, so relational expressions
// with zero inputs do not generally need to implement their own copy
// method.
if (getInputs().equals(inputs)
&& traitSet == getTraitSet()) {
return this;
}
throw new AssertionError("Relational expression should override copy. "
+ "Class=[" + getClass()
+ "]; traits=[" + getTraitSet()
+ "]; desired traits=[" + traitSet
+ "]");
}
protected static <T> T sole(List<T> collection) {
assert collection.size() == 1;
return collection.get(0);
}
@Override public final RelOptCluster getCluster() {
return cluster;
}
@Pure
@Override public final @Nullable Convention getConvention(
@UnknownInitialization AbstractRelNode this
) {
return traitSet == null ? null : traitSet.getTrait(ConventionTraitDef.INSTANCE);
}
@Override public RelTraitSet getTraitSet() {
return traitSet;
}
@Override public @Nullable String getCorrelVariable() {
return null;
}
@Override public int getId() {
return id;
}
@Override public RelNode getInput(int i) {
List<RelNode> inputs = getInputs();
return inputs.get(i);
}
@Override public void register(RelOptPlanner planner) {
Util.discard(planner);
}
// It is not recommended to override this method, but sub-classes can do it at their own risk.
@Override public String getRelTypeName() {
String cn = getClass().getName();
int i = cn.length();
while (--i >= 0) {
if (cn.charAt(i) == '$' || cn.charAt(i) == '.') {
return cn.substring(i + 1);
}
}
return cn;
}
@Override public boolean isValid(Litmus litmus, @Nullable Context context) {
return litmus.succeed();
}
@Override public final RelDataType getRowType() {
if (rowType == null) {
rowType = deriveRowType();
assert rowType != null : this;
}
return rowType;
}
protected RelDataType deriveRowType() {
// This method is only called if rowType is null, so you don't NEED to
// implement it if rowType is always set.
throw new UnsupportedOperationException();
}
@Override public RelDataType getExpectedInputRowType(int ordinalInParent) {
return getRowType();
}
@Override public List<RelNode> getInputs() {
return Collections.emptyList();
}
@Override public double estimateRowCount(RelMetadataQuery mq) {
return 1.0;
}
@Override public Set<CorrelationId> getVariablesSet() {
return ImmutableSet.of();
}
@Override public void collectVariablesUsed(Set<CorrelationId> variableSet) {
// for default case, nothing to do
}
@Override public boolean isEnforcer() {
return false;
}
@Override public void collectVariablesSet(Set<CorrelationId> variableSet) {
}
@Override public void childrenAccept(RelVisitor visitor) {
List<RelNode> inputs = getInputs();
for (int i = 0; i < inputs.size(); i++) {
visitor.visit(inputs.get(i), i, this);
}
}
@Override public RelNode accept(RelShuttle shuttle) {
// Call fall-back method. Specific logical types (such as LogicalProject
// and LogicalJoin) have their own RelShuttle.visit methods.
return shuttle.visit(this);
}
@Override public RelNode accept(RexShuttle shuttle) {
return this;
}
@Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
// by default, assume cost is proportional to number of rows
double rowCount = mq.getRowCount(this);
return planner.getCostFactory().makeCost(rowCount, rowCount, 0);
}
@Deprecated // to be removed before 2.0
@Override public final <@Nullable M extends @Nullable Metadata> M metadata(Class<M> metadataClass,
RelMetadataQuery mq) {
final MetadataFactory factory = cluster.getMetadataFactory();
final M metadata = factory.query(this, mq, metadataClass);
assert metadata != null
: "no provider found (rel=" + this + ", m=" + metadataClass
+ "); a backstop provider is recommended";
// Usually the metadata belongs to the rel that created it. RelSubset and
// HepRelVertex are notable exceptions, so disable the assert. It's not
// worth the performance hit to override this method for them.
// assert metadata.rel() == this : "someone else's metadata";
return metadata;
}
@Override public void explain(RelWriter pw) {
explainTerms(pw).done(this);
}
/**
* Describes the inputs and attributes of this relational expression.
* Each node should call {@code super.explainTerms}, then call the
* {@link org.apache.calcite.rel.externalize.RelWriterImpl#input(String, RelNode)}
* and
* {@link RelWriter#item(String, Object)}
* methods for each input and attribute.
*
* @param pw Plan writer
* @return Plan writer for fluent-explain pattern
*/
public RelWriter explainTerms(RelWriter pw) {
return pw;
}
@Override public RelNode onRegister(RelOptPlanner planner) {
List<RelNode> oldInputs = getInputs();
List<RelNode> inputs = new ArrayList<>(oldInputs.size());
for (final RelNode input : oldInputs) {
RelNode e = planner.ensureRegistered(input, null);
assert e == input || RelOptUtil.equal("rowtype of rel before registration",
input.getRowType(),
"rowtype of rel after registration",
e.getRowType(),
Litmus.THROW);
inputs.add(e);
}
RelNode r = this;
if (!Util.equalShallow(oldInputs, inputs)) {
r = copy(getTraitSet(), inputs);
}
r.recomputeDigest();
assert r.isValid(Litmus.THROW, null);
return r;
}
@Override public void recomputeDigest() {
digest.clear();
}
@Override public void replaceInput(
int ordinalInParent,
RelNode p) {
throw new UnsupportedOperationException("replaceInput called on " + this);
}
/** Description; consists of id plus digest. */
@Override public String toString() {
return "rel#" + id + ':' + getDigest();
}
@Deprecated // to be removed before 2.0
@Override public final String getDescription() {
return this.toString();
}
@Override public String getDigest() {
return digest.toString();
}
@Override public final RelDigest getRelDigest() {
return digest;
}
@Override public @Nullable RelOptTable getTable() {
return null;
}
/**
* {@inheritDoc}
*
* <p>This method (and {@link #hashCode} is intentionally final. We do not want
* sub-classes of {@link RelNode} to redefine identity. Various algorithms
* (e.g. visitors, planner) can define the identity as meets their needs.
*/
@Override public final boolean equals(@Nullable Object obj) {
return super.equals(obj);
}
/**
* {@inheritDoc}
*
* <p>This method (and {@link #equals} is intentionally final. We do not want
* sub-classes of {@link RelNode} to redefine identity. Various algorithms
* (e.g. visitors, planner) can define the identity as meets their needs.
*/
@Override public final int hashCode() {
return super.hashCode();
}
/**
* Equality check for RelNode digest.
*
* <p>By default this method collects digest attributes from
* {@link #explainTerms(RelWriter)}, then compares each attribute pair.
* This should work well for most cases. If this method is a performance
* bottleneck for your project, or the default behavior can't handle
* your scenario properly, you can choose to override this method and
* {@link #deepHashCode()}. See {@code LogicalJoin} as an example.</p>
*
* @return Whether the 2 RelNodes are equivalent or have the same digest.
* @see #deepHashCode()
*/
@API(since = "1.25", status = API.Status.MAINTAINED)
@Override public boolean deepEquals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
AbstractRelNode that = (AbstractRelNode) obj;
boolean result = this.getTraitSet().equals(that.getTraitSet())
&& this.getRowType().equalsSansFieldNames(that.getRowType());
if (!result) {
return false;
}
List<Pair<String, @Nullable Object>> items1 = this.getDigestItems();
List<Pair<String, @Nullable Object>> items2 = that.getDigestItems();
if (items1.size() != items2.size()) {
return false;
}
for (int i = 0; result && i < items1.size(); i++) {
Pair<String, @Nullable Object> attr1 = items1.get(i);
Pair<String, @Nullable Object> attr2 = items2.get(i);
if (attr1.right instanceof RelNode) {
result = ((RelNode) attr1.right).deepEquals(attr2.right);
} else {
result = attr1.equals(attr2);
}
}
return result;
}
/**
* Compute hash code for RelNode digest.
*
* @see RelNode#deepEquals(Object)
*/
@API(since = "1.25", status = API.Status.MAINTAINED)
@Override public int deepHashCode() {
int result = 31 + getTraitSet().hashCode();
List<Pair<String, @Nullable Object>> items = this.getDigestItems();
for (Pair<String, @Nullable Object> item : items) {
Object value = item.right;
final int h;
if (value == null) {
h = 0;
} else if (value instanceof RelNode) {
h = ((RelNode) value).deepHashCode();
} else {
h = value.hashCode();
}
result = result * 31 + h;
}
return result;
}
private List<Pair<String, @Nullable Object>> getDigestItems() {
RelDigestWriter rdw = new RelDigestWriter();
explainTerms(rdw);
if (this instanceof Hintable) {
List<RelHint> hints = ((Hintable) this).getHints();
rdw.itemIf("hints", hints, !hints.isEmpty());
}
return rdw.attrs;
}
/** Implementation of {@link RelDigest}. */
private class InnerRelDigest implements RelDigest {
/** Cached hash code. */
private int hash = 0;
@Override public RelNode getRel() {
return AbstractRelNode.this;
}
@Override public void clear() {
hash = 0;
}
@Override public boolean equals(final @Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final InnerRelDigest relDigest = (InnerRelDigest) o;
return deepEquals(relDigest.getRel());
}
@Override public int hashCode() {
if (hash == 0) {
hash = deepHashCode();
}
return hash;
}
@Override public String toString() {
RelDigestWriter rdw = new RelDigestWriter();
explain(rdw);
return requireNonNull(rdw.digest, "digest");
}
}
/**
* A writer object used exclusively for computing the digest of a RelNode.
*
* <p>The writer is meant to be used only for computing a single digest and then thrown away.
* After calling {@link #done(RelNode)} the writer should be used only to obtain the computed
* {@link #digest}. Any other action is prohibited.</p>
*
*/
private static final class RelDigestWriter implements RelWriter {
private final List<Pair<String, @Nullable Object>> attrs = new ArrayList<>();
@Nullable String digest = null;
@Override public void explain(final RelNode rel,
final List<Pair<String, @Nullable Object>> valueList) {
throw new IllegalStateException("Should not be called for computing digest");
}
@Override public SqlExplainLevel getDetailLevel() {
return SqlExplainLevel.DIGEST_ATTRIBUTES;
}
@Override public RelWriter item(String term, @Nullable Object value) {
if (value != null && value.getClass().isArray()) {
// We can't call hashCode and equals on Array, so
// convert it to String to keep the same behaviour.
value = "" + value;
}
attrs.add(Pair.of(term, value));
return this;
}
@Override public RelWriter done(RelNode node) {
StringBuilder sb = new StringBuilder();
sb.append(node.getRelTypeName());
sb.append('.');
sb.append(node.getTraitSet());
sb.append('(');
int j = 0;
for (Pair<String, @Nullable Object> attr : attrs) {
if (j++ > 0) {
sb.append(',');
}
sb.append(attr.left);
sb.append('=');
if (attr.right instanceof RelNode) {
RelNode input = (RelNode) attr.right;
sb.append(input.getRelTypeName());
sb.append('#');
sb.append(input.getId());
} else {
sb.append(attr.right);
}
}
sb.append(')');
digest = sb.toString();
return this;
}
}
}