| /* |
| * 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.carbondata.core.metadata.datatype; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| |
| import org.apache.carbondata.core.scan.result.vector.CarbonColumnVector; |
| import org.apache.carbondata.core.scan.result.vector.ColumnVectorInfo; |
| import org.apache.carbondata.core.scan.result.vector.impl.directread.ColumnarVectorWrapperDirectFactory; |
| import org.apache.carbondata.core.util.ByteUtil; |
| import org.apache.carbondata.core.util.DataTypeUtil; |
| |
| /** |
| * Decimal converter to keep the data compact. |
| */ |
| public final class DecimalConverterFactory { |
| |
| public static final DecimalConverterFactory INSTANCE = new DecimalConverterFactory(); |
| |
| private int[] minBytesForPrecision = minBytesForPrecision(); |
| |
| private DecimalConverterFactory() { |
| |
| } |
| |
| private int computeMinBytesForPrecision(int precision) { |
| int numBytes = 1; |
| while (Math.pow(2.0, 8 * numBytes - 1) < Math.pow(10.0, precision)) { |
| numBytes += 1; |
| } |
| return numBytes; |
| } |
| |
| private int[] minBytesForPrecision() { |
| int[] data = new int[39]; |
| for (int i = 0; i < data.length; i++) { |
| data[i] = computeMinBytesForPrecision(i); |
| } |
| return data; |
| } |
| |
| public enum DecimalConverterType { |
| DECIMAL_LV(-1), DECIMAL_INT(4), DECIMAL_LONG(8), DECIMAL_UNSCALED(-1); |
| |
| private int sizeInBytes; |
| |
| DecimalConverterType(int sizeInBytes) { |
| this.sizeInBytes = sizeInBytes; |
| } |
| |
| public int getSizeInBytes() { |
| return sizeInBytes; |
| } |
| |
| } |
| |
| public interface DecimalConverter { |
| |
| Object convert(BigDecimal decimal); |
| |
| BigDecimal getDecimal(Object valueToBeConverted); |
| |
| void fillVector(Object valuesToBeConverted, int size, ColumnVectorInfo info, BitSet nullBitset, |
| DataType pageType); |
| |
| int getSize(); |
| |
| DecimalConverterType getDecimalConverterType(); |
| |
| } |
| |
| public static class DecimalIntConverter implements DecimalConverter { |
| |
| protected int scale; |
| |
| DecimalIntConverter(int scale) { |
| this.scale = scale; |
| } |
| |
| @Override |
| public Object convert(BigDecimal decimal) { |
| long longValue = decimal.unscaledValue().longValue(); |
| return (int) longValue; |
| } |
| |
| @Override |
| public BigDecimal getDecimal(Object valueToBeConverted) { |
| return BigDecimal.valueOf((Long) valueToBeConverted, scale); |
| } |
| |
| @Override |
| public void fillVector(Object valuesToBeConverted, int size, |
| ColumnVectorInfo vectorInfo, BitSet nullBitSet, DataType pageType) { |
| // TODO we need to find way to directly set to vector with out conversion. This way is very |
| // inefficient. |
| CarbonColumnVector vector = getCarbonColumnVector(vectorInfo, nullBitSet); |
| int precision; |
| int newMeasureScale; |
| if (vectorInfo.measure == null) { |
| // complex primitive decimal flow comes as dimension |
| precision = ((DecimalType) vector.getType()).getPrecision(); |
| newMeasureScale = ((DecimalType) vector.getType()).getScale(); |
| size = ColumnVectorInfo.getUpdatedPageSizeForChildVector(vectorInfo, size); |
| } else { |
| precision = vectorInfo.measure.getMeasure().getPrecision(); |
| newMeasureScale = vectorInfo.measure.getMeasure().getScale(); |
| } |
| if (!(valuesToBeConverted instanceof byte[])) { |
| throw new UnsupportedOperationException("This object type " + valuesToBeConverted.getClass() |
| + " is not supported in this method"); |
| } |
| byte[] data = (byte[]) valuesToBeConverted; |
| if (pageType == DataTypes.BYTE) { |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigDecimal value = BigDecimal.valueOf(data[i], scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } else if (pageType == DataTypes.SHORT) { |
| int shortSizeInBytes = DataTypes.SHORT.getSizeInBytes(); |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigDecimal value = BigDecimal |
| .valueOf(ByteUtil.toShortLittleEndian(data, i * shortSizeInBytes), |
| scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } else if (pageType == DataTypes.SHORT_INT) { |
| int shortIntSizeInBytes = DataTypes.SHORT_INT.getSizeInBytes(); |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigDecimal value = BigDecimal |
| .valueOf(ByteUtil.valueOf3Bytes(data, i * shortIntSizeInBytes), |
| scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } else { |
| int intSizeInBytes = DataTypes.INT.getSizeInBytes(); |
| if (pageType == DataTypes.INT) { |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigDecimal value = BigDecimal |
| .valueOf(ByteUtil.toIntLittleEndian(data, i * intSizeInBytes), |
| scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } else if (pageType == DataTypes.LONG) { |
| int longSizeInBytes = DataTypes.LONG.getSizeInBytes(); |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigDecimal value = BigDecimal |
| .valueOf(ByteUtil.toLongLittleEndian(data, i * longSizeInBytes), |
| scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } else if (pageType == DataTypes.BYTE_ARRAY) { |
| // complex primitive decimal dimension |
| int offset = 0; |
| for (int j = 0; j < size; j++) { |
| // here decimal data will be Length[4 byte], scale[1 byte], value[Length byte] |
| int len = ByteBuffer.wrap(data, offset, intSizeInBytes).getInt(); |
| offset += intSizeInBytes; |
| if (len == 0) { |
| vector.putNull(j); |
| continue; |
| } |
| // jump the scale offset |
| offset += 1; |
| // remove scale from the length |
| len -= 1; |
| byte[] row = new byte[len]; |
| System.arraycopy(data, offset, row, 0, len); |
| long val; |
| if (len == 1) { |
| val = row[0]; |
| } else if (len == 2) { |
| val = ByteUtil.toShort(row, 0); |
| } else if (len == 4) { |
| val = ByteUtil.toInt(row, 0); |
| } else if (len == 3) { |
| val = ByteUtil.valueOf3Bytes(row, 0); |
| } else { |
| // TODO: check if other value can come |
| val = ByteUtil.toLong(row, 0, len); |
| } |
| BigDecimal value = BigDecimal.valueOf(val, scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(j, value, precision); |
| offset += len; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public int getSize() { |
| return 4; |
| } |
| |
| @Override |
| public DecimalConverterType getDecimalConverterType() { |
| return DecimalConverterType.DECIMAL_INT; |
| } |
| } |
| |
| public static class DecimalLongConverter extends DecimalIntConverter { |
| |
| DecimalLongConverter(int scale) { |
| super(scale); |
| } |
| |
| @Override |
| public Object convert(BigDecimal decimal) { |
| return decimal.unscaledValue().longValue(); |
| } |
| |
| @Override |
| public BigDecimal getDecimal(Object valueToBeConverted) { |
| return BigDecimal.valueOf((Long) valueToBeConverted, scale); |
| } |
| |
| @Override |
| public int getSize() { |
| return 8; |
| } |
| |
| @Override |
| public DecimalConverterType getDecimalConverterType() { |
| return DecimalConverterType.DECIMAL_LONG; |
| } |
| } |
| |
| public class DecimalUnscaledConverter implements DecimalConverter { |
| |
| private int scale; |
| |
| private int numBytes; |
| |
| private byte[] decimalBuffer = new byte[minBytesForPrecision[38]]; |
| |
| DecimalUnscaledConverter(int precision, int scale) { |
| this.scale = scale; |
| this.numBytes = minBytesForPrecision[precision]; |
| } |
| |
| @Override |
| public Object convert(BigDecimal decimal) { |
| byte[] bytes = decimal.unscaledValue().toByteArray(); |
| byte[] fixedLengthBytes = null; |
| if (bytes.length == numBytes) { |
| // If the length of the underlying byte array of the unscaled `BigInteger` happens to be |
| // `numBytes`, just reuse it, so that we don't bother copying it to `decimalBuffer`. |
| fixedLengthBytes = bytes; |
| } else { |
| // Otherwise, the length must be less than `numBytes`. In this case we copy contents of |
| // the underlying bytes with padding sign bytes to `decimalBuffer` to form the result |
| // fixed-length byte array. |
| byte signByte = 0; |
| if (bytes[0] < 0) { |
| signByte = (byte) -1; |
| } else { |
| signByte = (byte) 0; |
| } |
| Arrays.fill(decimalBuffer, 0, numBytes - bytes.length, signByte); |
| System.arraycopy(bytes, 0, decimalBuffer, numBytes - bytes.length, bytes.length); |
| fixedLengthBytes = decimalBuffer; |
| } |
| byte[] value = new byte[numBytes]; |
| System.arraycopy(fixedLengthBytes, 0, value, 0, numBytes); |
| return value; |
| } |
| |
| @Override |
| public BigDecimal getDecimal(Object valueToBeConverted) { |
| BigInteger bigInteger = new BigInteger((byte[]) valueToBeConverted); |
| return new BigDecimal(bigInteger, scale); |
| } |
| |
| @Override |
| public void fillVector(Object valuesToBeConverted, int size, |
| ColumnVectorInfo vectorInfo, BitSet nullBitSet, DataType pageType) { |
| CarbonColumnVector vector = getCarbonColumnVector(vectorInfo, nullBitSet); |
| //TODO handle complex child |
| int precision = vectorInfo.measure.getMeasure().getPrecision(); |
| int newMeasureScale = vectorInfo.measure.getMeasure().getScale(); |
| if (scale < newMeasureScale) { |
| scale = newMeasureScale; |
| } |
| if (valuesToBeConverted instanceof byte[][]) { |
| byte[][] data = (byte[][]) valuesToBeConverted; |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigInteger bigInteger = new BigInteger(data[i]); |
| BigDecimal value = new BigDecimal(bigInteger, scale); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public int getSize() { |
| return numBytes; |
| } |
| |
| @Override |
| public DecimalConverterType getDecimalConverterType() { |
| return DecimalConverterType.DECIMAL_UNSCALED; |
| } |
| } |
| |
| public static class LVBytesDecimalConverter implements DecimalConverter { |
| |
| public static LVBytesDecimalConverter INSTANCE = new LVBytesDecimalConverter(); |
| |
| @Override |
| public Object convert(BigDecimal decimal) { |
| return DataTypeUtil.bigDecimalToByte(decimal); |
| } |
| |
| @Override |
| public BigDecimal getDecimal(Object valueToBeConverted) { |
| return DataTypeUtil.byteToBigDecimal((byte[]) valueToBeConverted); |
| } |
| |
| @Override |
| public void fillVector(Object valuesToBeConverted, int size, |
| ColumnVectorInfo vectorInfo, BitSet nullBitSet, DataType pageType) { |
| CarbonColumnVector vector = getCarbonColumnVector(vectorInfo, nullBitSet); |
| //TODO handle complex child |
| int precision = vectorInfo.measure.getMeasure().getPrecision(); |
| int newMeasureScale = vectorInfo.measure.getMeasure().getScale(); |
| if (valuesToBeConverted instanceof byte[][]) { |
| byte[][] data = (byte[][]) valuesToBeConverted; |
| for (int i = 0; i < size; i++) { |
| if (nullBitSet.get(i)) { |
| vector.putNull(i); |
| } else { |
| BigDecimal value = DataTypeUtil.byteToBigDecimal(data[i]); |
| if (value.scale() < newMeasureScale) { |
| value = value.setScale(newMeasureScale); |
| } |
| vector.putDecimal(i, value, precision); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public int getSize() { |
| return -1; |
| } |
| |
| @Override |
| public DecimalConverterType getDecimalConverterType() { |
| return DecimalConverterType.DECIMAL_LV; |
| } |
| } |
| |
| private static CarbonColumnVector getCarbonColumnVector(ColumnVectorInfo vectorInfo, |
| BitSet nullBitSet) { |
| CarbonColumnVector vector = vectorInfo.vector; |
| BitSet deletedRows = vectorInfo.deletedRows; |
| vector = ColumnarVectorWrapperDirectFactory |
| .getDirectVectorWrapperFactory(vectorInfo, vector, vectorInfo.invertedIndex, nullBitSet, |
| deletedRows, true, false); |
| return vector; |
| } |
| |
| public DecimalConverter getDecimalConverter(int precision, int scale) { |
| if (precision < 0) { |
| return new LVBytesDecimalConverter(); |
| } else if (precision <= 9) { |
| return new DecimalIntConverter(scale); |
| } else if (precision <= 18) { |
| return new DecimalLongConverter(scale); |
| } else { |
| return new DecimalUnscaledConverter(precision, scale); |
| } |
| } |
| |
| } |