/**
* 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.collect.ObjectArrays;
import org.apache.tajo.algebra.*;
import org.apache.tajo.catalog.CatalogService;
import org.apache.tajo.catalog.proto.CatalogProtos;
import org.apache.tajo.util.TUtil;
import java.util.Arrays;
import java.util.Set;
import java.util.Stack;
public class PreLogicalPlanVerifier extends BaseAlgebraVisitor <VerificationState, Expr> {
private CatalogService catalog;
public PreLogicalPlanVerifier(CatalogService catalog) {
this.catalog = catalog;
}
public Expr visitProjection(VerificationState state, Stack<Expr> stack, Projection expr) throws PlanningException {
super.visitProjection(state, stack, expr);
Set<String> names = TUtil.newHashSet();
Expr [] distinctValues = null;
for (NamedExpr namedExpr : expr.getNamedExprs()) {
if (namedExpr.hasAlias()) {
if (names.contains(namedExpr.getAlias())) {
state.addVerification(String.format("column name \"%s\" specified more than once", namedExpr.getAlias()));
} else {
names.add(namedExpr.getAlias());
}
}
// no two aggregations can have different DISTINCT columns.
//
// For example, the following query will work
// SELECT count(DISTINCT col1) and sum(DISTINCT col1) ..
//
// But, the following query will not work in this time
//
// SELECT count(DISTINCT col1) and SUM(DISTINCT col2) ..
Set<GeneralSetFunctionExpr> exprs = ExprFinder.finds(namedExpr.getExpr(), OpType.GeneralSetFunction);
if (exprs.size() > 0) {
for (GeneralSetFunctionExpr setFunction : exprs) {
if (distinctValues == null && setFunction.isDistinct()) {
distinctValues = setFunction.getParams();
} else if (distinctValues != null) {
if (!Arrays.equals(distinctValues, setFunction.getParams())) {
Expr [] differences = ObjectArrays.concat(distinctValues, setFunction.getParams(), Expr.class);
throw new PlanningException("different DISTINCT columns are not supported yet: "
+ TUtil.arrayToString(differences));
}
}
}
}
// Currently, avg functions with distinct aggregation are not supported.
// This code does not allow users to use avg functions with distinct aggregation.
if (distinctValues != null) {
for (GeneralSetFunctionExpr setFunction : exprs) {
if (setFunction.getSignature().equalsIgnoreCase("avg")) {
if (setFunction.isDistinct()) {
throw new PlanningException("avg(distinct) function is not supported yet.");
} else {
throw new PlanningException("avg() function with distinct aggregation functions is not supported yet.");
}
}
}
}
}
return expr;
}
@Override
public Expr visitGroupBy(VerificationState ctx, Stack<Expr> stack, Aggregation expr) throws PlanningException {
super.visitGroupBy(ctx, stack, expr);
// Enforcer only ordinary grouping set.
for (Aggregation.GroupElement groupingElement : expr.getGroupSet()) {
if (groupingElement.getType() != Aggregation.GroupType.OrdinaryGroup) {
ctx.addVerification(groupingElement.getType() + " is not supported yet");
}
}
Projection projection = null;
for (Expr parent : stack) {
if (parent.getType() == OpType.Projection) {
projection = (Projection) parent;
break;
}
}
if (projection == null) {
throw new PlanningException("No Projection");
}
return expr;
}
@Override
public Expr visitRelation(VerificationState state, Stack<Expr> stack, Relation expr) throws PlanningException {
assertRelationExistence(state, expr.getName());
return expr;
}
private boolean assertRelationExistence(VerificationState state, String name) {
if (!catalog.existsTable(name)) {
state.addVerification(String.format("relation \"%s\" does not exist", name));
return false;
}
return true;
}
private boolean assertRelationNoExistence(VerificationState state, String name) {
if (catalog.existsTable(name)) {
state.addVerification(String.format("relation \"%s\" already exists", name));
return false;
}
return true;
}
private boolean assertUnsupportedStoreType(VerificationState state, String name) {
if (name != null && name.equals(CatalogProtos.StoreType.RAW.name())) {
state.addVerification(String.format("Unsupported store type :%s", name));
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Data Definition Language Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public Expr visitCreateTable(VerificationState state, Stack<Expr> stack, CreateTable expr) throws PlanningException {
super.visitCreateTable(state, stack, expr);
assertRelationNoExistence(state, expr.getTableName());
assertUnsupportedStoreType(state, expr.getStorageType());
return expr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Insert or Update Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
public Expr visitInsert(VerificationState state, Stack<Expr> stack, Insert expr) throws PlanningException {
Expr child = super.visitInsert(state, stack, expr);
if (expr.hasTableName()) {
assertRelationExistence(state, expr.getTableName());
}
if (child != null && child.getType() == OpType.Projection) {
if (expr.hasTargetColumns()) {
Projection projection = (Projection) child;
int projectColumnNum = projection.getNamedExprs().length;
int targetColumnNum = expr.getTargetColumns().length;
if (targetColumnNum > projectColumnNum) {
state.addVerification("INSERT has more target columns than expressions");
} else if (targetColumnNum < projectColumnNum) {
state.addVerification("INSERT has more expressions than target columns");
}
}
}
return expr;
}
}