You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tajo.apache.org by hy...@apache.org on 2014/04/18 13:44:38 UTC
[35/57] [abbrv] [partial] TAJO-752: Escalate sub modules in tajo-core
into the top-level modules. (hyunsik)
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java
new file mode 100644
index 0000000..bf2bf7d
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java
@@ -0,0 +1,1187 @@
+/**
+ * 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.global;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.fs.Path;
+import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.catalog.*;
+import org.apache.tajo.catalog.partition.PartitionMethodDesc;
+import org.apache.tajo.catalog.proto.CatalogProtos;
+import org.apache.tajo.common.TajoDataTypes;
+import org.apache.tajo.conf.TajoConf;
+import org.apache.tajo.engine.eval.AggregationFunctionCallEval;
+import org.apache.tajo.engine.eval.EvalNode;
+import org.apache.tajo.engine.eval.EvalTreeUtil;
+import org.apache.tajo.engine.eval.FieldEval;
+import org.apache.tajo.engine.function.AggFunction;
+import org.apache.tajo.engine.planner.*;
+import org.apache.tajo.engine.planner.logical.*;
+import org.apache.tajo.engine.planner.rewrite.ProjectionPushDownRule;
+import org.apache.tajo.exception.InternalException;
+import org.apache.tajo.util.TUtil;
+import org.apache.tajo.worker.TajoWorker;
+
+import java.io.IOException;
+import java.util.*;
+
+import static org.apache.tajo.conf.TajoConf.ConfVars;
+import static org.apache.tajo.ipc.TajoWorkerProtocol.ShuffleType.*;
+
+/**
+ * Build DAG
+ */
+public class GlobalPlanner {
+ private static Log LOG = LogFactory.getLog(GlobalPlanner.class);
+
+ private final TajoConf conf;
+ private final CatalogProtos.StoreType storeType;
+ private CatalogService catalog;
+ private TajoWorker.WorkerContext workerContext;
+
+ public GlobalPlanner(final TajoConf conf, final CatalogService catalog) throws IOException {
+ this.conf = conf;
+ this.catalog = catalog;
+ this.storeType = CatalogProtos.StoreType.valueOf(conf.getVar(ConfVars.SHUFFLE_FILE_FORMAT).toUpperCase());
+ Preconditions.checkArgument(storeType != null);
+ }
+
+ public GlobalPlanner(final TajoConf conf, final TajoWorker.WorkerContext workerContext) throws IOException {
+ this.conf = conf;
+ this.workerContext = workerContext;
+ this.storeType = CatalogProtos.StoreType.valueOf(conf.getVar(ConfVars.SHUFFLE_FILE_FORMAT).toUpperCase());
+ Preconditions.checkArgument(storeType != null);
+ }
+
+ /**
+ * TODO: this is hack. it must be refactored at TAJO-602.
+ */
+ public CatalogService getCatalog() {
+ if (workerContext.getCatalog() != null) {
+ return workerContext.getCatalog();
+ } else if (catalog != null) {
+ return catalog;
+ } else {
+ throw new IllegalStateException("No Catalog Instance");
+ }
+ }
+
+ public class GlobalPlanContext {
+ MasterPlan plan;
+ Map<Integer, ExecutionBlock> execBlockMap = Maps.newHashMap();
+ }
+
+ /**
+ * Builds a master plan from the given logical plan.
+ */
+ public void build(MasterPlan masterPlan) throws IOException, PlanningException {
+
+ DistributedPlannerVisitor planner = new DistributedPlannerVisitor();
+ GlobalPlanContext globalPlanContext = new GlobalPlanContext();
+ globalPlanContext.plan = masterPlan;
+
+ LOG.info(masterPlan.getLogicalPlan());
+
+ // copy a logical plan in order to keep the original logical plan. The distributed planner can modify
+ // an input logical plan.
+ LogicalNode inputPlan = PlannerUtil.clone(masterPlan.getLogicalPlan(),
+ masterPlan.getLogicalPlan().getRootBlock().getRoot());
+
+ boolean autoBroadcast = conf.getBoolVar(TajoConf.ConfVars.DIST_QUERY_BROADCAST_JOIN_AUTO);
+ if (autoBroadcast) {
+ // pre-visit finding broadcast join target table
+ // this visiting doesn't make any execution block and change plan
+ BroadcastJoinPlanVisitor broadcastJoinPlanVisitor = new BroadcastJoinPlanVisitor();
+ broadcastJoinPlanVisitor.visit(globalPlanContext,
+ masterPlan.getLogicalPlan(), masterPlan.getLogicalPlan().getRootBlock(), inputPlan, new Stack<LogicalNode>());
+ }
+
+ // create a distributed execution plan by visiting each logical node.
+ // Its output is a graph, where each vertex is an execution block, and each edge is a data channel.
+ // MasterPlan contains them.
+ LogicalNode lastNode = planner.visit(globalPlanContext,
+ masterPlan.getLogicalPlan(), masterPlan.getLogicalPlan().getRootBlock(), inputPlan, new Stack<LogicalNode>());
+ ExecutionBlock childExecBlock = globalPlanContext.execBlockMap.get(lastNode.getPID());
+
+ ExecutionBlock terminalBlock;
+ // TODO - consider two terminal types: specified output or not
+ if (childExecBlock.getPlan() != null) {
+ terminalBlock = masterPlan.createTerminalBlock();
+ DataChannel finalChannel = new DataChannel(childExecBlock.getId(), terminalBlock.getId());
+ setFinalOutputChannel(finalChannel, lastNode.getOutSchema());
+ masterPlan.addConnect(finalChannel);
+ } else { // if one or more unions is terminal
+ terminalBlock = childExecBlock;
+ for (DataChannel outputChannel : masterPlan.getIncomingChannels(terminalBlock.getId())) {
+ setFinalOutputChannel(outputChannel, lastNode.getOutSchema());
+ }
+ }
+
+ masterPlan.setTerminal(terminalBlock);
+ LOG.info(masterPlan.toString());
+ }
+
+ private static void setFinalOutputChannel(DataChannel outputChannel, Schema outputSchema) {
+ outputChannel.setShuffleType(NONE_SHUFFLE);
+ outputChannel.setShuffleOutputNum(1);
+ outputChannel.setStoreType(CatalogProtos.StoreType.CSV);
+ outputChannel.setSchema(outputSchema);
+ }
+
+ public static ScanNode buildInputExecutor(LogicalPlan plan, DataChannel channel) {
+ Preconditions.checkArgument(channel.getSchema() != null,
+ "Channel schema (" + channel.getSrcId().getId() + " -> " + channel.getTargetId().getId() +
+ ") is not initialized");
+ TableMeta meta = new TableMeta(channel.getStoreType(), new Options());
+ TableDesc desc = new TableDesc(channel.getSrcId().toString(), channel.getSchema(), meta, new Path("/"));
+ ScanNode scanNode = plan.createNode(ScanNode.class);
+ scanNode.init(desc);
+ return scanNode;
+ }
+
+ private DataChannel createDataChannelFromJoin(ExecutionBlock leftBlock, ExecutionBlock rightBlock,
+ ExecutionBlock parent, JoinNode join, boolean leftTable) {
+ ExecutionBlock childBlock = leftTable ? leftBlock : rightBlock;
+
+ DataChannel channel = new DataChannel(childBlock, parent, HASH_SHUFFLE, 32);
+ channel.setStoreType(storeType);
+ if (join.getJoinType() != JoinType.CROSS) {
+ Column [][] joinColumns = PlannerUtil.joinJoinKeyForEachTable(join.getJoinQual(),
+ leftBlock.getPlan().getOutSchema(), rightBlock.getPlan().getOutSchema());
+ if (leftTable) {
+ channel.setShuffleKeys(joinColumns[0]);
+ } else {
+ channel.setShuffleKeys(joinColumns[1]);
+ }
+ }
+ return channel;
+ }
+
+ /**
+ * It calculates the total volume of all descendent relation nodes.
+ */
+ public static long computeDescendentVolume(LogicalNode node) throws PlanningException {
+
+ if (node instanceof RelationNode) {
+ switch (node.getType()) {
+ case SCAN:
+ case PARTITIONS_SCAN:
+ ScanNode scanNode = (ScanNode) node;
+ if (scanNode.getTableDesc().getStats() == null) {
+ // TODO - this case means that data is not located in HDFS. So, we need additional
+ // broadcast method.
+ return Long.MAX_VALUE;
+ } else {
+ return scanNode.getTableDesc().getStats().getNumBytes();
+ }
+ case TABLE_SUBQUERY:
+ return computeDescendentVolume(((TableSubQueryNode) node).getSubQuery());
+ default:
+ throw new IllegalArgumentException("Not RelationNode");
+ }
+ } else if (node instanceof UnaryNode) {
+ return computeDescendentVolume(((UnaryNode) node).getChild());
+ } else if (node instanceof BinaryNode) {
+ BinaryNode binaryNode = (BinaryNode) node;
+ return computeDescendentVolume(binaryNode.getLeftChild()) + computeDescendentVolume(binaryNode.getRightChild());
+ }
+
+ throw new PlanningException("Invalid State");
+ }
+
+ private ExecutionBlock buildJoinPlan(GlobalPlanContext context, JoinNode joinNode,
+ ExecutionBlock leftBlock, ExecutionBlock rightBlock)
+ throws PlanningException {
+ MasterPlan masterPlan = context.plan;
+ ExecutionBlock currentBlock = null;
+
+ boolean autoBroadcast = conf.getBoolVar(TajoConf.ConfVars.DIST_QUERY_BROADCAST_JOIN_AUTO);
+
+ if (autoBroadcast && joinNode.isCandidateBroadcast()) {
+ long broadcastThreshold = conf.getLongVar(TajoConf.ConfVars.DIST_QUERY_BROADCAST_JOIN_THRESHOLD);
+ List<LogicalNode> broadtargetTables = new ArrayList<LogicalNode>();
+ int numLargeTables = 0;
+ for(LogicalNode eachNode: joinNode.getBroadcastTargets()) {
+ ScanNode scanNode = (ScanNode)eachNode;
+ TableDesc tableDesc = scanNode.getTableDesc();
+ if (tableDesc.getStats().getNumBytes() < broadcastThreshold) {
+ broadtargetTables.add(scanNode);
+ LOG.info("The table " + scanNode.getCanonicalName() + " ("
+ + scanNode.getTableDesc().getStats().getNumBytes() + ") is marked a broadcasted table");
+ } else {
+ numLargeTables++;
+ }
+ }
+
+ //large table must be one
+ if (numLargeTables <= 1 && !broadtargetTables.isEmpty()) {
+ currentBlock = masterPlan.newExecutionBlock();
+ currentBlock.setPlan(joinNode);
+
+ for (LogicalNode eachBroadcastTargetNode: broadtargetTables) {
+ currentBlock.addBroadcastTable(((ScanNode)eachBroadcastTargetNode).getCanonicalName());
+ }
+
+ for (LogicalNode eachNode: joinNode.getBroadcastTargets()) {
+ context.execBlockMap.remove(eachNode.getPID());
+ }
+ return currentBlock;
+ }
+ }
+
+ // symmetric repartition join
+ currentBlock = masterPlan.newExecutionBlock();
+
+ DataChannel leftChannel = createDataChannelFromJoin(leftBlock, rightBlock, currentBlock, joinNode, true);
+ DataChannel rightChannel = createDataChannelFromJoin(leftBlock, rightBlock, currentBlock, joinNode, false);
+
+ ScanNode leftScan = buildInputExecutor(masterPlan.getLogicalPlan(), leftChannel);
+ ScanNode rightScan = buildInputExecutor(masterPlan.getLogicalPlan(), rightChannel);
+
+ joinNode.setLeftChild(leftScan);
+ joinNode.setRightChild(rightScan);
+ currentBlock.setPlan(joinNode);
+
+ masterPlan.addConnect(leftChannel);
+ masterPlan.addConnect(rightChannel);
+
+ return currentBlock;
+ }
+
+ private AggregationFunctionCallEval createSumFunction(EvalNode [] args) throws InternalException {
+ FunctionDesc functionDesc = getCatalog().getFunction("sum", CatalogProtos.FunctionType.AGGREGATION,
+ args[0].getValueType());
+ return new AggregationFunctionCallEval(functionDesc, (AggFunction) functionDesc.newInstance(), args);
+ }
+
+ private AggregationFunctionCallEval createCountFunction(EvalNode [] args) throws InternalException {
+ FunctionDesc functionDesc = getCatalog().getFunction("count", CatalogProtos.FunctionType.AGGREGATION,
+ args[0].getValueType());
+ return new AggregationFunctionCallEval(functionDesc, (AggFunction) functionDesc.newInstance(), args);
+ }
+
+ private AggregationFunctionCallEval createCountRowFunction(EvalNode[] args) throws InternalException {
+ FunctionDesc functionDesc = getCatalog().getFunction("count", CatalogProtos.FunctionType.AGGREGATION,
+ new TajoDataTypes.DataType[]{});
+ return new AggregationFunctionCallEval(functionDesc, (AggFunction) functionDesc.newInstance(), args);
+ }
+
+ private AggregationFunctionCallEval createMaxFunction(EvalNode [] args) throws InternalException {
+ FunctionDesc functionDesc = getCatalog().getFunction("max", CatalogProtos.FunctionType.AGGREGATION,
+ args[0].getValueType());
+ return new AggregationFunctionCallEval(functionDesc, (AggFunction) functionDesc.newInstance(), args);
+ }
+
+ private AggregationFunctionCallEval createMinFunction(EvalNode [] args) throws InternalException {
+ FunctionDesc functionDesc = getCatalog().getFunction("min", CatalogProtos.FunctionType.AGGREGATION,
+ args[0].getValueType());
+ return new AggregationFunctionCallEval(functionDesc, (AggFunction) functionDesc.newInstance(), args);
+ }
+
+ /**
+ * It contains transformed functions and it related data.
+ * Each non-distinct function is transformed into two functions for both first and second stages.
+ */
+ private static class RewrittenFunctions {
+ AggregationFunctionCallEval [] firstStageEvals;
+ Target [] firstStageTargets;
+ AggregationFunctionCallEval secondStageEvals;
+
+ public RewrittenFunctions(int firstStageEvalNum) {
+ firstStageEvals = new AggregationFunctionCallEval[firstStageEvalNum];
+ firstStageTargets = new Target[firstStageEvalNum];
+ }
+ }
+
+ /**
+ * Tajo uses three execution blocks for an aggregation operator including distinct aggregations.
+ * We call this approach <i><b>three-phase aggregation</b></i>.
+ *
+ * In this case, non-distinct set functions (i.e., <code>count(1), sum(col1)</code>) should be rewritten
+ * to other forms. Please see the following example. This is a rewriting case for a query which includes distinct
+ * aggregation functions. In this example, <code>count(*)</code> functions are transformed into two
+ * functions: count(*) in the inner query and sum() in the outer query.
+ *
+ * <h2>Original query</h2>
+ * <pre>
+ * SELECT
+ * grp1, grp2, count(*) as total, count(distinct grp3) as distinct_col
+ * from
+ * rel1
+ * group by
+ * grp1, grp2;
+ * </pre>
+ *
+ * <h2>Rewritten query</h2>
+ * <pre>
+ * SELECT grp1, grp2, sum(cnt) as total, count(grp3) as distinct_col from (
+ * SELECT
+ * grp1, grp2, grp3, count(*) as cnt
+ * from
+ * rel1
+ * group by
+ * grp1, grp2, grp3) tmp1
+ * group by
+ * grp1, grp2
+ * ) table1;
+ * </pre>
+ *
+ * The main objective of this method is to transform non-distinct aggregation functions for three-phase aggregation.
+ */
+ private RewrittenFunctions rewriteAggFunctionsForDistinctAggregation(GlobalPlanContext context,
+ AggregationFunctionCallEval function)
+ throws PlanningException {
+
+ LogicalPlan plan = context.plan.getLogicalPlan();
+ RewrittenFunctions rewritten = null;
+
+ try {
+ if (function.getName().equalsIgnoreCase("count")) {
+ rewritten = new RewrittenFunctions(1);
+
+ if (function.getArgs().length == 0) {
+ rewritten.firstStageEvals[0] = createCountRowFunction(function.getArgs());
+ } else {
+ rewritten.firstStageEvals[0] = createCountFunction(function.getArgs());
+ }
+ String referenceName = plan.generateUniqueColumnName(rewritten.firstStageEvals[0]);
+ FieldEval fieldEval = new FieldEval(referenceName, rewritten.firstStageEvals[0].getValueType());
+ rewritten.firstStageTargets[0] = new Target(fieldEval);
+ rewritten.secondStageEvals = createSumFunction(new EvalNode[] {fieldEval});
+ } else if (function.getName().equalsIgnoreCase("sum")) {
+ rewritten = new RewrittenFunctions(1);
+
+ rewritten.firstStageEvals[0] = createSumFunction(function.getArgs());
+ String referenceName = plan.generateUniqueColumnName(rewritten.firstStageEvals[0]);
+ FieldEval fieldEval = new FieldEval(referenceName, rewritten.firstStageEvals[0].getValueType());
+ rewritten.firstStageTargets[0] = new Target(fieldEval);
+ rewritten.secondStageEvals = createSumFunction(new EvalNode[] {fieldEval});
+
+ } else if (function.getName().equals("max")) {
+ rewritten = new RewrittenFunctions(1);
+
+ rewritten.firstStageEvals[0] = createMaxFunction(function.getArgs());
+ String referenceName = plan.generateUniqueColumnName(rewritten.firstStageEvals[0]);
+ FieldEval fieldEval = new FieldEval(referenceName, rewritten.firstStageEvals[0].getValueType());
+ rewritten.firstStageTargets[0] = new Target(fieldEval);
+ rewritten.secondStageEvals = createMaxFunction(new EvalNode[]{fieldEval});
+
+ } else if (function.getName().equals("min")) {
+
+ rewritten = new RewrittenFunctions(1);
+
+ rewritten.firstStageEvals[0] = createMinFunction(function.getArgs());
+ String referenceName = plan.generateUniqueColumnName(rewritten.firstStageEvals[0]);
+ FieldEval fieldEval = new FieldEval(referenceName, rewritten.firstStageEvals[0].getValueType());
+ rewritten.firstStageTargets[0] = new Target(fieldEval);
+ rewritten.secondStageEvals = createMinFunction(new EvalNode[]{fieldEval});
+
+ } else {
+ throw new PlanningException("Cannot support a mix of other functions");
+ }
+ } catch (InternalException e) {
+ LOG.error(e);
+ }
+
+ return rewritten;
+ }
+
+ /**
+ * If there are at least one distinct aggregation function, a query works as if the query is rewritten as follows:
+ *
+ * <h2>Original query</h2>
+ * <pre>
+ * SELECT
+ * grp1, grp2, count(*) as total, count(distinct grp3) as distinct_col
+ * from
+ * rel1
+ * group by
+ * grp1, grp2;
+ * </pre>
+ *
+ * The query will work as if the query is rewritten into two queries as follows:
+ *
+ * <h2>Rewritten query</h2>
+ * <pre>
+ * SELECT grp1, grp2, sum(cnt) as total, count(grp3) as distinct_col from (
+ * SELECT
+ * grp1, grp2, grp3, count(*) as cnt
+ * from
+ * rel1
+ * group by
+ * grp1, grp2, grp3) tmp1
+ * group by
+ * grp1, grp2
+ * ) table1;
+ * </pre>
+ *
+ * In more detail, the first aggregation aggregates not only original grouping fields but also distinct columns.
+ * Non-distinct aggregation functions should be transformed to proper functions.
+ * Then, the second aggregation aggregates only original grouping fields with distinct aggregation functions and
+ * transformed non-distinct aggregation functions.
+ *
+ * As a result, although a no-distinct aggregation requires two stages, a distinct aggregation requires three
+ * execution blocks.
+ */
+ private ExecutionBlock buildGroupByIncludingDistinctFunctions(GlobalPlanContext context,
+ ExecutionBlock latestExecBlock,
+ GroupbyNode groupbyNode) throws PlanningException {
+
+ Column [] originalGroupingColumns = groupbyNode.getGroupingColumns();
+ LinkedHashSet<Column> firstStageGroupingColumns =
+ Sets.newLinkedHashSet(Arrays.asList(groupbyNode.getGroupingColumns()));
+ List<AggregationFunctionCallEval> firstStageAggFunctions = Lists.newArrayList();
+ List<AggregationFunctionCallEval> secondPhaseEvalNodes = Lists.newArrayList();
+ List<Target> firstPhaseEvalNodeTargets = Lists.newArrayList();
+
+ for (AggregationFunctionCallEval aggFunction : groupbyNode.getAggFunctions()) {
+
+ if (aggFunction.isDistinct()) {
+ // add distinct columns to first stage's grouping columns
+ firstStageGroupingColumns.addAll(EvalTreeUtil.findUniqueColumns(aggFunction));
+ // keep distinct aggregation functions for the second stage
+ secondPhaseEvalNodes.add(aggFunction);
+
+ } else {
+ // Rewrite non-distinct aggregation functions
+ RewrittenFunctions rewritten = rewriteAggFunctionsForDistinctAggregation(context, aggFunction);
+ firstStageAggFunctions.addAll(Lists.newArrayList(rewritten.firstStageEvals));
+ firstPhaseEvalNodeTargets.addAll(Lists.newArrayList(rewritten.firstStageTargets));
+
+ // keep rewritten non-aggregation functions for the second stage
+ secondPhaseEvalNodes.add(rewritten.secondStageEvals);
+ }
+ }
+
+ int firstStageAggFunctionNum = firstStageAggFunctions.size();
+ int firstStageGroupingKeyNum = firstStageGroupingColumns.size();
+
+ int i = 0;
+ Target [] firstStageTargets = new Target[firstStageGroupingKeyNum + firstStageAggFunctionNum];
+ for (Column column : firstStageGroupingColumns) {
+ Target target = new Target(new FieldEval(column));
+ firstStageTargets[i++] = target;
+ }
+ for (Target target : firstPhaseEvalNodeTargets) {
+ firstStageTargets[i++] = target;
+ }
+ // Create the groupby node for the first stage and set all necessary descriptions
+ GroupbyNode firstStageGroupby = new GroupbyNode(context.plan.getLogicalPlan().newPID());
+ firstStageGroupby.setGroupingColumns(TUtil.toArray(firstStageGroupingColumns, Column.class));
+ firstStageGroupby.setAggFunctions(TUtil.toArray(firstStageAggFunctions, AggregationFunctionCallEval.class));
+ firstStageGroupby.setTargets(firstStageTargets);
+ firstStageGroupby.setChild(groupbyNode.getChild());
+ firstStageGroupby.setInSchema(groupbyNode.getInSchema());
+
+ // Makes two execution blocks for the first stage
+ ExecutionBlock firstStage = buildGroupBy(context, latestExecBlock, firstStageGroupby);
+
+ // Create the groupby node for the second stage.
+ GroupbyNode secondPhaseGroupby = new GroupbyNode(context.plan.getLogicalPlan().newPID());
+ secondPhaseGroupby.setGroupingColumns(originalGroupingColumns);
+ secondPhaseGroupby.setAggFunctions(TUtil.toArray(secondPhaseEvalNodes, AggregationFunctionCallEval.class));
+ secondPhaseGroupby.setTargets(groupbyNode.getTargets());
+
+ ExecutionBlock secondStage = context.plan.newExecutionBlock();
+ secondStage.setPlan(secondPhaseGroupby);
+ SortSpec [] sortSpecs = PlannerUtil.columnsToSortSpec(firstStageGroupingColumns);
+ secondStage.getEnforcer().enforceSortAggregation(secondPhaseGroupby.getPID(), sortSpecs);
+
+ // Create a data channel between the first and second stages
+ DataChannel channel;
+ channel = new DataChannel(firstStage, secondStage, HASH_SHUFFLE, 32);
+ channel.setShuffleKeys(secondPhaseGroupby.getGroupingColumns().clone());
+ channel.setSchema(firstStage.getPlan().getOutSchema());
+ channel.setStoreType(storeType);
+
+ // Setting for the second phase's logical plan
+ ScanNode scanNode = buildInputExecutor(context.plan.getLogicalPlan(), channel);
+ secondPhaseGroupby.setChild(scanNode);
+ secondPhaseGroupby.setInSchema(scanNode.getOutSchema());
+ secondStage.setPlan(secondPhaseGroupby);
+
+ context.plan.addConnect(channel);
+
+ return secondStage;
+ }
+
+ private ExecutionBlock buildGroupBy(GlobalPlanContext context, ExecutionBlock lastBlock,
+ GroupbyNode groupbyNode) throws PlanningException {
+
+ MasterPlan masterPlan = context.plan;
+ ExecutionBlock currentBlock;
+
+ if (groupbyNode.isDistinct()) { // if there is at one distinct aggregation function
+ return buildGroupByIncludingDistinctFunctions(context, lastBlock, groupbyNode);
+ } else {
+ GroupbyNode firstPhaseGroupby = createFirstPhaseGroupBy(masterPlan.getLogicalPlan(), groupbyNode);
+
+ if (hasUnionChild(firstPhaseGroupby)) {
+ currentBlock = buildGroupbyAndUnionPlan(masterPlan, lastBlock, firstPhaseGroupby, groupbyNode);
+ } else {
+ // general hash-shuffled aggregation
+ currentBlock = buildTwoPhaseGroupby(masterPlan, lastBlock, firstPhaseGroupby, groupbyNode);
+ }
+ }
+
+ return currentBlock;
+ }
+
+ public boolean hasUnionChild(UnaryNode node) {
+
+ // there are two cases:
+ //
+ // The first case is:
+ //
+ // create table [tbname] as select * from ( select ... UNION select ...) T
+ //
+ // We can generalize this case as 'a store operator on the top of union'.
+ // In this case, a store operator determines a shuffle method.
+ //
+ // The second case is:
+ //
+ // select avg(..) from (select ... UNION select ) T
+ //
+ // We can generalize this case as 'a shuffle required operator on the top of union'.
+
+ if (node.getChild() instanceof UnaryNode) { // first case
+ UnaryNode child = node.getChild();
+
+ if (child.getChild().getType() == NodeType.PROJECTION) {
+ child = child.getChild();
+ }
+
+ if (child.getChild().getType() == NodeType.TABLE_SUBQUERY) {
+ TableSubQueryNode tableSubQuery = child.getChild();
+ return tableSubQuery.getSubQuery().getType() == NodeType.UNION;
+ }
+
+ } else if (node.getChild().getType() == NodeType.TABLE_SUBQUERY) { // second case
+ TableSubQueryNode tableSubQuery = node.getChild();
+ return tableSubQuery.getSubQuery().getType() == NodeType.UNION;
+ }
+
+ return false;
+ }
+
+ private ExecutionBlock buildGroupbyAndUnionPlan(MasterPlan masterPlan, ExecutionBlock lastBlock,
+ GroupbyNode firstPhaseGroupBy, GroupbyNode secondPhaseGroupBy) {
+ DataChannel lastDataChannel = null;
+
+ // It pushes down the first phase group-by operator into all child blocks.
+ //
+ // (second phase) G (currentBlock)
+ // /|\
+ // / / | \
+ // (first phase) G G G G (child block)
+
+ // They are already connected one another.
+ // So, we don't need to connect them again.
+ for (DataChannel dataChannel : masterPlan.getIncomingChannels(lastBlock.getId())) {
+ if (firstPhaseGroupBy.isEmptyGrouping()) {
+ dataChannel.setShuffle(HASH_SHUFFLE, firstPhaseGroupBy.getGroupingColumns(), 1);
+ } else {
+ dataChannel.setShuffle(HASH_SHUFFLE, firstPhaseGroupBy.getGroupingColumns(), 32);
+ }
+ dataChannel.setSchema(firstPhaseGroupBy.getOutSchema());
+ ExecutionBlock childBlock = masterPlan.getExecBlock(dataChannel.getSrcId());
+
+ // Why must firstPhaseGroupby be copied?
+ //
+ // A groupby in each execution block can have different child.
+ // It affects groupby's input schema.
+ GroupbyNode firstPhaseGroupbyCopy = PlannerUtil.clone(masterPlan.getLogicalPlan(), firstPhaseGroupBy);
+ firstPhaseGroupbyCopy.setChild(childBlock.getPlan());
+ childBlock.setPlan(firstPhaseGroupbyCopy);
+
+ // just keep the last data channel.
+ lastDataChannel = dataChannel;
+ }
+
+ ScanNode scanNode = buildInputExecutor(masterPlan.getLogicalPlan(), lastDataChannel);
+ secondPhaseGroupBy.setChild(scanNode);
+ lastBlock.setPlan(secondPhaseGroupBy);
+ return lastBlock;
+ }
+
+ private ExecutionBlock buildTwoPhaseGroupby(MasterPlan masterPlan, ExecutionBlock latestBlock,
+ GroupbyNode firstPhaseGroupby, GroupbyNode secondPhaseGroupby) {
+ ExecutionBlock childBlock = latestBlock;
+ childBlock.setPlan(firstPhaseGroupby);
+ ExecutionBlock currentBlock = masterPlan.newExecutionBlock();
+
+ DataChannel channel;
+ if (firstPhaseGroupby.isEmptyGrouping()) {
+ channel = new DataChannel(childBlock, currentBlock, HASH_SHUFFLE, 1);
+ channel.setShuffleKeys(firstPhaseGroupby.getGroupingColumns());
+ } else {
+ channel = new DataChannel(childBlock, currentBlock, HASH_SHUFFLE, 32);
+ channel.setShuffleKeys(firstPhaseGroupby.getGroupingColumns());
+ }
+ channel.setSchema(firstPhaseGroupby.getOutSchema());
+ channel.setStoreType(storeType);
+
+ ScanNode scanNode = buildInputExecutor(masterPlan.getLogicalPlan(), channel);
+ secondPhaseGroupby.setChild(scanNode);
+ secondPhaseGroupby.setInSchema(scanNode.getOutSchema());
+ currentBlock.setPlan(secondPhaseGroupby);
+
+ masterPlan.addConnect(channel);
+
+ return currentBlock;
+ }
+
+ public static GroupbyNode createFirstPhaseGroupBy(LogicalPlan plan, GroupbyNode groupBy) {
+ Preconditions.checkNotNull(groupBy);
+
+ GroupbyNode firstPhaseGroupBy = PlannerUtil.clone(plan, groupBy);
+ GroupbyNode secondPhaseGroupBy = groupBy;
+
+ // Set first phase expressions
+ if (secondPhaseGroupBy.hasAggFunctions()) {
+ int evalNum = secondPhaseGroupBy.getAggFunctions().length;
+ AggregationFunctionCallEval [] secondPhaseEvals = secondPhaseGroupBy.getAggFunctions();
+ AggregationFunctionCallEval [] firstPhaseEvals = new AggregationFunctionCallEval[evalNum];
+
+ String [] firstPhaseEvalNames = new String[evalNum];
+ for (int i = 0; i < evalNum; i++) {
+ try {
+ firstPhaseEvals[i] = (AggregationFunctionCallEval) secondPhaseEvals[i].clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+
+ firstPhaseEvals[i].setFirstPhase();
+ firstPhaseEvalNames[i] = plan.generateUniqueColumnName(firstPhaseEvals[i]);
+ FieldEval param = new FieldEval(firstPhaseEvalNames[i], firstPhaseEvals[i].getValueType());
+ secondPhaseEvals[i].setArgs(new EvalNode[] {param});
+ }
+
+ secondPhaseGroupBy.setAggFunctions(secondPhaseEvals);
+ firstPhaseGroupBy.setAggFunctions(firstPhaseEvals);
+ Target [] firstPhaseTargets = ProjectionPushDownRule.buildGroupByTarget(firstPhaseGroupBy, null,
+ firstPhaseEvalNames);
+ firstPhaseGroupBy.setTargets(firstPhaseTargets);
+ secondPhaseGroupBy.setInSchema(PlannerUtil.targetToSchema(firstPhaseTargets));
+ }
+ return firstPhaseGroupBy;
+ }
+
+ private ExecutionBlock buildSortPlan(GlobalPlanContext context, ExecutionBlock childBlock, SortNode currentNode) {
+ MasterPlan masterPlan = context.plan;
+ ExecutionBlock currentBlock;
+
+ SortNode firstSortNode = PlannerUtil.clone(context.plan.getLogicalPlan(), currentNode);
+
+ if (firstSortNode.getChild().getType() == NodeType.TABLE_SUBQUERY &&
+ ((TableSubQueryNode)firstSortNode.getChild()).getSubQuery().getType() == NodeType.UNION) {
+
+ currentBlock = childBlock;
+ for (DataChannel channel : masterPlan.getIncomingChannels(childBlock.getId())) {
+ channel.setShuffle(RANGE_SHUFFLE, PlannerUtil.sortSpecsToSchema(currentNode.getSortKeys()).toArray(), 32);
+ channel.setSchema(firstSortNode.getOutSchema());
+
+ ExecutionBlock subBlock = masterPlan.getExecBlock(channel.getSrcId());
+ SortNode s1 = PlannerUtil.clone(context.plan.getLogicalPlan(), firstSortNode);
+ s1.setChild(subBlock.getPlan());
+ subBlock.setPlan(s1);
+
+ ScanNode secondScan = buildInputExecutor(masterPlan.getLogicalPlan(), channel);
+ currentNode.setChild(secondScan);
+ currentNode.setInSchema(secondScan.getOutSchema());
+ currentBlock.setPlan(currentNode);
+ currentBlock.getEnforcer().addSortedInput(secondScan.getTableName(), currentNode.getSortKeys());
+ }
+ } else {
+ LogicalNode childBlockPlan = childBlock.getPlan();
+ firstSortNode.setChild(childBlockPlan);
+ // sort is a non-projectable operator. So, in/out schemas are the same to its child operator.
+ firstSortNode.setInSchema(childBlockPlan.getOutSchema());
+ firstSortNode.setOutSchema(childBlockPlan.getOutSchema());
+ childBlock.setPlan(firstSortNode);
+
+ currentBlock = masterPlan.newExecutionBlock();
+ DataChannel channel = new DataChannel(childBlock, currentBlock, RANGE_SHUFFLE, 32);
+ channel.setShuffleKeys(PlannerUtil.sortSpecsToSchema(currentNode.getSortKeys()).toArray());
+ channel.setSchema(firstSortNode.getOutSchema());
+
+ ScanNode secondScan = buildInputExecutor(masterPlan.getLogicalPlan(), channel);
+ currentNode.setChild(secondScan);
+ currentNode.setInSchema(secondScan.getOutSchema());
+ currentBlock.setPlan(currentNode);
+ currentBlock.getEnforcer().addSortedInput(secondScan.getTableName(), currentNode.getSortKeys());
+ masterPlan.addConnect(channel);
+ }
+
+ return currentBlock;
+ }
+
+ /**
+ * It builds a distributed execution block for CTAS, InsertNode, and StoreTableNode.
+ */
+ private ExecutionBlock buildStorePlan(GlobalPlanContext context,
+ ExecutionBlock lastBlock,
+ StoreTableNode currentNode) throws PlanningException {
+
+
+ if(currentNode.hasPartition()) { // if a target table is a partitioned table
+
+ // Verify supported partition types
+ PartitionMethodDesc partitionMethod = currentNode.getPartitionMethod();
+ if (partitionMethod.getPartitionType() != CatalogProtos.PartitionType.COLUMN) {
+ throw new PlanningException(String.format("Not supported partitionsType :%s",
+ partitionMethod.getPartitionType()));
+ }
+
+ if (hasUnionChild(currentNode)) { // if it has union children
+ return buildShuffleAndStorePlanToPartitionedTableWithUnion(context, currentNode, lastBlock);
+ } else { // otherwise
+ return buildShuffleAndStorePlanToPartitionedTable(context, currentNode, lastBlock);
+ }
+ } else { // if result table is not a partitioned table, directly store it
+ return buildNoPartitionedStorePlan(context, currentNode, lastBlock);
+ }
+ }
+
+ /**
+ * It makes a plan to store directly union plans into a non-partitioned table.
+ */
+ private ExecutionBlock buildShuffleAndStorePlanNoPartitionedTableWithUnion(GlobalPlanContext context,
+ StoreTableNode currentNode,
+ ExecutionBlock childBlock) {
+ for (ExecutionBlock grandChildBlock : context.plan.getChilds(childBlock)) {
+ StoreTableNode copy = PlannerUtil.clone(context.plan.getLogicalPlan(), currentNode);
+ copy.setChild(grandChildBlock.getPlan());
+ grandChildBlock.setPlan(copy);
+ }
+ return childBlock;
+ }
+
+ /**
+ * It inserts shuffle and adds store plan on a partitioned table,
+ * and it push downs those plans into child unions.
+ */
+ private ExecutionBlock buildShuffleAndStorePlanToPartitionedTableWithUnion(GlobalPlanContext context,
+ StoreTableNode currentNode,
+ ExecutionBlock lastBlock)
+ throws PlanningException {
+
+ MasterPlan masterPlan = context.plan;
+ DataChannel lastChannel = null;
+ for (DataChannel channel : masterPlan.getIncomingChannels(lastBlock.getId())) {
+ ExecutionBlock childBlock = masterPlan.getExecBlock(channel.getSrcId());
+ setShuffleKeysFromPartitionedTableStore(currentNode, channel);
+ channel.setSchema(childBlock.getPlan().getOutSchema());
+ channel.setStoreType(storeType);
+ lastChannel = channel;
+ }
+
+ ScanNode scanNode = buildInputExecutor(masterPlan.getLogicalPlan(), lastChannel);
+ currentNode.setChild(scanNode);
+ currentNode.setInSchema(scanNode.getOutSchema());
+ lastBlock.setPlan(currentNode);
+ return lastBlock;
+ }
+
+ /**
+ * It inserts shuffle and adds store plan on a partitioned table.
+ */
+ private ExecutionBlock buildShuffleAndStorePlanToPartitionedTable(GlobalPlanContext context,
+ StoreTableNode currentNode,
+ ExecutionBlock lastBlock)
+ throws PlanningException {
+ MasterPlan masterPlan = context.plan;
+
+ ExecutionBlock nextBlock = masterPlan.newExecutionBlock();
+ DataChannel channel = new DataChannel(lastBlock, nextBlock, HASH_SHUFFLE, 32);
+ setShuffleKeysFromPartitionedTableStore(currentNode, channel);
+ channel.setSchema(lastBlock.getPlan().getOutSchema());
+ channel.setStoreType(storeType);
+
+ ScanNode scanNode = buildInputExecutor(masterPlan.getLogicalPlan(), channel);
+ currentNode.setChild(scanNode);
+ currentNode.setInSchema(scanNode.getOutSchema());
+ nextBlock.setPlan(currentNode);
+
+ masterPlan.addConnect(channel);
+
+ return nextBlock;
+ }
+
+ private ExecutionBlock buildNoPartitionedStorePlan(GlobalPlanContext context,
+ StoreTableNode currentNode,
+ ExecutionBlock childBlock) {
+ if (hasUnionChild(currentNode)) { // when the below is union
+ return buildShuffleAndStorePlanNoPartitionedTableWithUnion(context, currentNode, childBlock);
+ } else {
+ currentNode.setChild(childBlock.getPlan());
+ currentNode.setInSchema(childBlock.getPlan().getOutSchema());
+ childBlock.setPlan(currentNode);
+ return childBlock;
+ }
+ }
+
+ private void setShuffleKeysFromPartitionedTableStore(StoreTableNode node, DataChannel channel) {
+ Preconditions.checkState(node.hasTargetTable(), "A target table must be a partitioned table.");
+ PartitionMethodDesc partitionMethod = node.getPartitionMethod();
+
+ if (node.getType() == NodeType.INSERT) {
+ InsertNode insertNode = (InsertNode) node;
+ channel.setSchema(((InsertNode)node).getProjectedSchema());
+ Column [] shuffleKeys = new Column[partitionMethod.getExpressionSchema().size()];
+ int i = 0;
+ for (Column column : partitionMethod.getExpressionSchema().getColumns()) {
+ int id = insertNode.getTableSchema().getColumnId(column.getQualifiedName());
+ shuffleKeys[i++] = insertNode.getProjectedSchema().getColumn(id);
+ }
+ channel.setShuffleKeys(shuffleKeys);
+ } else {
+ channel.setShuffleKeys(partitionMethod.getExpressionSchema().toArray());
+ }
+ channel.setShuffleType(HASH_SHUFFLE);
+ channel.setShuffleOutputNum(32);
+ }
+
+ public class DistributedPlannerVisitor extends BasicLogicalPlanVisitor<GlobalPlanContext, LogicalNode> {
+
+ @Override
+ public LogicalNode visitRoot(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ LogicalRootNode node, Stack<LogicalNode> stack) throws PlanningException {
+ return super.visitRoot(context, plan, block, node, stack);
+ }
+
+ @Override
+ public LogicalNode visitProjection(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ ProjectionNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitProjection(context, plan, block, node, stack);
+
+ ExecutionBlock execBlock = context.execBlockMap.remove(child.getPID());
+
+ if (child.getType() == NodeType.TABLE_SUBQUERY &&
+ ((TableSubQueryNode)child).getSubQuery().getType() == NodeType.UNION) {
+ MasterPlan masterPlan = context.plan;
+ for (DataChannel dataChannel : masterPlan.getIncomingChannels(execBlock.getId())) {
+ ExecutionBlock subBlock = masterPlan.getExecBlock(dataChannel.getSrcId());
+
+ ProjectionNode copy = PlannerUtil.clone(plan, node);
+ copy.setChild(subBlock.getPlan());
+ subBlock.setPlan(copy);
+ }
+ execBlock.setPlan(null);
+ } else {
+ node.setChild(execBlock.getPlan());
+ node.setInSchema(execBlock.getPlan().getOutSchema());
+ execBlock.setPlan(node);
+ }
+
+ context.execBlockMap.put(node.getPID(), execBlock);
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitLimit(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ LimitNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitLimit(context, plan, block, node, stack);
+
+ ExecutionBlock execBlock;
+ execBlock = context.execBlockMap.remove(child.getPID());
+ if (child.getType() == NodeType.SORT) {
+ node.setChild(execBlock.getPlan());
+ execBlock.setPlan(node);
+
+ ExecutionBlock childBlock = context.plan.getChild(execBlock, 0);
+ LimitNode childLimit = PlannerUtil.clone(context.plan.getLogicalPlan(), node);
+ childLimit.setChild(childBlock.getPlan());
+ childBlock.setPlan(childLimit);
+
+ DataChannel channel = context.plan.getChannel(childBlock, execBlock);
+ channel.setShuffleOutputNum(1);
+ context.execBlockMap.put(node.getPID(), execBlock);
+ } else {
+ node.setChild(execBlock.getPlan());
+ execBlock.setPlan(node);
+
+ ExecutionBlock newExecBlock = context.plan.newExecutionBlock();
+ DataChannel newChannel = new DataChannel(execBlock, newExecBlock, HASH_SHUFFLE, 1);
+ newChannel.setShuffleKeys(new Column[]{});
+ newChannel.setSchema(node.getOutSchema());
+ newChannel.setStoreType(storeType);
+
+ ScanNode scanNode = buildInputExecutor(plan, newChannel);
+ LimitNode parentLimit = PlannerUtil.clone(context.plan.getLogicalPlan(), node);
+ parentLimit.setChild(scanNode);
+ newExecBlock.setPlan(parentLimit);
+ context.plan.addConnect(newChannel);
+ context.execBlockMap.put(parentLimit.getPID(), newExecBlock);
+ node = parentLimit;
+ }
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitSort(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ SortNode node, Stack<LogicalNode> stack) throws PlanningException {
+
+ LogicalNode child = super.visitSort(context, plan, block, node, stack);
+
+ ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+ ExecutionBlock newExecBlock = buildSortPlan(context, childBlock, node);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitHaving(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ HavingNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitHaving(context, plan, block, node, stack);
+
+ // Don't separate execution block. Having is pushed to the second grouping execution block.
+ ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+ node.setChild(childBlock.getPlan());
+ childBlock.setPlan(node);
+ context.execBlockMap.put(node.getPID(), childBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitGroupBy(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ GroupbyNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitGroupBy(context, plan, block, node, stack);
+
+ ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+ ExecutionBlock newExecBlock = buildGroupBy(context, childBlock, node);
+ context.execBlockMap.put(newExecBlock.getPlan().getPID(), newExecBlock);
+
+ return newExecBlock.getPlan();
+ }
+
+ @Override
+ public LogicalNode visitFilter(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ SelectionNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitFilter(context, plan, block, node, stack);
+
+ ExecutionBlock execBlock = context.execBlockMap.remove(child.getPID());
+ node.setChild(execBlock.getPlan());
+ node.setInSchema(execBlock.getPlan().getOutSchema());
+ execBlock.setPlan(node);
+ context.execBlockMap.put(node.getPID(), execBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitJoin(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+ JoinNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode leftChild = visit(context, plan, block, node.getLeftChild(), stack);
+ LogicalNode rightChild = visit(context, plan, block, node.getRightChild(), stack);
+
+ ExecutionBlock leftChildBlock = context.execBlockMap.get(leftChild.getPID());
+ ExecutionBlock rightChildBlock = context.execBlockMap.get(rightChild.getPID());
+
+ ExecutionBlock newExecBlock = buildJoinPlan(context, node, leftChildBlock, rightChildBlock);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitUnion(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ UnionNode node, Stack<LogicalNode> stack) throws PlanningException {
+ stack.push(node);
+ LogicalPlan.QueryBlock leftQueryBlock = plan.getBlock(node.getLeftChild());
+ LogicalNode leftChild = visit(context, plan, leftQueryBlock, leftQueryBlock.getRoot(), stack);
+
+ LogicalPlan.QueryBlock rightQueryBlock = plan.getBlock(node.getRightChild());
+ LogicalNode rightChild = visit(context, plan, rightQueryBlock, rightQueryBlock.getRoot(), stack);
+ stack.pop();
+
+ List<ExecutionBlock> unionBlocks = Lists.newArrayList();
+ List<ExecutionBlock> queryBlockBlocks = Lists.newArrayList();
+
+ ExecutionBlock leftBlock = context.execBlockMap.remove(leftChild.getPID());
+ ExecutionBlock rightBlock = context.execBlockMap.remove(rightChild.getPID());
+ if (leftChild.getType() == NodeType.UNION) {
+ unionBlocks.add(leftBlock);
+ } else {
+ queryBlockBlocks.add(leftBlock);
+ }
+ if (rightChild.getType() == NodeType.UNION) {
+ unionBlocks.add(rightBlock);
+ } else {
+ queryBlockBlocks.add(rightBlock);
+ }
+
+ ExecutionBlock execBlock;
+ if (unionBlocks.size() == 0) {
+ execBlock = context.plan.newExecutionBlock();
+ } else {
+ execBlock = unionBlocks.get(0);
+ }
+
+ for (ExecutionBlock childBlocks : unionBlocks) {
+ for (ExecutionBlock grandChildBlock : context.plan.getChilds(childBlocks)) {
+ queryBlockBlocks.add(grandChildBlock);
+ }
+ }
+
+ for (ExecutionBlock childBlocks : queryBlockBlocks) {
+ DataChannel channel = new DataChannel(childBlocks, execBlock, NONE_SHUFFLE, 1);
+ channel.setStoreType(storeType);
+ context.plan.addConnect(channel);
+ }
+
+ context.execBlockMap.put(node.getPID(), execBlock);
+
+ return node;
+ }
+
+ private LogicalNode handleUnaryNode(GlobalPlanContext context, LogicalNode child, LogicalNode node) {
+ ExecutionBlock execBlock = context.execBlockMap.remove(child.getPID());
+ execBlock.setPlan(node);
+ context.execBlockMap.put(node.getPID(), execBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitExcept(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ ExceptNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitExcept(context, plan, queryBlock, node, stack);
+ return handleUnaryNode(context, child, node);
+ }
+
+ @Override
+ public LogicalNode visitIntersect(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ IntersectNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitIntersect(context, plan, queryBlock, node, stack);
+ return handleUnaryNode(context, child, node);
+ }
+
+ @Override
+ public LogicalNode visitTableSubQuery(GlobalPlanContext context, LogicalPlan plan,
+ LogicalPlan.QueryBlock queryBlock,
+ TableSubQueryNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitTableSubQuery(context, plan, queryBlock, node, stack);
+ node.setSubQuery(child);
+
+ ExecutionBlock currentBlock = context.execBlockMap.remove(child.getPID());
+
+ if (child.getType() == NodeType.UNION) {
+ for (ExecutionBlock childBlock : context.plan.getChilds(currentBlock.getId())) {
+ TableSubQueryNode copy = PlannerUtil.clone(plan, node);
+ copy.setSubQuery(childBlock.getPlan());
+ childBlock.setPlan(copy);
+ }
+ } else {
+ currentBlock.setPlan(node);
+ }
+ context.execBlockMap.put(node.getPID(), currentBlock);
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitScan(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ ScanNode node, Stack<LogicalNode> stack) throws PlanningException {
+ ExecutionBlock newExecBlock = context.plan.newExecutionBlock();
+ newExecBlock.setPlan(node);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitPartitionedTableScan(GlobalPlanContext context, LogicalPlan plan,
+ LogicalPlan.QueryBlock block, PartitionedTableScanNode node,
+ Stack<LogicalNode> stack)throws PlanningException {
+ ExecutionBlock newExecBlock = context.plan.newExecutionBlock();
+ newExecBlock.setPlan(node);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitStoreTable(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ StoreTableNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitStoreTable(context, plan, queryBlock, node, stack);
+
+ ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+ ExecutionBlock newExecBlock = buildStorePlan(context, childBlock, node);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitCreateTable(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ CreateTableNode node, Stack<LogicalNode> stack) throws PlanningException {
+ LogicalNode child = super.visitStoreTable(context, plan, queryBlock, node, stack);
+
+ ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+ ExecutionBlock newExecBlock = buildStorePlan(context, childBlock, node);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+
+ return node;
+ }
+
+ @Override
+ public LogicalNode visitInsert(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ InsertNode node, Stack<LogicalNode> stack)
+ throws PlanningException {
+ LogicalNode child = super.visitInsert(context, plan, queryBlock, node, stack);
+
+ ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+ ExecutionBlock newExecBlock = buildStorePlan(context, childBlock, node);
+ context.execBlockMap.put(node.getPID(), newExecBlock);
+
+ return node;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private class ConsecutiveUnionFinder extends BasicLogicalPlanVisitor<List<UnionNode>, LogicalNode> {
+ @Override
+ public LogicalNode visitUnion(List<UnionNode> unionNodeList, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+ UnionNode node, Stack<LogicalNode> stack)
+ throws PlanningException {
+ if (node.getType() == NodeType.UNION) {
+ unionNodeList.add(node);
+ }
+
+ stack.push(node);
+ TableSubQueryNode leftSubQuery = node.getLeftChild();
+ TableSubQueryNode rightSubQuery = node.getRightChild();
+ if (leftSubQuery.getSubQuery().getType() == NodeType.UNION) {
+ visit(unionNodeList, plan, queryBlock, leftSubQuery, stack);
+ }
+ if (rightSubQuery.getSubQuery().getType() == NodeType.UNION) {
+ visit(unionNodeList, plan, queryBlock, rightSubQuery, stack);
+ }
+ stack.pop();
+
+ return node;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/MasterPlan.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/MasterPlan.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/MasterPlan.java
new file mode 100644
index 0000000..37b0db1
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/MasterPlan.java
@@ -0,0 +1,268 @@
+/**
+ * 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.global;
+
+import org.apache.tajo.ExecutionBlockId;
+import org.apache.tajo.QueryId;
+import org.apache.tajo.engine.planner.LogicalPlan;
+import org.apache.tajo.engine.planner.PlannerUtil;
+import org.apache.tajo.engine.planner.enforce.Enforcer;
+import org.apache.tajo.engine.planner.graph.SimpleDirectedGraph;
+import org.apache.tajo.engine.query.QueryContext;
+import org.apache.tajo.ipc.TajoWorkerProtocol;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MasterPlan {
+ private final QueryId queryId;
+ private final QueryContext context;
+ private final LogicalPlan plan;
+ private ExecutionBlock root;
+ private AtomicInteger nextId = new AtomicInteger(0);
+
+ private ExecutionBlock terminalBlock;
+ private Map<ExecutionBlockId, ExecutionBlock> execBlockMap = new HashMap<ExecutionBlockId, ExecutionBlock>();
+ private SimpleDirectedGraph<ExecutionBlockId, DataChannel> execBlockGraph =
+ new SimpleDirectedGraph<ExecutionBlockId, DataChannel>();
+
+ public ExecutionBlockId newExecutionBlockId() {
+ return new ExecutionBlockId(queryId, nextId.incrementAndGet());
+ }
+
+ public boolean isTerminal(ExecutionBlock execBlock) {
+ return terminalBlock.getId().equals(execBlock.getId());
+ }
+
+ public ExecutionBlock getTerminalBlock() {
+ return terminalBlock;
+ }
+
+ public ExecutionBlock createTerminalBlock() {
+ terminalBlock = newExecutionBlock();
+ return terminalBlock;
+ }
+
+ public MasterPlan(QueryId queryId, QueryContext context, LogicalPlan plan) {
+ this.queryId = queryId;
+ this.context = context;
+ this.plan = plan;
+ }
+
+ public QueryId getQueryId() {
+ return this.queryId;
+ }
+
+ public QueryContext getContext() {
+ return this.context;
+ }
+
+ public LogicalPlan getLogicalPlan() {
+ return this.plan;
+ }
+
+ public void setTerminal(ExecutionBlock root) {
+ this.root = root;
+ this.terminalBlock = root;
+ }
+
+ public ExecutionBlock getRoot() {
+ return this.root;
+ }
+
+ public ExecutionBlock newExecutionBlock() {
+ ExecutionBlock newExecBlock = new ExecutionBlock(newExecutionBlockId());
+ execBlockMap.put(newExecBlock.getId(), newExecBlock);
+ return newExecBlock;
+ }
+
+ public boolean containsExecBlock(ExecutionBlockId execBlockId) {
+ return execBlockMap.containsKey(execBlockId);
+ }
+
+ public ExecutionBlock getExecBlock(ExecutionBlockId execBlockId) {
+ return execBlockMap.get(execBlockId);
+ }
+
+ public void addConnect(DataChannel dataChannel) {
+ execBlockGraph.addEdge(dataChannel.getSrcId(), dataChannel.getTargetId(), dataChannel);
+ }
+
+ public void addConnect(ExecutionBlock src, ExecutionBlock target, TajoWorkerProtocol.ShuffleType type) {
+ addConnect(src.getId(), target.getId(), type);
+ }
+
+ public void addConnect(ExecutionBlockId src, ExecutionBlockId target, TajoWorkerProtocol.ShuffleType type) {
+ addConnect(new DataChannel(src, target, type));
+ }
+
+ public boolean isConnected(ExecutionBlock src, ExecutionBlock target) {
+ return isConnected(src.getId(), target.getId());
+ }
+
+ public boolean isConnected(ExecutionBlockId src, ExecutionBlockId target) {
+ return execBlockGraph.hasEdge(src, target);
+ }
+
+ public boolean isReverseConnected(ExecutionBlock target, ExecutionBlock src) {
+ return execBlockGraph.hasReversedEdge(target.getId(), src.getId());
+ }
+
+ public boolean isReverseConnected(ExecutionBlockId target, ExecutionBlockId src) {
+ return execBlockGraph.hasReversedEdge(target, src);
+ }
+
+ public DataChannel getChannel(ExecutionBlock src, ExecutionBlock target) {
+ return execBlockGraph.getEdge(src.getId(), target.getId());
+ }
+
+ public DataChannel getChannel(ExecutionBlockId src, ExecutionBlockId target) {
+ return execBlockGraph.getEdge(src, target);
+ }
+
+ public List<DataChannel> getOutgoingChannels(ExecutionBlockId src) {
+ return execBlockGraph.getOutgoingEdges(src);
+ }
+
+ public boolean isRoot(ExecutionBlock execBlock) {
+ if (!execBlock.getId().equals(terminalBlock.getId())) {
+ return execBlockGraph.getParent(execBlock.getId(), 0).equals(terminalBlock.getId());
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isLeaf(ExecutionBlock execBlock) {
+ return execBlockGraph.isLeaf(execBlock.getId());
+ }
+
+ public boolean isLeaf(ExecutionBlockId id) {
+ return execBlockGraph.isLeaf(id);
+ }
+
+ public List<DataChannel> getIncomingChannels(ExecutionBlockId target) {
+ return execBlockGraph.getIncomingEdges(target);
+ }
+
+ public void disconnect(ExecutionBlock src, ExecutionBlock target) {
+ disconnect(src.getId(), target.getId());
+ }
+
+ public void disconnect(ExecutionBlockId src, ExecutionBlockId target) {
+ execBlockGraph.removeEdge(src, target);
+ }
+
+ public ExecutionBlock getParent(ExecutionBlock executionBlock) {
+ return execBlockMap.get(execBlockGraph.getParent(executionBlock.getId(), 0));
+ }
+
+ public List<ExecutionBlock> getChilds(ExecutionBlock execBlock) {
+ return getChilds(execBlock.getId());
+ }
+
+ public List<ExecutionBlock> getChilds(ExecutionBlockId id) {
+ List<ExecutionBlock> childBlocks = new ArrayList<ExecutionBlock>();
+ for (ExecutionBlockId cid : execBlockGraph.getChilds(id)) {
+ childBlocks.add(execBlockMap.get(cid));
+ }
+ return childBlocks;
+ }
+
+ public int getChildCount(ExecutionBlockId blockId) {
+ return execBlockGraph.getChildCount(blockId);
+ }
+
+ public ExecutionBlock getChild(ExecutionBlockId execBlockId, int idx) {
+ return execBlockMap.get(execBlockGraph.getChild(execBlockId, idx));
+ }
+
+ public ExecutionBlock getChild(ExecutionBlock executionBlock, int idx) {
+ return getChild(executionBlock.getId(), idx);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ ExecutionBlockCursor cursor = new ExecutionBlockCursor(this);
+ sb.append("-------------------------------------------------------------------------------\n");
+ sb.append("Execution Block Graph (TERMINAL - " + getTerminalBlock() + ")\n");
+ sb.append("-------------------------------------------------------------------------------\n");
+ sb.append(execBlockGraph.toStringGraph(getRoot().getId()));
+ sb.append("-------------------------------------------------------------------------------\n");
+
+ while(cursor.hasNext()) {
+ ExecutionBlock block = cursor.nextBlock();
+
+ boolean terminal = false;
+ sb.append("\n");
+ sb.append("=======================================================\n");
+ sb.append("Block Id: " + block.getId());
+ if (isTerminal(block)) {
+ sb.append(" [TERMINAL]");
+ terminal = true;
+ } else if (isRoot(block)) {
+ sb.append(" [ROOT]");
+ } else if (isLeaf(block)) {
+ sb.append(" [LEAF]");
+ } else {
+ sb.append(" [INTERMEDIATE]");
+ }
+ sb.append("\n");
+ sb.append("=======================================================\n");
+ if (terminal) {
+ continue;
+ }
+
+ if (!isLeaf(block)) {
+ sb.append("\n[Incoming]\n");
+ for (DataChannel channel : getIncomingChannels(block.getId())) {
+ sb.append(channel).append("\n");
+ }
+ }
+
+ if (!isRoot(block)) {
+ sb.append("\n[Outgoing]\n");
+ for (DataChannel channel : getOutgoingChannels(block.getId())) {
+ sb.append(channel);
+ sb.append("\n");
+ }
+ }
+
+ if (block.getEnforcer().getProperties().size() > 0) {
+ sb.append("\n[Enforcers]\n");
+ int i = 0;
+ for (TajoWorkerProtocol.EnforceProperty enforce : block.getEnforcer().getProperties()) {
+ sb.append(" ").append(i++).append(": ");
+ sb.append(Enforcer.toString(enforce));
+ sb.append("\n");
+ }
+ }
+
+ sb.append("\n").append(PlannerUtil.buildExplainString(block.getPlan()));
+ }
+
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraph.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraph.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraph.java
new file mode 100644
index 0000000..c6f4aaa
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraph.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.engine.planner.graph;
+
+import org.apache.tajo.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * This represents a directed graph.
+ *
+ * @param <V> The vertex class type
+ * @param <E> The edge class type
+ */
+public interface DirectedGraph<V, E> extends Graph<V, E> {
+
+ boolean hasReversedEdge(V head, V tail);
+
+ E getReverseEdge(V head, V tail);
+
+ List<E> getIncomingEdges(V head);
+
+ List<E> getOutgoingEdges(V tail);
+
+ /////////////////////////////////
+ // belows are tree features
+ /////////////////////////////////
+ boolean isRoot(V v);
+
+ boolean isLeaf(V v);
+
+ int getParentCount(V block);
+
+ @Nullable V getParent(V block, int idx);
+
+ List<V> getParents(V block);
+
+ int getChildCount(V block);
+
+ @Nullable V getChild(V block, int idx);
+
+ List<V> getChilds(V block);
+
+ /**
+ * It visits all vertices in a post-order traverse way.
+ */
+ void accept(V src, DirectedGraphVisitor<V> visitor);
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphCursor.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphCursor.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphCursor.java
new file mode 100644
index 0000000..7db9b83
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphCursor.java
@@ -0,0 +1,65 @@
+/**
+ * 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.graph;
+
+import java.util.ArrayList;
+
+public class DirectedGraphCursor<V,E> {
+ private DirectedGraph<V,E> graph;
+ private ArrayList<V> orderedVertices = new ArrayList<V>();
+ private int cursor = 0;
+
+ public DirectedGraphCursor(DirectedGraph<V, E> graph, V startVertex) {
+ this.graph = graph;
+ buildOrder(startVertex);
+ }
+
+ public int size() {
+ return orderedVertices.size();
+ }
+
+ private void buildOrder(V current) {
+ if (!graph.isLeaf(current)) {
+ for (V child : graph.getChilds(current)) {
+ buildOrder(child);
+ }
+ }
+ orderedVertices.add(current);
+ }
+
+ public boolean hasNext() {
+ return cursor < orderedVertices.size();
+ }
+
+ public V nextBlock() {
+ return orderedVertices.get(cursor++);
+ }
+
+ public V peek() {
+ return orderedVertices.get(cursor);
+ }
+
+ public V peek(int skip) {
+ return orderedVertices.get(cursor + skip);
+ }
+
+ public void reset() {
+ cursor = 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphVisitor.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphVisitor.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphVisitor.java
new file mode 100644
index 0000000..3deec7f
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/DirectedGraphVisitor.java
@@ -0,0 +1,25 @@
+/**
+ * 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.graph;
+
+import java.util.Stack;
+
+public interface DirectedGraphVisitor<V> {
+ void visit(Stack<V> stack, V v);
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/Graph.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/Graph.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/Graph.java
new file mode 100644
index 0000000..d380b94
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/Graph.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.engine.planner.graph;
+
+import org.apache.tajo.annotation.Nullable;
+
+import java.util.Collection;
+
+/**
+ * This is the topmost graph interface. It only provides essential graph features.
+ * @param <V> Vertex class
+ * @param <E> Edge Class
+ */
+
+public interface Graph<V, E> {
+ int getVertexSize();
+
+ int getEdgeNum();
+
+ void addEdge(V tail, V head, E edge);
+
+ void removeEdge(V tail, V head);
+
+ boolean hasEdge(V tail, V head);
+
+ @Nullable E getEdge(V tail, V head);
+
+ Collection<E> getEdgesAll();
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleDirectedGraph.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleDirectedGraph.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleDirectedGraph.java
new file mode 100644
index 0000000..396eb8b
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleDirectedGraph.java
@@ -0,0 +1,270 @@
+/**
+ * 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.graph;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.apache.tajo.annotation.Nullable;
+import org.apache.tajo.util.TUtil;
+
+import java.util.*;
+
+/**
+ * This represents a simple directed graph. It does not support multiple edges between both vertices.
+ *
+ * @param <V> The vertex class type
+ * @param <E> The edge class type
+ */
+public class SimpleDirectedGraph<V, E> implements DirectedGraph<V,E> {
+ /** map: child -> parent */
+ protected Map<V, Map<V, E>> directedEdges = TUtil.newLinkedHashMap();
+ /** map: parent -> child */
+ protected Map<V, Map<V, E>> reversedEdges = TUtil.newLinkedHashMap();
+
+ @Override
+ public int getVertexSize() {
+ return directedEdges.size();
+ }
+
+ @Override
+ public int getEdgeNum() {
+ int edgeNum = 0;
+ for (Map<V, E> map : directedEdges.values()) {
+ edgeNum += map.values().size();
+ }
+
+ return edgeNum;
+ }
+
+ @Override
+ public void addEdge(V tail, V head, E edge) {
+ TUtil.putToNestedMap(directedEdges, tail, head, edge);
+ TUtil.putToNestedMap(reversedEdges, head, tail, edge);
+ }
+
+ @Override
+ public void removeEdge(V tail, V head) {
+ if (directedEdges.containsKey(tail)) {
+ directedEdges.get(tail).remove(head);
+ if (directedEdges.get(tail).isEmpty()) {
+ directedEdges.remove(tail);
+ }
+
+ reversedEdges.get(head).remove(tail);
+ if (reversedEdges.get(head).isEmpty()) {
+ reversedEdges.remove(head);
+ }
+ } else {
+ throw new RuntimeException("Not connected channel: " + tail + " -> " + head);
+ }
+ }
+
+ @Override
+ public boolean hasEdge(V tail, V head) {
+ return directedEdges.containsKey(tail) && directedEdges.get(tail).containsKey(head);
+ }
+
+ @Override
+ public boolean hasReversedEdge(V head, V tail) {
+ return reversedEdges.containsKey(head) && reversedEdges.get(head).containsKey(tail);
+ }
+
+ @Override
+ public @Nullable E getEdge(V tail, V head) {
+ if (hasEdge(tail, head)) {
+ return directedEdges.get(tail).get(head);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public @Nullable
+ E getReverseEdge(V head, V tail) {
+ if (hasReversedEdge(head, tail)) {
+ return reversedEdges.get(head).get(tail);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Collection<E> getEdgesAll() {
+ List<E> edges = Lists.newArrayList();
+ for (Map<V, E> map : directedEdges.values()) {
+ edges.addAll(map.values());
+ }
+ return edges;
+ }
+
+ @Override
+ public int getChildCount(V v) {
+ if (reversedEdges.containsKey(v)) {
+ return reversedEdges.get(v).size();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public List<E> getIncomingEdges(V head) {
+ if (reversedEdges.containsKey(head)) {
+ return ImmutableList.copyOf(reversedEdges.get(head).values());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public List<E> getOutgoingEdges(V tail) {
+ if (directedEdges.containsKey(tail)) {
+ return ImmutableList.copyOf(directedEdges.get(tail).values());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public List<V> getChilds(V v) {
+ List<V> childBlocks = new ArrayList<V>();
+ if (reversedEdges.containsKey(v)) {
+ for (Map.Entry<V, E> entry: reversedEdges.get(v).entrySet()) {
+ childBlocks.add(entry.getKey());
+ }
+ }
+ return childBlocks;
+ }
+
+ @Override
+ public V getChild(V block, int idx) {
+ if (reversedEdges.containsKey(block)) {
+ int i = 0;
+ for (Map.Entry<V, E> entry: reversedEdges.get(block).entrySet()) {
+ if (idx == i++) {
+ return entry.getKey();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public @Nullable V getParent(V block, int idx) {
+ if (directedEdges.containsKey(block)) {
+ int i = 0;
+ for (Map.Entry<V, E> entry: directedEdges.get(block).entrySet()) {
+ if (idx == i++) {
+ return entry.getKey();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List<V> getParents(V block) {
+ List<V> childBlocks = new ArrayList<V>();
+ if (directedEdges.containsKey(block)) {
+ for (Map.Entry<V, E> entry: directedEdges.get(block).entrySet()) {
+ childBlocks.add(entry.getKey());
+ }
+ }
+ return childBlocks;
+ }
+
+ @Override
+ public boolean isRoot(V v) {
+ return !directedEdges.containsKey(v);
+ }
+
+ @Override
+ public boolean isLeaf(V v) {
+ return !reversedEdges.containsKey(v);
+ }
+
+ @Override
+ public int getParentCount(V block) {
+ if (directedEdges.containsKey(block)) {
+ return directedEdges.get(block).size();
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public void accept(V source, DirectedGraphVisitor<V> visitor) {
+ Stack<V> stack = new Stack<V>();
+ visitRecursive(stack, source, visitor);
+ }
+
+ private void visitRecursive(Stack<V> stack, V current, DirectedGraphVisitor<V> visitor) {
+ stack.push(current);
+ for (V child : getChilds(current)) {
+ visitRecursive(stack, child, visitor);
+ }
+ stack.pop();
+ visitor.visit(stack, current);
+ }
+
+ public String toString() {
+ return "G (|v| = " + directedEdges.size() +")";
+ }
+
+ public String printDepthString(DepthString planStr) {
+ StringBuilder output = new StringBuilder();
+ String pad = new String(new char[planStr.depth * 3]).replace('\0', ' ');
+ output.append(pad + "|-" + planStr.vertexStr).append("\n");
+
+ return output.toString();
+ }
+
+ public String toStringGraph(V vertex) {
+ StringBuilder sb = new StringBuilder();
+ QueryGraphTopologyStringBuilder visitor = new QueryGraphTopologyStringBuilder();
+ accept(vertex, visitor);
+ Stack<DepthString> depthStrings = visitor.getDepthStrings();
+ while(!depthStrings.isEmpty()) {
+ sb.append(printDepthString(depthStrings.pop()));
+ }
+ return sb.toString();
+ }
+
+ private class DepthString {
+ int depth;
+ String vertexStr;
+
+ DepthString(int depth, String vertexStr) {
+ this.depth = depth;
+ this.vertexStr = vertexStr;
+ }
+ }
+
+ private class QueryGraphTopologyStringBuilder implements DirectedGraphVisitor<V> {
+ Stack<DepthString> depthString = new Stack<DepthString>();
+
+ @Override
+ public void visit(Stack<V> stack, V vertex) {
+ depthString.push(new DepthString(stack.size(), vertex.toString()));
+ }
+
+ public Stack<DepthString> getDepthStrings() {
+ return depthString;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleUndirectedGraph.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleUndirectedGraph.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleUndirectedGraph.java
new file mode 100644
index 0000000..3822ad7
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/SimpleUndirectedGraph.java
@@ -0,0 +1,102 @@
+/**
+ * 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.graph;
+
+import com.google.common.collect.Lists;
+import org.apache.tajo.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation is based on a directed graph implementation. Each edge is connected in bidirection
+ * between two vertices.
+ *
+ * @param <V> Vertex Class
+ * @param <E> Edge Class
+ */
+public class SimpleUndirectedGraph<V, E> extends SimpleDirectedGraph<V, E> implements UndirectedGraph<V, E> {
+
+ @Override
+ public @Nullable E getEdge(V tail, V head) {
+ E edge = super.getEdge(tail, head);
+ if (edge != null) {
+ return edge;
+ }
+ edge = super.getEdge(head, tail);
+ if (edge != null) {
+ return edge;
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<E> getEdges(V v) {
+ List<E> edges = Lists.newArrayList();
+ List<E> outgoingEdges = getOutgoingEdges(v);
+ if (outgoingEdges != null) {
+ edges.addAll(outgoingEdges);
+ }
+ List<E> incomingEdges = getIncomingEdges(v);
+ if (incomingEdges != null) {
+ edges.addAll(incomingEdges);
+ }
+ return edges;
+ }
+
+ @Override
+ public int getDegree(V v) {
+ return getEdges(v).size();
+ }
+
+ @Override
+ public Collection<E> getEdgesAll() {
+ List<E> edges = Lists.newArrayList();
+ for (Map<V, E> map : directedEdges.values()) {
+ edges.addAll(map.values());
+ }
+ return edges;
+ }
+
+ @Override
+ public V getParent(V v, int idx) {
+ throw new UnsupportedOperationException("Cannot support getParent(V v) in UndirectedGraph");
+ }
+
+ @Override
+ public int getParentCount(V v) {
+ throw new UnsupportedOperationException("Cannot support getParent(V v) in UndirectedGraph");
+ }
+
+ @Override
+ public List<V> getParents(V v) {
+ throw new UnsupportedOperationException("Cannot support getParent(V v) in UndirectedGraph");
+ }
+
+ @Override
+ public boolean isRoot(V v) {
+ throw new UnsupportedOperationException("Cannot support isRoot(V v) in UndirectedGraph");
+ }
+
+ @Override
+ public boolean isLeaf(V v) {
+ throw new UnsupportedOperationException("Cannot support isLeaf(V v) in UndirectedGraph");
+ }
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/UndirectedGraph.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/UndirectedGraph.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/UndirectedGraph.java
new file mode 100644
index 0000000..7f74cd1
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/graph/UndirectedGraph.java
@@ -0,0 +1,30 @@
+/**
+ * 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.graph;
+
+
+import org.apache.tajo.annotation.NotNull;
+
+import java.util.Collection;
+
+public interface UndirectedGraph<V, E> extends Graph<V, E> {
+ Collection<E> getEdges(@NotNull V v);
+
+ int getDegree(@NotNull V v);
+}
http://git-wip-us.apache.org/repos/asf/tajo/blob/6594ac1c/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/AlterTableNode.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/AlterTableNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/AlterTableNode.java
new file mode 100644
index 0000000..76a47d0
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/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.engine.planner.logical;
+
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.algebra.AlterTableOpType;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.engine.planner.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);
+ }
+}