From 92e7f1e5e4bb4fd4d04793bb6736eab8aae7ede2 Mon Sep 17 00:00:00 2001 From: AlexisCubilla Date: Tue, 12 May 2026 15:47:12 -0300 Subject: [PATCH] [CALCITE-5212] Include DECIMAL scale in type digest when precision unspecified Problem: createSqlType(DECIMAL, PRECISION_NOT_SPECIFIED, scale) could return types that collided in DATATYPE_CACHE: same digest/hash for different scales because BasicSqlType.generateTypeString() only appended scale when printPrecision was true, so scale was omitted when precision was PRECISION_NOT_SPECIFIED. Root cause: The digest string did not distinguish DECIMAL types that differed only by scale while precision stayed unspecified, breaking equals/hashCode/canonize. Fix: When precision is not specified but scale is, append "(PRECISION_NOT_SPECIFIED, scale)" to the digest for DECIMAL (see generateTypeString). Testing: ./gradlew :core:test --tests "org.apache.calcite.sql.type.SqlTypeFactoryTest" https://issues.apache.org/jira/browse/CALCITE-5212 --- .../apache/calcite/sql/type/BasicSqlType.java | 7 ++++++ .../calcite/sql/type/SqlTypeFactoryTest.java | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java b/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java index e8e9332d6197..635c07c662d3 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java +++ b/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java @@ -205,6 +205,13 @@ BasicSqlType createWithCharsetAndCollation(Charset charset, sb.append(getScale()); } sb.append(')'); + } else if (printScale && typeName == SqlTypeName.DECIMAL) { + // Include scale in the digest when precision is not specified (CALCITE-5212). + sb.append('('); + sb.append(PRECISION_NOT_SPECIFIED); + sb.append(", "); + sb.append(scale); + sb.append(')'); } if (!withDetail) { return; diff --git a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java index 8f5e5e4018db..cab5de8f68f8 100644 --- a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java +++ b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java @@ -19,6 +19,7 @@ import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rel.type.RelDataTypeFieldImpl; +import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rel.type.RelRecordType; import org.apache.calcite.rel.type.StructKind; @@ -35,7 +36,9 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasToString; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -239,6 +242,27 @@ private void checkPrecision(int p0, int p1, int expectedMax, assertThat(SqlTypeUtil.comparePrecision(p1, p1), is(0)); } + /** Test case for + * [CALCITE-5212] + * Decimal with scale but unspecified precision collides in type cache. */ + @Test void testCreateSqlTypeDecimalUnspecifiedPrecisionWithScale() { + SqlTypeFixture f = new SqlTypeFixture(); + RelDataTypeFactory tf = f.typeFactory; + final int pUn = RelDataType.PRECISION_NOT_SPECIFIED; + RelDataType d5 = tf.createSqlType(SqlTypeName.DECIMAL, pUn, 5); + RelDataType d3 = tf.createSqlType(SqlTypeName.DECIMAL, pUn, 3); + assertEquals(5, d5.getScale()); + assertEquals(3, d3.getScale()); + assertNotEquals(d5, d3); + assertNotEquals(d5.getFullTypeString(), d3.getFullTypeString()); + // Order independence: first created shape must not be returned for another scale. + SqlTypeFactoryImpl tf2 = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT); + RelDataType d3b = tf2.createSqlType(SqlTypeName.DECIMAL, pUn, 3); + RelDataType d5b = tf2.createSqlType(SqlTypeName.DECIMAL, pUn, 5); + assertEquals(3, d3b.getScale()); + assertEquals(5, d5b.getScale()); + } + /** Test case for * [CALCITE-2464] * Allow to set nullability for columns of structured types. */