This is an automated email from the ASF dual-hosted git repository.
gianm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new bfcd037be3e fix: DECIMAL type coercion losing fractions. (#20456)
bfcd037be3e is described below
commit bfcd037be3e296e2a164322365e04d1e9b833dfb
Author: Gian Merlino <[email protected]>
AuthorDate: Wed Sep 30 14:20:09 2026 -0700
fix: DECIMAL type coercion losing fractions. (#20456)
DECIMAL type coercion could lose fractions due to the least restrictive
type of DECIMAL(19, 0) and DECIMAL(2, 1) being computed as DECIMAL(19, 0).
This could arise with JSON_VALUE because JSON_VALUE with RETURNING
DECIMAL uses the default precision of DECIMAL(19, 0).
The behavior seems new since #19370, so may be connected to an
underlying Calcite change. At any rate, this type system change on our
end avoids the problem.
---
.../druid/sql/calcite/planner/DruidTypeSystem.java | 36 ++++++++++------
.../sql/calcite/CalciteNestedDataQueryTest.java | 34 ++++++++++++---
.../apache/druid/sql/calcite/CalciteQueryTest.java | 50 ++++++++++++++++++++++
.../sql/calcite/planner/DruidTypeFactoryTest.java | 37 ++++++++++++++++
4 files changed, 138 insertions(+), 19 deletions(-)
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidTypeSystem.java
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidTypeSystem.java
index bfa6bf3cf49..e773780e60b 100644
---
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidTypeSystem.java
+++
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidTypeSystem.java
@@ -37,6 +37,18 @@ public class DruidTypeSystem implements RelDataTypeSystem
*/
public static final int DEFAULT_TIMESTAMP_PRECISION = 3;
+ /**
+ * Default precision of DECIMAL, same as Calcite's default. This only
affects planning, since we process
+ * DECIMAL as DOUBLE when executing queries.
+ */
+ public static final int DEFAULT_DECIMAL_PRECISION = 19;
+
+ /**
+ * Maximum precision of DECIMAL. Larger than {@link
#DEFAULT_DECIMAL_PRECISION}, so the common type of a
+ * default-precision DECIMAL and a fractional value, like DECIMAL(19, 0) and
0.1, keeps the fractional digits.
+ */
+ public static final int MAX_DECIMAL_PRECISION = 38;
+
private DruidTypeSystem()
{
// Singleton.
@@ -51,23 +63,21 @@ public class DruidTypeSystem implements RelDataTypeSystem
@Override
public int getDefaultPrecision(final SqlTypeName typeName)
{
- switch (typeName) {
- case TIMESTAMP:
- case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
- return DEFAULT_TIMESTAMP_PRECISION;
- default:
- return RelDataTypeSystem.DEFAULT.getDefaultPrecision(typeName);
- }
+ return switch (typeName) {
+ case TIME, TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE ->
DEFAULT_TIMESTAMP_PRECISION;
+ case DECIMAL -> DEFAULT_DECIMAL_PRECISION;
+ default -> RelDataTypeSystem.DEFAULT.getDefaultPrecision(typeName);
+ };
}
@Override
public int getMaxPrecision(final SqlTypeName typeName)
{
- if (typeName == SqlTypeName.TIME || typeName == SqlTypeName.TIMESTAMP) {
- return DEFAULT_TIMESTAMP_PRECISION;
- } else {
- return RelDataTypeSystem.DEFAULT.getMaxPrecision(typeName);
- }
+ return switch (typeName) {
+ case TIME, TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE ->
DEFAULT_TIMESTAMP_PRECISION;
+ case DECIMAL -> MAX_DECIMAL_PRECISION;
+ default -> RelDataTypeSystem.DEFAULT.getMaxPrecision(typeName);
+ };
}
@Override
@@ -79,7 +89,7 @@ public class DruidTypeSystem implements RelDataTypeSystem
@Override
public int getMaxNumericPrecision()
{
- return RelDataTypeSystem.DEFAULT.getMaxNumericPrecision();
+ return getMaxPrecision(SqlTypeName.DECIMAL);
}
@Override
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
index 72fe7f3993c..b28bb8b63eb 100644
---
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
+++
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
@@ -4319,8 +4319,6 @@ public abstract class CalciteNestedDataQueryTest extends
BaseCalciteQueryTest
@Test
public void testSumPathMixed()
{
- // throws a "Cannot make vector value selector for variant typed nested
field [[LONG, DOUBLE]]"
- skipVectorize();
testQuery(
"SELECT "
+ "SUM(JSON_VALUE(nest, '$.mixed')) "
@@ -4347,8 +4345,6 @@ public abstract class CalciteNestedDataQueryTest extends
BaseCalciteQueryTest
@Test
public void testSumPathMixedFilteredAggLong()
{
- // throws a "Cannot make vector value selector for variant typed nested
field [[LONG, DOUBLE]]"
- skipVectorize();
// this one actually equals 2.1 because the filter is a long so double is
cast and is 1 so both rows match
testQuery(
"SELECT "
@@ -4386,8 +4382,6 @@ public abstract class CalciteNestedDataQueryTest extends
BaseCalciteQueryTest
@Test
public void testSumPathMixedFilteredAggDouble()
{
- // throws a "Cannot make vector value selector for variant typed nested
field [[LONG, DOUBLE]]"
- skipVectorize();
// with double matcher, only the one row matches since the long value cast
is not picked up
testQuery(
"SELECT "
@@ -4579,6 +4573,34 @@ public abstract class CalciteNestedDataQueryTest extends
BaseCalciteQueryTest
);
}
+ @Test
+ public void testReturningDecimalFilterFractionalLiteral()
+ {
+ testQuery(
+ "SELECT string, JSON_VALUE(nest, '$.mixed' RETURNING DECIMAL) "
+ + "FROM druid.nested "
+ + "WHERE JSON_VALUE(nest, '$.mixed' RETURNING DECIMAL) = 1.1",
+ ImmutableList.of(
+ newScanQueryBuilder()
+ .dataSource(DATA_SOURCE)
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .virtualColumns(new NestedFieldVirtualColumn("nest",
"$.mixed", "v0", ColumnType.DOUBLE))
+ .filters(equality("v0", 1.1, ColumnType.DOUBLE))
+ .columns("string", "v0")
+ .columnTypes(ColumnType.STRING, ColumnType.DOUBLE)
+ .context(QUERY_CONTEXT_DEFAULT)
+ .build()
+ ),
+ ImmutableList.of(
+ new Object[]{"ccc", 1.1}
+ ),
+ RowSignature.builder()
+ .add("string", ColumnType.STRING)
+ .add("EXPR$1", ColumnType.DOUBLE)
+ .build()
+ );
+ }
+
@Test
public void testReturningAndSumPathDecimalWithMaths()
{
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
index 9d92bcc2b9f..75c8d4e385c 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
@@ -13214,6 +13214,56 @@ public class CalciteQueryTest extends
BaseCalciteQueryTest
);
}
+ @Test
+ public void testFilterDoubleDimensionCastAsDecimal()
+ {
+ testQuery(
+ "SELECT dim1 FROM numfoo WHERE CAST(dbl1 AS DECIMAL) = 1.7 LIMIT 1",
+ ImmutableList.of(
+ newScanQueryBuilder()
+ .dataSource(CalciteTests.DATASOURCE3)
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .columns("dim1")
+ .columnTypes(ColumnType.STRING)
+ .filters(equality("dbl1", 1.7, ColumnType.DOUBLE))
+
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+ .limit(1)
+ .context(QUERY_CONTEXT_DEFAULT)
+ .build()
+ ),
+ ImmutableList.of(
+ new Object[]{"10.1"}
+ )
+ );
+ }
+
+ @Test
+ public void testCoalesceDoubleDimensionCastAsDecimal()
+ {
+ testQuery(
+ "SELECT dim1, COALESCE(CAST(dbl1 AS DECIMAL), 0.5) FROM numfoo",
+ ImmutableList.of(
+ newScanQueryBuilder()
+ .dataSource(CalciteTests.DATASOURCE3)
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .virtualColumns(expressionVirtualColumn("v0",
"nvl(\"dbl1\",0.5)", ColumnType.DOUBLE))
+ .columns("dim1", "v0")
+ .columnTypes(ColumnType.STRING, ColumnType.DOUBLE)
+
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+ .context(QUERY_CONTEXT_DEFAULT)
+ .build()
+ ),
+ ImmutableList.of(
+ new Object[]{"", 1.0},
+ new Object[]{"10.1", 1.7},
+ new Object[]{"2", 0.0},
+ new Object[]{"1", 0.5},
+ new Object[]{"def", 0.5},
+ new Object[]{"abc", 0.5}
+ )
+ );
+ }
+
@Test
public void testFilterLongDimension()
{
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidTypeFactoryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidTypeFactoryTest.java
index a0b81056061..5da8dfc2bc0 100644
---
a/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidTypeFactoryTest.java
+++
b/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidTypeFactoryTest.java
@@ -72,4 +72,41 @@ public class DruidTypeFactoryTest
Assertions.assertNotNull(leastRestrictive);
Assertions.assertEquals(SqlTypeName.VARCHAR,
leastRestrictive.getSqlTypeName());
}
+
+ @Test
+ public void testLeastRestrictiveDefaultDecimalAndFractionalDecimal()
+ {
+ final RelDataType defaultDecimalType =
typeFactory.createSqlType(SqlTypeName.DECIMAL);
+ Assertions.assertEquals(19, defaultDecimalType.getPrecision());
+ Assertions.assertEquals(0, defaultDecimalType.getScale());
+ Assertions.assertEquals(
+ typeFactory.createSqlType(SqlTypeName.DECIMAL, 20, 1),
+ typeFactory.leastRestrictive(
+ ImmutableList.of(defaultDecimalType,
typeFactory.createSqlType(SqlTypeName.DECIMAL, 2, 1))
+ )
+ );
+ }
+
+ @Test
+ public void testLeastRestrictiveDecimals()
+ {
+ Assertions.assertEquals(
+ typeFactory.createSqlType(SqlTypeName.DECIMAL, 10, 2),
+ typeFactory.leastRestrictive(
+ ImmutableList.of(
+ typeFactory.createSqlType(SqlTypeName.DECIMAL, 10, 2),
+ typeFactory.createSqlType(SqlTypeName.DECIMAL, 2, 1)
+ )
+ )
+ );
+ Assertions.assertEquals(
+ typeFactory.createSqlType(SqlTypeName.DECIMAL),
+ typeFactory.leastRestrictive(
+ ImmutableList.of(
+ typeFactory.createSqlType(SqlTypeName.DECIMAL),
+ typeFactory.createSqlType(SqlTypeName.INTEGER)
+ )
+ )
+ );
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]