/* * 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.spi.type.Type; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import io.airlift.slice.Slice; import java.math.BigDecimal; import java.math.BigInteger; import static com.facebook.presto.metadata.FunctionKind.SCALAR; import static com.facebook.presto.spi.function.OperatorType.SATURATED_FLOOR_CAST; import static com.facebook.presto.spi.type.BigintType.BIGINT; 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.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 java.lang.Float.intBitsToFloat; import static java.lang.Math.toIntExact; import static java.math.BigInteger.ONE; import static java.math.RoundingMode.FLOOR; public final class DecimalSaturatedFloorCasts { private DecimalSaturatedFloorCasts() {} public static final SqlScalarFunction DECIMAL_TO_DECIMAL_SATURATED_FLOOR_CAST = SqlScalarFunction.builder(DecimalSaturatedFloorCasts.class) .signature(Signature.builder() .kind(SCALAR) .operatorType(SATURATED_FLOOR_CAST) .argumentTypes(parseTypeSignature("decimal(source_precision,source_scale)", ImmutableSet.of("source_precision", "source_scale"))) .returnType(parseTypeSignature("decimal(result_precision,result_scale)", ImmutableSet.of("result_precision", "result_scale"))) .build() ) .implementation(b -> b .methods("shortDecimalToShortDecimal", "shortDecimalToLongDecimal", "longDecimalToShortDecimal", "longDecimalToLongDecimal") .withExtraParameters((context) -> { int sourcePrecision = toIntExact(context.getLiteral("source_precision")); int sourceScale = toIntExact(context.getLiteral("source_scale")); int resultPrecision = toIntExact(context.getLiteral("result_precision")); int resultScale = toIntExact(context.getLiteral("result_scale")); return ImmutableList.of(sourcePrecision, sourceScale, resultPrecision, resultScale); }) ).build(); @UsedByGeneratedCode public static long shortDecimalToShortDecimal(long value, int sourcePrecision, int sourceScale, int resultPrecision, int resultScale) { return bigintToBigintFloorSaturatedCast(BigInteger.valueOf(value), sourceScale, resultPrecision, resultScale).longValueExact(); } @UsedByGeneratedCode public static Slice shortDecimalToLongDecimal(long value, int sourcePrecision, int sourceScale, int resultPrecision, int resultScale) { return encodeUnscaledValue(bigintToBigintFloorSaturatedCast(BigInteger.valueOf(value), sourceScale, resultPrecision, resultScale)); } @UsedByGeneratedCode public static long longDecimalToShortDecimal(Slice value, int sourcePrecision, int sourceScale, int resultPrecision, int resultScale) { return bigintToBigintFloorSaturatedCast(decodeUnscaledValue(value), sourceScale, resultPrecision, resultScale).longValueExact(); } @UsedByGeneratedCode public static Slice longDecimalToLongDecimal(Slice value, int sourcePrecision, int sourceScale, int resultPrecision, int resultScale) { return encodeUnscaledValue(bigintToBigintFloorSaturatedCast(decodeUnscaledValue(value), sourceScale, resultPrecision, resultScale)); } private static BigInteger bigintToBigintFloorSaturatedCast(BigInteger value, int sourceScale, int resultPrecision, int resultScale) { return bigDecimalToBigintFloorSaturatedCast(new BigDecimal(value, sourceScale), resultPrecision, resultScale); } private static BigInteger bigDecimalToBigintFloorSaturatedCast(BigDecimal bigDecimal, int resultPrecision, int resultScale) { BigDecimal rescaledValue = bigDecimal.setScale(resultScale, FLOOR); BigInteger unscaledValue = rescaledValue.unscaledValue(); BigInteger maxUnscaledValue = bigIntegerTenToNth(resultPrecision).subtract(ONE); if (unscaledValue.compareTo(maxUnscaledValue) > 0) { return maxUnscaledValue; } BigInteger minUnscaledValue = maxUnscaledValue.negate(); if (unscaledValue.compareTo(minUnscaledValue) < 0) { return minUnscaledValue; } return unscaledValue; } public static final SqlScalarFunction DOUBLE_TO_DECIMAL_SATURATED_FLOOR_CAST = SqlScalarFunction.builder(DecimalSaturatedFloorCasts.class) .signature(Signature.builder() .kind(SCALAR) .operatorType(SATURATED_FLOOR_CAST) .argumentTypes(DOUBLE.getTypeSignature()) .returnType(parseTypeSignature("decimal(result_precision,result_scale)", ImmutableSet.of("result_precision", "result_scale"))) .build() ) .implementation(b -> b .methods("doubleToShortDecimal", "doubleToLongDecimal") .withExtraParameters((context) -> { int resultPrecision = toIntExact(context.getLiteral("result_precision")); int resultScale = toIntExact(context.getLiteral("result_scale")); return ImmutableList.of(resultPrecision, resultScale); }) ).build(); @UsedByGeneratedCode public static long doubleToShortDecimal(double value, int resultPrecision, int resultScale) { return bigDecimalToBigintFloorSaturatedCast(new BigDecimal(value), resultPrecision, resultScale).longValueExact(); } @UsedByGeneratedCode public static Slice doubleToLongDecimal(double value, int resultPrecision, int resultScale) { return encodeUnscaledValue(bigDecimalToBigintFloorSaturatedCast(new BigDecimal(value), resultPrecision, resultScale)); } public static final SqlScalarFunction REAL_TO_DECIMAL_SATURATED_FLOOR_CAST = SqlScalarFunction.builder(DecimalSaturatedFloorCasts.class) .signature(Signature.builder() .kind(SCALAR) .operatorType(SATURATED_FLOOR_CAST) .argumentTypes(REAL.getTypeSignature()) .returnType(parseTypeSignature("decimal(result_precision,result_scale)", ImmutableSet.of("result_precision", "result_scale"))) .build() ) .implementation(b -> b .methods("realToShortDecimal", "realToLongDecimal") .withExtraParameters((context) -> { int resultPrecision = toIntExact(context.getLiteral("result_precision")); int resultScale = toIntExact(context.getLiteral("result_scale")); return ImmutableList.of(resultPrecision, resultScale); }) ).build(); @UsedByGeneratedCode public static long realToShortDecimal(long value, int resultPrecision, int resultScale) { return bigDecimalToBigintFloorSaturatedCast(new BigDecimal(intBitsToFloat((int) value)), resultPrecision, resultScale).longValueExact(); } @UsedByGeneratedCode public static Slice realToLongDecimal(long value, int resultPrecision, int resultScale) { return encodeUnscaledValue(bigDecimalToBigintFloorSaturatedCast(new BigDecimal(intBitsToFloat((int) value)), resultPrecision, resultScale)); } public static final SqlScalarFunction DECIMAL_TO_BIGINT_SATURATED_FLOOR_CAST = decimalToGenericIntegerTypeSaturatedFloorCast(BIGINT, Long.MIN_VALUE, Long.MAX_VALUE); public static final SqlScalarFunction DECIMAL_TO_INTEGER_SATURATED_FLOOR_CAST = decimalToGenericIntegerTypeSaturatedFloorCast(INTEGER, Integer.MIN_VALUE, Integer.MAX_VALUE); public static final SqlScalarFunction DECIMAL_TO_SMALLINT_SATURATED_FLOOR_CAST = decimalToGenericIntegerTypeSaturatedFloorCast(SMALLINT, Short.MIN_VALUE, Short.MAX_VALUE); public static final SqlScalarFunction DECIMAL_TO_TINYINT_SATURATED_FLOOR_CAST = decimalToGenericIntegerTypeSaturatedFloorCast(TINYINT, Byte.MIN_VALUE, Byte.MAX_VALUE); private static SqlScalarFunction decimalToGenericIntegerTypeSaturatedFloorCast(Type type, long minValue, long maxValue) { return SqlScalarFunction.builder(DecimalSaturatedFloorCasts.class) .signature(Signature.builder() .kind(SCALAR) .operatorType(SATURATED_FLOOR_CAST) .argumentTypes(parseTypeSignature("decimal(source_precision,source_scale)", ImmutableSet.of("source_precision", "source_scale"))) .returnType(type.getTypeSignature()) .build() ) .implementation(b -> b .methods("shortDecimalToGenericIntegerType", "longDecimalToGenericIntegerType") .withExtraParameters((context) -> { int sourceScale = toIntExact(context.getLiteral("source_scale")); return ImmutableList.of(sourceScale, minValue, maxValue); }) ).build(); } @UsedByGeneratedCode public static long shortDecimalToGenericIntegerType(long value, int sourceScale, long minValue, long maxValue) { return bigIntegerDecimalToGenericIntegerType(BigInteger.valueOf(value), sourceScale, minValue, maxValue); } @UsedByGeneratedCode public static long longDecimalToGenericIntegerType(Slice value, int sourceScale, long minValue, long maxValue) { return bigIntegerDecimalToGenericIntegerType(decodeUnscaledValue(value), sourceScale, minValue, maxValue); } private static long bigIntegerDecimalToGenericIntegerType(BigInteger bigInteger, int sourceScale, long minValue, long maxValue) { BigDecimal bigDecimal = new BigDecimal(bigInteger, sourceScale); BigInteger unscaledValue = bigDecimal.setScale(0, FLOOR).unscaledValue(); if (unscaledValue.compareTo(BigInteger.valueOf(maxValue)) > 0) { return maxValue; } if (unscaledValue.compareTo(BigInteger.valueOf(minValue)) < 0) { return minValue; } return unscaledValue.longValueExact(); } public static final SqlScalarFunction BIGINT_TO_DECIMAL_SATURATED_FLOOR_CAST = genericIntegerTypeToDecimalSaturatedFloorCast(BIGINT); public static final SqlScalarFunction INTEGER_TO_DECIMAL_SATURATED_FLOOR_CAST = genericIntegerTypeToDecimalSaturatedFloorCast(INTEGER); public static final SqlScalarFunction SMALLINT_TO_DECIMAL_SATURATED_FLOOR_CAST = genericIntegerTypeToDecimalSaturatedFloorCast(SMALLINT); public static final SqlScalarFunction TINYINT_TO_DECIMAL_SATURATED_FLOOR_CAST = genericIntegerTypeToDecimalSaturatedFloorCast(TINYINT); private static SqlScalarFunction genericIntegerTypeToDecimalSaturatedFloorCast(Type integerType) { return SqlScalarFunction.builder(DecimalSaturatedFloorCasts.class) .signature(Signature.builder() .kind(SCALAR) .operatorType(SATURATED_FLOOR_CAST) .argumentTypes(integerType.getTypeSignature()) .returnType(parseTypeSignature("decimal(result_precision,result_scale)", ImmutableSet.of("result_precision", "result_scale"))) .build() ) .implementation(b -> b .methods("genericIntegerTypeToShortDecimal", "genericIntegerTypeToLongDecimal") .withExtraParameters((context) -> { int resultPrecision = toIntExact(context.getLiteral("result_precision")); int resultScale = toIntExact(context.getLiteral("result_scale")); return ImmutableList.of(resultPrecision, resultScale); }) ).build(); } @UsedByGeneratedCode public static long genericIntegerTypeToShortDecimal(long value, int resultPrecision, int resultScale) { return bigDecimalToBigintFloorSaturatedCast(BigDecimal.valueOf(value), resultPrecision, resultScale).longValueExact(); } @UsedByGeneratedCode public static Slice genericIntegerTypeToLongDecimal(long value, int resultPrecision, int resultScale) { return encodeUnscaledValue(bigDecimalToBigintFloorSaturatedCast(BigDecimal.valueOf(value), resultPrecision, resultScale)); } }