/* * 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.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import java.text.Format; import java.util.Random; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.types.DataType; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Order; import org.apache.hadoop.hbase.util.PositionedByteRange; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.exception.SQLExceptionInfo; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.schema.ConstraintViolationException; import org.apache.phoenix.schema.IllegalDataException; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.util.ByteUtil; import com.google.common.base.Preconditions; import com.google.common.math.LongMath; import com.google.common.primitives.Doubles; import com.google.common.primitives.Longs; /** * The data types of PColumns */ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType<?>> { private final String sqlTypeName; private final int sqlType; private final Class clazz; private final byte[] clazzNameBytes; private final byte[] sqlTypeNameBytes; private final PDataCodec codec; private final int ordinal; protected PDataType(String sqlTypeName, int sqlType, Class clazz, PDataCodec codec, int ordinal) { this.sqlTypeName = sqlTypeName; this.sqlType = sqlType; this.clazz = clazz; this.clazzNameBytes = Bytes.toBytes(clazz.getName()); this.sqlTypeNameBytes = Bytes.toBytes(sqlTypeName); this.codec = codec; this.ordinal = ordinal; } @Deprecated public static PDataType[] values() { return PDataTypeFactory.getInstance().getOrderedTypes(); } @Deprecated public int ordinal() { return ordinal; } @Override public Class<T> encodedClass() { return getJavaClass(); } public boolean isCastableTo(PDataType targetType) { return isComparableTo(targetType); } public final PDataCodec getCodec() { return codec; } public boolean isBytesComparableWith(PDataType otherType) { return this == otherType || this.getClass() == PVarbinary.class || otherType == PVarbinary.INSTANCE || this.getClass() == PBinary.class || otherType == PBinary.INSTANCE; } public int estimateByteSize(Object o) { if (isFixedWidth()) { return getByteSize(); } if (isArrayType()) { PhoenixArray array = (PhoenixArray) o; int noOfElements = array.numElements; int totalVarSize = 0; for (int i = 0; i < noOfElements; i++) { totalVarSize += array.estimateByteSize(i); } return totalVarSize; } // Non fixed width types must override this throw new UnsupportedOperationException(); } public Integer getMaxLength(Object o) { return null; } public Integer getScale(Object o) { return null; } /** * Estimate the byte size from the type length. For example, for char, byte size would be the * same as length. For decimal, byte size would have no correlation with the length. */ public Integer estimateByteSizeFromLength(Integer length) { if (isFixedWidth()) { return getByteSize(); } if (isArrayType()) { return null; } // If not fixed width, default to say the byte size is the same as length. return length; } public final String getSqlTypeName() { return sqlTypeName; } public final int getSqlType() { return sqlType; } public final Class getJavaClass() { return clazz; } public boolean isArrayType() { return false; } public final int compareTo(byte[] lhs, int lhsOffset, int lhsLength, SortOrder lhsSortOrder, byte[] rhs, int rhsOffset, int rhsLength, SortOrder rhsSortOrder, PDataType rhsType) { Preconditions.checkNotNull(lhsSortOrder); Preconditions.checkNotNull(rhsSortOrder); if (this.isBytesComparableWith(rhsType)) { // directly compare the bytes return compareTo(lhs, lhsOffset, lhsLength, lhsSortOrder, rhs, rhsOffset, rhsLength, rhsSortOrder); } PDataCodec lhsCodec = this.getCodec(); if (lhsCodec == null) { // no lhs native type representation, so convert rhsType to bytes representation of lhsType byte[] rhsConverted = this.toBytes(this.toObject(rhs, rhsOffset, rhsLength, rhsType, rhsSortOrder)); if (rhsSortOrder == SortOrder.DESC) { rhsSortOrder = SortOrder.ASC; } if (lhsSortOrder == SortOrder.DESC) { lhs = SortOrder.invert(lhs, lhsOffset, new byte[lhsLength], 0, lhsLength); } return Bytes.compareTo(lhs, lhsOffset, lhsLength, rhsConverted, 0, rhsConverted.length); } PDataCodec rhsCodec = rhsType.getCodec(); if (rhsCodec == null) { byte[] lhsConverted = rhsType.toBytes(rhsType.toObject(lhs, lhsOffset, lhsLength, this, lhsSortOrder)); if (lhsSortOrder == SortOrder.DESC) { lhsSortOrder = SortOrder.ASC; } if (rhsSortOrder == SortOrder.DESC) { rhs = SortOrder.invert(rhs, rhsOffset, new byte[rhsLength], 0, rhsLength); } return Bytes.compareTo(lhsConverted, 0, lhsConverted.length, rhs, rhsOffset, rhsLength); } // convert to native and compare if (this.isCoercibleTo(PLong.INSTANCE) && rhsType .isCoercibleTo(PLong.INSTANCE)) { // native long to long comparison return Longs.compare(this.getCodec().decodeLong(lhs, lhsOffset, lhsSortOrder), rhsType.getCodec().decodeLong(rhs, rhsOffset, rhsSortOrder)); } else if (isDoubleOrFloat(this) && isDoubleOrFloat( rhsType)) { // native double to double comparison return Doubles.compare(this.getCodec().decodeDouble(lhs, lhsOffset, lhsSortOrder), rhsType.getCodec().decodeDouble(rhs, rhsOffset, rhsSortOrder)); } else { // native float/double to long comparison float fvalue = 0.0F; double dvalue = 0.0; long lvalue = 0; boolean isFloat = false; int invert = 1; if (this.isCoercibleTo(PLong.INSTANCE)) { lvalue = this.getCodec().decodeLong(lhs, lhsOffset, lhsSortOrder); } else if (this.getClass() == PFloat.class) { isFloat = true; fvalue = this.getCodec().decodeFloat(lhs, lhsOffset, lhsSortOrder); } else if (this.isCoercibleTo(PDouble.INSTANCE)) { dvalue = this.getCodec().decodeDouble(lhs, lhsOffset, lhsSortOrder); } if (rhsType.isCoercibleTo(PLong.INSTANCE)) { lvalue = rhsType.getCodec().decodeLong(rhs, rhsOffset, rhsSortOrder); } else if (rhsType == PFloat.INSTANCE) { invert = -1; isFloat = true; fvalue = rhsType.getCodec().decodeFloat(rhs, rhsOffset, rhsSortOrder); } else if (rhsType.isCoercibleTo(PDouble.INSTANCE)) { invert = -1; dvalue = rhsType.getCodec().decodeDouble(rhs, rhsOffset, rhsSortOrder); } // Invert the comparison if float/double value is on the RHS return invert * (isFloat ? compareFloatToLong(fvalue, lvalue) : compareDoubleToLong(dvalue, lvalue)); } } public static boolean isDoubleOrFloat(PDataType type) { return type == PFloat.INSTANCE || type == PDouble.INSTANCE || type == PUnsignedFloat.INSTANCE || type == PUnsignedDouble.INSTANCE; } /** * Compares a float against a long. Behaves better than * {@link #compareDoubleToLong(double, long)} for float * values outside of Integer.MAX_VALUE and Integer.MIN_VALUE. * * @param f a float value * @param l a long value * @return -1 if f is less than l, 1 if f is greater than l, and 0 if f is equal to l */ private static int compareFloatToLong(float f, long l) { if (f > Integer.MAX_VALUE || f < Integer.MIN_VALUE) { return f < l ? -1 : f > l ? 1 : 0; } long diff = (long) f - l; return Long.signum(diff); } /** * Compares a double against a long. * * @param d a double value * @param l a long value * @return -1 if d is less than l, 1 if d is greater than l, and 0 if d is equal to l */ private static int compareDoubleToLong(double d, long l) { if (d > Long.MAX_VALUE) { return 1; } if (d < Long.MIN_VALUE) { return -1; } long diff = (long) d - l; return Long.signum(diff); } protected static void checkForSufficientLength(byte[] b, int offset, int requiredLength) { if (b.length < offset + requiredLength) { throw new RuntimeException(new SQLExceptionInfo.Builder(SQLExceptionCode.ILLEGAL_DATA) .setMessage( "Expected length of at least " + requiredLength + " bytes, but had " + (b.length - offset)).build().buildException()); } } protected static Void throwConstraintViolationException(PDataType source, PDataType target) { throw new ConstraintViolationException( new SQLExceptionInfo.Builder(SQLExceptionCode.TYPE_MISMATCH) .setMessage(source + " cannot be coerced to " + target).build().buildException()); } protected static RuntimeException newIllegalDataException() { return new IllegalDataException(new SQLExceptionInfo.Builder(SQLExceptionCode.ILLEGAL_DATA) .build().buildException()); } protected static RuntimeException newIllegalDataException(String msg) { return new IllegalDataException(new SQLExceptionInfo.Builder(SQLExceptionCode.ILLEGAL_DATA) .setMessage(msg).build().buildException()); } protected static RuntimeException newIllegalDataException(Exception e) { return new IllegalDataException(new SQLExceptionInfo.Builder(SQLExceptionCode.ILLEGAL_DATA) .setRootCause(e).build().buildException()); } @Override public boolean equals(Object o) { // PDataType's are expected to be singletons. // TODO: this doesn't jive with HBase's DataType if (o == null) return false; return getClass() == o.getClass(); } /** * @return true when {@code lhs} equals any of {@code rhs}. */ public static boolean equalsAny(PDataType lhs, PDataType... rhs) { for (int i = 0; i < rhs.length; i++) { if (lhs.equals(rhs[i])) return true; } return false; } public static interface PDataCodec { public long decodeLong(ImmutableBytesWritable ptr, SortOrder sortOrder); public long decodeLong(byte[] b, int o, SortOrder sortOrder); public int decodeInt(ImmutableBytesWritable ptr, SortOrder sortOrder); public int decodeInt(byte[] b, int o, SortOrder sortOrder); public byte decodeByte(ImmutableBytesWritable ptr, SortOrder sortOrder); public byte decodeByte(byte[] b, int o, SortOrder sortOrder); public short decodeShort(ImmutableBytesWritable ptr, SortOrder sortOrder); public short decodeShort(byte[] b, int o, SortOrder sortOrder); public float decodeFloat(ImmutableBytesWritable ptr, SortOrder sortOrder); public float decodeFloat(byte[] b, int o, SortOrder sortOrder); public double decodeDouble(ImmutableBytesWritable ptr, SortOrder sortOrder); public double decodeDouble(byte[] b, int o, SortOrder sortOrder); public int encodeLong(long v, ImmutableBytesWritable ptr); public int encodeLong(long v, byte[] b, int o); public int encodeInt(int v, ImmutableBytesWritable ptr); public int encodeInt(int v, byte[] b, int o); public int encodeByte(byte v, ImmutableBytesWritable ptr); public int encodeByte(byte v, byte[] b, int o); public int encodeShort(short v, ImmutableBytesWritable ptr); public int encodeShort(short v, byte[] b, int o); public int encodeFloat(float v, ImmutableBytesWritable ptr); public int encodeFloat(float v, byte[] b, int o); public int encodeDouble(double v, ImmutableBytesWritable ptr); public int encodeDouble(double v, byte[] b, int o); public PhoenixArrayFactory getPhoenixArrayFactory(); } public static abstract class BaseCodec implements PDataCodec { @Override public int decodeInt(ImmutableBytesWritable ptr, SortOrder sortOrder) { return decodeInt(ptr.get(), ptr.getOffset(), sortOrder); } @Override public long decodeLong(ImmutableBytesWritable ptr, SortOrder sortOrder) { return decodeLong(ptr.get(), ptr.getOffset(), sortOrder); } @Override public byte decodeByte(ImmutableBytesWritable ptr, SortOrder sortOrder) { return decodeByte(ptr.get(), ptr.getOffset(), sortOrder); } @Override public short decodeShort(ImmutableBytesWritable ptr, SortOrder sortOrder) { return decodeShort(ptr.get(), ptr.getOffset(), sortOrder); } @Override public float decodeFloat(ImmutableBytesWritable ptr, SortOrder sortOrder) { return decodeFloat(ptr.get(), ptr.getOffset(), sortOrder); } @Override public float decodeFloat(byte[] b, int o, SortOrder sortOrder) { throw new UnsupportedOperationException(); } @Override public double decodeDouble(ImmutableBytesWritable ptr, SortOrder sortOrder) { return decodeDouble(ptr.get(), ptr.getOffset(), sortOrder); } @Override public double decodeDouble(byte[] b, int o, SortOrder sortOrder) { throw new UnsupportedOperationException(); } @Override public int encodeInt(int v, ImmutableBytesWritable ptr) { return encodeInt(v, ptr.get(), ptr.getOffset()); } @Override public int encodeLong(long v, ImmutableBytesWritable ptr) { return encodeLong(v, ptr.get(), ptr.getOffset()); } @Override public int encodeByte(byte v, ImmutableBytesWritable ptr) { return encodeByte(v, ptr.get(), ptr.getOffset()); } @Override public int encodeShort(short v, ImmutableBytesWritable ptr) { return encodeShort(v, ptr.get(), ptr.getOffset()); } @Override public int encodeFloat(float v, ImmutableBytesWritable ptr) { return encodeFloat(v, ptr.get(), ptr.getOffset()); } @Override public int encodeDouble(double v, ImmutableBytesWritable ptr) { return encodeDouble(v, ptr.get(), ptr.getOffset()); } @Override public int encodeInt(int v, byte[] b, int o) { throw new UnsupportedOperationException(); } @Override public int encodeLong(long v, byte[] b, int o) { throw new UnsupportedOperationException(); } @Override public int encodeByte(byte v, byte[] b, int o) { throw new UnsupportedOperationException(); } @Override public int encodeShort(short v, byte[] b, int o) { throw new UnsupportedOperationException(); } @Override public int encodeFloat(float v, byte[] b, int o) { throw new UnsupportedOperationException(); } @Override public int encodeDouble(double v, byte[] b, int o) { throw new UnsupportedOperationException(); } } public static final int MAX_PRECISION = 38; // Max precision guaranteed to fit into a long (and this should be plenty) public static final int MIN_DECIMAL_AVG_SCALE = 4; public static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(MAX_PRECISION, RoundingMode.HALF_UP); public static final int DEFAULT_SCALE = 0; protected static final Integer MAX_BIG_DECIMAL_BYTES = 21; protected static final Integer MAX_TIMESTAMP_BYTES = Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT; protected static final byte ZERO_BYTE = (byte) 0x80; protected static final byte NEG_TERMINAL_BYTE = (byte) 102; protected static final int EXP_BYTE_OFFSET = 65; protected static final int POS_DIGIT_OFFSET = 1; protected static final int NEG_DIGIT_OFFSET = 101; protected static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); protected static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); protected static final long MAX_LONG_FOR_DESERIALIZE = Long.MAX_VALUE / 1000; protected static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100); protected static final byte FALSE_BYTE = 0; protected static final byte TRUE_BYTE = 1; public static final byte[] FALSE_BYTES = new byte[] { FALSE_BYTE }; public static final byte[] TRUE_BYTES = new byte[] { TRUE_BYTE }; public static final byte[] NULL_BYTES = ByteUtil.EMPTY_BYTE_ARRAY; protected static final Integer BOOLEAN_LENGTH = 1; public final static Integer ZERO = 0; public final static Integer INT_PRECISION = 10; public final static Integer LONG_PRECISION = 19; public final static Integer SHORT_PRECISION = 5; public final static Integer BYTE_PRECISION = 3; public static final int ARRAY_TYPE_BASE = 3000; public static final String ARRAY_TYPE_SUFFIX = "ARRAY"; protected static final ThreadLocal<Random> RANDOM = new ThreadLocal<Random>() { @Override protected Random initialValue() { return new Random(); } }; /** * Serialize a BigDecimal into a variable length byte array in such a way that it is * binary comparable. * * @param v the BigDecimal * @param result the byte array to contain the serialized bytes. Max size * necessary would be 21 bytes. * @param length the number of bytes required to store the big decimal. May be * adjusted down if it exceeds {@link #MAX_BIG_DECIMAL_BYTES} * @return the number of bytes that make up the serialized BigDecimal */ protected static int toBytes(BigDecimal v, byte[] result, final int offset, int length) { // From scale to exponent byte (if BigDecimal is positive): (-(scale+(scale % 2 == 0 : 0 : 1)) / 2 + 65) | 0x80 // If scale % 2 is 1 (i.e. it's odd), then multiple last base-100 digit by 10 // For example: new BigDecimal(BigInteger.valueOf(1), -4); // (byte)((-(-4+0) / 2 + 65) | 0x80) = -61 // From scale to exponent byte (if BigDecimal is negative): ~(-(scale+1)/2 + 65 + 128) & 0x7F // For example: new BigDecimal(BigInteger.valueOf(1), 2); // ~(-2/2 + 65 + 128) & 0x7F = 63 int signum = v.signum(); if (signum == 0) { result[offset] = ZERO_BYTE; return 1; } int index = offset + length; int scale = v.scale(); int expOffset = scale % 2 * (scale < 0 ? -1 : 1); // In order to get twice as much of a range for scale, it // is multiplied by 2. If the scale is an odd number, then // the first digit is multiplied by 10 to make up for the // scale being off by one. int multiplyBy; BigInteger divideBy; if (expOffset == 0) { multiplyBy = 1; divideBy = ONE_HUNDRED; } else { multiplyBy = 10; divideBy = BigInteger.TEN; } // Normalize the scale based on what is necessary to end up with a base 100 decimal (i.e. 10.123e3) int digitOffset; BigInteger compareAgainst; if (signum == 1) { digitOffset = POS_DIGIT_OFFSET; compareAgainst = MAX_LONG; scale -= (length - 2) * 2; result[offset] = (byte) ((-(scale + expOffset) / 2 + EXP_BYTE_OFFSET) | 0x80); } else { digitOffset = NEG_DIGIT_OFFSET; compareAgainst = MIN_LONG; // Scale adjustment shouldn't include terminal byte in length scale -= (length - 2 - 1) * 2; result[offset] = (byte) (~(-(scale + expOffset) / 2 + EXP_BYTE_OFFSET + 128) & 0x7F); if (length <= MAX_BIG_DECIMAL_BYTES) { result[--index] = NEG_TERMINAL_BYTE; } else { // Adjust length and offset down because we don't have enough room length = MAX_BIG_DECIMAL_BYTES; index = offset + length; } } BigInteger bi = v.unscaledValue(); // Use BigDecimal arithmetic until we can fit into a long while (bi.compareTo(compareAgainst) * signum > 0) { BigInteger[] dandr = bi.divideAndRemainder(divideBy); bi = dandr[0]; int digit = dandr[1].intValue(); result[--index] = (byte) (digit * multiplyBy + digitOffset); multiplyBy = 1; divideBy = ONE_HUNDRED; } long l = bi.longValue(); do { long divBy = 100 / multiplyBy; long digit = l % divBy; l /= divBy; result[--index] = (byte) (digit * multiplyBy + digitOffset); multiplyBy = 1; } while (l != 0); return length; } /** * Deserialize a variable length byte array into a BigDecimal. Note that because of * the normalization that gets done to the scale, if you roundtrip a BigDecimal, * it may not be equal before and after. However, the before and after number will * always compare to be equal (i.e. <nBefore>.compareTo(<nAfter>) == 0) * * @param bytes the bytes containing the number * @param offset the offset into the byte array * @param length the length of the serialized BigDecimal * @return the BigDecimal value. */ protected static BigDecimal toBigDecimal(byte[] bytes, int offset, int length) { // From exponent byte back to scale: (<exponent byte> & 0x7F) - 65) * 2 // For example, (((-63 & 0x7F) - 65) & 0xFF) * 2 = 0 // Another example: ((-64 & 0x7F) - 65) * 2 = -2 (then swap the sign for the scale) // If number is negative, going from exponent byte back to scale: (byte)((~<exponent byte> - 65 - 128) * 2) // For example: new BigDecimal(new BigInteger("-1"), -2); // (byte)((~61 - 65 - 128) * 2) = 2, so scale is -2 // Potentially, when switching back, the scale can be added by one and the trailing zero dropped // For digits, just do a mod 100 on the BigInteger. Use long if BigInteger fits if (length == 1 && bytes[offset] == ZERO_BYTE) { return BigDecimal.ZERO; } int signum = ((bytes[offset] & 0x80) == 0) ? -1 : 1; int scale; int index; int digitOffset; long multiplier = 100L; int begIndex = offset + 1; if (signum == 1) { scale = (byte) (((bytes[offset] & 0x7F) - 65) * -2); index = offset + length; digitOffset = POS_DIGIT_OFFSET; } else { scale = (byte) ((~bytes[offset] - 65 - 128) * -2); index = offset + length - (bytes[offset + length - 1] == NEG_TERMINAL_BYTE ? 1 : 0); digitOffset = -NEG_DIGIT_OFFSET; } length = index - offset; long l = signum * bytes[--index] - digitOffset; if (l % 10 == 0) { // trailing zero scale--; // drop trailing zero and compensate in the scale l /= 10; multiplier = 10; } // Use long arithmetic for as long as we can while (index > begIndex) { if (l >= MAX_LONG_FOR_DESERIALIZE || multiplier >= Long.MAX_VALUE / 100) { multiplier = LongMath.divide(multiplier, 100L, RoundingMode.UNNECESSARY); break; // Exit loop early so we don't overflow our multiplier } int digit100 = signum * bytes[--index] - digitOffset; l += digit100 * multiplier; multiplier = LongMath.checkedMultiply(multiplier, 100); } BigInteger bi; // If still more digits, switch to BigInteger arithmetic if (index > begIndex) { bi = BigInteger.valueOf(l); BigInteger biMultiplier = BigInteger.valueOf(multiplier).multiply(ONE_HUNDRED); do { int digit100 = signum * bytes[--index] - digitOffset; bi = bi.add(biMultiplier.multiply(BigInteger.valueOf(digit100))); biMultiplier = biMultiplier.multiply(ONE_HUNDRED); } while (index > begIndex); if (signum == -1) { bi = bi.negate(); } } else { bi = BigInteger.valueOf(l * signum); } // Update the scale based on the precision scale += (length - 2) * 2; BigDecimal v = new BigDecimal(bi, scale); return v; } // Calculate the precision and scale of a raw decimal bytes. Returns the values as an int // array. The first value is precision, the second value is scale. // Default scope for testing protected static int[] getDecimalPrecisionAndScale(byte[] bytes, int offset, int length) { // 0, which should have no precision nor scale. if (length == 1 && bytes[offset] == ZERO_BYTE) { return new int[] { 0, 0 }; } int signum = ((bytes[offset] & 0x80) == 0) ? -1 : 1; int scale; int index; int digitOffset; if (signum == 1) { scale = (byte) (((bytes[offset] & 0x7F) - 65) * -2); index = offset + length; digitOffset = POS_DIGIT_OFFSET; } else { scale = (byte) ((~bytes[offset] - 65 - 128) * -2); index = offset + length - (bytes[offset + length - 1] == NEG_TERMINAL_BYTE ? 1 : 0); digitOffset = -NEG_DIGIT_OFFSET; } length = index - offset; int precision = 2 * (length - 1); int d = signum * bytes[--index] - digitOffset; if (d % 10 == 0) { // trailing zero // drop trailing zero and compensate in the scale and precision. d /= 10; scale--; precision -= 1; } d = signum * bytes[offset + 1] - digitOffset; if (d < 10) { // Leading single digit // Compensate in the precision. precision -= 1; } // Update the scale based on the precision scale += (length - 2) * 2; if (scale < 0) { precision = precision - scale; scale = 0; } return new int[] { precision, scale }; } public boolean isCoercibleTo(PDataType targetType) { return this.equals(targetType) || targetType.equals(PVarbinary.INSTANCE); } // Specialized on enums to take into account type hierarchy (i.e. UNSIGNED_LONG is comparable to INTEGER) public boolean isComparableTo(PDataType targetType) { return targetType.isCoercibleTo(this) || this.isCoercibleTo(targetType); } public boolean isCoercibleTo(PDataType targetType, Object value) { return isCoercibleTo(targetType); } public boolean isSizeCompatible(ImmutableBytesWritable ptr, Object value, PDataType srcType, Integer maxLength, Integer scale, Integer desiredMaxLength, Integer desiredScale) { return true; } public int compareTo(byte[] b1, byte[] b2) { return compareTo(b1, 0, b1.length, SortOrder.getDefault(), b2, 0, b2.length, SortOrder.getDefault()); } public final int compareTo(ImmutableBytesWritable ptr1, ImmutableBytesWritable ptr2) { return compareTo(ptr1.get(), ptr1.getOffset(), ptr1.getLength(), SortOrder.getDefault(), ptr2.get(), ptr2.getOffset(), ptr2.getLength(), SortOrder.getDefault()); } public final int compareTo(byte[] ba1, int offset1, int length1, SortOrder so1, byte[] ba2, int offset2, int length2, SortOrder so2) { Preconditions.checkNotNull(so1); Preconditions.checkNotNull(so2); if (so1 != so2) { int length = Math.min(length1, length2); for (int i = 0; i < length; i++) { byte b1 = ba1[offset1 + i]; byte b2 = ba2[offset2 + i]; if (so1 == SortOrder.DESC) { b1 = SortOrder.invert(b1); } else { b2 = SortOrder.invert(b2); } int c = b1 - b2; if (c != 0) { return c; } } return (length1 - length2); } return Bytes.compareTo(ba1, offset1, length1, ba2, offset2, length2) * (so1 == SortOrder.DESC ? -1 : 1); } public final int compareTo(ImmutableBytesWritable ptr1, SortOrder ptr1SortOrder, ImmutableBytesWritable ptr2, SortOrder ptr2SortOrder, PDataType type2) { return compareTo(ptr1.get(), ptr1.getOffset(), ptr1.getLength(), ptr1SortOrder, ptr2.get(), ptr2.getOffset(), ptr2.getLength(), ptr2SortOrder, type2); } public int compareTo(Object lhs, Object rhs) { return compareTo(lhs, rhs, this); } /* * We need an empty byte array to mean null, since * we have no other representation in the row key * for null. */ public final boolean isNull(byte[] value) { return value == null || value.length == 0; } public byte[] toBytes(Object object, SortOrder sortOrder) { Preconditions.checkNotNull(sortOrder); byte[] bytes = toBytes(object); if (sortOrder == SortOrder.DESC) { SortOrder.invert(bytes, 0, bytes, 0, bytes.length); } return bytes; } public void coerceBytes(ImmutableBytesWritable ptr, Object o, PDataType actualType, Integer actualMaxLength, Integer actualScale, SortOrder actualModifier, Integer desiredMaxLength, Integer desiredScale, SortOrder expectedModifier) { Preconditions.checkNotNull(actualModifier); Preconditions.checkNotNull(expectedModifier); if (ptr.getLength() == 0) { return; } if (this.isBytesComparableWith(actualType)) { // No coerce necessary if (actualModifier == expectedModifier) { return; } byte[] b = ptr.copyBytes(); SortOrder.invert(b, 0, b, 0, b.length); ptr.set(b); return; } // Optimization for cases in which we already have the object around if (o == null) { o = actualType.toObject(ptr, actualType, actualModifier); } o = toObject(o, actualType); byte[] b = toBytes(o, expectedModifier); ptr.set(b); } public final void coerceBytes(ImmutableBytesWritable ptr, PDataType actualType, SortOrder actualModifier, SortOrder expectedModifier) { coerceBytes(ptr, null, actualType, null, null, actualModifier, null, null, expectedModifier); } public final void coerceBytes(ImmutableBytesWritable ptr, PDataType actualType, SortOrder actualModifier, SortOrder expectedModifier, Integer desiredMaxLength) { coerceBytes(ptr, null, actualType, null, null, actualModifier, desiredMaxLength, null, expectedModifier); } protected static boolean isNonNegativeDate(java.util.Date date) { return (date == null || date.getTime() >= 0); } protected static void throwIfNonNegativeDate(java.util.Date date) { if (!isNonNegativeDate(date)) { throw newIllegalDataException("Value may not be negative(" + date + ")"); } } protected static boolean isNonNegativeNumber(Number v) { return v == null || v.longValue() >= 0; } protected static void throwIfNonNegativeNumber(Number v) { if (!isNonNegativeNumber(v)) { throw newIllegalDataException("Value may not be negative(" + v + ")"); } } @Override public boolean isNullable() { return false; } public abstract Integer getByteSize(); @Override public int encodedLength(T val) { // default implementation based on existing PDataType methods. return getByteSize(); } @Override public int skip(PositionedByteRange pbr) { // default implementation based on existing PDataType methods. int len = getByteSize(); pbr.setPosition(pbr.getPosition() + len); return len; } @Override public boolean isOrderPreserving() { return true; } @Override public boolean isSkippable() { return true; } @Override public Order getOrder() { return Order.ASCENDING; } public abstract boolean isFixedWidth(); public abstract int compareTo(Object lhs, Object rhs, PDataType rhsType); @Override public int compareTo(PDataType<?> other) { return Integer.compare(this.ordinal(), other.ordinal()); } /** * Convert from the object representation of a data type value into * the serialized byte form. * * @param object the object to convert * @param bytes the byte array into which to put the serialized form of object * @param offset the offset from which to start writing the serialized form * @return the byte length of the serialized object */ public abstract int toBytes(Object object, byte[] bytes, int offset); @Override public int encode(PositionedByteRange pbr, T val) { // default implementation based on existing PDataType methods. int pos = pbr.getPosition(); pbr.put(toBytes(val)); return pbr.getPosition() - pos; } @Override public String toString() { return sqlTypeName; } public abstract byte[] toBytes(Object object); /** * Convert from a string to the object representation of a given type * * @param value a stringified value * @return the object representation of a string value */ public abstract Object toObject(String value); /* * Each enum must override this to define the set of object it may be coerced to */ public abstract Object toObject(Object object, PDataType actualType); /* * Each enum must override this to define the set of objects it may create */ public abstract Object toObject(byte[] bytes, int offset, int length, PDataType actualType, SortOrder sortOrder, Integer maxLength, Integer scale); @Override public T decode(PositionedByteRange pbr) { // default implementation based on existing PDataType methods. byte[] b = new byte[getByteSize()]; pbr.get(b); return (T) toObject(b, 0, b.length, this, SortOrder.ASC, getMaxLength(null), getScale(null)); } /* * Return a valid object of this enum type */ public abstract Object getSampleValue(Integer maxLength, Integer arrayLength); public final Object getSampleValue() { return getSampleValue(null); } public final Object getSampleValue(Integer maxLength) { return getSampleValue(maxLength, null); } public final Object toObject(byte[] bytes, int offset, int length, PDataType actualType, SortOrder sortOrder) { return toObject(bytes, offset, length, actualType, sortOrder, null, null); } public final Object toObject(byte[] bytes, int offset, int length, PDataType actualType) { return toObject(bytes, offset, length, actualType, SortOrder.getDefault()); } public final Object toObject(ImmutableBytesWritable ptr, PDataType actualType) { return toObject(ptr, actualType, SortOrder.getDefault()); } public final Object toObject(ImmutableBytesWritable ptr, PDataType actualType, SortOrder sortOrder) { return this.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), actualType, sortOrder); } public final Object toObject(ImmutableBytesWritable ptr, PDataType actualType, SortOrder sortOrder, Integer maxLength, Integer scale) { return this .toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), actualType, sortOrder, maxLength, scale); } public final Object toObject(ImmutableBytesWritable ptr, SortOrder sortOrder, Integer maxLength, Integer scale) { return this .toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), this, sortOrder, maxLength, scale); } public final Object toObject(ImmutableBytesWritable ptr) { return toObject(ptr.get(), ptr.getOffset(), ptr.getLength()); } public final Object toObject(ImmutableBytesWritable ptr, SortOrder sortOrder) { return toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), this, sortOrder); } public final Object toObject(byte[] bytes, int offset, int length) { return toObject(bytes, offset, length, this); } public final Object toObject(byte[] bytes) { return toObject(bytes, SortOrder.getDefault()); } public final Object toObject(byte[] bytes, SortOrder sortOrder) { return toObject(bytes, 0, bytes.length, this, sortOrder); } public final Object toObject(byte[] bytes, SortOrder sortOrder, PDataType actualType) { return toObject(bytes, 0, bytes.length, actualType, sortOrder); } public static PDataType fromSqlTypeName(String sqlTypeName) { for (PDataType t : PDataTypeFactory.getInstance().getTypes()) { if (t.getSqlTypeName().equals(sqlTypeName)) return t; } throw newIllegalDataException("Unsupported sql type: " + sqlTypeName); } public static int sqlArrayType(String sqlTypeName) { PDataType fromSqlTypeName = fromSqlTypeName(sqlTypeName); return fromSqlTypeName.getSqlType() + PDataType.ARRAY_TYPE_BASE; } protected static interface PhoenixArrayFactory { PhoenixArray newArray(PDataType type, Object[] elements); } public static PDataType fromTypeId(int typeId) { for (PDataType t : PDataTypeFactory.getInstance().getTypes()) { if (t.getSqlType() == typeId) return t; } throw newIllegalDataException("Unsupported sql type: " + typeId); } public String getJavaClassName() { return getJavaClass().getName(); } public byte[] getJavaClassNameBytes() { return clazzNameBytes; } public byte[] getSqlTypeNameBytes() { return sqlTypeNameBytes; } /** * By default returns sqlType for the PDataType, * however it allows unknown types (our unsigned types) * to return the regular corresponding sqlType so * that tools like SQuirrel correctly display values * of this type. * * @return integer representing the SQL type for display * of a result set of this type */ public int getResultSetSqlType() { return this.sqlType; } public KeyRange getKeyRange(byte[] point) { return getKeyRange(point, true, point, true); } public final String toStringLiteral(ImmutableBytesWritable ptr, Format formatter) { return toStringLiteral(ptr.get(), ptr.getOffset(), ptr.getLength(), formatter); } public final String toStringLiteral(byte[] b, Format formatter) { return toStringLiteral(b, 0, b.length, formatter); } public String toStringLiteral(byte[] b, int offset, int length, Format formatter) { Object o = toObject(b, offset, length); return toStringLiteral(o, formatter); } public String toStringLiteral(Object o, Format formatter) { if (formatter != null) { return formatter.format(o); } return o.toString(); } private static final PhoenixArrayFactory DEFAULT_ARRAY_FACTORY = new PhoenixArrayFactory() { @Override public PhoenixArray newArray(PDataType type, Object[] elements) { return new PhoenixArray(type, elements); } }; public PhoenixArrayFactory getArrayFactory() { if (getCodec() != null) return getCodec().getPhoenixArrayFactory(); else return DEFAULT_ARRAY_FACTORY; } public static PhoenixArray instantiatePhoenixArray(PDataType actualType, Object[] elements) { return actualType.getArrayFactory().newArray(actualType, elements); } public KeyRange getKeyRange(byte[] lowerRange, boolean lowerInclusive, byte[] upperRange, boolean upperInclusive) { /* * Force lower bound to be inclusive for fixed width keys because it makes * comparisons less expensive when you can count on one bound or the other * being inclusive. Comparing two fixed width exclusive bounds against each * other is inherently more expensive, because you need to take into account * if the bigger key is equal to the next key after the smaller key. For * example: * (A-B] compared against [A-B) * An exclusive lower bound A is bigger than an exclusive upper bound B. * Forcing a fixed width exclusive lower bound key to be inclusive prevents * us from having to do this extra logic in the compare function. */ if (lowerRange != KeyRange.UNBOUND && !lowerInclusive && isFixedWidth()) { lowerRange = ByteUtil.nextKey(lowerRange); lowerInclusive = true; } return KeyRange.getKeyRange(lowerRange, lowerInclusive, upperRange, upperInclusive); } public static PDataType fromLiteral(Object value) { if (value == null) { return null; } for (PDataType type : PDataType.values()) { if (type.isArrayType()) { PhoenixArray arr = (PhoenixArray) value; if ((type.getSqlType() == arr.baseType.sqlType + PDataType.ARRAY_TYPE_BASE) && type.getJavaClass().isInstance(value)) { return type; } } else { if (type.getJavaClass().isInstance(value)) { return type; } } } throw new UnsupportedOperationException( "Unsupported literal value [" + value + "] of type " + value.getClass().getName()); } public int getNanos(ImmutableBytesWritable ptr, SortOrder sortOrder) { throw new UnsupportedOperationException("Operation not supported for type " + this); } public long getMillis(ImmutableBytesWritable ptr, SortOrder sortOrder) { throw new UnsupportedOperationException("Operation not supported for type " + this); } public Object pad(Object object, Integer maxLength) { return object; } public void pad(ImmutableBytesWritable ptr, Integer maxLength) { } public static PDataType arrayBaseType(PDataType arrayType) { Preconditions.checkArgument(arrayType.isArrayType(), "Not a phoenix array type"); return fromTypeId(arrayType.getSqlType() - ARRAY_TYPE_BASE); } }