/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tajo.engine.planner; import com.google.common.base.Preconditions; import org.apache.tajo.catalog.CatalogService; import org.apache.tajo.catalog.Column; import org.apache.tajo.catalog.Schema; import org.apache.tajo.conf.TajoConf; import org.apache.tajo.engine.planner.logical.*; import org.apache.tajo.master.session.Session; import java.util.Stack; public class LogicalPlanVerifier extends BasicLogicalPlanVisitor<LogicalPlanVerifier.Context, LogicalNode> { private TajoConf conf; private CatalogService catalog; public LogicalPlanVerifier(TajoConf conf, CatalogService catalog) { this.conf = conf; this.catalog = catalog; } public static class Context { Session session; VerificationState state; public Context(Session session, VerificationState state) { this.session = session; this.state = state; } } public VerificationState verify(Session session, VerificationState state, LogicalPlan plan) throws PlanningException { Context context = new Context(session, state); visit(context, plan, plan.getRootBlock()); return context.state; } /** * It checks if an output schema of a projectable node and target's output data types are equivalent to each other. */ private static void verifyProjectableOutputSchema(Projectable node) throws PlanningException { Schema outputSchema = node.getOutSchema(); Schema targetSchema = PlannerUtil.targetToSchema(node.getTargets()); if (outputSchema.size() != node.getTargets().length) { throw new PlanningException(String.format("Output schema and Target's schema are mismatched at Node (%d)", + node.getPID())); } for (int i = 0; i < outputSchema.size(); i++) { if (!outputSchema.getColumn(i).getDataType().equals(targetSchema.getColumn(i).getDataType())) { Column targetColumn = targetSchema.getColumn(i); Column insertColumn = outputSchema.getColumn(i); throw new PlanningException("ERROR: " + insertColumn.getSimpleName() + " is of type " + insertColumn.getDataType().getType().name() + ", but target column '" + targetColumn.getSimpleName() + "' is of type " + targetColumn.getDataType().getType().name()); } } } @Override public LogicalNode visitProjection(Context state, LogicalPlan plan, LogicalPlan.QueryBlock block, ProjectionNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitProjection(state, plan, block, node, stack); for (Target target : node.getTargets()) { ExprsVerifier.verify(state.state, node, target.getEvalTree()); } verifyProjectableOutputSchema(node); return node; } @Override public LogicalNode visitLimit(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, LimitNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitLimit(context, plan, block, node, stack); if (node.getFetchFirstNum() < 0) { context.state.addVerification("LIMIT must not be negative"); } return node; } @Override public LogicalNode visitGroupBy(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, GroupbyNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitGroupBy(context, plan, block, node, stack); verifyProjectableOutputSchema(node); return node; } @Override public LogicalNode visitFilter(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, SelectionNode node, Stack<LogicalNode> stack) throws PlanningException { visit(context, plan, block, node.getChild(), stack); ExprsVerifier.verify(context.state, node, node.getQual()); return node; } @Override public LogicalNode visitJoin(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, JoinNode node, Stack<LogicalNode> stack) throws PlanningException { visit(context, plan, block, node.getLeftChild(), stack); visit(context, plan, block, node.getRightChild(), stack); if (node.hasJoinQual()) { ExprsVerifier.verify(context.state, node, node.getJoinQual()); } verifyProjectableOutputSchema(node); return node; } private void verifySetStatement(VerificationState state, BinaryNode setNode) { Preconditions.checkArgument(setNode.getType() == NodeType.UNION || setNode.getType() == NodeType.INTERSECT || setNode.getType() == NodeType.EXCEPT); Schema left = setNode.getLeftChild().getOutSchema(); Schema right = setNode.getRightChild().getOutSchema(); NodeType type = setNode.getType(); if (left.size() != right.size()) { state.addVerification("each " + type.name() + " query must have the same number of columns"); return; } Column[] leftColumns = left.toArray(); Column[] rightColumns = right.toArray(); for (int i = 0; i < leftColumns.length; i++) { if (!leftColumns[i].getDataType().equals(rightColumns[i].getDataType())) { state.addVerification(type + " types " + leftColumns[i].getDataType().getType() + " and " + rightColumns[i].getDataType().getType() + " cannot be matched"); } } } @Override public LogicalNode visitUnion(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, UnionNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitUnion(context, plan, block, node, stack); verifySetStatement(context.state, node); return node; } @Override public LogicalNode visitExcept(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, ExceptNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitExcept(context, plan, block, node, stack); verifySetStatement(context.state, node); return node; } @Override public LogicalNode visitIntersect(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, IntersectNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitIntersect(context, plan, block, node, stack); verifySetStatement(context.state, node); return node; } @Override public LogicalNode visitScan(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, ScanNode node, Stack<LogicalNode> stack) throws PlanningException { if (node.hasTargets()) { for (Target target : node.getTargets()) { ExprsVerifier.verify(context.state, node, target.getEvalTree()); } } if (node.hasQual()) { ExprsVerifier.verify(context.state, node, node.getQual()); } verifyProjectableOutputSchema(node); return node; } @Override public LogicalNode visitStoreTable(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, StoreTableNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitStoreTable(context, plan, block, node, stack); return node; } @Override public LogicalNode visitInsert(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, InsertNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitInsert(context, plan, block, node, stack); return node; } /** * This ensures that corresponding columns in both tables are equivalent to each other. */ private static void ensureDomains(VerificationState state, Schema targetTableScheme, Schema schema) throws PlanningException { for (int i = 0; i < schema.size(); i++) { if (!schema.getColumn(i).getDataType().equals(targetTableScheme.getColumn(i).getDataType())) { Column targetColumn = targetTableScheme.getColumn(i); Column insertColumn = schema.getColumn(i); state.addVerification("ERROR: " + insertColumn.getSimpleName() + " is of type " + insertColumn.getDataType().getType().name() + ", but target column '" + targetColumn.getSimpleName() + "' is of type " + targetColumn.getDataType().getType().name()); } } } @Override public LogicalNode visitCreateTable(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, CreateTableNode node, Stack<LogicalNode> stack) throws PlanningException { super.visitCreateTable(context, plan, block, node, stack); // here, we don't need check table existence because this check is performed in PreLogicalPlanVerifier. return node; } @Override public LogicalNode visitDropTable(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, DropTableNode node, Stack<LogicalNode> stack) { // here, we don't need check table existence because this check is performed in PreLogicalPlanVerifier. return node; } }