/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.types.common.types;
import com.foundationdb.server.error.AkibanInternalException;
import com.foundationdb.server.rowdata.ConversionHelperBigDecimal;
import com.foundationdb.server.types.*;
import com.foundationdb.server.types.aksql.AkCategory;
import com.foundationdb.server.types.common.BigDecimalWrapper;
import com.foundationdb.server.types.common.BigDecimalWrapperImpl;
import com.foundationdb.server.types.common.NumericFormatter;
import com.foundationdb.server.types.mcompat.MParsers;
import com.foundationdb.server.types.value.*;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.sql.types.TypeId;
import com.foundationdb.util.AkibanAppender;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.sql.Types;
public class TBigDecimal extends TClassBase {
public static final int MAX_INDEX = 0;
public static final int MIN_INDEX = 1;
public static BigDecimalWrapper getWrapper(ValueSource source, TInstance type) {
if (source.hasCacheValue())
return (BigDecimalWrapper) source.getObject();
byte[] bytes = source.getBytes();
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
StringBuilder sb = new StringBuilder();
ConversionHelperBigDecimal.decodeToString(bytes, 0, precision, scale, AkibanAppender.of(sb));
return new BigDecimalWrapperImpl(sb.toString());
}
public static BigDecimalWrapper getWrapper(TExecutionContext context, int index) {
BigDecimalWrapper wrapper = (BigDecimalWrapper)context.exectimeObjectAt(index);
if (wrapper == null) {
wrapper = new BigDecimalWrapperImpl();
context.putExectimeObject(index, wrapper);
}
wrapper.reset();
return wrapper;
}
public static void adjustAttrsAsNeeded(TExecutionContext context, ValueSource source,
TInstance targetInstance, ValueTarget target)
{
TInstance inputInstance = source.getType();
int inputPrecision = inputInstance.attribute(DecimalAttribute.PRECISION);
int targetPrecision = targetInstance.attribute(DecimalAttribute.PRECISION);
int inputScale = inputInstance.attribute(DecimalAttribute.SCALE);
int targetScale = targetInstance.attribute(DecimalAttribute.SCALE);
if ( (inputPrecision != targetPrecision) || (inputScale != targetScale) ) {
BigDecimalWrapper bdw = getCastWrapper(source, targetInstance);
target.putObject(bdw);
}
else if (source.hasCacheValue()) {
target.putObject(source.getObject());
}
else if (source.hasRawValue()) {
target.putBytes(source.getBytes());
}
else {
throw new IllegalStateException("no value set");
}
}
private static BigDecimalWrapper getCastWrapper(ValueSource source, TInstance type) {
String normalizedValue;
if (source.hasCacheValue()) {
BigDecimal sourceValue = ((BigDecimalWrapper)source.getObject()).asBigDecimal();
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
normalizedValue = ConversionHelperBigDecimal.normalizeToString(sourceValue, precision, scale);
} else if (source.hasRawValue()) {
int precision = source.getType().attribute(DecimalAttribute.PRECISION);
int scale = source.getType().attribute(DecimalAttribute.SCALE);
StringBuilder sb = new StringBuilder();
ConversionHelperBigDecimal.decodeToString(source.getBytes(), 0, precision, scale, AkibanAppender.of(sb));
normalizedValue = sb.toString();
} else {
assert false : "Invalid ValueSource: " + source;
normalizedValue = null;
}
return new BigDecimalWrapperImpl(normalizedValue);
}
@Override
public boolean attributeIsPhysical(int attributeIndex) {
return true;
}
@Override
protected boolean attributeAlwaysDisplayed(int attributeIndex) {
return true;
}
@Override
protected ValueIO getValueIO() {
return valueIO;
}
protected TBigDecimal(TBundle bundle, String name, int defaultVarcharLen){
super(bundle.id(), name, AkCategory.DECIMAL, DecimalAttribute.class, NumericFormatter.FORMAT.BIGDECIMAL, 1, 1, -1,
UnderlyingType.BYTES, MParsers.DECIMAL, defaultVarcharLen);
}
@Override
public Object formatCachedForNiceRow(ValueSource source) {
return ((BigDecimalWrapper)source.getObject()).asBigDecimal();
}
@Override
protected int doCompare(TInstance typeA, ValueSource sourceA, TInstance typeB, ValueSource sourceB)
{
if (sourceA.hasRawValue() && sourceB.hasRawValue()) // both have bytearrays
return super.doCompare(typeA, sourceA, typeB, sourceB);
else
return getWrapper(sourceA, typeA).compareTo(getWrapper(sourceB, typeB));
}
@Override
public void selfCast(TExecutionContext context, TInstance sourceInstance, ValueSource source,
TInstance targetInstance, ValueTarget target)
{
adjustAttrsAsNeeded(context, source, targetInstance, target);
}
@Override
public boolean normalizeInstancesBeforeComparison() {
return true;
}
@Override
public int jdbcType() {
return Types.DECIMAL;
}
@Override
protected DataTypeDescriptor dataTypeDescriptor(TInstance type) {
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
return new DataTypeDescriptor(TypeId.DECIMAL_ID, precision, scale, type.nullability(),
DataTypeDescriptor.computeMaxWidth(precision, scale));
}
public TClass widestComparable()
{
return this;
}
@Override
public ValueCacher cacher() {
return cacher;
}
@Override
public TInstance instance(boolean nullable) {
return instance(10, 0, nullable);
}
@Override
protected void validate(TInstance type) {
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
if (precision < scale)
throw new IllegalNameException("precision must be >= scale");
}
@Override
protected TInstance doPickInstance(TInstance left, TInstance right, boolean suggestedNullability) {
int scaleL = left.attribute(DecimalAttribute.SCALE);
int scaleR = right.attribute(DecimalAttribute.SCALE);
int precisionL = left.attribute(DecimalAttribute.PRECISION);
int precisionR = right.attribute(DecimalAttribute.PRECISION);
return pickPrecisionAndScale(TBigDecimal.this, precisionL, scaleL, precisionR, scaleR, suggestedNullability);
}
public static TInstance pickPrecisionAndScale(TClass tclass,
int precisionL, int scaleL, int precisionR, int scaleR,
boolean nullable)
{
int resultPrecision, resultScale;
if (scaleL == scaleR) {
resultScale = scaleL;
resultPrecision = Math.max(precisionL, precisionR);
}
else {
int precisionOfSmallerScale;
if (scaleL > scaleR) {
resultScale = scaleL;
resultPrecision = scaleL; // might be swapped later
precisionOfSmallerScale = precisionR;
}
else {
resultScale = scaleR;
resultPrecision = scaleR; // might be swapped later
precisionOfSmallerScale = precisionL;
}
// Whatever the precision was of the DECIMAL of smaller scale, widen it so that we can have this DECIMAL
// have the big scale
precisionOfSmallerScale += Math.abs(scaleL - scaleR);
resultPrecision = Math.max(precisionOfSmallerScale, resultPrecision);
}
return tclass.instance(resultPrecision, resultScale, nullable);
}
public static final ValueCacher cacher = new ValueCacher() {
@Override
public void cacheToValue(Object bdw, TInstance type, BasicValueTarget target) {
BigDecimal bd = ((BigDecimalWrapper)bdw).asBigDecimal();
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
byte[] bb = ConversionHelperBigDecimal.bytesFromObject(bd, precision, scale);
target.putBytes(bb);
}
@Override
public BigDecimalWrapper valueToCache(BasicValueSource value, TInstance type) {
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
byte[] bb = value.getBytes();
StringBuilder sb = new StringBuilder(precision + 2); // +2 for dot and minus sign
ConversionHelperBigDecimal.decodeToString(bb, 0, precision, scale, AkibanAppender.of(sb));
return new BigDecimalWrapperImpl(sb.toString());
}
@Override
public Object sanitize(Object object) {
if (object instanceof BigDecimal)
return new BigDecimalWrapperImpl((BigDecimal)object);
else if (object instanceof BigDecimalWrapperImpl)
return object;
else if (object instanceof String)
return new BigDecimalWrapperImpl((String)object);
else if (object instanceof Long)
return new BigDecimalWrapperImpl((long)object);
throw new UnsupportedOperationException(String.valueOf(object));
}
@Override
public boolean canConvertToValue(Object cached) {
return true;
}
};
private static final ValueIO valueIO = new ValueIO() {
@Override
public void copyCanonical(ValueSource in, TInstance typeInstance, ValueTarget out) {
if (in.hasCacheValue()) {
if (out.supportsCachedObjects())
out.putObject(in.getObject());
else
cacher.cacheToValue(in.getObject(), typeInstance, out);
}
else if (in.hasRawValue()) {
out.putBytes(in.getBytes());
}
else
throw new AssertionError("no value");
}
@Override
public void writeCollating(ValueSource in, TInstance typeInstance, ValueTarget out) {
BigDecimalWrapper wrapper = getWrapper(in, typeInstance);
out.putObject(wrapper.asBigDecimal());
}
@Override
public void readCollating(ValueSource in, TInstance typeInstance, ValueTarget out) {
Object input = in.getObject();
BigDecimal bigDecimal;
if (input instanceof BigDecimalWrapperImpl) {
bigDecimal = ((BigDecimalWrapperImpl)input).asBigDecimal();
} else if (input instanceof BigDecimal) {
bigDecimal = (BigDecimal)input;
} else {
bigDecimal = null;
assert false : "bad ValueSource input type: " + input.getClass().toString();
}
int allowedScale = typeInstance.attribute(DecimalAttribute.SCALE);
int allowedPrecision = typeInstance.attribute(DecimalAttribute.PRECISION);
int actualScale = BigDecimalWrapperImpl.sqlScale(bigDecimal);
int actualPrecision = BigDecimalWrapperImpl.sqlPrecision(bigDecimal);
if (allowedPrecision < actualPrecision) {
throw new AkibanInternalException("precision of " + actualPrecision
+ " is greater than " + allowedPrecision + " for value " + bigDecimal);
}
if (allowedScale < actualScale) {
throw new AkibanInternalException("scale of " + actualScale
+ " is greater than " + allowedScale + " for value " + bigDecimal);
}
BigDecimalWrapper wrapper = new BigDecimalWrapperImpl(bigDecimal).round(allowedScale);
out.putObject(wrapper);
}
};
@Override
protected boolean tryFromObject(TExecutionContext context, ValueSource in, ValueTarget out) {
// If the incoming ValueSource is a DECIMAL, *and* it has a cache value (ie an BigDecimalWrapper), then
// we can just copy the wrapper into the output. If the incoming is a DECIMAL with bytes (its raw form), we
// can only copy those bytes if the TInstance match -- and super.tryFromObject already makes that check.
if (in.getType().typeClass() instanceof TBigDecimal && in.hasCacheValue()) {
BigDecimalWrapper cached = (BigDecimalWrapper) in.getObject();
out.putObject(new BigDecimalWrapperImpl(cached.asBigDecimal()));
return true;
}
return super.tryFromObject(context, in, out);
}
@Override
public boolean hasFixedSerializationSize(TInstance type) {
return true;
}
@Override
public int fixedSerializationSize(TInstance type) {
final int TYPE_SIZE = 4;
final int DIGIT_PER = 9;
final int BYTE_DIGITS[] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 4 };
final int precision = type.attribute(DecimalAttribute.PRECISION);
final int scale = type.attribute(DecimalAttribute.SCALE);
final int intCount = precision - scale;
final int intFull = intCount / DIGIT_PER;
final int intPart = intCount % DIGIT_PER;
final int fracFull = scale / DIGIT_PER;
final int fracPart = scale % DIGIT_PER;
return (intFull + fracFull) * TYPE_SIZE +
BYTE_DIGITS[intPart] + BYTE_DIGITS[fracPart];
}
}