blob: f67b50f839e783e820a50f7e171b03abdba39740 [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.plan;
import com.google.common.base.Preconditions;
import org.apache.tajo.DataTypeUtil;
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.proto.CatalogProtos;
import org.apache.tajo.common.TajoDataTypes;
import org.apache.tajo.exception.TajoException;
import org.apache.tajo.exception.TajoInternalError;
import org.apache.tajo.exception.UndefinedFunctionException;
import org.apache.tajo.function.FunctionUtil;
import org.apache.tajo.plan.nameresolver.NameResolver;
import org.apache.tajo.plan.nameresolver.NameResolvingMode;
import org.apache.tajo.plan.visitor.SimpleAlgebraVisitor;
import org.apache.tajo.type.Type;
import java.util.Stack;
import static org.apache.tajo.catalog.TypeConverter.convert;
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.function.FunctionUtil.buildSimpleFunctionSignature;
public class TypeDeterminant extends SimpleAlgebraVisitor<LogicalPlanner.PlanContext, DataType> {
private DataType BOOL_TYPE = CatalogUtil.newSimpleDataType(BOOLEAN);
private CatalogService catalog;
public TypeDeterminant(CatalogService catalog) {
this.catalog = catalog;
}
public DataType determineDataType(LogicalPlanner.PlanContext ctx, Expr expr) throws TajoException {
return visit(ctx, new Stack<>(), expr);
}
@Override
public DataType visitUnaryOperator(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, UnaryOperator expr)
throws TajoException {
stack.push(expr);
DataType dataType;
switch (expr.getType()) {
case IsNullPredicate:
case ExistsPredicate:
dataType = BOOL_TYPE;
break;
case Cast:
dataType = convert(LogicalPlanner.convertDataType(((CastExpr)expr).getTarget())).getDataType();
break;
default:
dataType = visit(ctx, stack, expr.getChild());
}
return dataType;
}
@Override
public DataType visitBinaryOperator(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, BinaryOperator expr)
throws TajoException {
stack.push(expr);
DataType lhsType = visit(ctx, stack, expr.getLeft());
DataType rhsType = visit(ctx, stack, expr.getRight());
stack.pop();
return convert(computeBinaryType(expr.getType(), convert(lhsType), convert(rhsType))).getDataType();
}
public Type computeBinaryType(OpType type, Type lhsDataType, Type rhsDataType) throws TajoException {
Preconditions.checkNotNull(type);
Preconditions.checkNotNull(lhsDataType);
Preconditions.checkNotNull(rhsDataType);
if(OpType.isLogicalType(type) || OpType.isComparisonType(type)) {
return Type.Bool;
} else if (OpType.isArithmeticType(type)) {
return DataTypeUtil.determineType(lhsDataType, rhsDataType);
} else if (type == OpType.Concatenate) {
return Type.Text;
} else if (type == OpType.InPredicate) {
return Type.Bool;
} else if (type == OpType.LikePredicate || type == OpType.SimilarToPredicate || type == OpType.Regexp) {
return Type.Bool;
} else {
throw new TajoInternalError(type.name() + "is not binary type");
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Other Predicates Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitBetween(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, BetweenPredicate expr)
throws TajoException {
return CatalogUtil.newSimpleDataType(BOOLEAN);
}
@Override
public DataType visitCaseWhen(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, CaseWhenPredicate caseWhen)
throws TajoException {
DataType lastDataType = null;
for (CaseWhenPredicate.WhenExpr when : caseWhen.getWhens()) {
DataType resultType = visit(ctx, stack, when.getResult());
if (lastDataType != null) {
lastDataType = CatalogUtil.getWidestType(lastDataType, resultType);
} else {
lastDataType = resultType;
}
}
if (caseWhen.hasElseResult()) {
DataType elseResultType = visit(ctx, stack, caseWhen.getElseResult());
lastDataType = CatalogUtil.getWidestType(lastDataType, elseResultType);
}
return lastDataType;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Other Expressions
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitColumnReference(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, ColumnReferenceExpr expr)
throws TajoException {
stack.push(expr);
Column column = NameResolver.resolve(ctx.plan, ctx.queryBlock, expr, NameResolvingMode.LEGACY, true);
stack.pop();
return column.getDataType();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// General Set Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitFunction(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, FunctionExpr expr)
throws TajoException {
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 UndefinedFunctionException(FunctionUtil.buildSimpleFunctionSignature(expr.getSignature(), paramTypes));
}
FunctionDesc funcDesc = catalog.getFunction(expr.getSignature(), paramTypes);
return funcDesc.getReturnType();
}
@Override
public DataType visitCountRowsFunction(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, CountRowsFunctionExpr expr)
throws TajoException {
FunctionDesc countRows = catalog.getFunction("count", CatalogProtos.FunctionType.AGGREGATION,
new DataType[] {});
return countRows.getReturnType();
}
@Override
public DataType visitGeneralSetFunction(LogicalPlanner.PlanContext ctx, Stack<Expr> stack,
GeneralSetFunctionExpr setFunction) throws TajoException {
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 UndefinedFunctionException(buildSimpleFunctionSignature(setFunction.getSignature(), paramTypes));
}
FunctionDesc funcDesc = catalog.getFunction(setFunction.getSignature(), functionType, paramTypes);
return funcDesc.getReturnType();
}
@Override
public DataType visitWindowFunction(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, WindowFunctionExpr windowFunc)
throws TajoException {
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];
}
for (int i = 1; i < params.length; i++) {
givenArgs[i] = visit(ctx, stack, params[i]);
paramTypes[i] = givenArgs[i];
}
}
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 UndefinedFunctionException("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 UndefinedFunctionException(FunctionUtil.buildSimpleFunctionSignature(funcName, paramTypes));
}
FunctionDesc funcDesc = catalog.getFunction(funcName, functionType, paramTypes);
return funcDesc.getReturnType();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Literal Section
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public DataType visitDataType(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, DataTypeExpr expr)
throws TajoException {
return convert(LogicalPlanner.convertDataType(expr)).getDataType();
}
@Override
public DataType visitLiteral(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, LiteralValue expr)
throws TajoException {
// 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(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, NullLiteral expr)
throws TajoException {
return CatalogUtil.newSimpleDataType(NULL_TYPE);
}
@Override
public DataType visitTimestampLiteral(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, TimestampLiteral expr)
throws TajoException {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.TIMESTAMP);
}
@Override
public DataType visitTimeLiteral(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, TimeLiteral expr)
throws TajoException {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.TIME);
}
@Override
public DataType visitDateLiteral(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, DateLiteral expr)
throws TajoException {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.DATE);
}
@Override
public DataType visitIntervalLiteral(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, IntervalLiteral expr)
throws TajoException {
return CatalogUtil.newSimpleDataType(TajoDataTypes.Type.INTERVAL);
}
}