diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/Schema.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/Schema.java index 35d2fe9225..3aee5d2e3d 100644 --- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/Schema.java +++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/Schema.java @@ -95,11 +95,22 @@ private void init() { * @param qualifier The qualifier */ public void setQualifier(String qualifier) { + Schema copy = null; + try { + copy = (Schema) clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + + fields.clear(); fieldsByQualifiedName.clear(); - for (int i = 0; i < size(); i++) { - Column column = fields.get(i); - fields.set(i, new Column(qualifier + "." + column.getSimpleName(), column.getDataType())); - fieldsByQualifiedName.put(fields.get(i).getQualifiedName(), i); + fieldsByName.clear(); + + Column newColumn; + for (int i = 0; i < copy.size(); i++) { + Column column = copy.getColumn(i); + newColumn = new Column(qualifier + "." + column.getSimpleName(), column.getDataType()); + addColumn(newColumn); } } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java index 2c386b2f03..39fd08acf5 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java @@ -32,6 +32,8 @@ import org.apache.tajo.engine.function.AggFunction; import org.apache.tajo.engine.function.GeneralFunction; import org.apache.tajo.engine.planner.logical.NodeType; +import org.apache.tajo.engine.planner.nameresolver.NameResolvingMode; +import org.apache.tajo.engine.planner.nameresolver.NameResolver; import org.apache.tajo.exception.InternalException; import org.apache.tajo.util.Pair; import org.apache.tajo.util.TUtil; @@ -66,16 +68,19 @@ public ExprAnnotator(CatalogService catalog) { static class Context { LogicalPlan plan; LogicalPlan.QueryBlock currentBlock; + NameResolvingMode columnRsvLevel; - public Context(LogicalPlan plan, LogicalPlan.QueryBlock block) { + public Context(LogicalPlan plan, LogicalPlan.QueryBlock block, NameResolvingMode colRsvLevel) { this.plan = plan; this.currentBlock = block; + this.columnRsvLevel = colRsvLevel; } } - public EvalNode createEvalNode(LogicalPlan plan, LogicalPlan.QueryBlock block, Expr expr) + public EvalNode createEvalNode(LogicalPlan plan, LogicalPlan.QueryBlock block, Expr expr, + NameResolvingMode colRsvLevel) throws PlanningException { - Context context = new Context(plan, block); + Context context = new Context(plan, block, colRsvLevel); return AlgebraicUtil.eliminateConstantExprs(visit(context, new Stack(), expr)); } @@ -540,7 +545,20 @@ public EvalNode visitSign(Context ctx, Stack stack, SignedExpr expr) throw @Override public EvalNode visitColumnReference(Context ctx, Stack stack, ColumnReferenceExpr expr) throws PlanningException { - Column column = ctx.plan.resolveColumn(ctx.currentBlock, expr); + Column column; + + switch (ctx.columnRsvLevel) { + case LEGACY: + column = ctx.plan.resolveColumn(ctx.currentBlock, expr); + break; + case RELS_ONLY: + case RELS_AND_SUBEXPRS: + case SUBEXPRS_AND_RELS: + column = NameResolver.resolve(ctx.plan, ctx.currentBlock, expr, ctx.columnRsvLevel); + break; + default: + throw new PlanningException("Unsupported column resolving level: " + ctx.columnRsvLevel.name()); + } return new FieldEval(column); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprNormalizer.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprNormalizer.java index 81bbd41247..5b61b74ec1 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprNormalizer.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprNormalizer.java @@ -18,16 +18,16 @@ package org.apache.tajo.engine.planner; -import com.google.common.collect.Sets; import com.google.common.collect.Sets; import org.apache.tajo.algebra.*; import org.apache.tajo.catalog.CatalogUtil; import org.apache.tajo.engine.exception.NoSuchColumnException; +import org.apache.tajo.engine.planner.nameresolver.NameResolvingMode; +import org.apache.tajo.engine.planner.nameresolver.NameResolver; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.Set; import java.util.Stack; /** @@ -330,11 +330,18 @@ public Expr visitCastExpr(ExprNormalizedResult ctx, Stack stack, CastExpr @Override public Expr visitColumnReference(ExprNormalizedResult ctx, Stack stack, ColumnReferenceExpr expr) throws PlanningException { + + if (ctx.block.isAliasedName(expr.getCanonicalName())) { + String originalName = ctx.block.getOriginalName(expr.getCanonicalName()); + expr.setName(originalName); + return expr; + } // if a column reference is not qualified, it finds and sets the qualified column name. if (!(expr.hasQualifier() && CatalogUtil.isFQTableName(expr.getQualifier()))) { if (!ctx.block.namedExprsMgr.contains(expr.getCanonicalName()) && expr.getType() == OpType.Column) { try { - String normalized = ctx.plan.getNormalizedColumnName(ctx.block, expr); + String normalized = + NameResolver.resolve(ctx.plan, ctx.block, expr, NameResolvingMode.LEGACY).getQualifiedName(); expr.setName(normalized); } catch (NoSuchColumnException nsc) { } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlan.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlan.java index 4e1d3137d6..86bacefbd5 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlan.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlan.java @@ -19,23 +19,20 @@ package org.apache.tajo.engine.planner; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import org.apache.commons.lang.ObjectUtils; import org.apache.tajo.algebra.*; import org.apache.tajo.annotation.NotThreadSafe; -import org.apache.tajo.catalog.CatalogUtil; import org.apache.tajo.catalog.Column; import org.apache.tajo.catalog.Schema; import org.apache.tajo.engine.eval.EvalNode; -import org.apache.tajo.engine.exception.AmbiguousFieldException; -import org.apache.tajo.engine.exception.NoSuchColumnException; -import org.apache.tajo.engine.exception.VerifyException; import org.apache.tajo.engine.planner.graph.DirectedGraphCursor; import org.apache.tajo.engine.planner.graph.SimpleDirectedGraph; import org.apache.tajo.engine.planner.logical.LogicalNode; import org.apache.tajo.engine.planner.logical.LogicalRootNode; import org.apache.tajo.engine.planner.logical.NodeType; import org.apache.tajo.engine.planner.logical.RelationNode; +import org.apache.tajo.engine.planner.nameresolver.NameResolvingMode; +import org.apache.tajo.engine.planner.nameresolver.NameResolver; import org.apache.tajo.util.TUtil; import java.lang.reflect.Constructor; @@ -49,7 +46,6 @@ public class LogicalPlan { /** the prefix character for virtual tables */ public static final char VIRTUAL_TABLE_PREFIX='#'; public static final char NONAMED_COLUMN_PREFIX='?'; - public static final char NONAMED_WINDOW_PREFIX='^'; /** it indicates the root block */ public static final String ROOT_BLOCK = VIRTUAL_TABLE_PREFIX + "ROOT"; @@ -58,7 +54,6 @@ public class LogicalPlan { private int nextPid = 0; private Integer noNameBlockId = 0; private Integer noNameColumnId = 0; - private Integer noNameWindowId = 0; /** a map from between a block name to a block plan */ private Map queryBlocks = new LinkedHashMap(); @@ -266,230 +261,8 @@ public SimpleDirectedGraph getQueryBlockGraph() { return queryBlockGraph; } - public String getNormalizedColumnName(QueryBlock block, ColumnReferenceExpr columnRef) - throws PlanningException { - Column found = resolveColumn(block, columnRef); - if (found == null) { - throw new NoSuchColumnException(columnRef.getCanonicalName()); - } - return found.getQualifiedName(); - } - - public String resolveDatabase(QueryBlock block, String tableName) throws PlanningException { - List found = new ArrayList(); - for (RelationNode relation : block.getRelations()) { - // check alias name or table name - if (CatalogUtil.extractSimpleName(relation.getCanonicalName()).equals(tableName) || - CatalogUtil.extractSimpleName(relation.getTableName()).equals(tableName)) { - // obtain the database name - found.add(CatalogUtil.extractQualifier(relation.getTableName())); - } - } - - if (found.size() == 0) { - return null; - } else if (found.size() > 1) { - throw new PlanningException("Ambiguous table name \"" + tableName + "\""); - } - - return found.get(0); - } - - /** - * It resolves a column. - */ public Column resolveColumn(QueryBlock block, ColumnReferenceExpr columnRef) throws PlanningException { - if (columnRef.hasQualifier()) { - return resolveColumnWithQualifier(block, columnRef); - } else { - return resolveColumnWithoutQualifier(block, columnRef); - } - } - - private Column resolveColumnWithQualifier(QueryBlock block, ColumnReferenceExpr columnRef) throws PlanningException { - String qualifier; - String canonicalName; - String qualifiedName; - - if (CatalogUtil.isFQTableName(columnRef.getQualifier())) { - qualifier = columnRef.getQualifier(); - canonicalName = columnRef.getCanonicalName(); - } else { - String resolvedDatabaseName = resolveDatabase(block, columnRef.getQualifier()); - if (resolvedDatabaseName == null) { - throw new NoSuchColumnException(columnRef.getQualifier()); - } - qualifier = CatalogUtil.buildFQName(resolvedDatabaseName, columnRef.getQualifier()); - canonicalName = CatalogUtil.buildFQName(qualifier, columnRef.getName()); - } - qualifiedName = CatalogUtil.buildFQName(qualifier, columnRef.getName()); - - RelationNode relationOp = block.getRelation(qualifier); - - // if a column name is outside of this query block - if (relationOp == null) { - // TODO - nested query can only refer outer query block? or not? - for (QueryBlock eachBlock : queryBlocks.values()) { - if (eachBlock.existsRelation(qualifier)) { - relationOp = eachBlock.getRelation(qualifier); - } - } - } - - // If we cannot find any relation against a qualified column name - if (relationOp == null) { - throw new NoSuchColumnException(canonicalName); - } - - if (block.isAlreadyRenamedTableName(CatalogUtil.extractQualifier(canonicalName))) { - String changedName = CatalogUtil.buildFQName( - relationOp.getCanonicalName(), - CatalogUtil.extractSimpleName(canonicalName)); - canonicalName = changedName; - } - - Schema schema = relationOp.getTableSchema(); - Column column = schema.getColumn(canonicalName); - if (column == null) { - throw new NoSuchColumnException(canonicalName); - } - - // If code reach here, a column is found. - // But, it may be aliased from bottom logical node. - // If the column is aliased, the found name may not be used in upper node. - - // Here, we try to check if column reference is already aliased. - // If so, it replaces the name with aliased name. - LogicalNode currentNode = block.getCurrentNode(); - - // The condition (currentNode.getInSchema().contains(column)) means - // the column can be used at the current node. So, we don't need to find aliase name. - Schema currentNodeSchema = null; - if (currentNode != null) { - if (currentNode instanceof RelationNode) { - currentNodeSchema = ((RelationNode) currentNode).getTableSchema(); - } else { - currentNodeSchema = currentNode.getInSchema(); - } - } - - if (currentNode != null && !currentNodeSchema.contains(column) - && currentNode.getType() != NodeType.TABLE_SUBQUERY) { - List candidates = TUtil.newList(); - if (block.namedExprsMgr.isAliased(qualifiedName)) { - String alias = block.namedExprsMgr.getAlias(canonicalName); - Column found = resolveColumn(block, new ColumnReferenceExpr(alias)); - if (found != null) { - candidates.add(found); - } - } - if (!candidates.isEmpty()) { - return ensureUniqueColumn(candidates); - } - } - - return column; - } - - private Column resolveColumnWithoutQualifier(QueryBlock block, - ColumnReferenceExpr columnRef)throws PlanningException { - - List candidates = TUtil.newList(); - - // It tries to find a full qualified column name from all relations in the current block. - for (RelationNode rel : block.getRelations()) { - Column found = rel.getTableSchema().getColumn(columnRef.getName()); - if (found != null) { - candidates.add(found); - } - } - - if (!candidates.isEmpty()) { - return ensureUniqueColumn(candidates); - } - - // Trying to find the column within the current block - if (block.currentNode != null && block.currentNode.getInSchema() != null) { - Column found = block.currentNode.getInSchema().getColumn(columnRef.getCanonicalName()); - if (found != null) { - return found; - } - } - - if (block.getLatestNode() != null) { - Column found = block.getLatestNode().getOutSchema().getColumn(columnRef.getName()); - if (found != null) { - return found; - } - } - - - // Trying to find columns from aliased references. - if (block.namedExprsMgr.isAliased(columnRef.getCanonicalName())) { - String originalName = block.namedExprsMgr.getAlias(columnRef.getCanonicalName()); - Column found = resolveColumn(block, new ColumnReferenceExpr(originalName)); - if (found != null) { - candidates.add(found); - } - } - if (!candidates.isEmpty()) { - return ensureUniqueColumn(candidates); - } - - // This is an exception case. It means that there are some bugs in other parts. - LogicalNode blockRootNode = block.getRoot(); - if (blockRootNode != null && blockRootNode.getOutSchema().getColumn(columnRef.getCanonicalName()) != null) { - throw new NoSuchColumnException("ERROR: no such a column name "+ columnRef.getCanonicalName()); - } - - // Trying to find columns from other relations in other blocks - for (QueryBlock eachBlock : queryBlocks.values()) { - for (RelationNode rel : eachBlock.getRelations()) { - Column found = rel.getTableSchema().getColumn(columnRef.getName()); - if (found != null) { - candidates.add(found); - } - } - } - - if (!candidates.isEmpty()) { - return ensureUniqueColumn(candidates); - } - - // Trying to find columns from schema in current block. - if (block.getSchema() != null) { - Column found = block.getSchema().getColumn(columnRef.getName()); - if (found != null) { - candidates.add(found); - } - } - - if (!candidates.isEmpty()) { - return ensureUniqueColumn(candidates); - } - - throw new NoSuchColumnException("ERROR: no such a column name "+ columnRef.getCanonicalName()); - } - - private static Column ensureUniqueColumn(List candidates) - throws VerifyException { - if (candidates.size() == 1) { - return candidates.get(0); - } else if (candidates.size() > 2) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (Column column : candidates) { - if (first) { - first = false; - } else { - sb.append(", "); - } - sb.append(column); - } - throw new AmbiguousFieldException("Ambiguous Column Name: " + sb.toString()); - } else { - return null; - } + return NameResolver.resolve(this, block, columnRef, NameResolvingMode.LEGACY); } public String getQueryGraphAsString() { @@ -601,7 +374,8 @@ public class QueryBlock { // transient states private final Map canonicalNameToRelationMap = TUtil.newHashMap(); - private final Map> aliasMap = TUtil.newHashMap(); + private final Map> relationAliasMap = TUtil.newHashMap(); + private final Map columnAliasMap = TUtil.newHashMap(); private final Map> operatorToExprMap = TUtil.newHashMap(); private final List relationList = TUtil.newList(); private boolean hasWindowFunction = false; @@ -669,7 +443,7 @@ public boolean existsRelation(String name) { } public boolean isAlreadyRenamedTableName(String name) { - return aliasMap.containsKey(name); + return relationAliasMap.containsKey(name); } public RelationNode getRelation(String name) { @@ -677,8 +451,8 @@ public RelationNode getRelation(String name) { return canonicalNameToRelationMap.get(name); } - if (aliasMap.containsKey(name)) { - return canonicalNameToRelationMap.get(aliasMap.get(name).get(0)); + if (relationAliasMap.containsKey(name)) { + return canonicalNameToRelationMap.get(relationAliasMap.get(name).get(0)); } return null; @@ -686,7 +460,7 @@ public RelationNode getRelation(String name) { public void addRelation(RelationNode relation) { if (relation.hasAlias()) { - TUtil.putToNestedList(aliasMap, relation.getTableName(), relation.getCanonicalName()); + TUtil.putToNestedList(relationAliasMap, relation.getTableName(), relation.getCanonicalName()); } canonicalNameToRelationMap.put(relation.getCanonicalName(), relation); relationList.add(relation); @@ -700,6 +474,18 @@ public boolean hasTableExpression() { return this.canonicalNameToRelationMap.size() > 0; } + public void addColumnAlias(String original, String alias) { + columnAliasMap.put(alias, original); + } + + public boolean isAliasedName(String alias) { + return columnAliasMap.containsKey(alias); + } + + public String getOriginalName(String alias) { + return columnAliasMap.get(alias); + } + public void setSchema(Schema schema) { this.schema = schema; } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanPreprocessor.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanPreprocessor.java index 4f1218f2ba..84fe6c2fb9 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanPreprocessor.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanPreprocessor.java @@ -26,6 +26,8 @@ import org.apache.tajo.engine.exception.NoSuchColumnException; import org.apache.tajo.engine.planner.LogicalPlan.QueryBlock; import org.apache.tajo.engine.planner.logical.*; +import org.apache.tajo.engine.planner.nameresolver.NameResolvingMode; +import org.apache.tajo.engine.planner.nameresolver.NameResolver; import org.apache.tajo.engine.utils.SchemaUtil; import org.apache.tajo.master.session.Session; import org.apache.tajo.util.TUtil; @@ -196,7 +198,19 @@ public LogicalNode visitProjection(PreprocessContext ctx, Stack stack, Pro expr.setNamedExprs(rewrittenTargets.toArray(new NamedExpr[rewrittenTargets.size()])); } + // 1) Normalize field names into full qualified names + // 2) Register explicit column aliases to block NamedExpr[] projectTargetExprs = expr.getNamedExprs(); + NameRefInSelectListNormalizer normalizer = new NameRefInSelectListNormalizer(); + for (int i = 0; i < expr.getNamedExprs().length; i++) { + NamedExpr namedExpr = projectTargetExprs[i]; + normalizer.visit(ctx, new Stack(), namedExpr.getExpr()); + + if (namedExpr.getExpr().getType() == OpType.Column && namedExpr.hasAlias()) { + ctx.currentBlock.addColumnAlias(((ColumnReferenceExpr)namedExpr.getExpr()).getCanonicalName(), + namedExpr.getAlias()); + } + } Target [] targets; targets = new Target[projectTargetExprs.length]; @@ -217,6 +231,8 @@ public LogicalNode visitProjection(PreprocessContext ctx, Stack stack, Pro ProjectionNode projectionNode = ctx.plan.createNode(ProjectionNode.class); projectionNode.setInSchema(child.getOutSchema()); projectionNode.setOutSchema(PlannerUtil.targetToSchema(targets)); + + ctx.currentBlock.setSchema(projectionNode.getOutSchema()); return projectionNode; } @@ -267,7 +283,8 @@ public LogicalNode visitGroupBy(PreprocessContext ctx, Stack stack, Aggreg for (int i = 0; i < finalTargetNum; i++) { NamedExpr namedExpr = projection.getNamedExprs()[i]; - EvalNode evalNode = annotator.createEvalNode(ctx.plan, ctx.currentBlock, namedExpr.getExpr()); + EvalNode evalNode = annotator.createEvalNode(ctx.plan, ctx.currentBlock, namedExpr.getExpr(), + NameResolvingMode.SUBEXPRS_AND_RELS); if (namedExpr.hasAlias()) { targets[i] = new Target(evalNode, namedExpr.getAlias()); @@ -449,4 +466,17 @@ public LogicalNode visitInsert(PreprocessContext ctx, Stack stack, Insert insertNode.setOutSchema(child.getOutSchema()); return insertNode; } + + class NameRefInSelectListNormalizer extends SimpleAlgebraVisitor { + @Override + public Expr visitColumnReference(PreprocessContext ctx, Stack stack, ColumnReferenceExpr expr) + throws PlanningException { + + String normalized = NameResolver.resolve(ctx.plan, ctx.currentBlock, expr, + NameResolvingMode.RELS_ONLY).getQualifiedName(); + expr.setName(normalized); + + return expr; + } + } } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanner.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanner.java index ea517c012c..a4820cb46e 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanner.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/LogicalPlanner.java @@ -40,6 +40,7 @@ import org.apache.tajo.engine.exception.VerifyException; import org.apache.tajo.engine.planner.LogicalPlan.QueryBlock; import org.apache.tajo.engine.planner.logical.*; +import org.apache.tajo.engine.planner.nameresolver.NameResolvingMode; import org.apache.tajo.engine.planner.rewrite.ProjectionPushDownRule; import org.apache.tajo.engine.utils.SchemaUtil; import org.apache.tajo.master.session.Session; @@ -51,6 +52,7 @@ import java.util.*; import static org.apache.tajo.algebra.CreateTable.PartitionType; + import static org.apache.tajo.engine.planner.ExprNormalizer.ExprNormalizedResult; import static org.apache.tajo.engine.planner.LogicalPlan.BlockType; import static org.apache.tajo.engine.planner.LogicalPlanPreprocessor.PreprocessContext; @@ -260,7 +262,8 @@ private void setRawTargets(PlanContext context, Target[] targets, String[] refer Target [] rawTargets = new Target[projection.getNamedExprs().length]; for (int i = 0; i < projection.getNamedExprs().length; i++) { NamedExpr namedExpr = projection.getNamedExprs()[i]; - EvalNode evalNode = exprAnnotator.createEvalNode(plan, block, namedExpr.getExpr()); + EvalNode evalNode = exprAnnotator.createEvalNode(plan, block, namedExpr.getExpr(), + NameResolvingMode.RELS_AND_SUBEXPRS); rawTargets[i] = new Target(evalNode, referenceNames[i]); } // it's for debugging or unit testing @@ -383,7 +386,7 @@ private EvalExprNode buildPlanForNoneFromStatement(PlanContext context, Stack it = block.namedExprsMgr.getIteratorForUnevaluatedExprs(); it.hasNext();) { NamedExpr rawTarget = it.next(); try { - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr()); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr(), + NameResolvingMode.SUBEXPRS_AND_RELS); if (evalNode.getType() == EvalType.WINDOW_FUNCTION) { winFuncRefs.add(rawTarget.getAlias()); winFuncs.add((WindowFunctionEval) evalNode); @@ -678,7 +683,8 @@ private LogicalNode insertGroupbyNode(PlanContext context, LogicalNode child, St try { // check if at least distinct aggregation function includeDistinctFunction |= PlannerUtil.existsDistinctAggregationFunction(rawTarget.getExpr()); - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr()); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr(), + NameResolvingMode.SUBEXPRS_AND_RELS); if (evalNode.getType() == EvalType.AGG_FUNCTION) { aggEvalNames.add(rawTarget.getAlias()); aggEvals.add((AggregationFunctionCallEval) evalNode); @@ -712,7 +718,8 @@ public LimitNode visitLimit(PlanContext context, Stack stack, Limit limit) EvalNode firstFetNum; LogicalNode child; if (limit.getFetchFirstNum().getType() == OpType.Literal) { - firstFetNum = exprAnnotator.createEvalNode(context.plan, block, limit.getFetchFirstNum()); + firstFetNum = exprAnnotator.createEvalNode(context.plan, block, limit.getFetchFirstNum(), + NameResolvingMode.RELS_ONLY); //////////////////////////////////////////////////////// // Visit and Build Child Plan @@ -739,7 +746,8 @@ public LimitNode visitLimit(PlanContext context, Stack stack, Limit limit) firstFetNum = block.namedExprsMgr.getTarget(referName).getEvalTree(); } else { NamedExpr namedExpr = block.namedExprsMgr.getNamedExpr(referName); - firstFetNum = exprAnnotator.createEvalNode(context.plan, block, namedExpr.getExpr()); + firstFetNum = exprAnnotator.createEvalNode(context.plan, block, namedExpr.getExpr(), + NameResolvingMode.SUBEXPRS_AND_RELS); block.namedExprsMgr.markAsEvaluated(referName, firstFetNum); } } @@ -835,7 +843,8 @@ public LogicalNode visitHaving(PlanContext context, Stack stack, Having ex havingCondition = block.namedExprsMgr.getTarget(referName).getEvalTree(); } else { NamedExpr namedExpr = block.namedExprsMgr.getNamedExpr(referName); - havingCondition = exprAnnotator.createEvalNode(context.plan, block, namedExpr.getExpr()); + havingCondition = exprAnnotator.createEvalNode(context.plan, block, namedExpr.getExpr(), + NameResolvingMode.SUBEXPRS_AND_RELS); block.namedExprsMgr.markAsEvaluated(referName, havingCondition); } @@ -902,7 +911,8 @@ public LogicalNode visitGroupBy(PlanContext context, Stack stack, Aggregat NamedExpr namedExpr = iterator.next(); try { includeDistinctFunction |= PlannerUtil.existsDistinctAggregationFunction(namedExpr.getExpr()); - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, namedExpr.getExpr()); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, namedExpr.getExpr(), + NameResolvingMode.SUBEXPRS_AND_RELS); if (evalNode.getType() == EvalType.AGG_FUNCTION) { block.namedExprsMgr.markAsEvaluated(namedExpr.getAlias(), evalNode); aggEvalNames.add(namedExpr.getAlias()); @@ -988,7 +998,8 @@ public SelectionNode visitFilter(PlanContext context, Stack stack, Selecti selectionNode.setOutSchema(child.getOutSchema()); // Create EvalNode for a search condition. - EvalNode searchCondition = exprAnnotator.createEvalNode(context.plan, block, selection.getQual()); + EvalNode searchCondition = exprAnnotator.createEvalNode(context.plan, block, selection.getQual(), + NameResolvingMode.RELS_AND_SUBEXPRS); EvalNode simplified = AlgebraicUtil.eliminateConstantExprs(searchCondition); // set selection condition selectionNode.setQual(simplified); @@ -1041,7 +1052,8 @@ public LogicalNode visitJoin(PlanContext context, Stack stack, Join join) // Create EvalNode for a search condition. EvalNode joinCondition = null; if (join.hasQual()) { - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, block, join.getQual()); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, block, join.getQual(), + NameResolvingMode.LEGACY); joinCondition = AlgebraicUtil.eliminateConstantExprs(evalNode); } @@ -1071,7 +1083,7 @@ private List getNewlyEvaluatedExprsForJoin(LogicalPlan plan, QueryBlock for (Iterator it = block.namedExprsMgr.getIteratorForUnevaluatedExprs(); it.hasNext();) { NamedExpr namedExpr = it.next(); try { - evalNode = exprAnnotator.createEvalNode(plan, block, namedExpr.getExpr()); + evalNode = exprAnnotator.createEvalNode(plan, block, namedExpr.getExpr(), NameResolvingMode.LEGACY); if (LogicalPlanner.checkIfBeEvaluatedAtJoin(block, evalNode, joinNode, stack.peek().getType() != OpType.Join)) { block.namedExprsMgr.markAsEvaluated(namedExpr.getAlias(), evalNode); newlyEvaluatedExprs.add(namedExpr.getAlias()); @@ -1141,7 +1153,7 @@ private LogicalNode createCartesianProduct(PlanContext context, LogicalNode left for (Iterator it = block.namedExprsMgr.getIteratorForUnevaluatedExprs(); it.hasNext();) { NamedExpr namedExpr = it.next(); try { - evalNode = exprAnnotator.createEvalNode(plan, block, namedExpr.getExpr()); + evalNode = exprAnnotator.createEvalNode(plan, block, namedExpr.getExpr(), NameResolvingMode.LEGACY); if (EvalTreeUtil.findDistinctAggFunction(evalNode).size() == 0) { block.namedExprsMgr.markAsEvaluated(namedExpr.getAlias(), evalNode); newlyEvaluatedExprs.add(namedExpr.getAlias()); @@ -1193,7 +1205,8 @@ public ScanNode visitRelation(PlanContext context, Stack stack, Relation e for (Iterator iterator = block.namedExprsMgr.getIteratorForUnevaluatedExprs(); iterator.hasNext();) { NamedExpr rawTarget = iterator.next(); try { - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr()); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr(), + NameResolvingMode.RELS_ONLY); if (checkIfBeEvaluatedAtRelation(block, evalNode, scanNode)) { block.namedExprsMgr.markAsEvaluated(rawTarget.getAlias(), evalNode); newlyEvaluatedExprsReferences.add(rawTarget.getAlias()); // newly added exr @@ -1208,8 +1221,9 @@ public ScanNode visitRelation(PlanContext context, Stack stack, Relation e // The fact the some expr is included in newlyEvaluatedExprsReferences means that it is already evaluated. // So, we get a raw expression and then creates a target. for (String reference : newlyEvaluatedExprsReferences) { - NamedExpr refrrer = block.namedExprsMgr.getNamedExpr(reference); - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, block, refrrer.getExpr()); + NamedExpr refrer = block.namedExprsMgr.getNamedExpr(reference); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, block, refrer.getExpr(), + NameResolvingMode.RELS_ONLY); targets.add(new Target(evalNode, reference)); } @@ -1267,7 +1281,8 @@ public TableSubQueryNode visitTableSubQuery(PlanContext context, Stack sta Set newlyEvaluatedExprs = TUtil.newHashSet(); for (NamedExpr rawTarget : block.namedExprsMgr.getAllNamedExprs()) { try { - EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr()); + EvalNode evalNode = exprAnnotator.createEvalNode(context.plan, context.queryBlock, rawTarget.getExpr(), + NameResolvingMode.RELS_ONLY); if (checkIfBeEvaluatedAtRelation(block, evalNode, subQueryNode)) { block.namedExprsMgr.markAsEvaluated(rawTarget.getAlias(), evalNode); newlyEvaluatedExprs.add(rawTarget.getAlias()); // newly added exr diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/TableSubQueryNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/TableSubQueryNode.java index 4d0090b31e..3c808fcd78 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/TableSubQueryNode.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/TableSubQueryNode.java @@ -86,7 +86,6 @@ public void setSubQuery(LogicalNode node) { } else { setOutSchema(SchemaUtil.clone(this.subQuery.getOutSchema())); } - getOutSchema().setQualifier(this.tableName); } public LogicalNode getSubQuery() { diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/NameResolver.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/NameResolver.java new file mode 100644 index 0000000000..aee5d43b6f --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/NameResolver.java @@ -0,0 +1,291 @@ +/** + * 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.nameresolver; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.tajo.algebra.ColumnReferenceExpr; +import org.apache.tajo.catalog.CatalogUtil; +import org.apache.tajo.catalog.Column; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.engine.exception.AmbiguousFieldException; +import org.apache.tajo.engine.exception.NoSuchColumnException; +import org.apache.tajo.engine.exception.VerifyException; +import org.apache.tajo.engine.planner.LogicalPlan; +import org.apache.tajo.engine.planner.PlanningException; +import org.apache.tajo.engine.planner.logical.RelationNode; +import org.apache.tajo.util.Pair; +import org.apache.tajo.util.TUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * NameResolver utility + */ +public abstract class NameResolver { + + public static Map resolverMap = Maps.newHashMap(); + + static { + resolverMap.put(NameResolvingMode.RELS_ONLY, new ResolverByRels()); + resolverMap.put(NameResolvingMode.RELS_AND_SUBEXPRS, new ResolverByRelsAndSubExprs()); + resolverMap.put(NameResolvingMode.SUBEXPRS_AND_RELS, new ResolverBySubExprsAndRels()); + resolverMap.put(NameResolvingMode.LEGACY, new ResolverByLegacy()); + } + + abstract Column resolve(LogicalPlan plan, LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) + throws PlanningException; + + /** + * Try to find the database name + * + * @param block the current block + * @param tableName The table name + * @return The found database name + * @throws PlanningException + */ + public static String resolveDatabase(LogicalPlan.QueryBlock block, String tableName) throws PlanningException { + List found = new ArrayList(); + for (RelationNode relation : block.getRelations()) { + // check alias name or table name + if (CatalogUtil.extractSimpleName(relation.getCanonicalName()).equals(tableName) || + CatalogUtil.extractSimpleName(relation.getTableName()).equals(tableName)) { + // obtain the database name + found.add(CatalogUtil.extractQualifier(relation.getTableName())); + } + } + + if (found.size() == 0) { + return null; + } else if (found.size() > 1) { + throw new PlanningException("Ambiguous table name \"" + tableName + "\""); + } + + return found.get(0); + } + + public static Column resolve(LogicalPlan plan, LogicalPlan.QueryBlock block, ColumnReferenceExpr column, + NameResolvingMode mode) throws PlanningException { + if (!resolverMap.containsKey(mode)) { + throw new PlanningException("Unsupported name resolving level: " + mode.name()); + } + return resolverMap.get(mode).resolve(plan, block, column); + } + + /** + * Try to find a column from all relations within a given query block. + * If a given column reference is qualified, it tries to resolve the name + * from only the relation corresponding to the qualifier. + * + * @param plan The logical plan + * @param block The current query block + * @param columnRef The column reference to be found + * @return The found column + * @throws PlanningException + */ + static Column resolveFromRelsWithinBlock(LogicalPlan plan, LogicalPlan.QueryBlock block, + ColumnReferenceExpr columnRef) throws PlanningException { + String qualifier; + String canonicalName; + + if (columnRef.hasQualifier()) { + Pair normalized = normalizeQualifierAndCanonicalName(block, columnRef); + qualifier = normalized.getFirst(); + canonicalName = normalized.getSecond(); + + RelationNode relationOp = block.getRelation(qualifier); + + // If we cannot find any relation against a qualified column name + if (relationOp == null) { + throw null; + } + + // Please consider a query case: + // select lineitem.l_orderkey from lineitem a order by lineitem.l_orderkey; + // + // The relation lineitem is already renamed to "a", but lineitem.l_orderkey still can be used. + // The below code makes it available. Otherwise, it cannot find any match in the relation schema. + if (block.isAlreadyRenamedTableName(CatalogUtil.extractQualifier(canonicalName))) { + canonicalName = + CatalogUtil.buildFQName(relationOp.getCanonicalName(), CatalogUtil.extractSimpleName(canonicalName)); + } + + Schema schema = relationOp.getTableSchema(); + Column column = schema.getColumn(canonicalName); + + return column; + } else { + return resolveFromAllRelsInBlock(block, columnRef); + } + } + + /** + * Try to find the column from the current node and child node. It can find subexprs generated from the optimizer. + * + * @param block The current query block + * @param columnRef The column reference to be found + * @return The found column + */ + static Column resolveFromCurrentAndChildNode(LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) + throws NoSuchColumnException { + + if (block.getCurrentNode() != null && block.getCurrentNode().getInSchema() != null) { + Column found = block.getCurrentNode().getInSchema().getColumn(columnRef.getCanonicalName()); + if (found != null) { + return found; + } else if (block.getLatestNode() != null) { + found = block.getLatestNode().getOutSchema().getColumn(columnRef.getName()); + if (found != null) { + return found; + } + } + } + return null; + } + + /** + * It tries to find a full qualified column name from all relations in the current block. + * + * @param block The current query block + * @param columnRef The column reference to be found + * @return The found column + */ + static Column resolveFromAllRelsInBlock(LogicalPlan.QueryBlock block, + ColumnReferenceExpr columnRef) throws VerifyException { + List candidates = TUtil.newList(); + + for (RelationNode rel : block.getRelations()) { + Column found = rel.getTableSchema().getColumn(columnRef.getName()); + if (found != null) { + candidates.add(found); + } + } + + if (!candidates.isEmpty()) { + return ensureUniqueColumn(candidates); + } else { + return null; + } + } + + /** + * Trying to find a column from all relations in other blocks + * + * @param plan The logical plan + * @param columnRef The column reference to be found + * @return The found column + */ + static Column resolveFromAllRelsInAllBlocks(LogicalPlan plan, ColumnReferenceExpr columnRef) throws VerifyException { + + List candidates = Lists.newArrayList(); + + // from all relations of all query blocks + for (LogicalPlan.QueryBlock eachBlock : plan.getQueryBlocks()) { + + for (RelationNode rel : eachBlock.getRelations()) { + Column found = rel.getTableSchema().getColumn(columnRef.getName()); + if (found != null) { + candidates.add(found); + } + } + } + + if (!candidates.isEmpty()) { + return NameResolver.ensureUniqueColumn(candidates); + } else { + return null; + } + } + + /** + * Try to find a column from the final schema of the current block. + * + * @param block The current query block + * @param columnRef The column reference to be found + * @return The found column + */ + static Column resolveAliasedName(LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) throws VerifyException { + List candidates = Lists.newArrayList(); + + if (block.getSchema() != null) { + Column found = block.getSchema().getColumn(columnRef.getName()); + if (found != null) { + candidates.add(found); + } + } + + if (!candidates.isEmpty()) { + return NameResolver.ensureUniqueColumn(candidates); + } else { + return null; + } + } + + /** + * It returns a pair of names, which the first value is ${database}.${table} and the second value + * is a simple column name. + * + * @param block The current block + * @param columnRef The column name + * @return A pair of normalized qualifier and column name + * @throws PlanningException + */ + static Pair normalizeQualifierAndCanonicalName(LogicalPlan.QueryBlock block, + ColumnReferenceExpr columnRef) + throws PlanningException { + String qualifier; + String canonicalName; + + if (CatalogUtil.isFQTableName(columnRef.getQualifier())) { + qualifier = columnRef.getQualifier(); + canonicalName = columnRef.getCanonicalName(); + } else { + String resolvedDatabaseName = resolveDatabase(block, columnRef.getQualifier()); + if (resolvedDatabaseName == null) { + throw new NoSuchColumnException(columnRef.getQualifier()); + } + qualifier = CatalogUtil.buildFQName(resolvedDatabaseName, columnRef.getQualifier()); + canonicalName = CatalogUtil.buildFQName(qualifier, columnRef.getName()); + } + + return new Pair(qualifier, canonicalName); + } + + static Column ensureUniqueColumn(List candidates) throws VerifyException { + if (candidates.size() == 1) { + return candidates.get(0); + } else if (candidates.size() > 2) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Column column : candidates) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(column); + } + throw new AmbiguousFieldException("Ambiguous Column Name: " + sb.toString()); + } else { + return null; + } + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/NameResolvingMode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/NameResolvingMode.java new file mode 100644 index 0000000000..dcbb5f1b96 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/NameResolvingMode.java @@ -0,0 +1,80 @@ +/** + * 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.nameresolver; + +/** + * + *

Motivation

+ * + * Please take a look at the following example query: + * + *
+ *   select (l_orderkey + l_orderkey) l_orderkey from lineitem where l_orderkey > 2 order by l_orderkey;
+ * 
+ * + * Although l_orderkey seems to be ambiguous, the above usages are available in commercial DBMSs. + * In order to eliminate the ambiguity, Tajo follows the behaviors of PostgreSQL. + * + *

Resolving Modes

+ * + * From the behaviors of PostgreSQL, we found that there are three kinds of name resolving modes. + * Each definition is as follows: + * + *
    + *
  • RELS_ONLY finds a column from the relations in the current block. + *
  • RELS_AND_SUBEXPRS finds a column from the all relations in the current block and + * from aliased temporal fields; a temporal field means an explicitly aliased expression. If there are duplicated + * columns in the relation and temporal fields, this level firstly chooses the field in a relation.
  • + *
  • SUBEXPRS_AND_RELS is very similar to RELS_AND_SUBEXPRS. The main difference is that it + * firstly chooses an aliased temporal field instead of the fields in a relation.
  • + *
+ * + *

The relationship between resolving modes and operators

+ * + *
    + *
  • fields in select list and LIMIT are resolved in the REL_ONLY mode.
  • + *
  • fields in WHERE clause are resolved in the RELS_AND_SUBEXPRS mode.
  • + *
  • fields in GROUP BY, HAVING, and ORDER BY are resolved in the SUBEXPRS_AND_RELS mode.
  • + *
+ * + *

Example

+ * + * Please revisit the aforementioned example: + * + *
+ *   select (l_orderkey + l_orderkey) l_orderkey from lineitem where l_orderkey > 2 order by l_orderkey;
+ * 
+ * + * With the above rules and the relationship between modes and operators, we can easily identify which reference + * points to which field. + *
    + *
  1. l_orderkey included in (l_orderkey + l_orderkey) points to the field + * in the relation lineitem.
  2. + *
  3. l_orderkey included in WHERE clause also points to the field in the relation + * lineitem.
  4. + *
  5. l_orderkey included in ORDER BY clause points to the temporal field + * (l_orderkey + l_orderkey).
  6. + *
+ */ +public enum NameResolvingMode { + RELS_ONLY, // finding from only relations + RELS_AND_SUBEXPRS, // finding from relations and subexprs in a place + SUBEXPRS_AND_RELS, // finding from subexprs and relations in a place + LEGACY // Finding in a legacy manner (globally) +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByLegacy.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByLegacy.java new file mode 100644 index 0000000000..396bc1b184 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByLegacy.java @@ -0,0 +1,126 @@ +/* + * 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.nameresolver; + +import org.apache.tajo.algebra.ColumnReferenceExpr; +import org.apache.tajo.catalog.CatalogUtil; +import org.apache.tajo.catalog.Column; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.engine.exception.NoSuchColumnException; +import org.apache.tajo.engine.planner.LogicalPlan; +import org.apache.tajo.engine.planner.PlanningException; +import org.apache.tajo.engine.planner.logical.LogicalNode; +import org.apache.tajo.engine.planner.logical.NodeType; +import org.apache.tajo.engine.planner.logical.RelationNode; +import org.apache.tajo.util.Pair; +import org.apache.tajo.util.TUtil; + +import java.util.List; + +public class ResolverByLegacy extends NameResolver { + @Override + public Column resolve(LogicalPlan plan, LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) + throws PlanningException { + + if (columnRef.hasQualifier()) { + return resolveColumnWithQualifier(plan, block, columnRef); + } else { + return resolveColumnWithoutQualifier(plan, block, columnRef); + } + } + + private static Column resolveColumnWithQualifier(LogicalPlan plan, LogicalPlan.QueryBlock block, + ColumnReferenceExpr columnRef) throws PlanningException { + final String qualifier; + String canonicalName; + final String qualifiedName; + + Pair normalized = normalizeQualifierAndCanonicalName(block, columnRef); + qualifier = normalized.getFirst(); + canonicalName = normalized.getSecond(); + qualifiedName = CatalogUtil.buildFQName(qualifier, columnRef.getName()); + + Column found = resolveFromRelsWithinBlock(plan, block, columnRef); + if (found == null) { + throw new NoSuchColumnException(columnRef.getCanonicalName()); + } + + // If code reach here, a column is found. + // But, it may be aliased from bottom logical node. + // If the column is aliased, the found name may not be used in upper node. + + // Here, we try to check if column reference is already aliased. + // If so, it replaces the name with aliased name. + LogicalNode currentNode = block.getCurrentNode(); + + // The condition (currentNode.getInSchema().contains(column)) means + // the column can be used at the current node. So, we don't need to find aliase name. + Schema currentNodeSchema = null; + if (currentNode != null) { + if (currentNode instanceof RelationNode) { + currentNodeSchema = ((RelationNode) currentNode).getTableSchema(); + } else { + currentNodeSchema = currentNode.getInSchema(); + } + } + + if (currentNode != null && !currentNodeSchema.contains(found) + && currentNode.getType() != NodeType.TABLE_SUBQUERY) { + List candidates = TUtil.newList(); + if (block.getNamedExprsManager().isAliased(qualifiedName)) { + String alias = block.getNamedExprsManager().getAlias(canonicalName); + found = resolve(plan, block, new ColumnReferenceExpr(alias), NameResolvingMode.LEGACY); + if (found != null) { + candidates.add(found); + } + } + if (!candidates.isEmpty()) { + return ensureUniqueColumn(candidates); + } + } + + return found; + } + + static Column resolveColumnWithoutQualifier(LogicalPlan plan, LogicalPlan.QueryBlock block, + ColumnReferenceExpr columnRef)throws PlanningException { + + Column found = resolveFromAllRelsInBlock(block, columnRef); + if (found != null) { + return found; + } + + found = resolveAliasedName(block, columnRef); + if (found != null) { + return found; + } + + found = resolveFromCurrentAndChildNode(block, columnRef); + if (found != null) { + return found; + } + + found = resolveFromAllRelsInAllBlocks(plan, columnRef); + if (found != null) { + return found; + } + + throw new NoSuchColumnException("ERROR: no such a column name "+ columnRef.getCanonicalName()); + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByRels.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByRels.java new file mode 100644 index 0000000000..9713e5251a --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByRels.java @@ -0,0 +1,38 @@ +/* + * 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.nameresolver; + +import org.apache.tajo.algebra.ColumnReferenceExpr; +import org.apache.tajo.catalog.Column; +import org.apache.tajo.engine.exception.NoSuchColumnException; +import org.apache.tajo.engine.planner.LogicalPlan; +import org.apache.tajo.engine.planner.PlanningException; + +public class ResolverByRels extends NameResolver { + @Override + public Column resolve(LogicalPlan plan, LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) + throws PlanningException { + + Column column = resolveFromRelsWithinBlock(plan, block, columnRef); + if (column == null) { + throw new NoSuchColumnException(columnRef.getCanonicalName()); + } + return column; + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByRelsAndSubExprs.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByRelsAndSubExprs.java new file mode 100644 index 0000000000..7ca3c535d3 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverByRelsAndSubExprs.java @@ -0,0 +1,42 @@ +/* + * 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.nameresolver; + +import org.apache.tajo.algebra.ColumnReferenceExpr; +import org.apache.tajo.catalog.Column; +import org.apache.tajo.engine.exception.NoSuchColumnException; +import org.apache.tajo.engine.planner.LogicalPlan; +import org.apache.tajo.engine.planner.PlanningException; + +public class ResolverByRelsAndSubExprs extends NameResolver { + @Override + public Column resolve(LogicalPlan plan, LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) + throws PlanningException { + + Column column = resolveFromRelsWithinBlock(plan, block, columnRef); + if (column == null) { + column = resolveFromCurrentAndChildNode(block, columnRef); + } + + if (column == null) { + throw new NoSuchColumnException(columnRef.getCanonicalName()); + } + return column; + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverBySubExprsAndRels.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverBySubExprsAndRels.java new file mode 100644 index 0000000000..7337ecee5e --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/nameresolver/ResolverBySubExprsAndRels.java @@ -0,0 +1,42 @@ +/* + * 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.nameresolver; + +import org.apache.tajo.algebra.ColumnReferenceExpr; +import org.apache.tajo.catalog.Column; +import org.apache.tajo.engine.exception.NoSuchColumnException; +import org.apache.tajo.engine.planner.LogicalPlan; +import org.apache.tajo.engine.planner.PlanningException; + +public class ResolverBySubExprsAndRels extends NameResolver { + @Override + public Column resolve(LogicalPlan plan, LogicalPlan.QueryBlock block, ColumnReferenceExpr columnRef) + throws PlanningException { + + Column column = resolveFromCurrentAndChildNode(block, columnRef); + if (column == null) { + column = resolveFromRelsWithinBlock(plan, block, columnRef); + } + + if (column == null) { + throw new NoSuchColumnException(columnRef.getCanonicalName()); + } + return column; + } +} diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestEvalTreeUtil.java b/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestEvalTreeUtil.java index 92770ec09f..7bb619d815 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestEvalTreeUtil.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestEvalTreeUtil.java @@ -39,6 +39,7 @@ import org.apache.tajo.engine.planner.Target; import org.apache.tajo.engine.planner.logical.GroupbyNode; import org.apache.tajo.engine.planner.logical.NodeType; +import org.apache.tajo.engine.planner.nameresolver.NameResolvingMode; import org.apache.tajo.exception.InternalException; import org.apache.tajo.master.TajoMaster; import org.apache.tajo.master.session.Session; @@ -154,7 +155,8 @@ public static EvalNode getRootSelection(String query) throws PlanningException { } Selection selection = plan.getRootBlock().getSingletonExpr(OpType.Filter); - return planner.getExprAnnotator().createEvalNode(plan, plan.getRootBlock(), selection.getQual()); + return planner.getExprAnnotator().createEvalNode(plan, plan.getRootBlock(), selection.getQual(), + NameResolvingMode.RELS_AND_SUBEXPRS); } @Test diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCaseByCases.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCaseByCases.java index 73df4e18a3..45a484262b 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCaseByCases.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCaseByCases.java @@ -18,6 +18,7 @@ package org.apache.tajo.engine.query; +import com.google.gson.annotations.Expose; import org.apache.tajo.QueryTestCaseBase; import org.apache.tajo.TajoConstants; import org.junit.Test; @@ -133,4 +134,11 @@ public final void testTAJO880_3() throws Exception { assertEquals(expected, resultSetToString(res)); cleanupQuery(res); } + + @Test + public final void testTAJO917Case1() throws Exception { + ResultSet res = executeQuery(); + assertResultSet(res); + cleanupQuery(res); + } } diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestSelectQuery.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestSelectQuery.java index 639c3efd99..8898067fe4 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestSelectQuery.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestSelectQuery.java @@ -48,6 +48,14 @@ public TestSelectQuery() { super(TajoConstants.DEFAULT_DATABASE_NAME); } + @Test + public final void testNonQualifiedNames() throws Exception { + // select l_orderkey, l_partkey from lineitem; + ResultSet res = executeQuery(); + assertResultSet(res); + cleanupQuery(res); + } + @Test public final void testNonFromSelect1() throws Exception { // select upper('abc'); @@ -112,6 +120,25 @@ public final void testSelectColumnAlias1() throws Exception { cleanupQuery(res); } + @Test + public final void testSelectColumnAliasExistingInRelation1() throws Exception { + // We intend that 'l_orderkey' in where clause points to "default.lineitem.l_orderkey" + // select (l_orderkey + l_orderkey) l_orderkey from lineitem where l_orderkey > 2; + ResultSet res = executeQuery(); + assertResultSet(res); + cleanupQuery(res); + } + + @Test + public final void testSelectColumnAliasExistingInRelation2() throws Exception { + // We intend that 'l_orderkey' in orderby clause points to (-l_orderkey). + // select (-l_orderkey) as l_orderkey from lineitem order by l_orderkey; + ResultSet res = executeQuery(); + assertResultSet(res); + cleanupQuery(res); + } + + @Test public final void testSelectSameConstantsWithDifferentAliases() throws Exception { // select l_orderkey, '20130819' as date1, '20130819' as date2 from lineitem where l_orderkey > -1; diff --git a/tajo-core/src/test/resources/queries/TestCaseByCases/testTAJO917Case1.sql b/tajo-core/src/test/resources/queries/TestCaseByCases/testTAJO917Case1.sql new file mode 100644 index 0000000000..5b3039ca07 --- /dev/null +++ b/tajo-core/src/test/resources/queries/TestCaseByCases/testTAJO917Case1.sql @@ -0,0 +1,13 @@ +select + temp.r_regionkey as r_regionkey +from + ( + select + region.r_regionkey as r_regionkey + from + region + ) temp +join + region b +on + temp.r_regionkey = b.r_regionkey; \ No newline at end of file diff --git a/tajo-core/src/test/resources/queries/TestSelectQuery/testNonQualifiedNames.sql b/tajo-core/src/test/resources/queries/TestSelectQuery/testNonQualifiedNames.sql new file mode 100644 index 0000000000..0c176b72ad --- /dev/null +++ b/tajo-core/src/test/resources/queries/TestSelectQuery/testNonQualifiedNames.sql @@ -0,0 +1 @@ +select l_orderkey, l_partkey from lineitem; \ No newline at end of file diff --git a/tajo-core/src/test/resources/queries/TestSelectQuery/testSelectColumnAliasExistingInRelation1.sql b/tajo-core/src/test/resources/queries/TestSelectQuery/testSelectColumnAliasExistingInRelation1.sql new file mode 100644 index 0000000000..91170e3468 --- /dev/null +++ b/tajo-core/src/test/resources/queries/TestSelectQuery/testSelectColumnAliasExistingInRelation1.sql @@ -0,0 +1 @@ +select (l_orderkey + l_orderkey) l_orderkey from lineitem where l_orderkey > 2; \ No newline at end of file diff --git a/tajo-core/src/test/resources/queries/TestSelectQuery/testSelectColumnAliasExistingInRelation2.sql b/tajo-core/src/test/resources/queries/TestSelectQuery/testSelectColumnAliasExistingInRelation2.sql new file mode 100644 index 0000000000..89f63fd772 --- /dev/null +++ b/tajo-core/src/test/resources/queries/TestSelectQuery/testSelectColumnAliasExistingInRelation2.sql @@ -0,0 +1 @@ +select (-l_orderkey) as l_orderkey from lineitem order by l_orderkey; \ No newline at end of file diff --git a/tajo-core/src/test/resources/results/TestCaseByCases/testTAJO917Case1.result b/tajo-core/src/test/resources/results/TestCaseByCases/testTAJO917Case1.result new file mode 100644 index 0000000000..5dbe646243 --- /dev/null +++ b/tajo-core/src/test/resources/results/TestCaseByCases/testTAJO917Case1.result @@ -0,0 +1,7 @@ +r_regionkey +------------------------------- +0 +1 +2 +3 +4 \ No newline at end of file diff --git a/tajo-core/src/test/resources/results/TestSelectQuery/testNonQualifiedNames.result b/tajo-core/src/test/resources/results/TestSelectQuery/testNonQualifiedNames.result new file mode 100644 index 0000000000..13785365c7 --- /dev/null +++ b/tajo-core/src/test/resources/results/TestSelectQuery/testNonQualifiedNames.result @@ -0,0 +1,7 @@ +l_orderkey,l_partkey +------------------------------- +1,1 +1,1 +2,2 +3,2 +3,3 \ No newline at end of file diff --git a/tajo-core/src/test/resources/results/TestSelectQuery/testSelectColumnAliasExistingInRelation1.result b/tajo-core/src/test/resources/results/TestSelectQuery/testSelectColumnAliasExistingInRelation1.result new file mode 100644 index 0000000000..55e2b42174 --- /dev/null +++ b/tajo-core/src/test/resources/results/TestSelectQuery/testSelectColumnAliasExistingInRelation1.result @@ -0,0 +1,4 @@ +l_orderkey +------------------------------- +6 +6 \ No newline at end of file diff --git a/tajo-core/src/test/resources/results/TestSelectQuery/testSelectColumnAliasExistingInRelation2.result b/tajo-core/src/test/resources/results/TestSelectQuery/testSelectColumnAliasExistingInRelation2.result new file mode 100644 index 0000000000..f0cf700625 --- /dev/null +++ b/tajo-core/src/test/resources/results/TestSelectQuery/testSelectColumnAliasExistingInRelation2.result @@ -0,0 +1,7 @@ +l_orderkey +------------------------------- +-3 +-3 +-2 +-1 +-1 \ No newline at end of file