/*
* Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software;Designed and Developed mainly by many Chinese
* opensource volunteers. you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 2 only, as published by the
* Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Any questions about this component can be directed to it's project Web address
* https://code.google.com/p/opencloudb/.
*
*/
package com.akiban.sql.compiler;
import com.akiban.sql.parser.*;
import com.akiban.sql.StandardException;
import com.akiban.sql.types.DataTypeDescriptor;
import com.akiban.sql.types.TypeId;
/**
* This class implements TypeId for the SQL numeric datatype.
*
*/
public final class NumericTypeCompiler extends TypeCompiler
{
protected NumericTypeCompiler(TypeId typeId) {
super(typeId);
}
/**
* @see TypeCompiler#getCorrespondingPrimitiveTypeName
*/
public String getCorrespondingPrimitiveTypeName() {
/* Only numerics and boolean get mapped to Java primitives */
int formatId = getStoredFormatIdFromTypeId();
switch (formatId) {
case TypeId.FormatIds.DOUBLE_TYPE_ID:
return "double";
case TypeId.FormatIds.INT_TYPE_ID:
return "int";
case TypeId.FormatIds.LONGINT_TYPE_ID:
return "long";
case TypeId.FormatIds.REAL_TYPE_ID:
return "float";
case TypeId.FormatIds.SMALLINT_TYPE_ID:
return "short";
case TypeId.FormatIds.TINYINT_TYPE_ID:
return "byte";
case TypeId.FormatIds.DECIMAL_TYPE_ID:
default:
assert false : "unexpected formatId in getCorrespondingPrimitiveTypeName() - " +
formatId;
return null;
}
}
/**
* Get the method name for getting out the corresponding primitive
* Java type.
*
* @return String The method call name for getting the
* corresponding primitive Java type.
*/
public String getPrimitiveMethodName() {
int formatId = getStoredFormatIdFromTypeId();
switch (formatId) {
case TypeId.FormatIds.DOUBLE_TYPE_ID:
return "getDouble";
case TypeId.FormatIds.INT_TYPE_ID:
return "getInt";
case TypeId.FormatIds.LONGINT_TYPE_ID:
return "getLong";
case TypeId.FormatIds.REAL_TYPE_ID:
return "getFloat";
case TypeId.FormatIds.SMALLINT_TYPE_ID:
return "getShort";
case TypeId.FormatIds.TINYINT_TYPE_ID:
return "getByte";
case TypeId.FormatIds.DECIMAL_TYPE_ID:
default:
assert false : "unexpected formatId in getPrimitiveMethodName() - " + formatId;
return null;
}
}
/**
* @see TypeCompiler#getCastToCharWidth
*/
public int getCastToCharWidth(DataTypeDescriptor dts) {
int formatId = getStoredFormatIdFromTypeId();
switch (formatId) {
case TypeId.FormatIds.DECIMAL_TYPE_ID:
// Need to have space for '-' and decimal point.
return dts.getPrecision() + 2;
case TypeId.FormatIds.DOUBLE_TYPE_ID:
return TypeCompiler.DOUBLE_MAXWIDTH_AS_CHAR;
case TypeId.FormatIds.INT_TYPE_ID:
return TypeCompiler.INT_MAXWIDTH_AS_CHAR;
case TypeId.FormatIds.LONGINT_TYPE_ID:
return TypeCompiler.LONGINT_MAXWIDTH_AS_CHAR;
case TypeId.FormatIds.REAL_TYPE_ID:
return TypeCompiler.REAL_MAXWIDTH_AS_CHAR;
case TypeId.FormatIds.SMALLINT_TYPE_ID:
return TypeCompiler.SMALLINT_MAXWIDTH_AS_CHAR;
case TypeId.FormatIds.TINYINT_TYPE_ID:
return TypeCompiler.TINYINT_MAXWIDTH_AS_CHAR;
default:
assert false : "unexpected formatId in getCastToCharWidth() - " + formatId;
return 0;
}
}
/**
* @see TypeCompiler#resolveArithmeticOperation
*
* @exception StandardException Thrown on error
*/
public DataTypeDescriptor resolveArithmeticOperation(DataTypeDescriptor leftType,
DataTypeDescriptor rightType,
String operator)
throws StandardException {
NumericTypeCompiler higherTC;
DataTypeDescriptor higherType;
boolean nullable;
int precision, scale, maximumWidth;
/*
** Check the right type to be sure it's a number. By convention,
** we call this method off the TypeId of the left operand, so if
** we get here, we know the left operand is a number.
*/
assert leftType.getTypeId().isNumericTypeId() : "The left type is supposed to be a number because we're resolving an arithmetic operator";
TypeId leftTypeId = leftType.getTypeId();
TypeId rightTypeId = rightType.getTypeId();
boolean supported = true;
if (!(rightTypeId.isNumericTypeId())) {
if (rightTypeId.isIntervalTypeId() &&
operator.equals(TypeCompiler.TIMES_OP)) {
// Let interval type resolve it.
return getTypeCompiler(rightTypeId).resolveArithmeticOperation(rightType, leftType, operator);
}
supported = false;
}
if (TypeCompiler.MOD_OP.equals(operator)) {
switch (leftTypeId.getJDBCTypeId()) {
case java.sql.Types.TINYINT:
case java.sql.Types.SMALLINT:
case java.sql.Types.INTEGER:
case java.sql.Types.BIGINT:
break;
default:
supported = false;
break;
}
switch (rightTypeId.getJDBCTypeId()) {
case java.sql.Types.TINYINT:
case java.sql.Types.SMALLINT:
case java.sql.Types.INTEGER:
case java.sql.Types.BIGINT:
break;
default:
supported = false;
break;
}
}
if (!supported) {
return super.resolveArithmeticOperation(leftType, rightType, operator);
}
/*
** Take left as the higher precedence if equal
*/
if (rightTypeId.typePrecedence() > leftTypeId.typePrecedence()) {
higherType = rightType;
higherTC = (NumericTypeCompiler)getTypeCompiler(rightTypeId);
}
else {
higherType = leftType;
higherTC = (NumericTypeCompiler)getTypeCompiler(leftTypeId);
}
/* The calculation of precision and scale should be based upon
* the type with higher precedence, which is going to be the result
* type, this is also to be consistent with maximumWidth. Beetle 3906.
*/
precision = higherTC.getPrecision(operator, leftType, rightType);
scale = higherTC.getScale(operator, leftType, rightType);
if (higherType.getTypeId().isDecimalTypeId()) {
maximumWidth = (scale > 0) ? precision + 3 : precision + 1;
/*
** Be careful not to overflow
*/
if (maximumWidth < precision) {
maximumWidth = Integer.MAX_VALUE;
}
}
else {
maximumWidth = higherType.getMaximumWidth();
}
/* The result is nullable if either side is nullable */
nullable = leftType.isNullable() || rightType.isNullable();
/*
** The higher type does not have the right nullability. Create a
** new DataTypeDescriptor that has the correct type and nullability.
**
** It's OK to call the implementation of the DataTypeDescriptorFactory
** here, because we're in the same package.
*/
return new DataTypeDescriptor(higherType.getTypeId(),
precision,
scale,
nullable,
maximumWidth);
}
/** @see TypeCompiler#convertible */
public boolean convertible(TypeId otherType, boolean forDataTypeFunction) {
return (numberConvertible(otherType, forDataTypeFunction));
}
/**
* Tell whether this type (numeric) is compatible with the given type.
*
* @param otherType The TypeId of the other type.
*/
public boolean compatible(TypeId otherType) {
// Numbers can only be compatible with other numbers.
return (otherType.isNumericTypeId());
}
/**
* Get the precision of the operation involving
* two of the same types. Only meaningful for
* decimals, which override this.
*
* @param operator a string representing the operator,
* null means no operator, just a type merge
* @param leftType the left type
* @param rightType the left type
*
* @return the resultant precision
*/
private int getPrecision(String operator,
DataTypeDescriptor leftType,
DataTypeDescriptor rightType) {
// Only meaningful for decimal
if (getStoredFormatIdFromTypeId() != TypeId.FormatIds.DECIMAL_TYPE_ID) {
return leftType.getPrecision();
}
long lscale = (long)leftType.getScale();
long rscale = (long)rightType.getScale();
long lprec = (long)leftType.getPrecision();
long rprec = (long)rightType.getPrecision();
long val;
/*
** Null means datatype merge. Take the maximum
** left of decimal digits plus the scale.
*/
if (operator == null) {
val = this.getScale(operator, leftType, rightType) +
Math.max(lprec - lscale, rprec - rscale);
}
else if (operator.equals(TypeCompiler.TIMES_OP)) {
val = lprec + rprec;
}
else if (operator.equals(TypeCompiler.SUM_OP)) {
val = lprec - lscale + rprec - rscale +
this.getScale(operator, leftType, rightType);
}
else if (operator.equals(TypeCompiler.DIVIDE_OP)) {
val = Math.min(TypeId.DECIMAL_SCALE,
this.getScale(operator, leftType, rightType) + lprec - lscale + rprec);
}
/*
** AVG, -, +
*/
else {
/*
** Take max scale and max left of decimal
** plus one.
*/
val = this.getScale(operator, leftType, rightType) +
Math.max(lprec - lscale, rprec - rscale) + 1;
if (val > TypeId.DECIMAL_SCALE)
// then, like DB2, just set it to the max possible.
val = TypeId.DECIMAL_SCALE;
}
if (val > Integer.MAX_VALUE) {
val = Integer.MAX_VALUE;
}
val = Math.min(TypeId.DECIMAL_SCALE, val);
return (int)val;
}
/**
* Get the scale of the operation involving
* two of the same types. Since we don't really
* have a good way to pass the resultant scale
* and precision around at execution time, we
* will model that BigDecimal does by default.
* This is good in most cases, though we would
* probably like to use something more sophisticated
* for division.
*
* @param operator a string representing the operator,
* null means no operator, just a type merge
* @param leftType the left type
* @param rightType the left type
*
* @return the resultant precision
*/
private int getScale(String operator,
DataTypeDescriptor leftType,
DataTypeDescriptor rightType) {
// Only meaningful for decimal
if (getStoredFormatIdFromTypeId() != TypeId.FormatIds.DECIMAL_TYPE_ID) {
return leftType.getScale();
}
long lscale = (long)leftType.getScale();
long rscale = (long)rightType.getScale();
long lprec = (long)leftType.getPrecision();
long rprec = (long)rightType.getPrecision();
long val;
/*
** Retain greatest scale, take sum of left
** of decimal
*/
if (TypeCompiler.TIMES_OP.equals(operator)) {
val = lscale + rscale;
}
else if (TypeCompiler.DIVIDE_OP.equals(operator)) {
/*
** Take max left scale + right precision - right scale + 1,
** or 4, whichever is biggest
*/
// Scale: 31 - left precision + left scale - right scale
val = Math.max(TypeId.DECIMAL_SCALE - lprec + lscale - rscale, 0);
}
else if (TypeCompiler.AVG_OP.equals(operator)) {
val = Math.max(Math.max(lscale, rscale),
TypeId.DECIMAL_SCALE);
}
/*
** SUM, -, + all take max(lscale,rscale)
*/
else {
val = Math.max(lscale, rscale);
}
if (val > Integer.MAX_VALUE) {
val = Integer.MAX_VALUE;
}
val = Math.min(TypeId.DECIMAL_SCALE, val);
return (int)val;
}
}