/** * 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.verifier; import com.google.common.base.Preconditions; import org.apache.tajo.catalog.CatalogUtil; import org.apache.tajo.catalog.Column; import org.apache.tajo.catalog.Schema; import org.apache.tajo.common.TajoDataTypes.Type; import org.apache.tajo.error.Errors; import org.apache.tajo.exception.TajoException; import org.apache.tajo.exception.TajoInternalError; import org.apache.tajo.exception.UnsupportedException; import org.apache.tajo.plan.LogicalPlan; import org.apache.tajo.plan.Target; import org.apache.tajo.plan.logical.*; import org.apache.tajo.plan.util.PlannerUtil; import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor; import org.apache.tajo.util.TUtil; import java.util.Stack; import static org.apache.tajo.plan.verifier.SyntaxErrorUtil.*; public class LogicalPlanVerifier extends BasicLogicalPlanVisitor<LogicalPlanVerifier.Context, LogicalNode> { public LogicalPlanVerifier() { } public static class Context { VerificationState state; public Context(VerificationState state) { this.state = state; } } public VerificationState verify(VerificationState state, LogicalPlan plan) throws TajoException { Context context = new Context(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(Context context, Projectable node) throws TajoException { Schema outputSchema = node.getOutSchema(); Schema targetSchema = PlannerUtil.targetToSchema(node.getTargets()); if (outputSchema.size() != node.getTargets().size()) { throw new TajoInternalError(String.format("Output schema and Target's schema are mismatched at Node (%d)", + node.getPID())); } for (int i = 0; i < outputSchema.size(); i++) { Column outputColumn = outputSchema.getColumn(i); if (outputColumn.getDataType().getType() == Type.RECORD) { context.state.addVerification(new UnsupportedException("record field in select list")); } if (!outputColumn.getDataType().equals(targetSchema.getColumn(i).getDataType())) { Column targetColumn = targetSchema.getColumn(i); Column insertColumn = outputColumn; throw new TajoInternalError(SyntaxErrorUtil.makeDataTypeMisMatch(insertColumn, targetColumn)); } } } @Override public LogicalNode visitProjection(Context state, LogicalPlan plan, LogicalPlan.QueryBlock block, ProjectionNode node, Stack<LogicalNode> stack) throws TajoException { super.visitProjection(state, plan, block, node, stack); for (Target target : node.getTargets()) { ExprsVerifier.verify(state.state, node, target.getEvalTree()); } verifyProjectableOutputSchema(state, node); return node; } @Override public LogicalNode visitLimit(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, LimitNode node, Stack<LogicalNode> stack) throws TajoException { super.visitLimit(context, plan, block, node, stack); if (node.getFetchFirstNum() < 0) { context.state.addVerification(makeSyntaxError("LIMIT must not be negative")); } return node; } @Override public LogicalNode visitGroupBy(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, GroupbyNode node, Stack<LogicalNode> stack) throws TajoException { super.visitGroupBy(context, plan, block, node, stack); verifyProjectableOutputSchema(context, node); return node; } @Override public LogicalNode visitFilter(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, SelectionNode node, Stack<LogicalNode> stack) throws TajoException { 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 TajoException { 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(context, 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(new TajoException(Errors.ResultCode.AMBIGUOUS_FUNCTION)); 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( makeSetOpDataTypeMisMatch(type, leftColumns[i].getDataType(), rightColumns[i].getDataType()) ); } } } @Override public LogicalNode visitUnion(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, UnionNode node, Stack<LogicalNode> stack) throws TajoException { 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 TajoException { 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 TajoException { super.visitIntersect(context, plan, block, node, stack); verifySetStatement(context.state, node); return node; } @Override public LogicalNode visitTableSubQuery(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, TableSubQueryNode node, Stack<LogicalNode> stack) throws TajoException { super.visitTableSubQuery(context, plan, block, node, stack); if (node.hasTargets()) { for (Target target : node.getTargets()) { ExprsVerifier.verify(context.state, node, target.getEvalTree()); } } verifyProjectableOutputSchema(context, node); return node; } @Override public LogicalNode visitScan(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, ScanNode node, Stack<LogicalNode> stack) throws TajoException { 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(context, node); return node; } @Override public LogicalNode visitStoreTable(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, StoreTableNode node, Stack<LogicalNode> stack) throws TajoException { 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 TajoException { 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 TajoException { for (int i = 0; i < schema.size(); i++) { // null can be used anywhere if (schema.getColumn(i).getDataType().getType() == Type.NULL_TYPE) { continue; } // checking castable between two data types if (!TUtil.containsInNestedMap(CatalogUtil.OPERATION_CASTING_MAP, schema.getColumn(i).getDataType().getType(), targetTableScheme.getColumn(i).getDataType().getType())) { Column targetColumn = targetTableScheme.getColumn(i); Column insertColumn = schema.getColumn(i); state.addVerification(makeDataTypeMisMatch(insertColumn, targetColumn)); } } } @Override public LogicalNode visitCreateTable(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block, CreateTableNode node, Stack<LogicalNode> stack) throws TajoException { super.visitCreateTable(context, plan, block, node, stack); // here, we don't need check table existence because this check is performed in PreLogicalPlanVerifier. if (node.hasSubQuery()) { ensureDomains(context.state, node.getLogicalSchema(), node.getChild(0).getOutSchema()); } 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; } }