/* * 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.eigenbase.rel; import java.io.*; import java.util.*; import java.util.logging.*; import org.eigenbase.rel.metadata.*; import org.eigenbase.relopt.*; import org.eigenbase.reltype.*; import org.eigenbase.rex.*; import org.eigenbase.sql.*; import org.eigenbase.trace.*; import org.eigenbase.util.*; import net.hydromatic.optiq.util.BitSets; import com.google.common.collect.ImmutableList; /** * Base class for every relational expression ({@link RelNode}). */ public abstract class AbstractRelNode implements RelNode { //~ Static fields/initializers --------------------------------------------- // TODO jvs 10-Oct-2003: Make this thread safe. Either synchronize, or // keep this per-VolcanoPlanner. /** Generator for {@link #id} values. */ static int nextId = 0; private static final Logger LOGGER = EigenbaseTrace.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 int id; /** * The variable by which to refer to rows from this relational expression, * as correlating expressions; null if this expression is not correlated on. */ private String correlVariable; /** * The RelTraitSet that describes the traits of this RelNode. */ protected RelTraitSet traitSet; //~ Constructors ----------------------------------------------------------- /** * Creates an <code>AbstractRelNode</code>. */ public AbstractRelNode(RelOptCluster cluster, RelTraitSet traitSet) { super(); assert cluster != null; this.cluster = cluster; this.traitSet = traitSet; this.id = nextId++; this.digest = getRelTypeName() + "#" + id; this.desc = digest; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("new " + digest); } } //~ Methods ---------------------------------------------------------------- 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); } public List<RexNode> 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 void setCorrelVariable(String correlVariable) { this.correlVariable = correlVariable; } public String getCorrelVariable() { return correlVariable; } public boolean isDistinct() { return isKey(BitSets.range(getRowType().getFieldCount())); } public boolean isKey(BitSet columns) { return false; } public int getId() { return id; } public RelNode getInput(int i) { List<RelNode> inputs = getInputs(); return inputs.get(i); } public String getOrCreateCorrelVariable() { if (correlVariable == null) { correlVariable = getQuery().createCorrel(); getQuery().mapCorrel(correlVariable, this); } return correlVariable; } public final RelOptQuery getQuery() { return getCluster().getQuery(); } public void register(RelOptPlanner planner) { Util.discard(planner); } public final String getRelTypeName() { String className = getClass().getName(); int i = className.lastIndexOf("$"); if (i >= 0) { return className.substring(i + 1); } i = className.lastIndexOf("."); if (i >= 0) { return className.substring(i + 1); } return className; } public boolean isValid(boolean fail) { return true; } public List<RelCollation> 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<RelNode> getInputs() { return Collections.emptyList(); } public double getRows() { return 1.0; } public Set<String> getVariablesStopped() { return Collections.emptySet(); } public void collectVariablesUsed(Set<String> variableSet) { // for default case, nothing to do } public void collectVariablesSet(Set<String> variableSet) { if (correlVariable != null) { variableSet.add(correlVariable); } } public void childrenAccept(RelVisitor visitor) { List<RelNode> 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 ProjectRel // and JoinRel) have their own RelShuttle.visit methods. return shuttle.visit(this); } public RelOptCost computeSelfCost(RelOptPlanner planner) { // by default, assume cost is proportional to number of rows double rowCount = RelMetadataQuery.getRowCount(this); double bytesPerRow = 1; return planner.getCostFactory().makeCost(rowCount, rowCount, 0); } public final <M extends Metadata> M metadata(Class<M> metadataClass) { final M metadata = cluster.getMetadataFactory().query(this, 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 RelWriterImpl#input(String, RelNode)} * and {@link RelWriterImpl#item(String, Object)} methods for each input * and attribute. * * @param pw Plan writer */ public RelWriter explainTerms(RelWriter pw) { return pw; } public RelNode onRegister(RelOptPlanner planner) { List<RelNode> oldInputs = getInputs(); List<RelNode> inputs = new ArrayList<RelNode>(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(), true); } inputs.add(e); } RelNode r = this; if (!Util.equalShallow(oldInputs, inputs)) { r = copy(getTraitSet(), inputs); } r.recomputeDigest(); assert r.isValid(true); return r; } public String recomputeDigest() { String tempDigest = computeDigest(); assert tempDigest != null : "post: return != null"; String prefix = "rel#" + id + ":"; // Substring uses the same underlying array of chars, so saves a bit // of memory. this.desc = prefix + tempDigest; this.digest = this.desc.substring(prefix.length()); return this.digest; } public void registerCorrelVariable(String correlVariable) { assert this.correlVariable == null; this.correlVariable = correlVariable; getQuery().mapCorrel(correlVariable, this); } public void replaceInput( int ordinalInParent, RelNode p) { throw Util.newInternal("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. */ protected String computeDigest() { StringWriter sw = new StringWriter(); RelWriter pw = new RelWriterImpl( new PrintWriter(sw), SqlExplainLevel.DIGEST_ATTRIBUTES, false) { protected void explain_( RelNode rel, List<Pair<String, Object>> values) { pw.write(getRelTypeName()); for (RelTrait trait : traitSet) { pw.write("."); pw.write(trait.toString()); } pw.write("("); int j = 0; for (Pair<String, Object> value : values) { if (j++ > 0) { pw.write(","); } pw.write(value.left + "=" + value.right); } pw.write(")"); } }; explain(pw); return sw.toString(); } } // End AbstractRelNode.java