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]

Reply via email to