blob: 4a1033c3deae2c33a2f96eb247d33f1e609f0984 [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.core;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.hint.Hintable;
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexChecker;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Permutation;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.mapping.MappingType;
import org.apache.calcite.util.mapping.Mappings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import static java.util.Objects.requireNonNull;
/**
* Relational expression that computes a set of
* 'select expressions' from its input relational expression.
*
* @see org.apache.calcite.rel.logical.LogicalProject
*/
public abstract class Project extends SingleRel implements Hintable {
//~ Instance fields --------------------------------------------------------
protected final ImmutableList<RexNode> exps;
protected final ImmutableList<RelHint> hints;
protected final ImmutableSet<CorrelationId> variablesSet;
//~ Constructors -----------------------------------------------------------
/**
* Creates a Project.
*
* @param cluster Cluster that this relational expression belongs to
* @param traits Traits of this relational expression
* @param hints Hints of this relation expression
* @param input Input relational expression
* @param projects List of expressions for the input columns
* @param rowType Output row type
* @param variableSet Correlation variables set by this relational expression
* to be used by nested expressions
*/
@SuppressWarnings("method.invocation.invalid")
protected Project(
RelOptCluster cluster,
RelTraitSet traits,
List<RelHint> hints,
RelNode input,
List<? extends RexNode> projects,
RelDataType rowType,
Set<CorrelationId> variableSet) {
super(cluster, traits, input);
assert rowType != null;
this.exps = ImmutableList.copyOf(projects);
this.hints = ImmutableList.copyOf(hints);
this.rowType = rowType;
this.variablesSet = ImmutableSet.copyOf(variableSet);
assert isValid(Litmus.THROW, null);
}
@Deprecated // to be removed before 2.0
protected Project(
RelOptCluster cluster,
RelTraitSet traits,
List<RelHint> hints,
RelNode input,
List<? extends RexNode> projects,
RelDataType rowType) {
this(cluster, traits, hints, input, projects, rowType, ImmutableSet.of());
}
@Deprecated // to be removed before 2.0
protected Project(RelOptCluster cluster, RelTraitSet traits,
RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
this(cluster, traits, ImmutableList.of(), input, projects, rowType, ImmutableSet.of());
}
@Deprecated // to be removed before 2.0
protected Project(RelOptCluster cluster, RelTraitSet traitSet, RelNode input,
List<? extends RexNode> projects, RelDataType rowType, int flags) {
this(cluster, traitSet, ImmutableList.of(), input, projects, rowType, ImmutableSet.of());
Util.discard(flags);
}
/**
* Creates a Project by parsing serialized output.
*/
protected Project(RelInput input) {
this(input.getCluster(),
input.getTraitSet(),
ImmutableList.of(),
input.getInput(),
requireNonNull(input.getExpressionList("exprs"), "exprs"),
input.getRowType("exprs", "fields"),
ImmutableSet.copyOf(
Util.transform(
Optional.ofNullable(input.getIntegerList("variablesSet"))
.orElse(ImmutableList.of()),
id -> new CorrelationId(id)
)
));
}
//~ Methods ----------------------------------------------------------------
@Override public final RelNode copy(RelTraitSet traitSet,
List<RelNode> inputs) {
return copy(traitSet, sole(inputs), exps, getRowType());
}
/**
* Copies a project.
*
* @param traitSet Traits
* @param input Input
* @param projects Project expressions
* @param rowType Output row type
* @return New {@code Project} if any parameter differs from the value of this
* {@code Project}, or just {@code this} if all the parameters are
* the same
*
* @see #copy(RelTraitSet, List)
*/
public abstract Project copy(RelTraitSet traitSet, RelNode input,
List<RexNode> projects, RelDataType rowType);
@Deprecated // to be removed before 2.0
public Project copy(RelTraitSet traitSet, RelNode input,
List<RexNode> projects, RelDataType rowType, int flags) {
Util.discard(flags);
return copy(traitSet, input, projects, rowType);
}
@Deprecated // to be removed before 2.0
public boolean isBoxed() {
return true;
}
@Override public RelNode accept(RexShuttle shuttle) {
List<RexNode> exps = shuttle.apply(this.exps);
if (this.exps == exps) {
return this;
}
final RelDataType rowType =
RexUtil.createStructType(
getInput().getCluster().getTypeFactory(),
exps,
getRowType().getFieldNames(),
null);
return copy(traitSet, getInput(), exps, rowType);
}
/**
* Returns the project expressions.
*
* @return Project expressions
*/
public List<RexNode> getProjects() {
return exps;
}
/**
* Returns a list of (expression, name) pairs. Convenient for various
* transformations.
*
* @return List of (expression, name) pairs
*/
public final List<Pair<RexNode, String>> getNamedProjects() {
return Pair.zip(getProjects(), getRowType().getFieldNames());
}
@Override public ImmutableList<RelHint> getHints() {
return hints;
}
@Deprecated // to be removed before 2.0
public int getFlags() {
return 1;
}
/** Returns whether this Project contains any windowed-aggregate functions. */
public final boolean containsOver() {
return RexOver.containsOver(getProjects(), null);
}
@Override public boolean isValid(Litmus litmus, @Nullable Context context) {
if (!super.isValid(litmus, context)) {
return litmus.fail(null);
}
if (!RexUtil.compatibleTypes(exps, getRowType(), litmus)) {
return litmus.fail("incompatible types");
}
RexChecker checker =
new RexChecker(
getInput().getRowType(), context, litmus);
for (RexNode exp : exps) {
exp.accept(checker);
if (checker.getFailureCount() > 0) {
return litmus.fail("{} failures in expression {}",
checker.getFailureCount(), exp);
}
}
if (!Util.isDistinct(getRowType().getFieldNames())) {
return litmus.fail("field names not distinct: {}", rowType);
}
//CHECKSTYLE: IGNORE 1
if (false && !Util.isDistinct(Util.transform(exps, RexNode::toString))) {
// Projecting the same expression twice is usually a bad idea,
// because it may create expressions downstream which are equivalent
// but which look different. We can't ban duplicate projects,
// because we need to allow
//
// SELECT a, b FROM c UNION SELECT x, x FROM z
return litmus.fail("duplicate expressions: {}", exps);
}
return litmus.succeed();
}
@Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
double dRows = mq.getRowCount(getInput());
double dCpu = dRows * exps.size();
double dIo = 0;
return planner.getCostFactory().makeCost(dRows, dCpu, dIo);
}
/**
* Returns the number of expressions at the front of an array which are
* simply projections of the same field.
*
* @param refs References
* @return the index of the first non-trivial expression, or list.size otherwise
*/
private static int countTrivial(List<RexNode> refs) {
for (int i = 0; i < refs.size(); i++) {
RexNode ref = refs.get(i);
if (!(ref instanceof RexInputRef)
|| ((RexInputRef) ref).getIndex() != i) {
return i;
}
}
return refs.size();
}
@Override public Set<CorrelationId> getVariablesSet() {
return variablesSet;
}
@Override public RelWriter explainTerms(RelWriter pw) {
super.explainTerms(pw);
pw.itemIf("variablesSet", variablesSet, !variablesSet.isEmpty());
// Skip writing field names so the optimizer can reuse the projects that differ in
// field names only
if (pw.getDetailLevel() == SqlExplainLevel.DIGEST_ATTRIBUTES) {
final int firstNonTrivial = countTrivial(exps);
if (firstNonTrivial == 1) {
pw.item("inputs", "0");
} else if (firstNonTrivial != 0) {
pw.item("inputs", "0.." + (firstNonTrivial - 1));
}
if (firstNonTrivial != exps.size()) {
pw.item("exprs", exps.subList(firstNonTrivial, exps.size()));
}
return pw;
}
if (pw.nest()) {
pw.item("fields", getRowType().getFieldNames());
pw.item("exprs", exps);
} else {
for (Ord<RelDataTypeField> field : Ord.zip(getRowType().getFieldList())) {
String fieldName = field.e.getName();
if (fieldName == null) {
fieldName = "field#" + field.i;
}
pw.item(fieldName, exps.get(field.i));
}
}
return pw;
}
@API(since = "1.24", status = API.Status.INTERNAL)
@EnsuresNonNullIf(expression = "#1", result = true)
protected boolean deepEquals0(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Project o = (Project) obj;
return traitSet.equals(o.traitSet)
&& input.deepEquals(o.input)
&& exps.equals(o.exps)
&& hints.equals(o.hints)
&& getRowType().equalsSansFieldNames(o.getRowType());
}
@API(since = "1.24", status = API.Status.INTERNAL)
protected int deepHashCode0() {
return Objects.hash(traitSet, input.deepHashCode(), exps, hints);
}
/**
* Returns a mapping, or null if this projection is not a mapping.
*
* @return Mapping, or null if this projection is not a mapping
*/
public Mappings.@Nullable TargetMapping getMapping() {
return getMapping(getInput().getRowType().getFieldCount(), exps);
}
/**
* Returns a mapping of a set of project expressions.
*
* <p>The mapping is an inverse surjection.
* Every target has a source field, but no
* source has more than one target.
* Thus you can safely call
* {@link org.apache.calcite.util.mapping.Mappings.TargetMapping#getSourceOpt(int)}.
*
* @param inputFieldCount Number of input fields
* @param projects Project expressions
* @return Mapping of a set of project expressions, or null if projection is
* not a mapping
*/
public static Mappings.@Nullable TargetMapping getMapping(int inputFieldCount,
List<? extends RexNode> projects) {
if (inputFieldCount < projects.size()) {
return null; // surjection is not possible
}
Mappings.TargetMapping mapping =
Mappings.create(MappingType.INVERSE_SURJECTION,
inputFieldCount, projects.size());
for (Ord<RexNode> exp : Ord.<RexNode>zip(projects)) {
if (!(exp.e instanceof RexInputRef)) {
return null;
}
int source = ((RexInputRef) exp.e).getIndex();
if (mapping.getTargetOpt(source) != -1) {
return null;
}
mapping.set(source, exp.i);
}
return mapping;
}
/**
* Returns a partial mapping of a set of project expressions.
*
* <p>The mapping is an inverse function.
* Every target has a source field, but
* a source might have 0, 1 or more targets.
* Project expressions that do not consist of
* a mapping are ignored.
*
* @param inputFieldCount Number of input fields
* @param projects Project expressions
* @return Mapping of a set of project expressions, never null
*/
public static Mappings.TargetMapping getPartialMapping(int inputFieldCount,
List<? extends RexNode> projects) {
Mappings.TargetMapping mapping =
Mappings.create(MappingType.INVERSE_FUNCTION,
inputFieldCount, projects.size());
for (Ord<RexNode> exp : Ord.<RexNode>zip(projects)) {
if (exp.e instanceof RexInputRef) {
mapping.set(((RexInputRef) exp.e).getIndex(), exp.i);
}
}
return mapping;
}
/**
* Returns a permutation, if this projection is merely a permutation of its
* input fields; otherwise null.
*
* @return Permutation, if this projection is merely a permutation of its
* input fields; otherwise null
*/
public @Nullable Permutation getPermutation() {
return getPermutation(getInput().getRowType().getFieldCount(), exps);
}
/**
* Returns a permutation, if this projection is merely a permutation of its
* input fields; otherwise null.
*/
public static @Nullable Permutation getPermutation(int inputFieldCount,
List<? extends RexNode> projects) {
final int fieldCount = projects.size();
if (fieldCount != inputFieldCount) {
return null;
}
final Permutation permutation = new Permutation(fieldCount);
final Set<Integer> alreadyProjected = new HashSet<>(fieldCount);
for (int i = 0; i < fieldCount; ++i) {
final RexNode exp = projects.get(i);
if (exp instanceof RexInputRef) {
final int index = ((RexInputRef) exp).getIndex();
if (!alreadyProjected.add(index)) {
return null;
}
permutation.set(i, index);
} else {
return null;
}
}
return permutation;
}
/**
* Checks whether this is a functional mapping.
* Every output is a source field, but
* a source field may appear as zero, one, or more output fields.
*/
public boolean isMapping() {
for (RexNode exp : exps) {
if (!(exp instanceof RexInputRef)) {
return false;
}
}
return true;
}
//~ Inner Classes ----------------------------------------------------------
/** No longer used. */
@Deprecated // to be removed before 2.0
public static class Flags {
public static final int ANON_FIELDS = 2;
public static final int BOXED = 1;
public static final int NONE = 0;
}
}