Skip to content

Commit f41eca1

Browse files
committed
Fail correctly when Python UDF decimal result overflows
Python function runtime expect that `decimal.Decimal` is used when function result is mapped to SQL DECIMAL value. Python's `Decimal` supports non-finite values: NaN, -Infinity and +Infinity and values outside of SQL DECIMAL range. Add tests that TrinoException is correctly thrown when an non-mappable value is returned. For overflow this was not the case.
1 parent 03f06bd commit f41eca1

2 files changed

Lines changed: 43 additions & 3 deletions

File tree

plugin/trino-functions-python/src/main/java/io/trino/plugin/functions/python/TrinoTypes.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import static io.trino.spi.type.TypeUtils.writeNativeValue;
7878
import static java.lang.Math.toIntExact;
7979
import static java.math.RoundingMode.HALF_UP;
80+
import static java.util.Objects.requireNonNullElse;
8081

8182
final class TrinoTypes
8283
{
@@ -381,9 +382,17 @@ public static Object binaryToJava(Type type, SliceInput input)
381382
case MapType mapType -> binaryMapToJava(mapType, input);
382383
case DecimalType decimalType -> {
383384
BigDecimal decimal = new BigDecimal(input.readSlice(input.readInt()).toStringUtf8());
384-
yield decimalType.isShort()
385-
? encodeShortScaledValue(decimal, decimalType.getScale(), HALF_UP)
386-
: encodeScaledValue(decimal, decimalType.getScale(), HALF_UP);
385+
try {
386+
yield decimalType.isShort()
387+
? encodeShortScaledValue(decimal, decimalType.getScale(), HALF_UP)
388+
: encodeScaledValue(decimal, decimalType.getScale(), HALF_UP);
389+
}
390+
catch (ArithmeticException e) {
391+
throw new TrinoException(
392+
FUNCTION_IMPLEMENTATION_ERROR,
393+
"Function result cannot be converted to %s: %s".formatted(decimalType.getDisplayName(), requireNonNullElse(e.getMessage(), e)),
394+
e);
395+
}
387396
}
388397
case TimeType timeType -> {
389398
long micros = roundMicros(input.readLong(), timeType.getPrecision()) % MICROSECONDS_PER_DAY;

plugin/trino-functions-python/src/test/java/io/trino/plugin/functions/python/TestPythonFunctions.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,37 @@ assert str(x) == '12345678901234567890.12340'
898898
SELECT test_decimal_long(12345678901234567890.1234)
899899
"""))
900900
.matches("VALUES cast(1524148134430814813443.07447 AS decimal(38, 5))");
901+
902+
String realToDecimalInPython =
903+
"""
904+
WITH FUNCTION test_cast_real_to_decimal(x real)
905+
RETURNS decimal(38, 5)
906+
LANGUAGE PYTHON
907+
WITH (handler = 'test')
908+
AS $$
909+
from decimal import Decimal
910+
def test(x):
911+
return Decimal.from_float(x)
912+
$$
913+
""";
914+
915+
// underflow
916+
assertThat(assertions.query(realToDecimalInPython + "SELECT test_cast_real_to_decimal(REAL '1e-17')"))
917+
.matches("VALUES CAST('0' AS decimal(38, 5))");
918+
919+
// overflow
920+
assertThat(assertions.query(realToDecimalInPython + "SELECT test_cast_real_to_decimal(REAL '1e+34')"))
921+
.failure().hasMessage("Function result cannot be converted to decimal(38,5): Decimal overflow");
922+
923+
// NaN
924+
assertThat(assertions.query( realToDecimalInPython + "SELECT test_cast_real_to_decimal(REAL 'NaN')"))
925+
.failure().hasMessage("Failed to convert Python result type 'decimal.Decimal' to Trino type DECIMAL: ValueError: Decimal is not finite: NaN");
926+
927+
// Infinity
928+
assertThat(assertions.query(realToDecimalInPython + "SELECT test_cast_real_to_decimal(REAL '-Infinity')"))
929+
.failure().hasMessage("Failed to convert Python result type 'decimal.Decimal' to Trino type DECIMAL: ValueError: Decimal is not finite: -Infinity");
930+
assertThat(assertions.query( realToDecimalInPython + "SELECT test_cast_real_to_decimal(REAL '+Infinity')"))
931+
.failure().hasMessage("Failed to convert Python result type 'decimal.Decimal' to Trino type DECIMAL: ValueError: Decimal is not finite: Infinity");
901932
}
902933

903934
@Test

0 commit comments

Comments
 (0)