[parquet] Use MICROS annotation for TIMESTAMP(n<=3) columns (Iceberg v2 compatibility)#8230
Open
q8webmaster wants to merge 5 commits into
Open
[parquet] Use MICROS annotation for TIMESTAMP(n<=3) columns (Iceberg v2 compatibility)#8230q8webmaster wants to merge 5 commits into
q8webmaster wants to merge 5 commits into
Conversation
added 3 commits
June 14, 2026 02:27
…v2 compatibility) Paimon emits TIMESTAMP(MILLIS) for precision <= 3 columns. The Iceberg v2 spec requires INT64 MICROS for timestamp/timestamptz; MILLIS is only valid under Iceberg v3. This causes Iceberg-aware engines (Athena, Trino, Spark) to reject Parquet files with a schema compatibility error. - ParquetSchemaConverter.createTimestampWithLogicalType: emit MICROS for precision <= 3 instead of MILLIS. - ParquetRowDataWriter.TimestampMillsWriter.writeTimestamp: call value.toMicros() so the stored INT64 matches the MICROS annotation unit. The reader path (MILLIS -> precision=3, MICROS -> precision=6) is left unchanged so files written by older versions remain readable. Existing tables with precision<=3 columns should be rebuilt after upgrading. Tests: testLowPrecisionTimestampUseMicrosAnnotation verifies MICROS annotation for precision 0-3; testPaimonParquetSchemaConvert updated for the widened round-trip precision.
…of micros ParquetSimpleStatsExtractor.toTimestampStats called fromEpochMillis for precision <= 3, but footer statistics for those columns now contain INT64 microseconds (matching the MICROS annotation). Switch to fromMicros so that Parquet column bounds are decoded correctly.
VectorizedColumnReader has a lazy dictionary fast path for INT64/ LongColumnVector: the raw Parquet dictionary is stored on the vector directly, bypassing LongTimestampUpdater.longTimestamp() which normalises on-disk microseconds to the milliseconds that ParquetTimestampVector. getTimestamp expects. The result is timestamps ~1000x too far in the future for any dictionary-encoded page (triggered when rowGroupSize is large enough to activate dictionary encoding). Exclude precision <= 3 timestamp types from lazy decoding via a new isLowPrecisionTimestamp helper so the eager path (decodeDictionaryIds) is always taken, applying the correct /1000 normalisation.
6ebb533 to
f9315e0
Compare
added 2 commits
June 14, 2026 23:06
…vs epoch_µs After the MICROS annotation change, ParquetRowDataWriter stores TIMESTAMP(n<=3) values as epoch microseconds. ParquetFilters.convertLiteral was still using getMillisecond() (epoch_ms) for those columns, so the Parquet row-group statistics comparison always failed against the new epoch_µs statistics — causing WHERE predicates on low-precision timestamp columns to filter out all row groups and return empty results. Fix: use toMicros() for all INT64 timestamp precisions (0-6) in ParquetFilters.convertLiteral, matching the storage unit written by the writer. Update ParquetFiltersTest assertions accordingly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Any Paimon table with a
TIMESTAMPorTIMESTAMP_WITH_LOCAL_TIME_ZONEcolumn of precision ≤ 3 cannot be queried by Iceberg-aware engines (Athena, Trino, Spark Iceberg reader). The engine rejects every Parquet file for that column with an error such as:The job writes data successfully and Paimon itself can read the files back, but any reader that enforces the Iceberg v2 Parquet spec rejects them.
Root cause
1. Wrong Parquet annotation
ParquetSchemaConverter.createTimestampWithLogicalTypeselects the annotation unit based on precision:TIMESTAMP(MILLIS)TIMESTAMP(MICROS)INT96The Iceberg v2 specification requires
INT64 MICROSfor bothtimestampandtimestamptzlogical types.MILLISis only permitted under Iceberg v3, which most engines do not yet support.2. Wrong Parquet footer-stats decoding
ParquetSimpleStatsExtractor.toTimestampStatscalledTimestamp.fromEpochMillisfor precision ≤ 3. After the annotation fix, footer stats for those columns are INT64 microseconds, so the correct call isTimestamp.fromMicros. UsingfromEpochMillisinflates the decoded bound by 1000×.3. Wrong lazy dictionary decoding
VectorizedColumnReaderhas a lazy dictionary fast path for INT64/LongColumnVector: the raw Parquet dictionary is attached to the vector without going throughLongTimestampUpdater.longTimestamp(), which normalises on-disk microseconds to the milliseconds thatParquetTimestampVector.getTimestampexpects. The result is timestamps ~1000× too large for any dictionary-encoded page.Fix
ParquetSchemaConverter: emitMICROSforprecision <= 3increateTimestampWithLogicalType.ParquetRowDataWriter.TimestampMillsWriter: callvalue.toMicros()(milliseconds × 1000) so the stored value matches theMICROSannotation.ParquetSimpleStatsExtractor: useTimestamp.fromMicrosfor precision ≤ 3 footer statistics.VectorizedColumnReader: exclude precision ≤ 3 timestamp types from lazy dictionary decoding viaisLowPrecisionTimestamphelper.The reader path for existing files (
MILLIS annotation → precision=3,MICROS annotation → precision=6) is intentionally left unchanged so that files written by older Paimon versions remain readable.Backward compatibility
Existing tables whose files carry a
MILLISannotation are still readable. Tables that mix old (MILLIS) and new (MICROS) files should be rebuilt after upgrading, because Iceberg-aware engines enforcing strict annotation checking will still reject the old files.After the fix, a
TIMESTAMP(3)column read back through Paimon's own Parquet reader returnsTIMESTAMP(6)(sinceMICROSmaps to precision 6 in the reader). Stored values remain millisecond-granular; only the declared precision widens.Prior art
PR #8222 fixed a related bug in
PostgresRecordParserwhere a Debeziumio.debezium.time.Timestamp(int64 millis) was mapped toBIGINTinstead ofTIMESTAMP(3). Both affect the same precision ≤ 3 path. The Iceberg manifest decoding fix is in the companion PR #8231.Changes
ParquetSchemaConverter.java: emitMICROSforprecision <= 3ParquetRowDataWriter.java:TimestampMillsWriter.writeTimestampcallsvalue.toMicros()ParquetSimpleStatsExtractor.java: usefromMicrosfor precision ≤ 3 footer statsParquetTimestampVector.java: update class javadoc to reflect MICROS storageVectorizedColumnReader.java: exclude precision ≤ 3 timestamps from lazy dictionary decoding; addisLowPrecisionTimestamphelperParquetSchemaConverterTest.java: new testtestLowPrecisionTimestampUseMicrosAnnotation;testPaimonParquetSchemaConvertupdated for widened round-trip precision