org.apache.calcite.rel.AbstractRelNode Maven / Gradle / Ivy
Show all versions of calcite-core Show documentation
/*
* 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.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptQuery;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTrait;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.core.CorrelationId;
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.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 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);
private static final Logger LOGGER = CalciteTrace.getPlannerTracer();
//~ Instance fields --------------------------------------------------------
/**
* Description, consists of id plus digest.
*/
private String desc;
/**
* Cached type of this relational expression.
*/
protected RelDataType rowType;
/**
* A short description of this relational expression's type, inputs, and
* other properties. The string uniquely identifies the node; another node
* is equivalent if and only if it has the same value. Computed by
* {@link #computeDigest}, assigned by {@link #onRegister}, returned by
* {@link #getDigest()}.
*
* @see #desc
*/
protected String digest;
private final RelOptCluster cluster;
/**
* unique id of this object -- for debugging
*/
protected final int id;
/**
* The RelTraitSet that describes the traits of this RelNode.
*/
protected RelTraitSet traitSet;
//~ Constructors -----------------------------------------------------------
/**
* Creates an AbstractRelNode
.
*/
public AbstractRelNode(RelOptCluster cluster, RelTraitSet traitSet) {
super();
assert cluster != null;
this.cluster = cluster;
this.traitSet = traitSet;
this.id = NEXT_ID.getAndIncrement();
this.digest = getRelTypeName() + "#" + id;
this.desc = digest;
LOGGER.trace("new {}", digest);
}
//~ Methods ----------------------------------------------------------------
public RelNode copy(RelTraitSet traitSet, List 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 sole(List collection) {
assert collection.size() == 1;
return collection.get(0);
}
@SuppressWarnings("deprecation")
public List getChildExps() {
return ImmutableList.of();
}
public final RelOptCluster getCluster() {
return cluster;
}
public final Convention getConvention() {
return traitSet.getTrait(ConventionTraitDef.INSTANCE);
}
public RelTraitSet getTraitSet() {
return traitSet;
}
public String getCorrelVariable() {
return null;
}
@SuppressWarnings("deprecation")
public boolean isDistinct() {
final RelMetadataQuery mq = RelMetadataQuery.instance();
return Boolean.TRUE.equals(mq.areRowsUnique(this));
}
@SuppressWarnings("deprecation")
public boolean isKey(ImmutableBitSet columns) {
final RelMetadataQuery mq = RelMetadataQuery.instance();
return Boolean.TRUE.equals(mq.areColumnsUnique(this, columns));
}
public int getId() {
return id;
}
public RelNode getInput(int i) {
List inputs = getInputs();
return inputs.get(i);
}
@SuppressWarnings("deprecation")
public final RelOptQuery getQuery() {
return getCluster().getQuery();
}
public void register(RelOptPlanner planner) {
Util.discard(planner);
}
public final 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;
}
public boolean isValid(Litmus litmus, Context context) {
return litmus.succeed();
}
@SuppressWarnings("deprecation")
public boolean isValid(boolean fail) {
return isValid(Litmus.THROW, null);
}
/** @deprecated Use {@link RelMetadataQuery#collations(RelNode)} */
@Deprecated // to be removed before 2.0
public List getCollationList() {
return ImmutableList.of();
}
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();
}
public RelDataType getExpectedInputRowType(int ordinalInParent) {
return getRowType();
}
public List getInputs() {
return Collections.emptyList();
}
@SuppressWarnings("deprecation")
public final double getRows() {
return estimateRowCount(RelMetadataQuery.instance());
}
public double estimateRowCount(RelMetadataQuery mq) {
return 1.0;
}
@SuppressWarnings("deprecation")
public final Set getVariablesStopped() {
return CorrelationId.names(getVariablesSet());
}
public Set getVariablesSet() {
return ImmutableSet.of();
}
public void collectVariablesUsed(Set variableSet) {
// for default case, nothing to do
}
public void collectVariablesSet(Set variableSet) {
}
public void childrenAccept(RelVisitor visitor) {
List inputs = getInputs();
for (int i = 0; i < inputs.size(); i++) {
visitor.visit(inputs.get(i), i, this);
}
}
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);
}
public RelNode accept(RexShuttle shuttle) {
return this;
}
@SuppressWarnings("deprecation")
public final RelOptCost computeSelfCost(RelOptPlanner planner) {
return computeSelfCost(planner, RelMetadataQuery.instance());
}
public RelOptCost computeSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
// by default, assume cost is proportional to number of rows
double rowCount = mq.getRowCount(this);
double bytesPerRow = 1;
return planner.getCostFactory().makeCost(rowCount, rowCount, 0);
}
public final M metadata(Class 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;
}
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 org.apache.calcite.rel.externalize.RelWriterImpl#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;
}
public RelNode onRegister(RelOptPlanner planner) {
List oldInputs = getInputs();
List inputs = new ArrayList<>(oldInputs.size());
for (final RelNode input : oldInputs) {
RelNode e = planner.ensureRegistered(input, null);
if (e != input) {
// TODO: change 'equal' to 'eq', which is stronger.
assert 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;
}
public String recomputeDigest() {
String tempDigest = computeDigest();
assert tempDigest != null : "computeDigest() should be non-null";
this.desc = "rel#" + id + ":" + tempDigest;
this.digest = tempDigest;
return this.digest;
}
public void replaceInput(
int ordinalInParent,
RelNode p) {
throw new UnsupportedOperationException("replaceInput called on " + this);
}
public String toString() {
return desc;
}
public final String getDescription() {
return desc;
}
public final String getDigest() {
return digest;
}
public RelOptTable getTable() {
return null;
}
/**
* Computes the digest. Does not modify this object.
*
* @return Digest
*/
protected String computeDigest() {
RelDigestWriter rdw = new RelDigestWriter();
explain(rdw);
return rdw.digest;
}
/**
* {@inheritDoc}
*
* 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(Object obj) {
return super.equals(obj);
}
/**
* {@inheritDoc}
*
*
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();
}
/**
* A writer object used exclusively for computing the digest of a RelNode.
*
*
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.
*
*/
private static final class RelDigestWriter implements RelWriter {
private final List> values = new ArrayList<>();
String digest = null;
@Override public void explain(final RelNode rel, final List> 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, Object value) {
values.add(Pair.of(term, value));
return this;
}
@Override public RelWriter done(RelNode node) {
StringBuilder sb = new StringBuilder();
sb.append(node.getRelTypeName());
for (RelTrait trait : node.getTraitSet()) {
sb.append('.');
sb.append(trait.toString());
}
sb.append('(');
int j = 0;
for (Pair value : values) {
if (j++ > 0) {
sb.append(',');
}
sb.append(value.left);
sb.append('=');
if (value.right instanceof RelNode) {
RelNode input = (RelNode) value.right;
sb.append(input.getRelTypeName());
sb.append('#');
sb.append(input.getId());
} else {
sb.append(value.right);
}
}
sb.append(')');
digest = sb.toString();
return this;
}
}
}
// End AbstractRelNode.java