You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tajo.apache.org by ji...@apache.org on 2014/10/26 06:46:05 UTC
[06/33] TAJO-1125: Separate logical plan and optimizer into a maven
module.
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java
new file mode 100644
index 0000000..a916d5c
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java
@@ -0,0 +1,304 @@
+/**
+ * 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.plan.joinorder;
+
+import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.plan.LogicalPlan;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.PlanningException;
+import org.apache.tajo.plan.util.SchemaUtil;
+import org.apache.tajo.plan.expr.AlgebraicUtil;
+import org.apache.tajo.plan.logical.*;
+import org.apache.tajo.util.TUtil;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * This is a greedy heuristic algorithm to find a bushy join tree. This algorithm finds
+ * the best join order with join conditions and pushed-down join conditions to
+ * all join operators.
+ */
+public class GreedyHeuristicJoinOrderAlgorithm implements JoinOrderAlgorithm {
+ public static double DEFAULT_SELECTION_FACTOR = 0.1;
+
+ @Override
+ public FoundJoinOrder findBestOrder(LogicalPlan plan, LogicalPlan.QueryBlock block, JoinGraph joinGraph,
+ Set<String> relationsWithoutQual) throws PlanningException {
+
+ // Setup a remain relation set to be joined
+ // Why we should use LinkedHashSet? - it should keep the deterministic for the order of joins.
+ // Otherwise, join orders can be different even if join costs are the same to each other.
+ Set<LogicalNode> remainRelations = new LinkedHashSet<LogicalNode>();
+ for (RelationNode relation : block.getRelations()) {
+ remainRelations.add(relation);
+ }
+
+ LogicalNode latestJoin;
+ JoinEdge bestPair;
+
+ while (remainRelations.size() > 1) {
+ // Find the best join pair among all joinable operators in candidate set.
+ bestPair = getBestPair(plan, joinGraph, remainRelations);
+
+ remainRelations.remove(bestPair.getLeftRelation()); // remainRels = remainRels \ Ti
+ remainRelations.remove(bestPair.getRightRelation()); // remainRels = remainRels \ Tj
+
+ latestJoin = createJoinNode(plan, bestPair);
+ remainRelations.add(latestJoin);
+
+ // all logical nodes should be registered to corresponding blocks
+ block.registerNode(latestJoin);
+ }
+
+ JoinNode joinTree = (JoinNode) remainRelations.iterator().next();
+ // all generated nodes should be registered to corresponding blocks
+ block.registerNode(joinTree);
+ return new FoundJoinOrder(joinTree, getCost(joinTree));
+ }
+
+ private static JoinNode createJoinNode(LogicalPlan plan, JoinEdge joinEdge) {
+ LogicalNode left = joinEdge.getLeftRelation();
+ LogicalNode right = joinEdge.getRightRelation();
+
+ JoinNode joinNode = plan.createNode(JoinNode.class);
+
+ if (PlannerUtil.isCommutativeJoin(joinEdge.getJoinType())) {
+ // if only one operator is relation
+ if ((left instanceof RelationNode) && !(right instanceof RelationNode)) {
+ // for left deep
+ joinNode.init(joinEdge.getJoinType(), right, left);
+ } else {
+ // if both operators are relation or if both are relations
+ // we don't need to concern the left-right position.
+ joinNode.init(joinEdge.getJoinType(), left, right);
+ }
+ } else {
+ joinNode.init(joinEdge.getJoinType(), left, right);
+ }
+
+ Schema mergedSchema = SchemaUtil.merge(joinNode.getLeftChild().getOutSchema(),
+ joinNode.getRightChild().getOutSchema());
+ joinNode.setInSchema(mergedSchema);
+ joinNode.setOutSchema(mergedSchema);
+ if (joinEdge.hasJoinQual()) {
+ joinNode.setJoinQual(AlgebraicUtil.createSingletonExprFromCNF(joinEdge.getJoinQual()));
+ }
+ return joinNode;
+ }
+
+ /**
+ * Find the best join pair among all joinable operators in candidate set.
+ *
+ * @param plan a logical plan
+ * @param graph a join graph which consists of vertices and edges, where vertex is relation and
+ * each edge is join condition.
+ * @param candidateSet candidate operators to be joined.
+ * @return The best join pair among them
+ * @throws PlanningException
+ */
+ private JoinEdge getBestPair(LogicalPlan plan, JoinGraph graph, Set<LogicalNode> candidateSet)
+ throws PlanningException {
+ double minCost = Double.MAX_VALUE;
+ JoinEdge bestJoin = null;
+
+ double minNonCrossJoinCost = Double.MAX_VALUE;
+ JoinEdge bestNonCrossJoin = null;
+
+ for (LogicalNode outer : candidateSet) {
+ for (LogicalNode inner : candidateSet) {
+ if (outer.equals(inner)) {
+ continue;
+ }
+
+ JoinEdge foundJoin = findJoin(plan, graph, outer, inner);
+ if (foundJoin == null) {
+ continue;
+ }
+ double cost = getCost(foundJoin);
+
+ if (cost < minCost) {
+ minCost = cost;
+ bestJoin = foundJoin;
+ }
+
+ // Keep the min cost join
+ // But, if there exists a qualified join, the qualified join must be chosen
+ // rather than cross join regardless of cost.
+ if (foundJoin.hasJoinQual()) {
+ if (cost < minNonCrossJoinCost) {
+ minNonCrossJoinCost = cost;
+ bestNonCrossJoin = foundJoin;
+ }
+ }
+ }
+ }
+
+ if (bestNonCrossJoin != null) {
+ return bestNonCrossJoin;
+ } else {
+ return bestJoin;
+ }
+ }
+
+ /**
+ * Find a join between two logical operator trees
+ *
+ * @return If there is no join condition between two relation, it returns NULL value.
+ */
+ private static JoinEdge findJoin(LogicalPlan plan, JoinGraph graph, LogicalNode outer, LogicalNode inner)
+ throws PlanningException {
+ JoinEdge foundJoinEdge = null;
+
+ // If outer is outer join, make edge key using all relation names in outer.
+ SortedSet<String> relationNames =
+ new TreeSet<String>(PlannerUtil.getRelationLineageWithinQueryBlock(plan, outer));
+ String outerEdgeKey = TUtil.collectionToString(relationNames, ", ");
+ for (String innerName : PlannerUtil.getRelationLineageWithinQueryBlock(plan, inner)) {
+ if (graph.hasEdge(outerEdgeKey, innerName)) {
+ JoinEdge existJoinEdge = graph.getEdge(outerEdgeKey, innerName);
+ if (foundJoinEdge == null) {
+ foundJoinEdge = new JoinEdge(existJoinEdge.getJoinType(), outer, inner,
+ existJoinEdge.getJoinQual());
+ } else {
+ foundJoinEdge.addJoinQual(AlgebraicUtil.createSingletonExprFromCNF(
+ existJoinEdge.getJoinQual()));
+ }
+ }
+ }
+ if (foundJoinEdge != null) {
+ return foundJoinEdge;
+ }
+
+ relationNames =
+ new TreeSet<String>(PlannerUtil.getRelationLineageWithinQueryBlock(plan, inner));
+ outerEdgeKey = TUtil.collectionToString(relationNames, ", ");
+ for (String outerName : PlannerUtil.getRelationLineageWithinQueryBlock(plan, outer)) {
+ if (graph.hasEdge(outerEdgeKey, outerName)) {
+ JoinEdge existJoinEdge = graph.getEdge(outerEdgeKey, outerName);
+ if (foundJoinEdge == null) {
+ foundJoinEdge = new JoinEdge(existJoinEdge.getJoinType(), inner, outer,
+ existJoinEdge.getJoinQual());
+ } else {
+ foundJoinEdge.addJoinQual(AlgebraicUtil.createSingletonExprFromCNF(
+ existJoinEdge.getJoinQual()));
+ }
+ }
+ }
+ if (foundJoinEdge != null) {
+ return foundJoinEdge;
+ }
+
+ for (String outerName : PlannerUtil.getRelationLineageWithinQueryBlock(plan, outer)) {
+ for (String innerName : PlannerUtil.getRelationLineageWithinQueryBlock(plan, inner)) {
+
+ // Find all joins between two relations and merge them into one join if possible
+ if (graph.hasEdge(outerName, innerName)) {
+ JoinEdge existJoinEdge = graph.getEdge(outerName, innerName);
+ if (foundJoinEdge == null) {
+ foundJoinEdge = new JoinEdge(existJoinEdge.getJoinType(), outer, inner,
+ existJoinEdge.getJoinQual());
+ } else {
+ foundJoinEdge.addJoinQual(AlgebraicUtil.createSingletonExprFromCNF(
+ existJoinEdge.getJoinQual()));
+ }
+ }
+ }
+ }
+
+ if (foundJoinEdge == null) {
+ foundJoinEdge = new JoinEdge(JoinType.CROSS, outer, inner);
+ }
+
+ return foundJoinEdge;
+ }
+
+ /**
+ * Getting a cost of one join
+ * @param joinEdge
+ * @return
+ */
+ public static double getCost(JoinEdge joinEdge) {
+ double filterFactor = 1;
+ if (joinEdge.hasJoinQual()) {
+ // TODO - should consider join type
+ // TODO - should statistic information obtained from query history
+ filterFactor = filterFactor * Math.pow(DEFAULT_SELECTION_FACTOR, joinEdge.getJoinQual().length);
+ return getCost(joinEdge.getLeftRelation()) * getCost(joinEdge.getRightRelation()) * filterFactor;
+ } else {
+ // make cost bigger if cross join
+ return Math.pow(getCost(joinEdge.getLeftRelation()) * getCost(joinEdge.getRightRelation()), 2);
+ }
+ }
+
+ // TODO - costs of other operator operators (e.g., group-by and sort) should be computed in proper manners.
+ public static double getCost(LogicalNode node) {
+ switch (node.getType()) {
+
+ case PROJECTION:
+ ProjectionNode projectionNode = (ProjectionNode) node;
+ return getCost(projectionNode.getChild());
+
+ case JOIN:
+ JoinNode joinNode = (JoinNode) node;
+ double filterFactor = 1;
+ if (joinNode.hasJoinQual()) {
+ filterFactor = Math.pow(DEFAULT_SELECTION_FACTOR,
+ AlgebraicUtil.toConjunctiveNormalFormArray(joinNode.getJoinQual()).length);
+ return getCost(joinNode.getLeftChild()) * getCost(joinNode.getRightChild()) * filterFactor;
+ } else {
+ return Math.pow(getCost(joinNode.getLeftChild()) * getCost(joinNode.getRightChild()), 2);
+ }
+
+ case SELECTION:
+ SelectionNode selectionNode = (SelectionNode) node;
+ return getCost(selectionNode.getChild()) *
+ Math.pow(DEFAULT_SELECTION_FACTOR, AlgebraicUtil.toConjunctiveNormalFormArray(selectionNode.getQual()).length);
+
+ case TABLE_SUBQUERY:
+ TableSubQueryNode subQueryNode = (TableSubQueryNode) node;
+ return getCost(subQueryNode.getSubQuery());
+
+ case SCAN:
+ ScanNode scanNode = (ScanNode) node;
+ if (scanNode.getTableDesc().getStats() != null) {
+ double cost = ((ScanNode)node).getTableDesc().getStats().getNumBytes();
+ return cost;
+ } else {
+ return Long.MAX_VALUE;
+ }
+
+ case UNION:
+ UnionNode unionNode = (UnionNode) node;
+ return getCost(unionNode.getLeftChild()) + getCost(unionNode.getRightChild());
+
+ case EXCEPT:
+ case INTERSECT:
+ throw new UnsupportedOperationException("getCost() does not support EXCEPT or INTERSECT yet");
+
+ default:
+ // all binary operators (join, union, except, and intersect) are handled in the above cases.
+ // So, we need to handle only unary nodes in default.
+ return getCost(((UnaryNode) node).getChild());
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinEdge.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinEdge.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinEdge.java
new file mode 100644
index 0000000..ce06748
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinEdge.java
@@ -0,0 +1,75 @@
+/**
+ * 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.plan.joinorder;
+
+import com.google.common.collect.Sets;
+import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.plan.expr.EvalNode;
+import org.apache.tajo.plan.logical.LogicalNode;
+import org.apache.tajo.util.TUtil;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class JoinEdge {
+ private final JoinType joinType;
+ private final LogicalNode leftRelation;
+ private final LogicalNode rightRelation;
+ private final Set<EvalNode> joinQual = Sets.newHashSet();
+
+ public JoinEdge(JoinType joinType, LogicalNode leftRelation, LogicalNode rightRelation) {
+ this.joinType = joinType;
+ this.leftRelation = leftRelation;
+ this.rightRelation = rightRelation;
+ }
+
+ public JoinEdge(JoinType joinType, LogicalNode leftRelation, LogicalNode rightRelation,
+ EvalNode ... condition) {
+ this(joinType, leftRelation, rightRelation);
+ Collections.addAll(joinQual, condition);
+ }
+
+ public JoinType getJoinType() {
+ return joinType;
+ }
+
+ public LogicalNode getLeftRelation() {
+ return leftRelation;
+ }
+
+ public LogicalNode getRightRelation() {
+ return rightRelation;
+ }
+
+ public boolean hasJoinQual() {
+ return joinQual.size() > 0;
+ }
+
+ public void addJoinQual(EvalNode joinQual) {
+ this.joinQual.add(joinQual);
+ }
+
+ public EvalNode [] getJoinQual() {
+ return joinQual.toArray(new EvalNode[joinQual.size()]);
+ }
+
+ public String toString() {
+ return leftRelation + " " + joinType + " " + rightRelation + " ON " + TUtil.collectionToString(joinQual, ", ");
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinGraph.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinGraph.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinGraph.java
new file mode 100644
index 0000000..f7fc30e
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinGraph.java
@@ -0,0 +1,154 @@
+/**
+ * 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.plan.joinorder;
+
+import com.google.common.collect.Sets;
+import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.catalog.CatalogUtil;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.util.graph.SimpleUndirectedGraph;
+import org.apache.tajo.plan.LogicalPlan;
+import org.apache.tajo.plan.NamedExprsManager;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.PlanningException;
+import org.apache.tajo.plan.expr.AlgebraicUtil;
+import org.apache.tajo.plan.expr.BinaryEval;
+import org.apache.tajo.plan.expr.EvalNode;
+import org.apache.tajo.plan.expr.EvalTreeUtil;
+import org.apache.tajo.plan.logical.JoinNode;
+import org.apache.tajo.plan.logical.RelationNode;
+import org.apache.tajo.util.TUtil;
+
+import java.util.*;
+
+public class JoinGraph extends SimpleUndirectedGraph<String, JoinEdge> {
+
+ private String [] guessRelationsFromJoinQual(LogicalPlan.QueryBlock block, BinaryEval joinCondition)
+ throws PlanningException {
+
+ // Note that we can guarantee that each join qual used here is a singleton.
+ // This is because we use dissect a join qual into conjunctive normal forms.
+ // In other words, each join qual has a form 'col1 = col2'.
+ Column leftExpr = EvalTreeUtil.findAllColumnRefs(joinCondition.getLeftExpr()).get(0);
+ Column rightExpr = EvalTreeUtil.findAllColumnRefs(joinCondition.getRightExpr()).get(0);
+
+ // 0 - left table, 1 - right table
+ String [] relationNames = new String[2];
+
+ NamedExprsManager namedExprsMgr = block.getNamedExprsManager();
+ if (leftExpr.hasQualifier()) {
+ relationNames[0] = leftExpr.getQualifier();
+ } else {
+ if (namedExprsMgr.isAliasedName(leftExpr.getSimpleName())) {
+ String columnName = namedExprsMgr.getOriginalName(leftExpr.getSimpleName());
+ String qualifier = CatalogUtil.extractQualifier(columnName);
+ relationNames[0] = qualifier;
+ } else {
+ // search for a relation which evaluates a right term included in a join condition
+ for (RelationNode rel : block.getRelations()) {
+ if (rel.getOutSchema().contains(leftExpr)) {
+ String qualifier = rel.getCanonicalName();
+ relationNames[0] = qualifier;
+ }
+ }
+
+ if (relationNames[0] == null) { // if not found
+ throw new PlanningException("Cannot expect a referenced relation: " + leftExpr);
+ }
+ }
+ }
+
+ if (rightExpr.hasQualifier()) {
+ relationNames[1] = rightExpr.getQualifier();
+ } else {
+ if (namedExprsMgr.isAliasedName(rightExpr.getSimpleName())) {
+ String columnName = namedExprsMgr.getOriginalName(rightExpr.getSimpleName());
+ String qualifier = CatalogUtil.extractQualifier(columnName);
+ relationNames[1] = qualifier;
+ } else {
+ // search for a relation which evaluates a right term included in a join condition
+ for (RelationNode rel : block.getRelations()) {
+ if (rel.getOutSchema().contains(rightExpr)) {
+ String qualifier = rel.getCanonicalName();
+ relationNames[1] = qualifier;
+ }
+ }
+
+ if (relationNames[1] == null) { // if not found
+ throw new PlanningException("Cannot expect a referenced relation: " + rightExpr);
+ }
+ }
+ }
+
+ return relationNames;
+ }
+
+ public Collection<EvalNode> addJoin(LogicalPlan plan, LogicalPlan.QueryBlock block,
+ JoinNode joinNode) throws PlanningException {
+ if (joinNode.getJoinType() == JoinType.LEFT_OUTER || joinNode.getJoinType() == JoinType.RIGHT_OUTER) {
+ JoinEdge edge = new JoinEdge(joinNode.getJoinType(),
+ joinNode.getLeftChild(), joinNode.getRightChild(), joinNode.getJoinQual());
+
+ SortedSet<String> leftNodeRelationName =
+ new TreeSet<String>(PlannerUtil.getRelationLineageWithinQueryBlock(plan, joinNode.getLeftChild()));
+ SortedSet<String> rightNodeRelationName =
+ new TreeSet<String>(PlannerUtil.getRelationLineageWithinQueryBlock(plan, joinNode.getRightChild()));
+
+ addEdge(
+ TUtil.collectionToString(leftNodeRelationName, ", "),
+ TUtil.collectionToString(rightNodeRelationName, ", "),
+ edge);
+
+ Set<EvalNode> allInOneCnf = new HashSet<EvalNode>();
+ allInOneCnf.add(joinNode.getJoinQual());
+
+ return allInOneCnf;
+ } else {
+ Set<EvalNode> cnf = Sets.newHashSet(AlgebraicUtil.toConjunctiveNormalFormArray(joinNode.getJoinQual()));
+
+ for (EvalNode singleQual : cnf) {
+ if (EvalTreeUtil.isJoinQual(block, singleQual, true)) {
+ String[] relations = guessRelationsFromJoinQual(block, (BinaryEval) singleQual);
+ String leftExprRelName = relations[0];
+ String rightExprRelName = relations[1];
+
+ Collection<String> leftLineage = PlannerUtil.getRelationLineageWithinQueryBlock(plan, joinNode.getLeftChild());
+
+ boolean isLeftExprForLeftTable = leftLineage.contains(leftExprRelName);
+
+ JoinEdge edge = getEdge(leftExprRelName, rightExprRelName);
+ if (edge != null) {
+ edge.addJoinQual(singleQual);
+ } else {
+ if (isLeftExprForLeftTable) {
+ edge = new JoinEdge(joinNode.getJoinType(),
+ block.getRelation(leftExprRelName), block.getRelation(rightExprRelName), singleQual);
+ addEdge(leftExprRelName, rightExprRelName, edge);
+ } else {
+ edge = new JoinEdge(joinNode.getJoinType(),
+ block.getRelation(rightExprRelName), block.getRelation(leftExprRelName), singleQual);
+ addEdge(rightExprRelName, leftExprRelName, edge);
+ }
+ }
+ }
+ }
+ return cnf;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinOrderAlgorithm.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinOrderAlgorithm.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinOrderAlgorithm.java
new file mode 100644
index 0000000..2dd0670
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/JoinOrderAlgorithm.java
@@ -0,0 +1,48 @@
+/**
+ * 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.plan.joinorder;
+
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.tajo.plan.LogicalPlan;
+import org.apache.tajo.plan.PlanningException;
+import org.apache.tajo.plan.joinorder.FoundJoinOrder;
+import org.apache.tajo.plan.joinorder.JoinGraph;
+
+import java.util.Set;
+
+/**
+ * An interface for join order algorithms
+ */
+@InterfaceStability.Evolving
+public interface JoinOrderAlgorithm {
+
+ /**
+ *
+ * @param plan
+ * @param block
+ * @param joinGraph A join graph represents join conditions and their connections among relations.
+ * Given a graph, each vertex represents a relation, and each edge contains a join condition.
+ * A join graph does not contain relations that do not have any corresponding join condition.
+ * @param relationsWithoutQual The names of relations that do not have any corresponding join condition.
+ * @return
+ * @throws org.apache.tajo.plan.PlanningException
+ */
+ FoundJoinOrder findBestOrder(LogicalPlan plan, LogicalPlan.QueryBlock block, JoinGraph joinGraph,
+ Set<String> relationsWithoutQual) throws PlanningException;
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTableNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTableNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTableNode.java
new file mode 100644
index 0000000..e9e2467
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTableNode.java
@@ -0,0 +1,134 @@
+/**
+ * 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.plan.logical;
+
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.algebra.AlterTableOpType;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.plan.PlanString;
+
+public class AlterTableNode extends LogicalNode {
+
+ @Expose
+ private String tableName;
+ @Expose
+ private String newTableName;
+ @Expose
+ private String columnName;
+ @Expose
+ private String newColumnName;
+ @Expose
+ private Column addNewColumn;
+ @Expose
+ private AlterTableOpType alterTableOpType;
+
+ public AlterTableNode(int pid) {
+ super(pid, NodeType.ALTER_TABLE);
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public String getNewTableName() {
+ return newTableName;
+ }
+
+ public void setNewTableName(String newTableName) {
+ this.newTableName = newTableName;
+ }
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public void setColumnName(String columnName) {
+ this.columnName = columnName;
+ }
+
+ public String getNewColumnName() {
+ return newColumnName;
+ }
+
+ public void setNewColumnName(String newColumnName) {
+ this.newColumnName = newColumnName;
+ }
+
+ public Column getAddNewColumn() {
+ return addNewColumn;
+ }
+
+ public void setAddNewColumn(Column addNewColumn) {
+ this.addNewColumn = addNewColumn;
+ }
+
+ public AlterTableOpType getAlterTableOpType() {
+ return alterTableOpType;
+ }
+
+ public void setAlterTableOpType(AlterTableOpType alterTableOpType) {
+ this.alterTableOpType = alterTableOpType;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AlterTableNode) {
+ AlterTableNode other = (AlterTableNode) obj;
+ return super.equals(other);
+ } else {
+ return false;
+ }
+ }
+
+ /*@Override
+ public Object clone() throws CloneNotSupportedException {
+ AlterTableNode alterTableNode = (AlterTableNode) super.clone();
+ alterTableNode.tableName = tableName;
+ alterTableNode.newTableName = newTableName;
+ alterTableNode.columnName = columnName;
+ alterTableNode.newColumnName=newColumnName;
+ alterTableNode.addNewColumn =(Column) addNewColumn.clone();
+ return alterTableNode;
+ }*/
+
+ @Override
+ public String toString() {
+ return "AlterTable (table=" + tableName + ")";
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTablespaceNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTablespaceNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTablespaceNode.java
new file mode 100644
index 0000000..7b79cc1
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/AlterTablespaceNode.java
@@ -0,0 +1,104 @@
+/**
+ * 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.plan.logical;
+
+
+import com.google.common.base.Objects;
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.algebra.AlterTablespaceSetType;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.plan.logical.LogicalNode;
+import org.apache.tajo.plan.logical.LogicalNodeVisitor;
+import org.apache.tajo.plan.logical.NodeType;
+
+public class AlterTablespaceNode extends LogicalNode implements Cloneable {
+
+ @Expose private String tablespaceName;
+ @Expose private AlterTablespaceSetType setType;
+ @Expose private String uri;
+
+
+ public AlterTablespaceNode(int pid) {
+ super(pid, NodeType.ALTER_TABLESPACE);
+ }
+
+ public String getTablespaceName() {
+ return tablespaceName;
+ }
+
+ public void setTablespaceName(String tablespaceName) {
+ this.tablespaceName = tablespaceName;
+ }
+
+ public AlterTablespaceSetType getSetType() {
+ return setType;
+ }
+
+ public String getLocation() {
+ return uri;
+ }
+
+ public void setLocation(String uri) {
+ this.setType = AlterTablespaceSetType.LOCATION;
+ this.uri = uri;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AlterTablespaceNode) {
+ AlterTablespaceNode other = (AlterTablespaceNode) obj;
+ return super.equals(other);
+ } else {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return Objects.hashCode(tablespaceName, setType, uri);
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ AlterTablespaceNode alter = (AlterTablespaceNode) super.clone();
+ alter.tablespaceName = tablespaceName;
+ alter.setType = setType;
+ alter.uri = uri;
+ return alter;
+ }
+
+ @Override
+ public String toString() {
+ return "AlterTablespace (space=" + tablespaceName + ")";
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/BinaryNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/BinaryNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/BinaryNode.java
new file mode 100644
index 0000000..709ef34
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/BinaryNode.java
@@ -0,0 +1,77 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.json.GsonObject;
+
+public abstract class BinaryNode extends LogicalNode implements Cloneable, GsonObject {
+ @Expose LogicalNode leftChild = null;
+ @Expose LogicalNode rightChild = null;
+
+ public BinaryNode(int pid, NodeType nodeType) {
+ super(pid, nodeType);
+ }
+
+ public <T extends LogicalNode> T getLeftChild() {
+ return (T) this.leftChild;
+ }
+
+ public void setLeftChild(LogicalNode op) {
+ this.leftChild = op;
+ }
+
+ public <T extends LogicalNode> T getRightChild() {
+ return (T) this.rightChild;
+ }
+
+ public void setRightChild(LogicalNode op) {
+ this.rightChild = op;
+ }
+
+ public boolean deepEquals(Object o) {
+ if (o instanceof BinaryNode) {
+ BinaryNode b = (BinaryNode) o;
+ return equals(o) &&
+ leftChild.deepEquals(b.leftChild) && rightChild.deepEquals(b.rightChild);
+ }
+ return false;
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ BinaryNode binNode = (BinaryNode) super.clone();
+ binNode.leftChild = (LogicalNode) leftChild.clone();
+ binNode.rightChild = (LogicalNode) rightChild.clone();
+
+ return binNode;
+ }
+
+ public void preOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ leftChild.postOrder(visitor);
+ rightChild.postOrder(visitor);
+ }
+
+ public void postOrder(LogicalNodeVisitor visitor) {
+ leftChild.postOrder(visitor);
+ rightChild.postOrder(visitor);
+ visitor.visit(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateDatabaseNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateDatabaseNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateDatabaseNode.java
new file mode 100644
index 0000000..e3f73fe
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateDatabaseNode.java
@@ -0,0 +1,87 @@
+/*
+ * 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.plan.logical;
+
+import com.google.common.base.Objects;
+import org.apache.tajo.catalog.CatalogUtil;
+import org.apache.tajo.plan.PlanString;
+
+public class CreateDatabaseNode extends LogicalNode implements Cloneable {
+ private String databaseName;
+ private boolean ifNotExists;
+
+ public CreateDatabaseNode(int pid) {
+ super(pid, NodeType.CREATE_DATABASE);
+ }
+
+ public void init(String databaseName, boolean ifNotExists) {
+ this.databaseName = databaseName;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public String getDatabaseName() {
+ return this.databaseName;
+ }
+
+ public boolean isIfNotExists() {
+ return ifNotExists;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this).appendTitle(ifNotExists ? " IF NOT EXISTS " : " ").appendTitle(databaseName);
+ }
+
+ public int hashCode() {
+ return Objects.hashCode(databaseName, ifNotExists);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof CreateDatabaseNode) {
+ CreateDatabaseNode other = (CreateDatabaseNode) obj;
+ return super.equals(other) && this.databaseName.equals(other.databaseName) && ifNotExists == other.ifNotExists;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ CreateDatabaseNode newNode = (CreateDatabaseNode) super.clone();
+ newNode.databaseName = databaseName;
+ newNode.ifNotExists = ifNotExists;
+ return newNode;
+ }
+
+ @Override
+ public String toString() {
+ return "CREATE DATABASE " + (ifNotExists ? " IF NOT EXISTS " : "")
+ + CatalogUtil.denormalizeIdentifier(databaseName);
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateTableNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateTableNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateTableNode.java
new file mode 100644
index 0000000..d03da6a
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateTableNode.java
@@ -0,0 +1,145 @@
+/**
+ * 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.plan.logical;
+
+import com.google.common.base.Objects;
+import com.google.gson.annotations.Expose;
+import org.apache.hadoop.fs.Path;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.util.KeyValueSet;
+import org.apache.tajo.util.TUtil;
+
+public class CreateTableNode extends StoreTableNode implements Cloneable {
+ @Expose private Schema schema;
+ @Expose private Path path;
+ @Expose private boolean external;
+ @Expose private boolean ifNotExists;
+
+ public CreateTableNode(int pid) {
+ super(pid, NodeType.CREATE_TABLE);
+ }
+
+ public void setTableSchema(Schema schema) {
+ this.schema = schema;
+ }
+
+ public Schema getTableSchema() {
+ return this.schema;
+ }
+
+ public Schema getLogicalSchema() {
+ if (hasPartition()) {
+ Schema logicalSchema = new Schema(schema);
+ logicalSchema.addColumns(getPartitionMethod().getExpressionSchema());
+ return logicalSchema;
+ } else {
+ return schema;
+ }
+ }
+
+ public boolean hasPath() {
+ return this.path != null;
+ }
+
+ public void setPath(Path path) {
+ this.path = path;
+ }
+
+ public Path getPath() {
+ return this.path;
+ }
+
+ public boolean isExternal() {
+ return external;
+ }
+
+ public void setExternal(boolean external) {
+ this.external = external;
+ }
+
+ public boolean hasSubQuery() {
+ return child != null;
+ }
+
+ public void setIfNotExists(boolean ifNotExists) {
+ this.ifNotExists = ifNotExists;
+ }
+
+ public boolean isIfNotExists() {
+ return ifNotExists;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this);
+ }
+
+ public int hashCode() {
+ return super.hashCode() ^ Objects.hashCode(schema, path, external, ifNotExists) * 31;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof CreateTableNode) {
+ CreateTableNode other = (CreateTableNode) obj;
+ return super.equals(other)
+ && this.schema.equals(other.schema)
+ && this.external == other.external
+ && TUtil.checkEquals(path, other.path)
+ && ifNotExists == other.ifNotExists;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ CreateTableNode createTableNode = (CreateTableNode) super.clone();
+ createTableNode.tableName = tableName;
+ createTableNode.schema = (Schema) schema.clone();
+ createTableNode.storageType = storageType;
+ createTableNode.external = external;
+ createTableNode.path = path != null ? new Path(path.toString()) : null;
+ createTableNode.options = (KeyValueSet) (options != null ? options.clone() : null);
+ createTableNode.ifNotExists = ifNotExists;
+ return createTableNode;
+ }
+
+ public String toString() {
+ return "CreateTable (table=" + tableName + ", external=" + external + ", storeType=" + storageType +
+ ", ifNotExists=" + ifNotExists +")";
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ if (hasSubQuery()) {
+ child.preOrder(visitor);
+ }
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ if (hasSubQuery()) {
+ child.preOrder(visitor);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DistinctGroupbyNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DistinctGroupbyNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DistinctGroupbyNode.java
new file mode 100644
index 0000000..e31e488
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DistinctGroupbyNode.java
@@ -0,0 +1,251 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.Target;
+import org.apache.tajo.plan.expr.AggregationFunctionCallEval;
+import org.apache.tajo.util.TUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DistinctGroupbyNode extends UnaryNode implements Projectable, Cloneable {
+ @Expose
+ private GroupbyNode groupbyPlan;
+
+ @Expose
+ private List<GroupbyNode> groupByNodes;
+
+ @Expose
+ private Target[] targets;
+
+ @Expose
+ private Column[] groupingColumns;
+
+ @Expose
+ private int[] resultColumnIds;
+
+ /** Aggregation Functions */
+ @Expose private AggregationFunctionCallEval[] aggrFunctions;
+
+ public DistinctGroupbyNode(int pid) {
+ super(pid, NodeType.DISTINCT_GROUP_BY);
+ }
+
+ @Override
+ public boolean hasTargets() {
+ return targets != null && targets.length > 0;
+ }
+
+ @Override
+ public void setTargets(Target[] targets) {
+ this.targets = targets;
+ setOutSchema(PlannerUtil.targetToSchema(targets));
+ }
+
+ @Override
+ public Target[] getTargets() {
+ if (hasTargets()) {
+ return targets;
+ } else {
+ return new Target[0];
+ }
+ }
+
+ public void setGroupbyNodes(List<GroupbyNode> groupByNodes) {
+ this.groupByNodes = groupByNodes;
+ }
+
+ public List<GroupbyNode> getGroupByNodes() {
+ return groupByNodes;
+ }
+
+ public final Column[] getGroupingColumns() {
+ return groupingColumns;
+ }
+
+ public final void setGroupColumns(Column[] groupingColumns) {
+ this.groupingColumns = groupingColumns;
+ }
+
+ public int[] getResultColumnIds() {
+ return resultColumnIds;
+ }
+
+ public void setResultColumnIds(int[] resultColumnIds) {
+ this.resultColumnIds = resultColumnIds;
+ }
+
+ public AggregationFunctionCallEval [] getAggFunctions() {
+ return this.aggrFunctions;
+ }
+
+ public void setAggFunctions(AggregationFunctionCallEval[] evals) {
+ this.aggrFunctions = evals;
+ }
+
+ public void setGroupbyPlan(GroupbyNode groupbyPlan) { this.groupbyPlan = groupbyPlan; }
+
+ public GroupbyNode getGroupbyPlan() { return this.groupbyPlan; }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ DistinctGroupbyNode cloneNode = (DistinctGroupbyNode)super.clone();
+
+ if (groupingColumns != null) {
+ cloneNode.groupingColumns = new Column[groupingColumns.length];
+ for (int i = 0; i < groupingColumns.length; i++) {
+ cloneNode.groupingColumns[i] = groupingColumns[i];
+ }
+ }
+
+ if (groupByNodes != null) {
+ cloneNode.groupByNodes = new ArrayList<GroupbyNode>();
+ for (GroupbyNode eachNode: groupByNodes) {
+ GroupbyNode groupbyNode = (GroupbyNode)eachNode.clone();
+ groupbyNode.setPID(-1);
+ cloneNode.groupByNodes.add(groupbyNode);
+ }
+ }
+
+ if (targets != null) {
+ cloneNode.targets = new Target[targets.length];
+ for (int i = 0; i < targets.length; i++) {
+ cloneNode.targets[i] = (Target) targets[i].clone();
+ }
+ }
+
+ if (groupbyPlan != null) {
+ cloneNode.groupbyPlan = (GroupbyNode)groupbyPlan.clone();
+ }
+ return cloneNode;
+ }
+
+ public final boolean isEmptyGrouping() {
+ return groupingColumns == null || groupingColumns.length == 0;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Distinct GroupBy (");
+ if (groupingColumns != null || groupingColumns.length > 0) {
+ sb.append("grouping set=").append(TUtil.arrayToString(groupingColumns));
+ sb.append(", ");
+ }
+ for (GroupbyNode eachNode: groupByNodes) {
+ sb.append(", groupbyNode=").append(eachNode.toString());
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DistinctGroupbyNode) {
+ DistinctGroupbyNode other = (DistinctGroupbyNode) obj;
+ boolean eq = super.equals(other);
+ eq = eq && TUtil.checkEquals(groupingColumns, other.groupingColumns);
+ eq = eq && TUtil.checkEquals(groupByNodes, other.groupByNodes);
+ eq = eq && TUtil.checkEquals(targets, other.targets);
+ return eq;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ PlanString planStr = new PlanString(this);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ Column [] groupingColumns = this.groupingColumns;
+ for (int j = 0; j < groupingColumns.length; j++) {
+ sb.append(groupingColumns[j].getSimpleName());
+ if(j < groupingColumns.length - 1) {
+ sb.append(",");
+ }
+ }
+
+ sb.append(")");
+
+ planStr.appendTitle(sb.toString());
+
+ sb = new StringBuilder();
+ sb.append("(");
+
+ String prefix = "";
+ for (GroupbyNode eachNode: groupByNodes) {
+ if (eachNode.hasAggFunctions()) {
+ AggregationFunctionCallEval[] aggrFunctions = eachNode.getAggFunctions();
+ for (int j = 0; j < aggrFunctions.length; j++) {
+ sb.append(prefix).append(aggrFunctions[j]);
+ prefix = ",";
+ }
+ }
+ }
+ sb.append(")");
+ planStr.appendExplain("exprs: ").appendExplain(sb.toString());
+
+ sb = new StringBuilder("target list: ");
+ for (int i = 0; i < targets.length; i++) {
+ sb.append(targets[i]);
+ if( i < targets.length - 1) {
+ sb.append(", ");
+ }
+ }
+ planStr.addExplan(sb.toString());
+
+ planStr.addDetail("out schema:").appendDetail(getOutSchema().toString());
+ planStr.addDetail("in schema:").appendDetail(getInSchema().toString());
+
+ for (GroupbyNode eachNode: groupByNodes) {
+ planStr.addDetail("\t").appendDetail("distinct: " + eachNode.isDistinct())
+ .appendDetail(", " + eachNode.getShortPlanString());
+ }
+
+ return planStr;
+ }
+
+ public Column[] getFirstStageShuffleKeyColumns() {
+ List<Column> shuffleKeyColumns = new ArrayList<Column>();
+ shuffleKeyColumns.add(getOutSchema().getColumn(0)); //distinctseq column
+ if (groupingColumns != null) {
+ for (Column eachColumn: groupingColumns) {
+ if (!shuffleKeyColumns.contains(eachColumn)) {
+ shuffleKeyColumns.add(eachColumn);
+ }
+ }
+ }
+ for (GroupbyNode eachGroupbyNode: groupByNodes) {
+ if (eachGroupbyNode.getGroupingColumns() != null && eachGroupbyNode.getGroupingColumns().length > 0) {
+ for (Column eachColumn: eachGroupbyNode.getGroupingColumns()) {
+ if (!shuffleKeyColumns.contains(eachColumn)) {
+ shuffleKeyColumns.add(eachColumn);
+ }
+ }
+ }
+ }
+
+ return shuffleKeyColumns.toArray(new Column[]{});
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropDatabaseNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropDatabaseNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropDatabaseNode.java
new file mode 100644
index 0000000..b88c384
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropDatabaseNode.java
@@ -0,0 +1,85 @@
+/*
+ * 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.plan.logical;
+
+import com.google.common.base.Objects;
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.plan.PlanString;
+
+public class DropDatabaseNode extends LogicalNode implements Cloneable {
+ @Expose private String databaseName;
+ @Expose private boolean ifExists;
+
+ public DropDatabaseNode(int pid) {
+ super(pid, NodeType.DROP_DATABASE);
+ }
+
+ public void init(String databaseName, boolean ifExists) {
+ this.databaseName = databaseName;
+ this.ifExists = ifExists;
+ }
+
+ public String getDatabaseName() {
+ return this.databaseName;
+ }
+
+ public boolean isIfExists() {
+ return ifExists;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this).appendTitle(ifExists ? " IF EXISTS " : " ").appendTitle(databaseName);
+ }
+
+ public int hashCode() {
+ return Objects.hashCode(databaseName, ifExists);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof DropDatabaseNode) {
+ DropDatabaseNode other = (DropDatabaseNode) obj;
+ return super.equals(other) && this.databaseName.equals(other.databaseName) && ifExists == other.ifExists;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ DropDatabaseNode dropTableNode = (DropDatabaseNode) super.clone();
+ dropTableNode.databaseName = databaseName;
+ return dropTableNode;
+ }
+
+ @Override
+ public String toString() {
+ return "DROP DATABASE " + (ifExists ? "IF EXISTS ":"") + databaseName;
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropTableNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropTableNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropTableNode.java
new file mode 100644
index 0000000..1a61852
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropTableNode.java
@@ -0,0 +1,95 @@
+/*
+ * 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.plan.logical;
+
+import com.google.common.base.Objects;
+import org.apache.tajo.plan.PlanString;
+
+public class DropTableNode extends LogicalNode implements Cloneable {
+ private String tableName;
+ private boolean ifExists;
+ private boolean purge;
+
+ public DropTableNode(int pid) {
+ super(pid, NodeType.DROP_TABLE);
+ }
+
+ public void init(String tableName, boolean ifExists, boolean purge) {
+ this.tableName = tableName;
+ this.ifExists = ifExists;
+ this.purge = purge;
+ }
+
+ public String getTableName() {
+ return this.tableName;
+ }
+
+ public boolean isIfExists() {
+ return this.ifExists;
+ }
+
+ public boolean isPurge() {
+ return this.purge;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this).appendTitle(ifExists ? " IF EXISTS" : "").appendTitle(purge ? " PURGE" : "");
+ }
+
+ public int hashCode() {
+ return Objects.hashCode(tableName, ifExists, purge);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof DropTableNode) {
+ DropTableNode other = (DropTableNode) obj;
+ return super.equals(other) &&
+ this.tableName.equals(other.tableName) &&
+ this.ifExists == other.ifExists &&
+ this.purge == other.purge;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ DropTableNode dropTableNode = (DropTableNode) super.clone();
+ dropTableNode.tableName = tableName;
+ dropTableNode.ifExists = ifExists;
+ dropTableNode.purge = purge;
+ return dropTableNode;
+ }
+
+ @Override
+ public String toString() {
+ return "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + tableName + (purge ? " PURGE" : "");
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/EvalExprNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/EvalExprNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/EvalExprNode.java
new file mode 100644
index 0000000..2519165
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/EvalExprNode.java
@@ -0,0 +1,85 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.Target;
+import org.apache.tajo.util.TUtil;
+
+public class EvalExprNode extends LogicalNode implements Projectable {
+ @Expose private Target[] exprs;
+
+ public EvalExprNode(int pid) {
+ super(pid, NodeType.EXPRS);
+ }
+
+ @Override
+ public boolean hasTargets() {
+ return true;
+ }
+
+ @Override
+ public void setTargets(Target[] targets) {
+ this.exprs = targets;
+ setOutSchema(PlannerUtil.targetToSchema(targets));
+ }
+
+ @Override
+ public Target[] getTargets() {
+ return exprs;
+ }
+
+ public Target[] getExprs() {
+ return this.exprs;
+ }
+
+ @Override
+ public String toString() {
+ return "EvalExprNode (" + TUtil.arrayToString(exprs) + ")";
+ }
+
+ public boolean equals(Object object) {
+ if (object instanceof EvalExprNode) {
+ EvalExprNode other = (EvalExprNode) object;
+ return TUtil.checkEquals(this.exprs, other.exprs);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ // nothing
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ // nothing
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/ExceptNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/ExceptNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/ExceptNode.java
new file mode 100644
index 0000000..221b62c
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/ExceptNode.java
@@ -0,0 +1,45 @@
+/**
+ * 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.plan.logical;
+
+import org.apache.tajo.plan.PlanString;
+
+public class ExceptNode extends BinaryNode {
+
+ public ExceptNode(int pid) {
+ super(pid, NodeType.EXCEPT);
+ }
+
+ public void init(LogicalNode left, LogicalNode right) {
+ setLeftChild(left);
+ setRightChild(right);
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ PlanString planStr = new PlanString(this);
+ planStr.appendTitle(" (L - " + ((TableSubQueryNode)getLeftChild()).getTableName());
+ planStr.appendTitle(", R - " + ((TableSubQueryNode)getRightChild()).getTableName());
+ planStr.appendTitle(")");
+ return planStr;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupElement.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupElement.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupElement.java
new file mode 100644
index 0000000..a969c30
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupElement.java
@@ -0,0 +1,64 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.algebra.Aggregation;
+import org.apache.tajo.plan.expr.EvalNode;
+
+public class GroupElement implements Cloneable {
+ @Expose private Aggregation.GroupType type;
+ @Expose private EvalNode [] groupingSets;
+
+ @SuppressWarnings("unused")
+ public GroupElement() {
+ // for gson
+ }
+
+ public GroupElement(Aggregation.GroupType type, EvalNode [] groupingSets) {
+ this.type = type;
+ this.groupingSets = groupingSets;
+ }
+
+ public Aggregation.GroupType getType() {
+ return this.type;
+ }
+
+ public EvalNode[] getGroupingSets() {
+ return this.groupingSets;
+ }
+
+ public String toString() {
+ Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
+ .setPrettyPrinting().create();
+ return gson.toJson(this);
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ GroupElement groups = (GroupElement) super.clone();
+ groups.type = type;
+ groups.groupingSets = new EvalNode [groupingSets.length];
+ for (int i = 0; i < groupingSets.length; i++) {
+ groups.groupingSets[i++] = (EvalNode) groupingSets[i].clone();
+ }
+ return groups;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupbyNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupbyNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupbyNode.java
new file mode 100644
index 0000000..2c74ce3
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/GroupbyNode.java
@@ -0,0 +1,254 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.Target;
+import org.apache.tajo.plan.expr.AggregationFunctionCallEval;
+import org.apache.tajo.util.TUtil;
+
+public class GroupbyNode extends UnaryNode implements Projectable, Cloneable {
+ /** Grouping key sets */
+ @Expose private Column [] groupingColumns;
+ /** Aggregation Functions */
+ @Expose private AggregationFunctionCallEval [] aggrFunctions;
+ /**
+ * It's a list of targets. The grouping columns should be followed by aggregation functions.
+ * aggrFunctions keep actual aggregation functions, but it only contains field references.
+ * */
+ @Expose private Target [] targets;
+ @Expose private boolean hasDistinct = false;
+
+ public GroupbyNode(int pid) {
+ super(pid, NodeType.GROUP_BY);
+ }
+
+ public final boolean isEmptyGrouping() {
+ return groupingColumns == null || groupingColumns.length == 0;
+ }
+
+ public void setGroupingColumns(Column [] groupingColumns) {
+ this.groupingColumns = groupingColumns;
+ }
+
+ public final Column [] getGroupingColumns() {
+ return this.groupingColumns;
+ }
+
+ public final boolean isDistinct() {
+ return hasDistinct;
+ }
+
+ public void setDistinct(boolean distinct) {
+ hasDistinct = distinct;
+ }
+
+ public boolean hasAggFunctions() {
+ return this.aggrFunctions != null;
+ }
+
+ public AggregationFunctionCallEval[] getAggFunctions() {
+ return this.aggrFunctions;
+ }
+
+ public void setAggFunctions(AggregationFunctionCallEval[] evals) {
+ this.aggrFunctions = evals;
+ }
+
+ @Override
+ public boolean hasTargets() {
+ return this.targets != null;
+ }
+
+ @Override
+ public void setTargets(Target[] targets) {
+ this.targets = targets;
+ setOutSchema(PlannerUtil.targetToSchema(targets));
+ }
+
+ @Override
+ public Target[] getTargets() {
+ return this.targets;
+ }
+
+ public void setChild(LogicalNode subNode) {
+ super.setChild(subNode);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder("GroupBy (");
+ if (groupingColumns != null || groupingColumns.length > 0) {
+ sb.append("grouping set=").append(TUtil.arrayToString(groupingColumns));
+ sb.append(", ");
+ }
+ if (hasAggFunctions()) {
+ sb.append("funcs=").append(TUtil.arrayToString(aggrFunctions));
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof GroupbyNode) {
+ GroupbyNode other = (GroupbyNode) obj;
+ boolean eq = super.equals(other);
+ eq = eq && TUtil.checkEquals(groupingColumns, other.groupingColumns);
+ eq = eq && TUtil.checkEquals(aggrFunctions, other.aggrFunctions);
+ eq = eq && TUtil.checkEquals(targets, other.targets);
+ return eq;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ GroupbyNode grp = (GroupbyNode) super.clone();
+ if (groupingColumns != null) {
+ grp.groupingColumns = new Column[groupingColumns.length];
+ for (int i = 0; i < groupingColumns.length; i++) {
+ grp.groupingColumns[i] = groupingColumns[i];
+ }
+ }
+
+ if (aggrFunctions != null) {
+ grp.aggrFunctions = new AggregationFunctionCallEval[aggrFunctions.length];
+ for (int i = 0; i < aggrFunctions.length; i++) {
+ grp.aggrFunctions[i] = (AggregationFunctionCallEval) aggrFunctions[i].clone();
+ }
+ }
+
+ if (targets != null) {
+ grp.targets = new Target[targets.length];
+ for (int i = 0; i < targets.length; i++) {
+ grp.targets[i] = (Target) targets[i].clone();
+ }
+ }
+
+ return grp;
+ }
+
+ public String getShortPlanString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getType().name() + "(" + getPID() + ")").append("(");
+ Column [] groupingColumns = this.groupingColumns;
+ for (int j = 0; j < groupingColumns.length; j++) {
+ sb.append(groupingColumns[j].getSimpleName());
+ if(j < groupingColumns.length - 1) {
+ sb.append(",");
+ }
+ }
+
+ sb.append(")");
+
+ // there can be no aggregation functions
+ if (hasAggFunctions()) {
+ sb.append(", exprs: (");
+
+ for (int j = 0; j < aggrFunctions.length; j++) {
+ sb.append(aggrFunctions[j]);
+ if(j < aggrFunctions.length - 1) {
+ sb.append(",");
+ }
+ }
+ sb.append(")");
+ }
+
+ if (targets != null) {
+ sb.append(", target list:{");
+ for (int i = 0; i < targets.length; i++) {
+ sb.append(targets[i]);
+ if (i < targets.length - 1) {
+ sb.append(", ");
+ }
+ }
+ sb.append("}");
+ }
+ sb.append(", out schema:").append(getOutSchema().toString());
+ sb.append(", in schema:").append(getInSchema().toString());
+
+ return sb.toString();
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ PlanString planStr = new PlanString(this);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ Column [] groupingColumns = this.groupingColumns;
+ for (int j = 0; j < groupingColumns.length; j++) {
+ sb.append(groupingColumns[j].getSimpleName());
+ if(j < groupingColumns.length - 1) {
+ sb.append(",");
+ }
+ }
+
+ sb.append(")");
+
+ planStr.appendTitle(sb.toString());
+
+ // there can be no aggregation functions
+ if (hasAggFunctions()) {
+ sb = new StringBuilder();
+ sb.append("(");
+
+ for (int j = 0; j < aggrFunctions.length; j++) {
+ sb.append(aggrFunctions[j]);
+ if(j < aggrFunctions.length - 1) {
+ sb.append(",");
+ }
+ }
+ sb.append(")");
+ planStr.appendExplain("exprs: ").appendExplain(sb.toString());
+ }
+
+ sb = new StringBuilder("target list: ");
+ for (int i = 0; i < targets.length; i++) {
+ sb.append(targets[i]);
+ if( i < targets.length - 1) {
+ sb.append(", ");
+ }
+ }
+ planStr.addExplan(sb.toString());
+
+ planStr.addDetail("out schema:").appendDetail(getOutSchema().toString());
+ planStr.addDetail("in schema:").appendDetail(getInSchema().toString());
+
+ return planStr;
+ }
+
+ /**
+ * It checks if an alias name included in the target of this node is for aggregation function.
+ * If so, it returns TRUE. Otherwise, it returns FALSE.
+ */
+ public boolean isAggregationColumn(String simpleName) {
+ for (int i = groupingColumns.length; i < targets.length; i++) {
+ if (simpleName.equals(targets[i].getNamedColumn().getSimpleName()) ||
+ simpleName.equals(targets[i].getAlias())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/HavingNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/HavingNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/HavingNode.java
new file mode 100644
index 0000000..a3371c7
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/HavingNode.java
@@ -0,0 +1,72 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.plan.expr.EvalNode;
+
+public class HavingNode extends UnaryNode implements SelectableNode, Cloneable {
+ @Expose private EvalNode qual;
+
+ public HavingNode(int pid) {
+ super(pid, NodeType.HAVING);
+ }
+
+ @Override
+ public boolean hasQual() {
+ return true;
+ }
+
+ public void setQual(EvalNode qual) {
+ this.qual = qual;
+ }
+
+ public EvalNode getQual() {
+ return this.qual;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof HavingNode) {
+ HavingNode other = (HavingNode) obj;
+ return super.equals(other)
+ && this.qual.equals(other.qual);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ HavingNode selNode = (HavingNode) super.clone();
+ selNode.qual = (EvalNode) this.qual.clone();
+
+ return selNode;
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ return new PlanString(this).appendTitle(" (").appendTitle(qual.toString()).appendTitle(")");
+ }
+
+ public String toString() {
+ return "Having (filter=" + qual + ")";
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java
new file mode 100644
index 0000000..0d59733
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java
@@ -0,0 +1,122 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.datum.Datum;
+import org.apache.tajo.plan.serder.PlanGsonHelper;
+
+public class IndexScanNode extends ScanNode {
+ @Expose private SortSpec [] sortKeys;
+ @Expose private Schema keySchema = null;
+ @Expose private Datum[] datum = null;
+
+ public IndexScanNode(int pid, ScanNode scanNode ,
+ Schema keySchema , Datum[] datum, SortSpec[] sortKeys ) {
+ super(pid);
+ init(scanNode.getTableDesc());
+ setQual(scanNode.getQual());
+ setInSchema(scanNode.getInSchema());
+ setTargets(scanNode.getTargets());
+ setType(NodeType.BST_INDEX_SCAN);
+ this.sortKeys = sortKeys;
+ this.keySchema = keySchema;
+ this.datum = datum;
+ }
+
+ public SortSpec[] getSortKeys() {
+ return this.sortKeys;
+ }
+
+ public Schema getKeySchema() {
+ return this.keySchema;
+ }
+
+ public Datum[] getDatum() {
+ return this.datum;
+ }
+
+ public void setSortKeys(SortSpec[] sortKeys) {
+ this.sortKeys = sortKeys;
+ }
+
+ public void setKeySchema( Schema keySchema ) {
+ this.keySchema = keySchema;
+ }
+
+ @Override
+ public String toString() {
+ Gson gson = PlanGsonHelper.getInstance();
+ StringBuilder builder = new StringBuilder();
+ builder.append("IndexScanNode : {\n");
+ builder.append(" \"keySchema\" : \"" + gson.toJson(this.keySchema) + "\"\n");
+ builder.append(" \"sortKeys\" : \"" + gson.toJson(this.sortKeys) + " \"\n");
+ builder.append(" \"datums\" : \"" + gson.toJson(this.datum) + "\"\n");
+ builder.append(" <<\"superClass\" : " + super.toString());
+ builder.append(">>}");
+ builder.append("}");
+ return builder.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof IndexScanNode) {
+ IndexScanNode other = (IndexScanNode) obj;
+
+ boolean eq = super.equals(other);
+ eq = eq && this.sortKeys.length == other.sortKeys.length;
+ if(eq) {
+ for(int i = 0 ; i < this.sortKeys.length ; i ++) {
+ eq = eq && this.sortKeys[i].getSortKey().equals(
+ other.sortKeys[i].getSortKey());
+ eq = eq && this.sortKeys[i].isAscending()
+ == other.sortKeys[i].isAscending();
+ eq = eq && this.sortKeys[i].isNullFirst()
+ == other.sortKeys[i].isNullFirst();
+ }
+ }
+ if(eq) {
+ for(int i = 0 ; i < this.datum.length ; i ++ ) {
+ eq = eq && this.datum[i].equals(other.datum[i]);
+ }
+ }
+ return eq;
+ }
+ return false;
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ IndexScanNode indexNode = (IndexScanNode) super.clone();
+ indexNode.keySchema = (Schema) this.keySchema.clone();
+ indexNode.sortKeys = new SortSpec[this.sortKeys.length];
+ for(int i = 0 ; i < sortKeys.length ; i ++ )
+ indexNode.sortKeys[i] = (SortSpec) this.sortKeys[i].clone();
+ indexNode.datum = new Datum[this.datum.length];
+ for(int i = 0 ; i < datum.length ; i ++ ) {
+ indexNode.datum[i] = this.datum[i];
+ }
+ return indexNode;
+ }
+}
+
+
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/InsertNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/InsertNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/InsertNode.java
new file mode 100644
index 0000000..f860f08
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/InsertNode.java
@@ -0,0 +1,182 @@
+/**
+ * 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.plan.logical;
+
+import com.google.gson.annotations.Expose;
+import org.apache.hadoop.fs.Path;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.TableDesc;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.util.TUtil;
+
+public class InsertNode extends StoreTableNode implements Cloneable {
+ @Expose private boolean overwrite;
+ @Expose private Schema tableSchema;
+
+ /** a target schema of a target table */
+ @Expose private Schema targetSchema;
+ /** a output schema of select clause */
+ @Expose private Schema projectedSchema;
+ @Expose private Path path;
+
+ public InsertNode(int pid) {
+ super(pid, NodeType.INSERT);
+ }
+
+ public void setTargetTable(TableDesc desc) {
+ setTableName(desc.getName());
+ if (desc.hasPartition()) {
+ tableSchema = desc.getLogicalSchema();
+ } else {
+ tableSchema = desc.getSchema();
+ }
+ setPath(desc.getPath());
+ setOptions(desc.getMeta().getOptions());
+ setStorageType(desc.getMeta().getStoreType());
+
+ if (desc.hasPartition()) {
+ this.setPartitionMethod(desc.getPartitionMethod());
+ }
+ }
+
+ public void setTargetLocation(Path path) {
+ this.path = path;
+ }
+
+ public void setSubQuery(LogicalNode subQuery) {
+ this.setChild(subQuery);
+ this.setInSchema(subQuery.getOutSchema());
+ this.setOutSchema(subQuery.getOutSchema());
+ }
+
+ public boolean isOverwrite() {
+ return overwrite;
+ }
+
+ public void setOverwrite(boolean overwrite) {
+ this.overwrite = overwrite;
+ }
+
+ public Schema getTableSchema() {
+ return tableSchema;
+ }
+
+ public void setTableSchema(Schema tableSchema) {
+ this.tableSchema = tableSchema;
+ }
+
+ public boolean hasTargetSchema() {
+ return this.targetSchema != null;
+ }
+
+ public Schema getTargetSchema() {
+ return this.targetSchema;
+ }
+
+ public void setTargetSchema(Schema schema) {
+ this.targetSchema = schema;
+ }
+
+ public Schema getProjectedSchema() {
+ return this.projectedSchema;
+ }
+
+ public void setProjectedSchema(Schema projected) {
+ this.projectedSchema = projected;
+ }
+
+ public boolean hasPath() {
+ return this.path != null;
+ }
+
+ public void setPath(Path path) {
+ this.path = path;
+ }
+
+ public Path getPath() {
+ return this.path;
+ }
+
+ public boolean hasStorageType() {
+ return this.storageType != null;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof InsertNode) {
+ InsertNode other = (InsertNode) obj;
+ return super.equals(other)
+ && this.overwrite == other.overwrite
+ && TUtil.checkEquals(this.tableSchema, other.tableSchema)
+ && TUtil.checkEquals(this.targetSchema, other.targetSchema)
+ && TUtil.checkEquals(path, other.path);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ InsertNode insertNode = (InsertNode) super.clone();
+ insertNode.overwrite = overwrite;
+ insertNode.tableSchema = new Schema(tableSchema);
+ insertNode.targetSchema = targetSchema != null ? new Schema(targetSchema) : null;
+ insertNode.path = path != null ? new Path(path.toString()) : null;
+ return insertNode;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Insert (overwrite=").append(overwrite);
+ if (hasTargetTable()) {
+ sb.append(",table=").append(tableName);
+ }
+ if (hasPath()) {
+ sb.append(", location=").append(path);
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public void preOrder(LogicalNodeVisitor visitor) {
+ getChild().preOrder(visitor);
+ visitor.visit(this);
+ }
+
+ @Override
+ public void postOrder(LogicalNodeVisitor visitor) {
+ visitor.visit(this);
+ getChild().postOrder(visitor);
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ PlanString planString = new PlanString(this);
+ planString.appendTitle(" INTO ");
+ if (hasTargetTable()) {
+ planString.appendTitle(getTableName());
+ if (hasTargetSchema()) {
+ planString.addExplan(getTargetSchema().toString());
+ }
+ } else {
+ planString.addExplan("LOCATION " + path);
+ }
+ return planString;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tajo/blob/b143f991/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IntersectNode.java
----------------------------------------------------------------------
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IntersectNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IntersectNode.java
new file mode 100644
index 0000000..3f1bf0d
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IntersectNode.java
@@ -0,0 +1,44 @@
+/**
+ * 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.plan.logical;
+
+import org.apache.tajo.plan.PlanString;
+
+public class IntersectNode extends BinaryNode {
+ public IntersectNode(int pid) {
+ super(pid, NodeType.INTERSECT);
+ }
+
+ public void init(LogicalNode left, LogicalNode right) {
+ setLeftChild(left);
+ setRightChild(right);
+ }
+
+ @Override
+ public PlanString getPlanString() {
+ PlanString planStr = new PlanString(this);
+ planStr.appendTitle(" (L - " + ((TableSubQueryNode)getLeftChild()).getTableName());
+ planStr.appendTitle(", R - " + ((TableSubQueryNode)getRightChild()).getTableName());
+ planStr.appendTitle(")");
+ return planStr;
+ }
+}