blob: baf7b61f0cda2beeec8ca7b88ce473960f2463fc [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.impala.analysis;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
/**
* Map of expression substitutions: lhs[i] gets substituted with rhs[i].
* To support expression substitution across query blocks, rhs exprs must already be
* analyzed when added to this map. Otherwise, analysis of a SlotRef may fail after
* substitution, e.g., because the table it refers to is in a different query block
* that is not visible.
* See Expr.substitute() and related functions for details on the actual substitution.
*/
public final class ExprSubstitutionMap {
private final static Logger LOG = LoggerFactory.getLogger(ExprSubstitutionMap.class);
private List<Expr> lhs_; // left-hand side
private List<Expr> rhs_; // right-hand side
public ExprSubstitutionMap() {
this(new ArrayList<>(), new ArrayList<>());
}
public ExprSubstitutionMap(List<Expr> lhs, List<Expr> rhs) {
lhs_ = lhs;
rhs_ = rhs;
}
/**
* Add an expr mapping. The rhsExpr must be analyzed to support correct substitution
* across query blocks. It is not required that the lhsExpr is analyzed.
*/
public void put(Expr lhsExpr, Expr rhsExpr) {
Preconditions.checkState(rhsExpr.isAnalyzed(), "Rhs expr must be analyzed.");
lhs_.add(lhsExpr);
rhs_.add(rhsExpr);
}
/**
* Returns the expr mapped to lhsExpr or null if no mapping to lhsExpr exists.
*/
public Expr get(Expr lhsExpr) {
for (int i = 0; i < lhs_.size(); ++i) {
if (lhsExpr.equals(lhs_.get(i))) return rhs_.get(i);
}
return null;
}
/**
* Returns true if the smap contains a mapping for lhsExpr.
*/
public boolean containsMappingFor(Expr lhsExpr) {
return lhs_.contains(lhsExpr);
}
/**
* Return a map which is equivalent to applying f followed by g,
* i.e., g(f()).
* Always returns a non-null map.
*/
public static ExprSubstitutionMap compose(ExprSubstitutionMap f, ExprSubstitutionMap g,
Analyzer analyzer) {
if (f == null && g == null) return new ExprSubstitutionMap();
if (f == null) return g;
if (g == null) return f;
ExprSubstitutionMap result = new ExprSubstitutionMap();
// f's substitution targets need to be substituted via g
result.lhs_ = Expr.cloneList(f.lhs_);
result.rhs_ = Expr.substituteList(f.rhs_, g, analyzer, false);
// substitution maps are cumulative: the combined map contains all
// substitutions from f and g.
for (int i = 0; i < g.lhs_.size(); i++) {
// If f contains expr1->fn(expr2) and g contains expr2->expr3,
// then result must contain expr1->fn(expr3).
// The check before adding to result.lhs is to ensure that cases
// where expr2.equals(expr1) are handled correctly.
// For example f: count(*) -> zeroifnull(count(*))
// and g: count(*) -> slotref
// result.lhs must only have: count(*) -> zeroifnull(slotref) from f above,
// and not count(*) -> slotref from g as well.
if (!result.lhs_.contains(g.lhs_.get(i))) {
result.lhs_.add(g.lhs_.get(i).clone());
result.rhs_.add(g.rhs_.get(i).clone());
}
}
result.verify();
return result;
}
/**
* Returns the union of two substitution maps. Always returns a non-null map.
*/
public static ExprSubstitutionMap combine(ExprSubstitutionMap f,
ExprSubstitutionMap g) {
if (f == null && g == null) return new ExprSubstitutionMap();
if (f == null) return g;
if (g == null) return f;
ExprSubstitutionMap result = new ExprSubstitutionMap();
result.lhs_ = Lists.newArrayList(f.lhs_);
result.lhs_.addAll(g.lhs_);
result.rhs_ = Lists.newArrayList(f.rhs_);
result.rhs_.addAll(g.rhs_);
result.verify();
return result;
}
public void substituteLhs(ExprSubstitutionMap lhsSmap, Analyzer analyzer) {
lhs_ = Expr.substituteList(lhs_, lhsSmap, analyzer, false);
}
public List<Expr> getLhs() { return lhs_; }
public List<Expr> getRhs() { return rhs_; }
public int size() { return lhs_.size(); }
public String debugString() {
Preconditions.checkState(lhs_.size() == rhs_.size());
List<String> output = new ArrayList<>();
for (int i = 0; i < lhs_.size(); ++i) {
output.add(lhs_.get(i).toSql() + ":" + rhs_.get(i).toSql());
output.add("(" + lhs_.get(i).debugString() + ":" + rhs_.get(i).debugString() + ")");
}
return "smap(" + Joiner.on(" ").join(output) + ")";
}
/**
* Verifies the internal state of this smap: Checks that the lhs_ has no duplicates,
* and that all rhs exprs are analyzed.
*/
private void verify() {
for (int i = 0; i < lhs_.size(); ++i) {
for (int j = i + 1; j < lhs_.size(); ++j) {
if (lhs_.get(i).equals(lhs_.get(j))) {
if (LOG.isTraceEnabled()) {
LOG.trace("verify: smap=" + this.debugString());
}
Preconditions.checkState(false);
}
}
Preconditions.checkState(rhs_.get(i).isAnalyzed());
}
}
public void clear() {
lhs_.clear();
rhs_.clear();
}
@Override
public ExprSubstitutionMap clone() {
return new ExprSubstitutionMap(Expr.cloneList(lhs_), Expr.cloneList(rhs_));
}
/**
* Returns whether we are composed from the other map.
*/
public boolean checkComposedFrom(ExprSubstitutionMap other) {
// If g is composed from f, then g(x) = h(f(x)), so "f(a) == f(b)" => "g(a) == g(b)".
for (int i = 0; i < other.lhs_.size() - 1; ++i) {
for (int j = i + 1; j < other.lhs_.size(); ++j) {
Expr a = other.lhs_.get(i);
Expr b = other.lhs_.get(j);
Expr finalA = get(a);
Expr finalB = get(b);
if (finalA == null || finalB == null) {
if (LOG.isTraceEnabled()) {
if (finalA == null) {
LOG.trace("current smap misses item for " + a.debugString());
}
if (finalB == null) {
LOG.trace("current smap misses item for " + b.debugString());
}
}
return false;
}
if (other.rhs_.get(i).equals(other.rhs_.get(j)) && !finalA.equals(finalB)) {
// f(a) == f(b) but g(a) != g(b)
if (LOG.isTraceEnabled()) {
LOG.trace(String.format("smap conflicts in substituting %s and %s. Result" +
" of the base map: %s. Results of current map: %s and %s",
a.debugString(), b.debugString(), other.rhs_.get(i).debugString(),
finalA.debugString(), finalB.debugString()));
}
return false;
}
}
}
return true;
}
}