/*
* Licensed 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 com.facebook.presto.type;
import com.facebook.presto.annotation.UsedByGeneratedCode;
import com.facebook.presto.metadata.Signature;
import com.facebook.presto.metadata.SqlScalarFunction;
import com.facebook.presto.metadata.SqlScalarFunctionBuilder;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.type.DecimalType;
import com.facebook.presto.spi.type.Decimals;
import com.facebook.presto.spi.type.StandardTypes;
import com.facebook.presto.spi.type.TypeSignature;
import com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.SignedBytes;
import io.airlift.slice.DynamicSliceOutput;
import io.airlift.slice.Slice;
import io.airlift.slice.SliceOutput;
import io.airlift.slice.Slices;
import javax.annotation.Nullable;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import static com.facebook.presto.metadata.FunctionKind.SCALAR;
import static com.facebook.presto.operator.scalar.JsonOperators.JSON_FACTORY;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
import static com.facebook.presto.spi.function.OperatorType.CAST;
import static com.facebook.presto.spi.type.BigintType.BIGINT;
import static com.facebook.presto.spi.type.BooleanType.BOOLEAN;
import static com.facebook.presto.spi.type.Decimals.bigIntegerTenToNth;
import static com.facebook.presto.spi.type.Decimals.decodeUnscaledValue;
import static com.facebook.presto.spi.type.Decimals.encodeUnscaledValue;
import static com.facebook.presto.spi.type.Decimals.isShortDecimal;
import static com.facebook.presto.spi.type.Decimals.longTenToNth;
import static com.facebook.presto.spi.type.Decimals.overflows;
import static com.facebook.presto.spi.type.DoubleType.DOUBLE;
import static com.facebook.presto.spi.type.IntegerType.INTEGER;
import static com.facebook.presto.spi.type.RealType.REAL;
import static com.facebook.presto.spi.type.SmallintType.SMALLINT;
import static com.facebook.presto.spi.type.TinyintType.TINYINT;
import static com.facebook.presto.spi.type.TypeSignature.parseTypeSignature;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.compareAbsolute;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.multiply;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.overflows;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.rescale;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.unscaledDecimal;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.unscaledDecimalToUnscaledLong;
import static com.facebook.presto.spi.type.UnscaledDecimal128Arithmetic.unscaledDecimalToUnscaledLongUnsafe;
import static com.facebook.presto.type.JsonType.JSON;
import static com.facebook.presto.util.Failures.checkCondition;
import static com.facebook.presto.util.JsonUtil.createJsonGenerator;
import static com.facebook.presto.util.JsonUtil.createJsonParser;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Double.parseDouble;
import static java.lang.Float.floatToRawIntBits;
import static java.lang.Float.intBitsToFloat;
import static java.lang.Float.parseFloat;
import static java.lang.Math.multiplyExact;
import static java.lang.Math.toIntExact;
import static java.lang.String.format;
import static java.math.BigInteger.ZERO;
import static java.math.RoundingMode.HALF_UP;
import static java.nio.charset.StandardCharsets.UTF_8;
public final class DecimalCasts
{
public static final SqlScalarFunction DECIMAL_TO_BOOLEAN_CAST = castFunctionFromDecimalTo(BOOLEAN.getTypeSignature(), "shortDecimalToBoolean", "longDecimalToBoolean");
public static final SqlScalarFunction BOOLEAN_TO_DECIMAL_CAST = castFunctionToDecimalFrom(BOOLEAN.getTypeSignature(), "booleanToShortDecimal", "booleanToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_BIGINT_CAST = castFunctionFromDecimalTo(BIGINT.getTypeSignature(), "shortDecimalToBigint", "longDecimalToBigint");
public static final SqlScalarFunction BIGINT_TO_DECIMAL_CAST = castFunctionToDecimalFrom(BIGINT.getTypeSignature(), "bigintToShortDecimal", "bigintToLongDecimal");
public static final SqlScalarFunction INTEGER_TO_DECIMAL_CAST = castFunctionToDecimalFrom(INTEGER.getTypeSignature(), "integerToShortDecimal", "integerToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_INTEGER_CAST = castFunctionFromDecimalTo(INTEGER.getTypeSignature(), "shortDecimalToInteger", "longDecimalToInteger");
public static final SqlScalarFunction SMALLINT_TO_DECIMAL_CAST = castFunctionToDecimalFrom(SMALLINT.getTypeSignature(), "smallintToShortDecimal", "smallintToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_SMALLINT_CAST = castFunctionFromDecimalTo(SMALLINT.getTypeSignature(), "shortDecimalToSmallint", "longDecimalToSmallint");
public static final SqlScalarFunction TINYINT_TO_DECIMAL_CAST = castFunctionToDecimalFrom(TINYINT.getTypeSignature(), "tinyintToShortDecimal", "tinyintToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_TINYINT_CAST = castFunctionFromDecimalTo(TINYINT.getTypeSignature(), "shortDecimalToTinyint", "longDecimalToTinyint");
public static final SqlScalarFunction DECIMAL_TO_DOUBLE_CAST = castFunctionFromDecimalTo(DOUBLE.getTypeSignature(), "shortDecimalToDouble", "longDecimalToDouble");
public static final SqlScalarFunction DOUBLE_TO_DECIMAL_CAST = castFunctionToDecimalFrom(DOUBLE.getTypeSignature(), "doubleToShortDecimal", "doubleToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_REAL_CAST = castFunctionFromDecimalTo(REAL.getTypeSignature(), "shortDecimalToReal", "longDecimalToReal");
public static final SqlScalarFunction REAL_TO_DECIMAL_CAST = castFunctionToDecimalFrom(REAL.getTypeSignature(), "realToShortDecimal", "realToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_VARCHAR_CAST = castFunctionFromDecimalTo(parseTypeSignature("varchar(x)", ImmutableSet.of("x")), "shortDecimalToVarchar", "longDecimalToVarchar");
public static final SqlScalarFunction VARCHAR_TO_DECIMAL_CAST = castFunctionToDecimalFrom(parseTypeSignature("varchar(x)", ImmutableSet.of("x")), "varcharToShortDecimal", "varcharToLongDecimal");
public static final SqlScalarFunction DECIMAL_TO_JSON_CAST = castFunctionFromDecimalTo(JSON.getTypeSignature(), "shortDecimalToJson", "longDecimalToJson");
public static final SqlScalarFunction JSON_TO_DECIMAL_CAST = castFunctionToDecimalFromBuilder(JSON.getTypeSignature(), "jsonToShortDecimal", "jsonToLongDecimal").nullableResult(true).build();
/**
* Powers of 10 which can be represented exactly in double.
*/
private static final double[] DOUBLE_10_POW = {
1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5,
1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11,
1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17,
1.0e18, 1.0e19, 1.0e20, 1.0e21, 1.0e22
};
/**
* Powers of 10 which can be represented exactly in float.
*/
private static final float[] FLOAT_10_POW = {
1.0e0f, 1.0e1f, 1.0e2f, 1.0e3f, 1.0e4f, 1.0e5f,
1.0e6f, 1.0e7f, 1.0e8f, 1.0e9f, 1.0e10f
};
private static final Slice MAX_EXACT_DOUBLE = unscaledDecimal((1L << 52) - 1);
private static final Slice MAX_EXACT_FLOAT = unscaledDecimal((1L << 22) - 1);
private static SqlScalarFunction castFunctionFromDecimalTo(TypeSignature to, String... methodNames)
{
Signature signature = Signature.builder()
.kind(SCALAR)
.operatorType(CAST)
.argumentTypes(parseTypeSignature("decimal(precision,scale)", ImmutableSet.of("precision", "scale")))
.returnType(to)
.build();
return SqlScalarFunction.builder(DecimalCasts.class)
.signature(signature)
.implementation(b -> b
.methods(methodNames)
.withExtraParameters((context) -> {
long precision = context.getLiteral("precision");
long scale = context.getLiteral("scale");
Number tenToScale;
if (isShortDecimal(context.getParameterTypes().get(0))) {
tenToScale = longTenToNth(intScale(scale));
}
else {
tenToScale = bigIntegerTenToNth(intScale(scale));
}
return ImmutableList.of(precision, scale, tenToScale);
})
)
.build();
}
private static SqlScalarFunction castFunctionToDecimalFrom(TypeSignature from, String... methodNames)
{
return castFunctionToDecimalFromBuilder(from, methodNames).build();
}
private static SqlScalarFunctionBuilder castFunctionToDecimalFromBuilder(TypeSignature from, String... methodNames)
{
Signature signature = Signature.builder()
.kind(SCALAR)
.operatorType(CAST)
.argumentTypes(from)
.returnType(parseTypeSignature("decimal(precision,scale)", ImmutableSet.of("precision", "scale")))
.build();
return SqlScalarFunction.builder(DecimalCasts.class)
.signature(signature)
.implementation(b -> b
.methods(methodNames)
.withExtraParameters((context) -> {
DecimalType resultType = (DecimalType) context.getReturnType();
Number tenToScale;
if (isShortDecimal(resultType)) {
tenToScale = longTenToNth(resultType.getScale());
}
else {
tenToScale = bigIntegerTenToNth(resultType.getScale());
}
return ImmutableList.of(resultType.getPrecision(), resultType.getScale(), tenToScale);
})
);
}
private DecimalCasts() {}
@UsedByGeneratedCode
public static boolean shortDecimalToBoolean(long decimal, long precision, long scale, long tenToScale)
{
return decimal != 0;
}
@UsedByGeneratedCode
public static boolean longDecimalToBoolean(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
return !decodeUnscaledValue(decimal).equals(ZERO);
}
@UsedByGeneratedCode
public static long booleanToShortDecimal(boolean value, long precision, long scale, long tenToScale)
{
return value ? tenToScale : 0;
}
@UsedByGeneratedCode
public static Slice booleanToLongDecimal(boolean value, long precision, long scale, BigInteger tenToScale)
{
return unscaledDecimal(value ? tenToScale : ZERO);
}
@UsedByGeneratedCode
public static long shortDecimalToBigint(long decimal, long precision, long scale, long tenToScale)
{
// this rounds the decimal value to the nearest integral value
if (decimal >= 0) {
return (decimal + tenToScale / 2) / tenToScale;
}
return -((-decimal + tenToScale / 2) / tenToScale);
}
@UsedByGeneratedCode
public static long longDecimalToBigint(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
try {
return unscaledDecimalToUnscaledLong(rescale(decimal, intScale(-scale)));
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to BIGINT", Decimals.toString(decimal, intScale(scale))));
}
}
@UsedByGeneratedCode
public static long bigintToShortDecimal(long value, long precision, long scale, long tenToScale)
{
try {
long decimal = multiplyExact(value, tenToScale);
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast BIGINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast BIGINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static Slice bigintToLongDecimal(long value, long precision, long scale, BigInteger tenToScale)
{
try {
Slice decimal = multiply(unscaledDecimal(value), unscaledDecimal(tenToScale));
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast BIGINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast BIGINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static long shortDecimalToInteger(long decimal, long precision, long scale, long tenToScale)
{
// this rounds the decimal value to the nearest integral value
long longResult = (decimal + tenToScale / 2) / tenToScale;
if (decimal < 0) {
longResult = -((-decimal + tenToScale / 2) / tenToScale);
}
try {
return toIntExact(longResult);
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to INTEGER", longResult));
}
}
@UsedByGeneratedCode
public static long longDecimalToInteger(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
try {
return toIntExact(unscaledDecimalToUnscaledLong(rescale(decimal, intScale(-scale))));
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to INTEGER", Decimals.toString(decimal, intScale(scale))));
}
}
@UsedByGeneratedCode
public static long integerToShortDecimal(long value, long precision, long scale, long tenToScale)
{
try {
long decimal = multiplyExact(value, tenToScale);
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast INTEGER '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast INTEGER '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static Slice integerToLongDecimal(long value, long precision, long scale, BigInteger tenToScale)
{
try {
Slice decimal = multiply(unscaledDecimal(value), unscaledDecimal(tenToScale));
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast INTEGER '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast INTEGER '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static long shortDecimalToSmallint(long decimal, long precision, long scale, long tenToScale)
{
// this rounds the decimal value to the nearest integral value
long longResult = (decimal + tenToScale / 2) / tenToScale;
if (decimal < 0) {
longResult = -((-decimal + tenToScale / 2) / tenToScale);
}
try {
return Shorts.checkedCast(longResult);
}
catch (IllegalArgumentException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to SMALLINT", longResult));
}
}
@UsedByGeneratedCode
public static long longDecimalToSmallint(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
try {
return Shorts.checkedCast(unscaledDecimalToUnscaledLong(rescale(decimal, intScale(-scale))));
}
catch (ArithmeticException | IllegalArgumentException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to SMALLINT", Decimals.toString(decimal, intScale(scale))));
}
}
@UsedByGeneratedCode
public static long smallintToShortDecimal(long value, long precision, long scale, long tenToScale)
{
try {
long decimal = multiplyExact(value, tenToScale);
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast SMALLINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast SMALLINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static Slice smallintToLongDecimal(long value, long precision, long scale, BigInteger tenToScale)
{
try {
Slice decimal = multiply(unscaledDecimal(value), unscaledDecimal(tenToScale));
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast SMALLINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast SMALLINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static long shortDecimalToTinyint(long decimal, long precision, long scale, long tenToScale)
{
// this rounds the decimal value to the nearest integral value
long longResult = (decimal + tenToScale / 2) / tenToScale;
if (decimal < 0) {
longResult = -((-decimal + tenToScale / 2) / tenToScale);
}
try {
return SignedBytes.checkedCast(longResult);
}
catch (IllegalArgumentException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to TINYINT", longResult));
}
}
@UsedByGeneratedCode
public static long longDecimalToTinyint(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
try {
return SignedBytes.checkedCast(unscaledDecimalToUnscaledLong(rescale(decimal, intScale(-scale))));
}
catch (ArithmeticException | IllegalArgumentException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to TINYINT", Decimals.toString(decimal, intScale(scale))));
}
}
@UsedByGeneratedCode
public static long tinyintToShortDecimal(long value, long precision, long scale, long tenToScale)
{
try {
long decimal = multiplyExact(value, tenToScale);
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast TINYINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast TINYINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static Slice tinyintToLongDecimal(long value, long precision, long scale, BigInteger tenToScale)
{
try {
Slice decimal = multiply(unscaledDecimal(value), unscaledDecimal(tenToScale));
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast TINYINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast TINYINT '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static double shortDecimalToDouble(long decimal, long precision, long scale, long tenToScale)
{
return ((double) decimal) / tenToScale;
}
@UsedByGeneratedCode
public static double longDecimalToDouble(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
// If both decimal and scale can be represented exactly in double then compute rescaled and rounded result directly in double.
if (scale < DOUBLE_10_POW.length && compareAbsolute(decimal, MAX_EXACT_DOUBLE) <= 0) {
return (double) unscaledDecimalToUnscaledLongUnsafe(decimal) / DOUBLE_10_POW[intScale(scale)];
}
// TODO: optimize and convert directly to double in similar fashion as in double to decimal casts
return parseDouble(Decimals.toString(decimal, intScale(scale)));
}
@UsedByGeneratedCode
public static long shortDecimalToReal(long decimal, long precision, long scale, long tenToScale)
{
return floatToRawIntBits(((float) decimal) / tenToScale);
}
@UsedByGeneratedCode
public static long longDecimalToReal(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
// If both decimal and scale can be represented exactly in float then compute rescaled and rounded result directly in float.
if (scale < FLOAT_10_POW.length && compareAbsolute(decimal, MAX_EXACT_FLOAT) <= 0) {
return floatToRawIntBits((float) unscaledDecimalToUnscaledLongUnsafe(decimal) / FLOAT_10_POW[intScale(scale)]);
}
// TODO: optimize and convert directly to float in similar fashion as in double to decimal casts
return floatToRawIntBits(parseFloat(Decimals.toString(decimal, intScale(scale))));
}
@UsedByGeneratedCode
public static long doubleToShortDecimal(double value, long precision, long scale, long tenToScale)
{
// TODO: implement specialized version for short decimals
Slice decimal = internalDoubleToLongDecimal(value, precision, scale);
long low = UnscaledDecimal128Arithmetic.getLong(decimal, 0);
long high = UnscaledDecimal128Arithmetic.getLong(decimal, 1);
checkState(high == 0 && low >= 0, "Unexpected long decimal");
if (UnscaledDecimal128Arithmetic.isNegative(decimal)) {
return -low;
}
else {
return low;
}
}
@UsedByGeneratedCode
public static Slice doubleToLongDecimal(double value, long precision, long scale, BigInteger tenToScale)
{
return internalDoubleToLongDecimal(value, precision, scale);
}
private static Slice internalDoubleToLongDecimal(double value, long precision, long scale)
{
try {
Slice decimal = UnscaledDecimal128Arithmetic.doubleToLongDecimal(value, precision, intScale(scale));
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast DOUBLE '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast DOUBLE '%s' to DECIMAL(%s, %s)", value, precision, scale));
}
}
@UsedByGeneratedCode
public static long realToShortDecimal(long value, long precision, long scale, long tenToScale)
{
// TODO: implement specialized version for short decimals
Slice decimal = realToLongDecimal(value, precision, scale);
long low = UnscaledDecimal128Arithmetic.getLong(decimal, 0);
long high = UnscaledDecimal128Arithmetic.getLong(decimal, 1);
checkState(high == 0 && low >= 0, "Unexpected long decimal");
if (UnscaledDecimal128Arithmetic.isNegative(decimal)) {
return -low;
}
else {
return low;
}
}
@UsedByGeneratedCode
public static Slice realToLongDecimal(long value, long precision, long scale, BigInteger tenToScale)
{
return realToLongDecimal(value, precision, scale);
}
private static Slice realToLongDecimal(long value, long precision, long scale)
{
float floatValue = intBitsToFloat(intScale(value));
try {
//TODO: optimize, implement specialized float to decimal conversion instead of using double to decimal
Slice decimal = UnscaledDecimal128Arithmetic.doubleToLongDecimal(floatValue, precision, intScale(scale));
if (overflows(decimal, intScale(precision))) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast REAL '%s' to DECIMAL(%s, %s)", floatValue, precision, scale));
}
return decimal;
}
catch (ArithmeticException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast REAL '%s' to DECIMAL(%s, %s)", floatValue, precision, scale));
}
}
@UsedByGeneratedCode
public static Slice shortDecimalToVarchar(long decimal, long precision, long scale, long tenToScale)
{
return Slices.copiedBuffer(Decimals.toString(decimal, intScale(scale)), UTF_8);
}
@UsedByGeneratedCode
public static Slice longDecimalToVarchar(Slice decimal, long precision, long scale, BigInteger tenToScale)
{
return Slices.copiedBuffer(Decimals.toString(decimal, intScale(scale)), UTF_8);
}
@UsedByGeneratedCode
public static long varcharToShortDecimal(Slice value, long precision, long scale, long tenToScale)
{
try {
String stringValue = value.toString(UTF_8);
BigDecimal decimal = new BigDecimal(stringValue).setScale(intScale(scale), HALF_UP);
if (overflows(decimal, precision)) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast VARCHAR '%s' to DECIMAL(%s, %s)", stringValue, precision, scale));
}
return decimal.unscaledValue().longValue();
}
catch (NumberFormatException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast VARCHAR '%s' to DECIMAL(%s, %s)", value.toString(UTF_8), precision, scale));
}
}
@UsedByGeneratedCode
public static Slice varcharToLongDecimal(Slice value, long precision, long scale, BigInteger tenToScale)
{
String stringValue = value.toString(UTF_8);
BigDecimal decimal = new BigDecimal(stringValue).setScale(intScale(scale), HALF_UP);
if (overflows(decimal, precision)) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast VARCHAR '%s' to DECIMAL(%s, %s)", stringValue, precision, scale));
}
return encodeUnscaledValue(decimal.unscaledValue());
}
@UsedByGeneratedCode
public static Slice shortDecimalToJson(long decimal, long precision, long scale, long tenToScale)
throws IOException
{
return decimalToJson(BigDecimal.valueOf(decimal, intScale(scale)));
}
@UsedByGeneratedCode
public static Slice longDecimalToJson(Slice decimal, long precision, long scale, BigInteger tenToScale)
throws IOException
{
return decimalToJson(new BigDecimal(decodeUnscaledValue(decimal), intScale(scale)));
}
private static Slice decimalToJson(BigDecimal bigDecimal)
{
try {
SliceOutput dynamicSliceOutput = new DynamicSliceOutput(32);
try (JsonGenerator jsonGenerator = createJsonGenerator(JSON_FACTORY, dynamicSliceOutput)) {
jsonGenerator.writeNumber(bigDecimal);
}
return dynamicSliceOutput.slice();
}
catch (IOException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%f' to %s", bigDecimal, StandardTypes.JSON));
}
}
@UsedByGeneratedCode
public static Slice jsonToLongDecimal(Slice json, long precision, long scale, BigInteger tenToScale)
throws IOException
{
BigDecimal bigDecimal = jsonToDecimal(json, precision, scale);
if (bigDecimal == null) {
return null;
}
return encodeUnscaledValue(bigDecimal.unscaledValue());
}
@UsedByGeneratedCode
public static Long jsonToShortDecimal(Slice json, long precision, long scale, long tenToScale)
throws IOException
{
BigDecimal bigDecimal = jsonToDecimal(json, precision, scale);
return bigDecimal != null ? bigDecimal.unscaledValue().longValue() : null;
}
@Nullable
private static BigDecimal jsonToDecimal(Slice json, long precision, long scale)
{
try (JsonParser parser = createJsonParser(JSON_FACTORY, json)) {
parser.nextToken();
BigDecimal result;
switch (parser.getCurrentToken()) {
case VALUE_NULL:
result = null;
break;
case VALUE_STRING:
result = new BigDecimal(parser.getText());
result = result.setScale(intScale(scale), HALF_UP);
break;
case VALUE_NUMBER_FLOAT:
case VALUE_NUMBER_INT:
result = parser.getDecimalValue();
result = result.setScale(intScale(scale), HALF_UP);
break;
case VALUE_TRUE:
result = BigDecimal.ONE.setScale(intScale(scale), HALF_UP);
break;
case VALUE_FALSE:
result = BigDecimal.ZERO.setScale(intScale(scale), HALF_UP);
break;
default:
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to DECIMAL(%s,%s)", json.toStringUtf8(), precision, scale));
}
checkCondition(
parser.nextToken() == null &&
(result == null || result.precision() <= precision),
INVALID_CAST_ARGUMENT, "Cannot cast input json to DECIMAL(%s,%s)", precision, scale); // check no trailing token
return result;
}
catch (IOException | NumberFormatException e) {
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Cannot cast '%s' to DECIMAL(%s,%s)", json.toStringUtf8(), precision, scale));
}
}
@SuppressWarnings("NumericCastThatLosesPrecision")
private static int intScale(long scale)
{
return (int) scale;
}
}