| /* |
| * 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())); |
| } |
| } |