/* * 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.util.*; import org.eigenbase.rel.metadata.*; import org.eigenbase.relopt.*; import org.eigenbase.reltype.*; import org.eigenbase.rex.*; import org.eigenbase.sql.*; import org.eigenbase.util.Pair; import org.eigenbase.util.Permutation; import org.eigenbase.util.Util; import org.eigenbase.util.mapping.MappingType; import org.eigenbase.util.mapping.Mappings; import net.hydromatic.linq4j.Ord; import net.hydromatic.linq4j.function.Function1; import net.hydromatic.linq4j.function.Functions; import com.google.common.collect.ImmutableList; /** * <code>ProjectRelBase</code> is an abstract base class for implementations of * {@link ProjectRel}. */ public abstract class ProjectRelBase extends SingleRel { //~ Instance fields -------------------------------------------------------- protected final ImmutableList<RexNode> exps; /** * Values defined in {@link Flags}. */ protected int flags; protected final ImmutableList<RelCollation> collationList; //~ Constructors ----------------------------------------------------------- /** * Creates a Project. * * @param cluster Cluster this relational expression belongs to * @param traits traits of this rel * @param child input relational expression * @param exps List of expressions for the input columns * @param rowType output row type * @param flags values as in {@link Flags} */ protected ProjectRelBase( RelOptCluster cluster, RelTraitSet traits, RelNode child, List<? extends RexNode> exps, RelDataType rowType, int flags) { super(cluster, traits, child); assert rowType != null; this.exps = ImmutableList.copyOf(exps); this.rowType = rowType; this.flags = flags; final RelCollation collation = traits.getTrait(RelCollationTraitDef.INSTANCE); this.collationList = collation == null ? ImmutableList.<RelCollation>of() : ImmutableList.of(collation); assert isValid(true); } /** * Creates a ProjectRelBase by parsing serialized output. */ protected ProjectRelBase(RelInput input) { this( input.getCluster(), input.getTraitSet(), input.getInput(), input.getExpressionList("exprs"), input.getRowType("exprs", "fields"), Flags.BOXED); } //~ Methods ---------------------------------------------------------------- @Override public final RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { return copy(traitSet, sole(inputs), exps, rowType); } /** Copies a project. * * @see #copy(RelTraitSet, List) */ public abstract ProjectRelBase copy(RelTraitSet traitSet, RelNode input, List<RexNode> exps, RelDataType rowType); public List<RelCollation> getCollationList() { return collationList; } public boolean isBoxed() { return (flags & Flags.BOXED) == Flags.BOXED; } @Override public List<RexNode> getChildExps() { return exps; } /** * Returns the project expressions. */ public List<RexNode> getProjects() { return exps; } /** * Returns a list of (expression, name) pairs. Convenient for various * transformations. */ public final List<Pair<RexNode, String>> getNamedProjects() { return Pair.zip(getProjects(), getRowType().getFieldNames()); } public int getFlags() { return flags; } public boolean isValid(boolean fail) { if (!super.isValid(fail)) { assert !fail; return false; } if (!RexUtil.compatibleTypes( exps, getRowType(), true)) { assert !fail; return false; } RexChecker checker = new RexChecker( getChild().getRowType(), fail); for (RexNode exp : exps) { exp.accept(checker); } if (checker.getFailureCount() > 0) { assert !fail; return false; } if (!isBoxed()) { if (exps.size() != 1) { assert !fail; return false; } } if (collationList == null) { assert !fail; return false; } if (!collationList.isEmpty() && collationList.get(0) != traitSet.getTrait(RelCollationTraitDef.INSTANCE)) { assert !fail; return false; } if (!Util.isDistinct(rowType.getFieldNames())) { assert !fail : rowType; return false; } //CHECKSTYLE: IGNORE 1 if (false && !Util.isDistinct( Functions.adapt( exps, new Function1<RexNode, Object>() { public Object apply(RexNode a0) { return a0.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 assert !fail : exps; return false; } return true; } public RelOptCost computeSelfCost(RelOptPlanner planner) { double dRows = RelMetadataQuery.getRowCount(getChild()); double dCpu = dRows * exps.size(); double dIo = 0; return planner.getCostFactory().makeCost(dRows, dCpu, dIo); } public RelWriter explainTerms(RelWriter pw) { super.explainTerms(pw); if (pw.nest()) { pw.item("fields", rowType.getFieldNames()); pw.item("exprs", exps); } else { for (Ord<RelDataTypeField> field : Ord.zip(rowType.getFieldList())) { String fieldName = field.e.getName(); if (fieldName == null) { fieldName = "field#" + field.i; } pw.item(fieldName, exps.get(field.i)); } } // If we're generating a digest, include the rowtype. If two projects // differ in return type, we don't want to regard them as equivalent, // otherwise we will try to put rels of different types into the same // planner equivalence set. //CHECKSTYLE: IGNORE 2 if ((pw.getDetailLevel() == SqlExplainLevel.DIGEST_ATTRIBUTES) && false) { pw.item("type", rowType); } return pw; } /** * Returns a mapping, or null if this projection is not a mapping. */ public Mappings.TargetMapping getMapping() { return getMapping(getChild().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 * a source field may appear as zero, one, or more target fields. * Thus you can safely call * {@link Mappings.TargetMapping#getTarget(int)}. * * @param inputFieldCount Number of input fields * @param projects Project expressions */ public static Mappings.TargetMapping getMapping(int inputFieldCount, List<RexNode> projects) { Mappings.TargetMapping mapping = Mappings.create(MappingType.INVERSE_SURJECTION, inputFieldCount, projects.size()); for (Ord<RexNode> exp : Ord.zip(projects)) { if (!(exp.e instanceof RexInputRef)) { return null; } 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. */ public Permutation getPermutation() { final int fieldCount = rowType.getFieldList().size(); if (fieldCount != getChild().getRowType().getFieldList().size()) { return null; } Permutation permutation = new Permutation(fieldCount); for (int i = 0; i < fieldCount; ++i) { final RexNode exp = exps.get(i); if (exp instanceof RexInputRef) { permutation.set(i, ((RexInputRef) exp).getIndex()); } 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 ---------------------------------------------------------- /** A collection of integer constants that describe the kind of project. */ public static class Flags { public static final int ANON_FIELDS = 2; /** * Whether the resulting row is to be a synthetic class whose fields are * the aliases of the fields. <code>boxed</code> must be true unless * there is only one field: <code>select {dept.deptno} from dept</code> * is boxed, <code>select dept.deptno from dept</code> is not. */ public static final int BOXED = 1; public static final int NONE = 0; } //~ Inner Classes ---------------------------------------------------------- /** * Visitor which walks over a program and checks validity. */ private static class Checker extends RexVisitorImpl<Boolean> { private final boolean fail; private final RelDataType inputRowType; int failCount = 0; /** * Creates a Checker. * * @param inputRowType Input row type to expressions * @param fail Whether to throw if checker finds an error */ private Checker(RelDataType inputRowType, boolean fail) { super(true); this.fail = fail; this.inputRowType = inputRowType; } public Boolean visitInputRef(RexInputRef inputRef) { final int index = inputRef.getIndex(); final List<RelDataTypeField> fields = inputRowType.getFieldList(); if ((index < 0) || (index >= fields.size())) { assert !fail; ++failCount; return false; } if (!RelOptUtil.eq("inputRef", inputRef.getType(), "underlying field", fields.get(index).getType(), fail)) { assert !fail; ++failCount; return false; } return true; } public Boolean visitLocalRef(RexLocalRef localRef) { assert !fail : "localRef invalid in project"; ++failCount; return false; } public Boolean visitFieldAccess(RexFieldAccess fieldAccess) { super.visitFieldAccess(fieldAccess); final RelDataType refType = fieldAccess.getReferenceExpr().getType(); assert refType.isStruct(); final RelDataTypeField field = fieldAccess.getField(); final int index = field.getIndex(); if ((index < 0) || (index > refType.getFieldList().size())) { assert !fail; ++failCount; return false; } final RelDataTypeField typeField = refType.getFieldList().get(index); if (!RelOptUtil.eq( "type1", typeField.getType(), "type2", fieldAccess.getType(), fail)) { assert !fail; ++failCount; return false; } return true; } } } // End ProjectRelBase.java