From c4803a052bcd683d9bb4baf9b5e42ec0dacbfd40 Mon Sep 17 00:00:00 2001 From: AbdelrahmanElhawary Date: Thu, 9 Jul 2026 14:13:45 +0300 Subject: [PATCH] Arrow: Fix TIMESTAMP_MILLIS scaling for dictionary-encoded columns --- .../vectorized/VectorizedArrowReader.java | 56 ++++++++ ...stVectorizedTimestampMillisDictionary.java | 122 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 arrow/src/test/java/org/apache/iceberg/arrow/vectorized/TestVectorizedTimestampMillisDictionary.java diff --git a/arrow/src/main/java/org/apache/iceberg/arrow/vectorized/VectorizedArrowReader.java b/arrow/src/main/java/org/apache/iceberg/arrow/vectorized/VectorizedArrowReader.java index 11c73aea4a44..578e25ee1970 100644 --- a/arrow/src/main/java/org/apache/iceberg/arrow/vectorized/VectorizedArrowReader.java +++ b/arrow/src/main/java/org/apache/iceberg/arrow/vectorized/VectorizedArrowReader.java @@ -384,6 +384,62 @@ public void setRowGroupInfo(PageReadStore source, Map metadataMap = Maps.newHashMap(); + metadataMap.put(columnPath, mockChunkMeta); + + Mockito.when(mockParquetDictionary.getEncoding()).thenReturn(Encoding.PLAIN_DICTIONARY); + Mockito.when(mockParquetDictionary.decodeToLong(0)).thenReturn(1783426418473L); + + // 2. Intercept internal instantiation of VectorizedColumnIterator using construction mocking + try (MockedConstruction mockedConstruction = + Mockito.mockConstruction( + VectorizedColumnIterator.class, + (mockIterator, context) -> { + Mockito.when(mockIterator.setRowGroupInfo(Mockito.any(), Mockito.anyBoolean())) + .thenReturn(mockParquetDictionary); + })) { + + VectorizedArrowReader reader = + new VectorizedArrowReader(mockDescriptor, icebergField, allocator, true); + + // 3. Execute target method + reader.setRowGroupInfo(mockPageReadStore, metadataMap); + + // 4. Extract private field using Java Reflection to bypass missing getters + Field dictionaryField = VectorizedArrowReader.class.getDeclaredField("dictionary"); + dictionaryField.setAccessible(true); + Dictionary activeDictionary = (Dictionary) dictionaryField.get(reader); + + // 5. Run Verification Assertions + assertThat(activeDictionary) + .as("The internal dictionary reference must be substituted by our proxy class") + .isNotNull() + .isNotEqualTo(mockParquetDictionary); + + long expectedMicros = 1783426418473L * 1000L; + assertThat(activeDictionary.decodeToLong(0)) + .as( + "The proxy hook must intercept lookups and multiply millisecond properties to microseconds") + .isEqualTo(expectedMicros); + } + } +}