blob: 2e3c1c198a0df317963831eea7f86b16922401ac [file] [log] [blame]
/**
* 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 org.apache.tajo.algebra.*;
import org.apache.tajo.catalog.CatalogService;
import org.apache.tajo.catalog.CatalogUtil;
import org.apache.tajo.catalog.Column;
import org.apache.tajo.catalog.FunctionDesc;
import org.apache.tajo.catalog.exception.NoSuchFunctionException;
import org.apache.tajo.catalog.proto.CatalogProtos;
import org.apache.tajo.common.TajoDataTypes;
import org.apache.tajo.engine.utils.DataTypeUtil;
import java.util.Stack;
import static org.apache.tajo.common.TajoDataTypes.DataType;
import static org.apache.tajo.common.TajoDataTypes.Type.BOOLEAN;
import static org.apache.tajo.common.TajoDataTypes.Type.NULL_TYPE;
import static org.apache.tajo.engine.planner.LogicalPlanPreprocessor.PreprocessContext;
public class TypeDeterminant extends SimpleAlgebraVisitor<PreprocessContext, DataType> {
private DataType BOOL_TYPE = CatalogUtil.newSimpleDataType(BOOLEAN);
private CatalogService catalog;
public TypeDeterminant(CatalogService catalog) {
this.catalog = catalog;
}
public DataType determineDataType(PreprocessContext ctx, Expr expr) throws PlanningException {
return visit(ctx, new Stack<Expr>(), expr);
}
@Override
public DataType visitUnaryOperator(PreprocessContext ctx, Stack<Expr> stack, UnaryOperator expr) throws PlanningException {
stack.push(expr);
DataType dataType = null;
switch (expr.getType()) {
case IsNullPredicate:
case ExistsPredicate:
dataType = BOOL_TYPE;
break;
case Cast:
dataType = LogicalPlanner.convertDataType(((CastExpr)expr).getTarget());
break;
default:
dataType = visit(ctx, stack, expr.getChild());
}
return dataType;
}
@Override
public DataType visitBinaryOperator(PreprocessContext ctx, Stack<Expr> stack, BinaryOperator expr)
throws PlanningException {
stack.push(expr);
DataType lhsType = visit(ctx, stack, expr.getLeft());
DataType rhsType = visit(ctx, stack, expr.getRight());
stack.pop();
return computeBinaryType(expr.getType(), lhsType, rhsType);
}
public DataType computeBinaryType(OpType type, DataType lhsDataType, DataType rhsDataType) throws PlanningException {
if(OpType.isLogicalType(type) || OpType.isComparisonType(type)) {
return BOOL_TYPE;
} else if (OpType.isArithmeticType(type)) {
return DataTypeUtil.determineType(lhsDataType, rhsDataType);
} else if (type == OpType.Concatenate) {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.TEXT);
} else if (type == OpType.InPredicate) {
return BOOL_TYPE;
} else if (type == OpType.LikePredicate || type == OpType.SimilarToPredicate || type == OpType.Regexp) {
return BOOL_TYPE;
} else {
throw new PlanningException(type.name() + "is not binary type");
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Other Predicates Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitBetween(PreprocessContext ctx, Stack<Expr> stack, BetweenPredicate expr)
throws PlanningException {
return CatalogUtil.newSimpleDataType(BOOLEAN);
}
@Override
public DataType visitCaseWhen(PreprocessContext ctx, Stack<Expr> stack, CaseWhenPredicate caseWhen)
throws PlanningException {
DataType lastDataType = null;
for (CaseWhenPredicate.WhenExpr when : caseWhen.getWhens()) {
DataType resultType = visit(ctx, stack, when.getResult());
if (lastDataType != null) {
lastDataType = ExprAnnotator.getWidestType(lastDataType, resultType);
} else {
lastDataType = resultType;
}
}
if (caseWhen.hasElseResult()) {
DataType elseResultType = visit(ctx, stack, caseWhen.getElseResult());
lastDataType = ExprAnnotator.getWidestType(lastDataType, elseResultType);
}
return lastDataType;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Other Expressions
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitColumnReference(PreprocessContext ctx, Stack<Expr> stack, ColumnReferenceExpr expr)
throws PlanningException {
stack.push(expr);
Column column = ctx.plan.resolveColumn(ctx.currentBlock, expr);
stack.pop();
return column.getDataType();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// General Set Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitFunction(PreprocessContext ctx, Stack<Expr> stack, FunctionExpr expr) throws PlanningException {
stack.push(expr); // <--- Push
// Given parameters
Expr[] params = expr.getParams();
if (params == null) {
params = new Expr[0];
}
DataType[] givenArgs = new DataType[params.length];
DataType[] paramTypes = new DataType[params.length];
for (int i = 0; i < params.length; i++) {
givenArgs[i] = visit(ctx, stack, params[i]);
paramTypes[i] = givenArgs[i];
}
stack.pop(); // <--- Pop
if (!catalog.containFunction(expr.getSignature(), paramTypes)) {
throw new NoSuchFunctionException(expr.getSignature(), paramTypes);
}
FunctionDesc funcDesc = catalog.getFunction(expr.getSignature(), paramTypes);
return funcDesc.getReturnType();
}
@Override
public DataType visitCountRowsFunction(PreprocessContext ctx, Stack<Expr> stack, CountRowsFunctionExpr expr)
throws PlanningException {
FunctionDesc countRows = catalog.getFunction("count", CatalogProtos.FunctionType.AGGREGATION,
new DataType[] {});
return countRows.getReturnType();
}
@Override
public DataType visitGeneralSetFunction(PreprocessContext ctx, Stack<Expr> stack, GeneralSetFunctionExpr setFunction)
throws PlanningException {
stack.push(setFunction);
Expr[] params = setFunction.getParams();
DataType[] givenArgs = new DataType[params.length];
DataType[] paramTypes = new DataType[params.length];
CatalogProtos.FunctionType functionType = setFunction.isDistinct() ?
CatalogProtos.FunctionType.DISTINCT_AGGREGATION : CatalogProtos.FunctionType.AGGREGATION;
givenArgs[0] = visit(ctx, stack, params[0]);
if (setFunction.getSignature().equalsIgnoreCase("count")) {
paramTypes[0] = CatalogUtil.newSimpleDataType(TajoDataTypes.Type.ANY);
} else {
paramTypes[0] = givenArgs[0];
}
stack.pop(); // <-- pop
if (!catalog.containFunction(setFunction.getSignature(), functionType, paramTypes)) {
throw new NoSuchFunctionException(setFunction.getSignature(), paramTypes);
}
FunctionDesc funcDesc = catalog.getFunction(setFunction.getSignature(), functionType, paramTypes);
return funcDesc.getReturnType();
}
@Override
public DataType visitWindowFunction(PreprocessContext ctx, Stack<Expr> stack, WindowFunctionExpr windowFunc)
throws PlanningException {
stack.push(windowFunc); // <--- Push
String funcName = windowFunc.getSignature();
boolean distinct = windowFunc.isDistinct();
Expr[] params = windowFunc.getParams();
DataType[] givenArgs = new DataType[params.length];
TajoDataTypes.DataType[] paramTypes = new TajoDataTypes.DataType[params.length];
CatalogProtos.FunctionType functionType;
// Rewrite parameters if necessary
if (params.length > 0) {
givenArgs[0] = visit(ctx, stack, params[0]);
if (windowFunc.getSignature().equalsIgnoreCase("count")) {
paramTypes[0] = CatalogUtil.newSimpleDataType(TajoDataTypes.Type.ANY);
} else if (windowFunc.getSignature().equalsIgnoreCase("row_number")) {
paramTypes[0] = CatalogUtil.newSimpleDataType(TajoDataTypes.Type.INT8);
} else {
paramTypes[0] = givenArgs[0];
}
}
stack.pop(); // <--- Pop
// TODO - containFunction and getFunction should support the function type mask which provides ORing multiple types.
// the below checking against WINDOW_FUNCTIONS is a workaround code for the above problem.
if (ExprAnnotator.WINDOW_FUNCTIONS.contains(funcName.toLowerCase())) {
if (distinct) {
throw new NoSuchFunctionException("row_number() does not support distinct keyword.");
}
functionType = CatalogProtos.FunctionType.WINDOW;
} else {
functionType = distinct ?
CatalogProtos.FunctionType.DISTINCT_AGGREGATION : CatalogProtos.FunctionType.AGGREGATION;
}
if (!catalog.containFunction(windowFunc.getSignature(), functionType, paramTypes)) {
throw new NoSuchFunctionException(funcName, paramTypes);
}
FunctionDesc funcDesc = catalog.getFunction(funcName, functionType, paramTypes);
return funcDesc.getReturnType();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Literal Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitDataType(PreprocessContext ctx, Stack<Expr> stack, DataTypeExpr expr) throws PlanningException {
return LogicalPlanner.convertDataType(expr);
}
@Override
public DataType visitLiteral(PreprocessContext ctx, Stack<Expr> stack, LiteralValue expr) throws PlanningException {
// It must be the same to ExprAnnotator::visitLiteral.
switch (expr.getValueType()) {
case Boolean:
return CatalogUtil.newSimpleDataType(BOOLEAN);
case String:
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.TEXT);
case Unsigned_Integer:
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.INT4);
case Unsigned_Large_Integer:
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.INT8);
case Unsigned_Float:
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.FLOAT8);
default:
throw new RuntimeException("Unsupported type: " + expr.getValueType());
}
}
@Override
public DataType visitNullLiteral(PreprocessContext ctx, Stack<Expr> stack, NullLiteral expr)
throws PlanningException {
return CatalogUtil.newSimpleDataType(NULL_TYPE);
}
@Override
public DataType visitTimestampLiteral(PreprocessContext ctx, Stack<Expr> stack, TimestampLiteral expr)
throws PlanningException {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.TIMESTAMP);
}
@Override
public DataType visitTimeLiteral(PreprocessContext ctx, Stack<Expr> stack, TimeLiteral expr)
throws PlanningException {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.TIME);
}
}