This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 7110b8fa73 [iceberg] Fix ClassCastException reading timestamp with
local timezone columns (#7764)
7110b8fa73 is described below
commit 7110b8fa73619961f66734fc0c70cb90e9e37544
Author: Arnav Balyan <[email protected]>
AuthorDate: Thu May 7 08:58:26 2026 +0530
[iceberg] Fix ClassCastException reading timestamp with local timezone
columns (#7764)
- Reading a timestamp with local timezone column from the Iceberg
manifest throws ClassCastException. In IcebergConversions.toPaimonObject
the two timestamp variants share a switch arm that casts the data type
to TimestampType, but LocalZonedTimestampType is not a subtype of
TimestampType.
- Split the arm so each case casts to its own type. Write side was
already doing this.
---
.../iceberg/manifest/IcebergConversions.java | 22 ++++++++++++----------
.../manifest/IcebergConversionsTimestampTest.java | 16 ++++++++++++++++
2 files changed, 28 insertions(+), 10 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/iceberg/manifest/IcebergConversions.java
b/paimon-core/src/main/java/org/apache/paimon/iceberg/manifest/IcebergConversions.java
index b1cc1abf0c..c5bdaf4420 100644
---
a/paimon-core/src/main/java/org/apache/paimon/iceberg/manifest/IcebergConversions.java
+++
b/paimon-core/src/main/java/org/apache/paimon/iceberg/manifest/IcebergConversions.java
@@ -126,6 +126,14 @@ public class IcebergConversions {
.putLong(0, timestamp.toMicros());
}
+ private static Timestamp timestampFromBytes(byte[] bytes, int precision) {
+ Preconditions.checkArgument(
+ precision >= 3 && precision <= 6,
+ "Paimon Iceberg compatibility only support timestamp type with
precision from 3 to 6.");
+ long encoded =
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getLong();
+ return precision == 3 ? Timestamp.fromEpochMillis(encoded) :
Timestamp.fromMicros(encoded);
+ }
+
private static ByteBuffer timeToByteBuffer(int millisOfDay, int precision)
{
Preconditions.checkArgument(
precision >= 0 && precision <= 3,
@@ -164,17 +172,11 @@ public class IcebergConversions {
return Decimal.fromUnscaledBytes(
bytes, decimalType.getPrecision(),
decimalType.getScale());
case TIMESTAMP_WITHOUT_TIME_ZONE:
+ return timestampFromBytes(bytes, ((TimestampType)
type).getPrecision());
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
- int timestampPrecision = ((TimestampType) type).getPrecision();
- long timestampLong =
-
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getLong();
- Preconditions.checkArgument(
- timestampPrecision >= 3 && timestampPrecision <= 6,
- "Paimon Iceberg compatibility only support timestamp
type with precision from 3 to 6.");
- if (timestampPrecision == 3) {
- return Timestamp.fromEpochMillis(timestampLong);
- }
- return Timestamp.fromMicros(timestampLong);
+ // LocalZonedTimestampType does not extend TimestampType, so
it cannot
+ // share a switch arm with TIMESTAMP_WITHOUT_TIME_ZONE.
+ return timestampFromBytes(bytes, ((LocalZonedTimestampType)
type).getPrecision());
case TIME_WITHOUT_TIME_ZONE:
int timePrecision = ((TimeType) type).getPrecision();
Preconditions.checkArgument(
diff --git
a/paimon-core/src/test/java/org/apache/paimon/iceberg/manifest/IcebergConversionsTimestampTest.java
b/paimon-core/src/test/java/org/apache/paimon/iceberg/manifest/IcebergConversionsTimestampTest.java
index 677b41d54d..73da8331c1 100644
---
a/paimon-core/src/test/java/org/apache/paimon/iceberg/manifest/IcebergConversionsTimestampTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/iceberg/manifest/IcebergConversionsTimestampTest.java
@@ -125,4 +125,20 @@ class IcebergConversionsTimestampTest {
private static Stream<Arguments> provideInvalidTimestampCases() {
return Stream.of(Arguments.of(0, 1698686153L), Arguments.of(9,
1698686153123456789L));
}
+
+ @ParameterizedTest
+ @MethodSource("provideTimestampToPaimonCases")
+ @DisplayName("toPaimonObject decodes TIMESTAMP_WITH_LOCAL_TIME_ZONE
without ClassCastException")
+ void testToPaimonObjectForTimestampWithLocalTimeZone(
+ int precision, long serializedMicros, String expectedTs) {
+ byte[] bytes = new byte[8];
+
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).putLong(serializedMicros);
+
+ Timestamp actual =
+ (Timestamp)
+ IcebergConversions.toPaimonObject(
+
DataTypes.TIMESTAMP_WITH_LOCAL_TIME_ZONE(precision), bytes);
+
+ assertThat(actual.toString()).isEqualTo(expectedTs);
+ }
}