/** * 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.tajo.engine.planner; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.tajo.algebra.ColumnReferenceExpr; import org.apache.tajo.algebra.Expr; import org.apache.tajo.algebra.NamedExpr; import org.apache.tajo.algebra.OpType; import org.apache.tajo.annotation.Nullable; import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.eval.EvalType; import org.apache.tajo.engine.eval.FieldEval; import org.apache.tajo.util.TUtil; import java.util.*; /** * NamedExprsManager manages an expressions used in a query block. All expressions used in a query block must be * added to NamedExprsManager. When an expression is added to NamedExprsManager, NamedExprsManager gives a reference * to the expression. If the expression already has an alias name, it gives the alias name as the reference * to the expression. If the expression does not have any alias, it gives a generated name as the reference to the * expression. Usually, predicates in WHERE clause, expressions in GROUP-BY, ORDER-BY, LIMIT clauses are not given * any alias name. Those reference names are used to identify an individual expression. * * NamedExprsManager only keeps unique expressions. Since expressions in a query block can be duplicated, * one or more reference names can point one expressions. Due to this process, it naturally removes duplicated * expression. * * As we mentioned above, one or more reference names can indicate one expression. Primary names are used for * representing expressions. A primary name of an expression indicates the reference obtained when * the expression is added firstly. All output schemas uses only primary names of expressions. * * Each expression that NamedExprsManager keeps has an boolean state to indicate whether the expression is evaluated * or not. The <code>evaluated</code> state means that upper logical operators can access this expression like a column * reference. For it, the reference name is used to access this expression like a column reference, * The evaluated state is set with an EvalNode which is an annotated expression. * {@link #getTarget(String)} returns EvalNodes by a reference name. */ public class NamedExprsManager { /** a sequence id */ private int sequenceId = 0; /** Map: Name -> ID. Two or more different names can indicates the same id. */ private LinkedHashMap<String, Integer> nameToIdMap = Maps.newLinkedHashMap(); /** Map; ID <-> EvalNode */ private BiMap<Integer, EvalNode> idToEvalMap = HashBiMap.create(); /** Map: ID -> Names */ private LinkedHashMap<Integer, List<String>> idToNamesMap = Maps.newLinkedHashMap(); /** Map: ID -> Expr */ private BiMap<Integer, Expr> idToExprBiMap = HashBiMap.create(); /** Map; Name -> Boolean (if it is resolved or not) */ private LinkedHashMap<Integer, Boolean> evaluationStateMap = Maps.newLinkedHashMap(); /** Map: Alias Name <-> Original Name */ private BiMap<String, String> aliasedColumnMap = HashBiMap.create(); private LogicalPlan plan; public NamedExprsManager(LogicalPlan plan) { this.plan = plan; } private int getNextId() { return sequenceId++; } /** * Check whether the expression corresponding to a given name was evaluated. * * @param name The name of a certain expression to be checked * @return true if resolved. Otherwise, false. */ public boolean isEvaluated(String name) { if (nameToIdMap.containsKey(name)) { int refId = nameToIdMap.get(name); return evaluationStateMap.containsKey(refId) && evaluationStateMap.get(refId); } else { return false; } } public boolean contains(String name) { return nameToIdMap.containsKey(name); } public boolean contains(Expr expr) { return idToExprBiMap.inverse().containsKey(expr); } private Expr getExpr(String name) { return idToExprBiMap.get(nameToIdMap.get(name)); } public NamedExpr getNamedExpr(String name) { String normalized = name; return new NamedExpr(getExpr(name), normalized); } public boolean isAliased(String name) { return aliasedColumnMap.containsKey(name); } public String getAlias(String originalName) { return aliasedColumnMap.get(originalName); } public boolean isAliasedName(String aliasName) { return aliasedColumnMap.inverse().containsKey(aliasName); } public String getOriginalName(String aliasName) { return aliasedColumnMap.inverse().get(aliasName); } /** * Adds an expression and returns a reference name. */ public String addExpr(Expr expr) throws PlanningException { if (idToExprBiMap.inverse().containsKey(expr)) { int refId = idToExprBiMap.inverse().get(expr); return idToNamesMap.get(refId).get(0); } String generatedName = plan.generateUniqueColumnName(expr); return addExpr(expr, generatedName); } /** * Adds an expression with an alias name and returns a reference name. * It specifies the alias as an reference name. */ public String addExpr(Expr expr, String alias) throws PlanningException { // if this name already exists, just returns the name. if (nameToIdMap.containsKey(alias)) { return alias; } // if the name is first int refId; if (idToExprBiMap.inverse().containsKey(expr)) { refId = idToExprBiMap.inverse().get(expr); } else { refId = getNextId(); idToExprBiMap.put(refId, expr); } nameToIdMap.put(alias, refId); evaluationStateMap.put(refId, false); // add the entry to idToNames map TUtil.putToNestedList(idToNamesMap, refId, alias); return alias; } /** * Adds an expression and returns a reference name. * If an alias is given, it specifies the alias as an reference name. */ public String addNamedExpr(NamedExpr namedExpr) throws PlanningException { if (namedExpr.hasAlias()) { return addExpr(namedExpr.getExpr(), namedExpr.getAlias()); } else { return addExpr(namedExpr.getExpr()); } } /** * Adds a list of expressions and returns a list of reference names. * If some NamedExpr has an alias, NamedExprsManager specifies the alias for the NamedExpr. */ public String [] addNamedExprArray(@Nullable Collection<NamedExpr> namedExprs) throws PlanningException { if (namedExprs != null && namedExprs.size() > 0) { String [] names = new String[namedExprs.size()]; int i = 0; for (NamedExpr target : namedExprs) { names[i++] = addNamedExpr(target); } return names; } else { return null; } } public Collection<NamedExpr> getAllNamedExprs() { List<NamedExpr> namedExprList = Lists.newArrayList(); for (Map.Entry<Integer, Expr> entry: idToExprBiMap.entrySet()) { namedExprList.add(new NamedExpr(entry.getValue(), idToNamesMap.get(entry.getKey()).get(0))); } return namedExprList; } /** * It marks the expression identified by the reference name as <code>evaluated</code>. * In addition, it adds an EvanNode for the expression identified by the reference. * * @param referenceName The reference name to be marked as 'evaluated'. * @param evalNode EvalNode to be added. */ public void markAsEvaluated(String referenceName, EvalNode evalNode) throws PlanningException { String normalized = referenceName; int refId = nameToIdMap.get(normalized); evaluationStateMap.put(refId, true); idToEvalMap.put(refId, evalNode); String originalName = checkAndGetIfAliasedColumn(normalized); if (originalName != null) { aliasedColumnMap.put(originalName, normalized); } } /** * It returns an original column name if it is aliased column reference. * Otherwise, it will return NULL. */ public String checkAndGetIfAliasedColumn(String name) { Expr expr = getExpr(name); if (expr != null && expr.getType() == OpType.Column) { ColumnReferenceExpr column = (ColumnReferenceExpr) expr; if (!column.getCanonicalName().equals(name)) { return column.getCanonicalName(); } } return null; } public Target getTarget(String name) { return getTarget(name, false); } /** * It checks if a given name is the primary name. * * @See {@link NamedExprsManager} * @see {@link NamedExprsManager#getPrimaryName} * * @param id The expression id * @param name The name to be checked if it is primary name. * @return The primary name */ private boolean isPrimaryName(int id, String name) { return idToNamesMap.get(id).get(0).equals(name); } /** * One or more reference names can indicate one expression. Primary names are used for * representing expressions. A primary name of an expression indicates the reference obtained when * the expression is added firstly. All output schemas uses only primary names of expressions. * * @param id The expression id * @return The primary name */ private String getPrimaryName(int id) { return idToNamesMap.get(id).get(0); } /** * get a Target instance. A target consists of a reference name and an EvalNode corresponding to the reference name. * According to evaluation state, it returns different EvalNodes. * If the expression corresponding to the reference name is evaluated, it just returns {@link FieldEval} * (i.e., a column reference). Otherwise, it returns the original EvalNode of the expression. * * @param referenceName The reference name to get EvalNode * @param unevaluatedForm If TRUE, it always return the annotated EvalNode of the expression. * @return */ public Target getTarget(String referenceName, boolean unevaluatedForm) { String normalized = referenceName; int refId = nameToIdMap.get(normalized); if (!unevaluatedForm && evaluationStateMap.containsKey(refId) && evaluationStateMap.get(refId)) { EvalNode evalNode = idToEvalMap.get(refId); // If the expression is already evaluated, it should use the FieldEval to access a field value. // But, if this reference name is not primary name, it cannot use the reference name. // It changes the given reference name to the primary name. if (isEvaluated(normalized) && !isPrimaryName(refId, referenceName)) { return new Target(new FieldEval(getPrimaryName(refId),evalNode.getValueType()), referenceName); } EvalNode referredEval; if (evalNode.getType() == EvalType.CONST) { referredEval = evalNode; } else { referredEval = new FieldEval(idToNamesMap.get(refId).get(0), evalNode.getValueType()); } return new Target(referredEval, referenceName); } else { if (idToEvalMap.containsKey(refId)) { return new Target(idToEvalMap.get(refId), referenceName); } else { return null; } } } public String toString() { return "unevaluated=" + nameToIdMap.size() + ", evaluated=" + idToEvalMap.size() + ", renamed=" + aliasedColumnMap.size(); } /** * It returns an iterator for unevaluated NamedExprs. */ public Iterator<NamedExpr> getIteratorForUnevaluatedExprs() { return new UnevaluatedIterator(); } public class UnevaluatedIterator implements Iterator<NamedExpr> { private final Iterator<NamedExpr> iterator; public UnevaluatedIterator() { List<NamedExpr> unEvaluatedList = TUtil.newList(); for (Integer refId: idToNamesMap.keySet()) { String name = idToNamesMap.get(refId).get(0); if (!isEvaluated(name)) { Expr expr = idToExprBiMap.get(refId); unEvaluatedList.add(new NamedExpr(expr, name)); } } if (unEvaluatedList.size() == 0) { iterator = null; } else { iterator = unEvaluatedList.iterator(); } } @Override public boolean hasNext() { return iterator != null && iterator.hasNext(); } @Override public NamedExpr next() { return iterator.next(); } @Override public void remove() { } } }