blob: b76febb2e8fad7aad934136e6d29b347f52f9d55 [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.phoenix.schema.types;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.Format;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.TypeMismatchException;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.DateUtil;
import org.apache.phoenix.util.NumberUtil;
import com.google.common.base.Preconditions;
public class PDecimal extends PRealNumber<BigDecimal> {
public static final PDecimal INSTANCE = new PDecimal();
private static final BigDecimal MIN_DOUBLE_AS_BIG_DECIMAL =
BigDecimal.valueOf(-Double.MAX_VALUE);
private static final BigDecimal MAX_DOUBLE_AS_BIG_DECIMAL =
BigDecimal.valueOf(Double.MAX_VALUE);
private static final BigDecimal MIN_FLOAT_AS_BIG_DECIMAL =
BigDecimal.valueOf(-Float.MAX_VALUE);
private static final BigDecimal MAX_FLOAT_AS_BIG_DECIMAL =
BigDecimal.valueOf(Float.MAX_VALUE);
private PDecimal() {
super("DECIMAL", Types.DECIMAL, BigDecimal.class, null, 8);
}
@Override
public byte[] toBytes(Object object) {
if (object == null) {
return ByteUtil.EMPTY_BYTE_ARRAY;
}
BigDecimal v = (BigDecimal) object;
v = NumberUtil.normalize(v);
int len = getLength(v);
byte[] result = new byte[Math.min(len, MAX_BIG_DECIMAL_BYTES)];
PDataType.toBytes(v, result, 0, len);
return result;
}
@Override
public int toBytes(Object object, byte[] bytes, int offset) {
if (object == null) {
return 0;
}
BigDecimal v = (BigDecimal) object;
v = NumberUtil.normalize(v);
int len = getLength(v);
return PDataType.toBytes(v, bytes, offset, len);
}
private int getLength(BigDecimal v) {
int signum = v.signum();
if (signum == 0) { // Special case for zero
return 1;
}
/*
* Size of DECIMAL includes:
* 1) one byte for exponent
* 2) one byte for terminal byte if negative
* 3) one byte for every two digits with the following caveats:
* a) add one to round up in the case when there is an odd number of digits
* b) add one in the case that the scale is odd to account for 10x of lowest significant digit
* (basically done to increase the range of exponents that can be represented)
*/
return (signum < 0 ? 2 : 1) + (v.precision() + 1 + (v.scale() % 2 == 0 ? 0 : 1)) / 2;
}
@Override
public int estimateByteSize(Object o) {
if (o == null) {
return 1;
}
BigDecimal v = (BigDecimal) o;
// TODO: should we strip zeros and round here too?
return Math.min(getLength(v), MAX_BIG_DECIMAL_BYTES);
}
@Override
public Integer getMaxLength(Object o) {
if (o == null) {
return MAX_PRECISION;
}
BigDecimal v = (BigDecimal) o;
return v.precision();
}
@Override
public Integer getScale(Object o) {
return null;
}
@Override
public Object toObject(byte[] b, int o, int l, PDataType actualType, SortOrder sortOrder,
Integer maxLength, Integer scale) {
Preconditions.checkNotNull(sortOrder);
if (l == 0) {
return null;
}
if (actualType == PDecimal.INSTANCE) {
if (sortOrder == SortOrder.DESC) {
b = SortOrder.invert(b, o, new byte[l], 0, l);
o = 0;
}
return toBigDecimal(b, o, l);
} else if (equalsAny(actualType, PDate.INSTANCE, PTime.INSTANCE, PUnsignedDate.INSTANCE,
PUnsignedTime.INSTANCE, PLong.INSTANCE, PUnsignedLong.INSTANCE, PInteger.INSTANCE,
PUnsignedInt.INSTANCE, PSmallint.INSTANCE, PUnsignedSmallint.INSTANCE, PTinyint.INSTANCE,
PUnsignedTinyint.INSTANCE)) {
return BigDecimal.valueOf(actualType.getCodec().decodeLong(b, o, sortOrder));
} else if (equalsAny(actualType, PFloat.INSTANCE, PUnsignedFloat.INSTANCE)) {
return BigDecimal.valueOf(actualType.getCodec().decodeFloat(b, o, sortOrder));
} else if (equalsAny(actualType, PDouble.INSTANCE, PUnsignedDouble.INSTANCE)) {
return BigDecimal.valueOf(actualType.getCodec().decodeDouble(b, o, sortOrder));
} else if (equalsAny(actualType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
long millisPart = DateUtil.getCodecFor(actualType).decodeLong(b, o, sortOrder);
int nanoPart = PUnsignedInt.INSTANCE.getCodec().decodeInt(b, o + Bytes.SIZEOF_LONG, sortOrder);
BigDecimal nanosPart = BigDecimal.valueOf(
(nanoPart % QueryConstants.MILLIS_TO_NANOS_CONVERTOR)
/ QueryConstants.MILLIS_TO_NANOS_CONVERTOR);
return BigDecimal.valueOf(millisPart).add(nanosPart);
} else if (actualType == PBoolean.INSTANCE) {
return (Boolean) PBoolean.INSTANCE.toObject(b, o, l, actualType, sortOrder) ?
BigDecimal.ONE :
BigDecimal.ZERO;
}
return throwConstraintViolationException(actualType, this);
}
@Override
public Object toObject(Object object, PDataType actualType) {
if (object == null) {
return null;
}
if (equalsAny(actualType, PInteger.INSTANCE, PUnsignedInt.INSTANCE)) {
return BigDecimal.valueOf((Integer) object);
} else if (equalsAny(actualType, PLong.INSTANCE, PUnsignedLong.INSTANCE)) {
return BigDecimal.valueOf((Long) object);
} else if (equalsAny(actualType, PSmallint.INSTANCE, PUnsignedSmallint.INSTANCE)) {
return BigDecimal.valueOf((Short) object);
} else if (equalsAny(actualType, PTinyint.INSTANCE, PUnsignedTinyint.INSTANCE)) {
return BigDecimal.valueOf((Byte) object);
} else if (equalsAny(actualType, PFloat.INSTANCE, PUnsignedFloat.INSTANCE)) {
return BigDecimal.valueOf((Float) object);
} else if (equalsAny(actualType, PDouble.INSTANCE, PUnsignedDouble.INSTANCE)) {
return BigDecimal.valueOf((Double) object);
} else if (actualType == PDecimal.INSTANCE) {
return object;
} else if (equalsAny(actualType, PDate.INSTANCE, PUnsignedDate.INSTANCE, PTime.INSTANCE,
PUnsignedTime.INSTANCE)) {
java.util.Date d = (java.util.Date) object;
return BigDecimal.valueOf(d.getTime());
} else if (equalsAny(actualType, PTimestamp.INSTANCE,
PUnsignedTimestamp.INSTANCE)) {
Timestamp ts = (Timestamp) object;
long millisPart = ts.getTime();
BigDecimal nanosPart = BigDecimal.valueOf(
(ts.getNanos() % QueryConstants.MILLIS_TO_NANOS_CONVERTOR)
/ QueryConstants.MILLIS_TO_NANOS_CONVERTOR);
BigDecimal value = BigDecimal.valueOf(millisPart).add(nanosPart);
return value;
} else if (actualType == PBoolean.INSTANCE) {
return ((Boolean) object) ? BigDecimal.ONE : BigDecimal.ZERO;
}
return throwConstraintViolationException(actualType, this);
}
@Override
public boolean isFixedWidth() {
return false;
}
@Override
public Integer getByteSize() {
return MAX_BIG_DECIMAL_BYTES;
}
@Override
public int compareTo(Object lhs, Object rhs, PDataType rhsType) {
if (rhsType == PDecimal.INSTANCE) {
return ((BigDecimal) lhs).compareTo((BigDecimal) rhs);
}
return -rhsType.compareTo(rhs, lhs, this);
}
@Override
public boolean isCastableTo(PDataType targetType) {
return super.isCastableTo(targetType) || targetType.isCoercibleTo(
PTimestamp.INSTANCE) || targetType.equals(PBoolean.INSTANCE);
}
@Override
public boolean isCoercibleTo(PDataType targetType, Object value) {
if (value != null) {
BigDecimal bd;
if (equalsAny(targetType, PUnsignedLong.INSTANCE, PUnsignedInt.INSTANCE,
PUnsignedSmallint.INSTANCE, PUnsignedTinyint.INSTANCE)) {
bd = (BigDecimal) value;
if (bd.signum() == -1) {
return false;
}
} else if (targetType.equals(PLong.INSTANCE)) {
bd = (BigDecimal) value;
try {
bd.longValueExact();
return true;
} catch (ArithmeticException e) {
return false;
}
} else if (targetType.equals(PInteger.INSTANCE)) {
bd = (BigDecimal) value;
try {
bd.intValueExact();
return true;
} catch (ArithmeticException e) {
return false;
}
} else if (targetType.equals(PSmallint.INSTANCE)) {
bd = (BigDecimal) value;
try {
bd.shortValueExact();
return true;
} catch (ArithmeticException e) {
return false;
}
} else if (targetType.equals(PTinyint.INSTANCE)) {
bd = (BigDecimal) value;
try {
bd.byteValueExact();
return true;
} catch (ArithmeticException e) {
return false;
}
} else if (targetType.equals(PUnsignedFloat.INSTANCE)) {
bd = (BigDecimal) value;
try {
BigDecimal maxFloat = MAX_FLOAT_AS_BIG_DECIMAL;
boolean isNegtive = (bd.signum() == -1);
return bd.compareTo(maxFloat) <= 0 && !isNegtive;
} catch (Exception e) {
return false;
}
} else if (targetType.equals(PFloat.INSTANCE)) {
bd = (BigDecimal) value;
try {
BigDecimal maxFloat = MAX_FLOAT_AS_BIG_DECIMAL;
// Float.MIN_VALUE should not be used here, as this is the
// smallest in terms of closest to zero.
BigDecimal minFloat = MIN_FLOAT_AS_BIG_DECIMAL;
return bd.compareTo(maxFloat) <= 0 && bd.compareTo(minFloat) >= 0;
} catch (Exception e) {
return false;
}
} else if (targetType.equals(PUnsignedDouble.INSTANCE)) {
bd = (BigDecimal) value;
try {
BigDecimal maxDouble = MAX_DOUBLE_AS_BIG_DECIMAL;
boolean isNegtive = (bd.signum() == -1);
return bd.compareTo(maxDouble) <= 0 && !isNegtive;
} catch (Exception e) {
return false;
}
} else if (targetType.equals(PDouble.INSTANCE)) {
bd = (BigDecimal) value;
try {
BigDecimal maxDouble = MAX_DOUBLE_AS_BIG_DECIMAL;
BigDecimal minDouble = MIN_DOUBLE_AS_BIG_DECIMAL;
return bd.compareTo(maxDouble) <= 0 && bd.compareTo(minDouble) >= 0;
} catch (Exception e) {
return false;
}
}
}
return super.isCoercibleTo(targetType, value);
}
@Override
public boolean isSizeCompatible(ImmutableBytesWritable ptr, Object value, PDataType srcType,
SortOrder sortOrder, Integer maxLength, Integer scale, Integer desiredMaxLength, Integer desiredScale) {
if (ptr.getLength() == 0) {
return true;
}
// Any numeric type fits into a DECIMAL
if (srcType != PDecimal.INSTANCE) {
if(!srcType.isCoercibleTo(this)) {
throw new IllegalArgumentException(TypeMismatchException.newException(srcType, this));
}
return true;
}
// Use the scale from the value if provided, as it prevents a deserialization.
// The maxLength and scale for the underlying expression are ignored, because they
// are not relevant in this case: for example a DECIMAL(10,2) may be assigned to a
// DECIMAL(5,0) as long as the value fits.
if (value != null) {
BigDecimal v = (BigDecimal) value;
maxLength = v.precision();
scale = v.scale();
} else {
this.coerceBytes(ptr, value, srcType, maxLength, scale, sortOrder, desiredMaxLength, desiredScale, sortOrder, true);
int[] v = getDecimalPrecisionAndScale(ptr.get(), ptr.getOffset(), ptr.getLength(), sortOrder);
maxLength = v[0];
scale = v[1];
}
if (desiredMaxLength != null && desiredScale != null && maxLength != null && scale != null &&
((desiredScale == null && desiredMaxLength < maxLength) ||
(desiredMaxLength - desiredScale) < (maxLength - scale))) {
return false;
}
return true;
}
@Override
public void coerceBytes(ImmutableBytesWritable ptr, Object object, PDataType actualType,
Integer maxLength, Integer scale, SortOrder actualModifier, Integer desiredMaxLength, Integer desiredScale,
SortOrder expectedModifier) {
if (desiredScale == null) {
// deiredScale not available, or we do not have scale requirement, delegate to parents.
super.coerceBytes(ptr, object, actualType, maxLength, scale, actualModifier, desiredMaxLength,
desiredScale, expectedModifier);
return;
}
if (ptr.getLength() == 0) {
return;
}
if (scale == null) {
if (object != null) {
BigDecimal v = (BigDecimal) object;
scale = v.scale();
} else {
int[] v = getDecimalPrecisionAndScale(ptr.get(), ptr.getOffset(), ptr.getLength(), actualModifier);
scale = v[1];
}
}
if (this == actualType && scale <= desiredScale) {
// No coerce and rescale necessary
return;
} else {
BigDecimal decimal;
// Rescale is necessary.
if (object != null) { // value object is passed in.
decimal = (BigDecimal) toObject(object, actualType);
} else { // only value bytes is passed in, need to convert to object first.
decimal = (BigDecimal) toObject(ptr);
}
decimal = decimal.setScale(desiredScale, BigDecimal.ROUND_DOWN);
ptr.set(toBytes(decimal));
}
}
@Override
public Object toObject(String value) {
if (value == null || value.length() == 0) {
return null;
}
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
throw newIllegalDataException(e);
}
}
@Override
public Integer estimateByteSizeFromLength(Integer length) {
// No association of runtime byte size from decimal precision.
return null;
}
@Override
public String toStringLiteral(byte[] b, int offset, int length, Format formatter) {
if (formatter == null) {
BigDecimal o = (BigDecimal) toObject(b, offset, length);
return o.toPlainString();
}
return super.toStringLiteral(b, offset, length, formatter);
}
@Override
public String toStringLiteral(Object o, Format formatter) {
if (formatter == null) {
if(o == null) {
return String.valueOf(o);
}
return ((BigDecimal)o).toPlainString();
}
return super.toStringLiteral(o, formatter);
}
@Override
public Object getSampleValue(Integer maxLength, Integer arrayLength) {
return new BigDecimal((Long) PLong.INSTANCE.getSampleValue(maxLength, arrayLength));
}
// take details from org.apache.phoenix.schema.types.PDataType#toBigDecimal(byte[], int, int)
@Override
public int signum(byte[] bytes, int offset, int length, SortOrder sortOrder, Integer maxLength,
Integer scale) {
byte signByte;
if (sortOrder == SortOrder.DESC) {
signByte = SortOrder.invert(bytes[offset]);
} else {
signByte = bytes[offset];
}
if (length == 1 && signByte == ZERO_BYTE) {
return 0;
}
return ((signByte & 0x80) == 0) ? -1 : 1;
}
@Override
public void abs(byte[] bytes, int offset, int length, SortOrder sortOrder,
ImmutableBytesWritable outPtr) {
if (sortOrder == SortOrder.DESC) {
bytes = SortOrder.invert(bytes, offset, new byte[length], 0, length);
offset = 0;
}
BigDecimal bigDecimal = toBigDecimal(bytes, offset, length);
outPtr.set(toBytes(bigDecimal.abs()));
}
}