You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by mb...@apache.org on 2026/10/18 06:55:56 UTC
(calcite) branch main updated: [CALCITE-7865] CASTS from string trim in inconsistent ways
This is an automated email from the ASF dual-hosted git repository.
mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 2350b9016d [CALCITE-7865] CASTS from string trim in inconsistent ways
2350b9016d is described below
commit 2350b9016dfc16e0737b64ec5fe59b35da9e4fc8
Author: Mihai Budiu <mb...@feldera.com>
AuthorDate: Sat Oct 17 17:33:53 2026 -0700
[CALCITE-7865] CASTS from string trim in inconsistent ways
Signed-off-by: Mihai Budiu <mb...@feldera.com>
---
.../calcite/adapter/enumerable/EnumUtils.java | 1 +
.../org/apache/calcite/runtime/SqlFunctions.java | 18 ++++++---
.../org/apache/calcite/util/BuiltInMethod.java | 2 +-
.../apache/calcite/linq4j/tree/UnsignedType.java | 8 ++--
.../org/apache/calcite/test/SqlOperatorTest.java | 44 ++++++++++++++++++++++
5 files changed, 63 insertions(+), 10 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
index 0aaf1f64fe..3711de36b8 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
@@ -502,6 +502,7 @@ public static Expression convert(Expression operand, Type fromType,
if (toPrimitive != null) {
switch (toPrimitive) {
case CHAR:
+ case BYTE:
case SHORT:
case INT:
case LONG:
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 2b3a45c479..4dceed1679 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -5285,7 +5285,7 @@ private static Object cannotConvert(Object o, Class toType) {
/** CAST(VARCHAR AS BOOLEAN). */
public static boolean toBoolean(String s) {
- s = trim(true, true, " ", s);
+ s = s.trim();
if (s.equalsIgnoreCase("TRUE")) {
return true;
} else if (s.equalsIgnoreCase("FALSE")) {
@@ -5321,12 +5321,14 @@ public static boolean toBoolean(Object o) {
: (Boolean) cannotConvert(o, boolean.class);
}
- // Don't need parseByte etc. - Byte.parseByte is sufficient.
+ public static byte toByte(String s) {
+ return parseByte(s.trim());
+ }
public static byte toByte(Object o) {
return o instanceof Byte ? (Byte) o
: o instanceof Number ? toByte((Number) o)
- : parseByte(o.toString());
+ : toByte(o.toString());
}
public static byte toByte(Number number) {
@@ -5698,11 +5700,17 @@ public static java.sql.Time internalToTime(int v) {
return v == null ? castNonNull(null) : internalToTime(v.intValue());
}
+ /** CAST(VARCHAR AS DATE). */
+ public static int stringToDate(String s) {
+ // Trim here, because Avatica rejects some characters below U+0020
+ return DateTimeUtils.dateStringToUnixDate(s.trim());
+ }
+
public static @PolyNull Integer toTimeWithLocalTimeZone(@PolyNull String v) {
if (v == null) {
return castNonNull(null);
}
- return new TimeWithTimeZoneString(v)
+ return new TimeWithTimeZoneString(v.trim())
.withTimeZone(DateTimeUtils.UTC_ZONE)
.getLocalTimeString()
.getMillisOfDay();
@@ -6360,7 +6368,7 @@ public static int time(long timestampMillis, String timeZone) {
if (v == null) {
return castNonNull(null);
}
- return new TimestampWithTimeZoneString(v)
+ return new TimestampWithTimeZoneString(v.trim())
.withTimeZone(DateTimeUtils.UTC_ZONE)
.getLocalTimestampString()
.getMillisSinceEpoch();
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 6aba599523..5fd9d9a715 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -735,7 +735,7 @@ public enum BuiltInMethod {
INTERNAL_TO_DATE(SqlFunctions.class, "internalToDate", int.class),
INTERNAL_TO_TIME(SqlFunctions.class, "internalToTime", int.class),
INTERNAL_TO_TIMESTAMP(SqlFunctions.class, "internalToTimestamp", long.class),
- STRING_TO_DATE(DateTimeUtils.class, "dateStringToUnixDate", String.class),
+ STRING_TO_DATE(SqlFunctions.class, "stringToDate", String.class),
STRING_TO_TIME(DateTimeUtils.class, "timeStringToUnixDate", String.class),
STRING_TO_TIMESTAMP(DateTimeUtils.class, "timestampStringToUnixDate", String.class),
STRING_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimeWithLocalTimeZone",
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
index 32622315f7..ae3d37ebd3 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
@@ -134,7 +134,7 @@ private static UByte toUByteNonNull(Number n, RoundingMode mode) {
if (s == null) {
return null;
}
- return Unsigned.ubyte(s);
+ return Unsigned.ubyte(s.trim());
}
// To UShort
@@ -192,7 +192,7 @@ private static UShort toUShortNonNull(Number n, RoundingMode mode) {
if (s == null) {
return null;
}
- return Unsigned.ushort(s);
+ return Unsigned.ushort(s.trim());
}
// UInteger
@@ -246,7 +246,7 @@ private static UInteger toUIntegerNonNull(Number n, RoundingMode mode) {
if (s == null) {
return null;
}
- return Unsigned.uint(s);
+ return Unsigned.uint(s.trim());
}
// ULong
@@ -304,7 +304,7 @@ private static ULong toULongNonNull(Number n, RoundingMode mode) {
if (s == null) {
return null;
}
- return Unsigned.ulong(s);
+ return Unsigned.ulong(s.trim());
}
public static @Nullable UShort toUShort(Object o) {
if (o == null) {
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 53d093144b..6895a4098b 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -1894,6 +1894,50 @@ void testCastToBoolean(CastType castType, SqlOperatorFixture f) {
}
}
+ /** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-7865">[CALCITE-7865]
+ * CASTS from string trim in inconsistent ways</a>. */
+ @ParameterizedTest
+ @MethodSource("safeParameters")
+ void testCastFromStringTrim(CastType castType, SqlOperatorFixture f) {
+ f.setFor(SqlStdOperatorTable.CAST, VmName.EXPAND);
+ // Target type, and a valid string for that type
+ final String[][] casts = {
+ {"TINYINT", "1"},
+ {"SMALLINT", "1"},
+ {"INTEGER", "1"},
+ {"BIGINT", "1"},
+ {"TINYINT UNSIGNED", "1"},
+ {"SMALLINT UNSIGNED", "1"},
+ {"INTEGER UNSIGNED", "1"},
+ {"BIGINT UNSIGNED", "1"},
+ {"REAL", "1.5"},
+ {"DOUBLE", "1.5"},
+ {"DECIMAL(5, 2)", "1.5"},
+ {"BOOLEAN", "true"},
+ {"DATE", "2020-01-02"},
+ {"TIME", "10:11:12"},
+ {"TIMESTAMP", "2020-01-02 10:11:12"},
+ {"TIME WITH LOCAL TIME ZONE", "10:11:12 UTC"},
+ {"TIMESTAMP WITH LOCAL TIME ZONE", "2020-01-02 10:11:12 UTC"},
+ {"INTERVAL DAY", "3"},
+ };
+ for (String[] cast : casts) {
+ final String type = cast[0];
+ final String value = cast[1];
+ // Each cast removes leading and trailing characters up to U+0020,
+ // as String.trim does
+ for (String c : new String[] {"\\0009", "\\000A", "\\001F", "\\0020"}) {
+ f.checkBoolean("cast(U&'" + c + value + c + "' as " + type + ")"
+ + " = cast('" + value + "' as " + type + ")", true);
+ }
+ if (castType != CastType.CAST) {
+ // No cast removes U+00A0 (no-break space), which is above U+0020
+ f.checkNull("cast(U&'\\00A0" + value + "' as " + type + ")");
+ }
+ }
+ }
+
@Test void testCastToDecimal() {
final SqlOperatorFixture f = fixture();
// test the minimum scale is 0