From 6d71e93427562b2d94e37c3a68324f47a36b10a0 Mon Sep 17 00:00:00 2001 From: Yang Date: Tue, 28 Jul 2026 10:37:29 +1000 Subject: [PATCH] fix date as parition key parsing issue --- .../xtable/delta/DeltaValueConverter.java | 31 ++++++++++++++++++- .../xtable/delta/TestDeltaValueConverter.java | 28 +++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/xtable-core/src/main/java/org/apache/xtable/delta/DeltaValueConverter.java b/xtable-core/src/main/java/org/apache/xtable/delta/DeltaValueConverter.java index 5eea1faa4..3f05bfd37 100644 --- a/xtable-core/src/main/java/org/apache/xtable/delta/DeltaValueConverter.java +++ b/xtable-core/src/main/java/org/apache/xtable/delta/DeltaValueConverter.java @@ -143,7 +143,7 @@ public static String convertToDeltaPartitionValue( } if (partitionTransformType == PartitionTransformType.VALUE) { if (fieldType == InternalType.DATE) { - return LocalDate.ofEpochDay((int) value).toString(); + return convertDatePartitionValueToString(value); } else { return value.toString(); } @@ -154,6 +154,35 @@ public static String convertToDeltaPartitionValue( } } + /** + * Serializes a DATE partition value to the canonical {@code yyyy-MM-dd} string used in the Delta + * partition path/log. + * + *

Different conversion sources surface DATE partition values in different runtime forms: the + * Iceberg and Delta sources provide an {@link Integer} epoch-day, whereas the Paimon source + * provides an already-formatted {@code yyyy-MM-dd} {@link String} (see {@code + * PaimonPartitionExtractor#toPartitionValues}, which derives values from {@code + * InternalRowPartitionComputer.generatePartValues}). This helper accepts both so the DATE + * partition case no longer fails with a {@link ClassCastException}. + */ + private static String convertDatePartitionValueToString(Object value) { + if (value instanceof Number) { + return LocalDate.ofEpochDay(((Number) value).longValue()).toString(); + } + if (value instanceof String) { + // Already an ISO-8601 date; parse to validate and normalize (also tolerates an epoch-day + // encoded as a string). + String stringValue = ((String) value).trim(); + try { + return LocalDate.parse(stringValue).toString(); + } catch (DateTimeParseException ex) { + return LocalDate.ofEpochDay(Long.parseLong(stringValue)).toString(); + } + } + throw new NotSupportedException( + "Unsupported DATE partition value type: " + value.getClass().getName()); + } + public static Object convertFromDeltaPartitionValue( String value, InternalType fieldType, diff --git a/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaValueConverter.java b/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaValueConverter.java index d69021f12..8faa4ec08 100644 --- a/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaValueConverter.java +++ b/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaValueConverter.java @@ -74,6 +74,34 @@ public void formattedValueDifferentTypesForPartition( assertEquals(fieldValue, internalRepresentation); } + /** + * Reproduces the Paimon-source -> Delta-target DATE partition failure. + * + *

{@link org.apache.xtable.paimon.PaimonPartitionExtractor#toPartitionValues} always emits + * partition values as {@link String} (via {@code InternalRowPartitionComputer.generatePartValues}, + * e.g. {@code "2019-10-12"}). When such a value reaches {@code convertToDeltaPartitionValue} for a + * DATE partition field with a VALUE transform, the current code executes {@code (int) value} on a + * String, throwing a {@link ClassCastException}. DATE as a regular (non-partition) column works + * because it flows through the column-stat path with a real epoch-day int. + */ + @ParameterizedTest + @MethodSource("datePartitionValues") + void convertDatePartitionValueAcrossSourceRepresentations(Object value, String expected) { + // Epoch day 18181 == "2019-10-12". Integer form is produced by the Iceberg/Delta sources; the + // String form is produced by the Paimon source (InternalRowPartitionComputer.generatePartValues). + String deltaRepresentation = + DeltaValueConverter.convertToDeltaPartitionValue( + value, InternalType.DATE, PartitionTransformType.VALUE, ""); + assertEquals(expected, deltaRepresentation); + } + + private static Stream datePartitionValues() { + return Stream.of( + Arguments.of(18181, "2019-10-12"), // Integer epoch-day (Iceberg / Delta source) + Arguments.of("2019-10-12", "2019-10-12"), // ISO date String (Paimon source) + Arguments.of("18181", "2019-10-12")); // epoch-day encoded as String + } + @Test void parseWrongDateTime() throws ParseException { String dateFormatString = "yyyy-MM-dd HH:mm:ss";