Skip to content

[SPARK-59284][SQL] Read INT64 TIMESTAMP(MICROS) columns as nanosecond timestamps - #58550

Open
stevomitric wants to merge 3 commits into
apache:masterfrom
stevomitric:stevomitric/nanos-read-micros-parquet
Open

[SPARK-59284][SQL] Read INT64 TIMESTAMP(MICROS) columns as nanosecond timestamps#58550
stevomitric wants to merge 3 commits into
apache:masterfrom
stevomitric:stevomitric/nanos-read-micros-parquet

Conversation

@stevomitric

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Let a nanosecond timestamp type read a Parquet INT64 TIMESTAMP(MICROS) column, promoting each micros value to the internal (epochMicros, nanosWithinMicro = 0) representation. Three touch points:

  • Row-based: the extended newConverter now accepts a TIMESTAMP(MICROS) column (LTZ applies the Julian→Gregorian rebase; NTZ needs none).
  • Vectorized: new MicrosAsTimestampNanos(Rebase)Updater updaters, dispatched for a nanos type over a MICROS file.
  • Filter pushdown: only push down a micros/millis timestamp bound that lies exactly on the microsecond grid, so a sub-µs bound never over-prunes row groups.

Scope: INT64 TIMESTAMP(MICROS) only; INT96 and TIMESTAMP_MILLIS sources remain unsupported.

Why are the changes needed?

It's the read side of widening a microsecond timestamp column (TIMESTAMP(6)) to nanosecond precision without rewriting existing files. Today the nanos reader fails loudly on any non-TIMESTAMP(NANOS) column, so old micros files would be unreadable after such a widening. The promotion is exact and range-complete - micros to (micros, 0) involves no ×1000 encode, so it avoids the INT64 epoch-nanos overflow range (~1677–2262).

Does this PR introduce any user-facing change?

Yes, behind the preview flag spark.sql.timestampNanosTypes.enabled. Previously, reading a TIMESTAMP(MICROS) Parquet column under a nanosecond timestamp type threw (SchemaColumnConvertNotSupportedException / PARQUET_CONVERSION_FAILURE); now it reads the values correctly.

How was this patch tested?

Added TimestampNanosParquetOpsSuite and updated ParquetTypeWideningSuite ParquetFilterSuite.

Was this patch authored or co-authored using generative AI tooling?

Co-authored-by: Claude Opus 4.8

stevomitric and others added 2 commits September 6, 2026 10:06
… timestamps

Enables reading a Parquet INT64 TIMESTAMP(MICROS) column under a nanosecond
timestamp type (TimestampLTZNanosType / TimestampNTZNanosType) -- the read side
of widening a microsecond-precision timestamp column (TIMESTAMP(6)) to
nanosecond precision without rewriting existing files.

Each micros value is promoted to the internal (epochMicros, nanosWithinMicro = 0)
representation. Because that representation is microsecond-based, the promotion is
exact and range-complete: there is no *1000 encode to epoch-nanoseconds and hence
no int64 overflow cliff (unlike the on-disk nanos encoding, which is bounded to
~1677..2262).

- Row-based read: TimestampNanosParquetOps overrides the extended newConverter
  (which supplies the datetime rebase spec) to accept a TIMESTAMP(MICROS) column.
  LTZ applies the Julian->Gregorian rebase like the microsecond TimestampType read
  path; NTZ needs none.
- Vectorized read: ParquetVectorUpdaterFactory dispatches a nanos-type-over-micros
  column to new MicrosAsTimestampNanos(Rebase)Updater updaters, mirroring
  LongWithRebaseUpdater.
- Filter pushdown: ParquetFilters is built from the file schema, so it only pushes
  down a micros/millis timestamp bound that lies exactly on the microsecond grid;
  a sub-microsecond bound (which can only arise from a nanos read) falls back to a
  full scan so row-group pruning never drops matching rows.

Scope: INT64 TIMESTAMP(MICROS) sources only. INT96 and TIMESTAMP_MILLIS sources
remain unsupported and fail loudly.

Tests: TimestampNanosParquetOpsSuite (row-based decode incl. a beyond-2262 range
value), ParquetTypeWideningSuite (end-to-end micros->nanos across vectorized and
row-based readers, dictionary on/off, and CORRECTED/LEGACY rebase, plus INT96 and
TIMESTAMP_MILLIS fail-loud), and ParquetFilterSuite (sub-microsecond pushdown
guard).

Co-authored-by: Isaac <no-reply@databricks.com>
…comment

Addresses code-review feedback on the micros->nanos read support:

- Cross-family guard: when reading an INT64 TIMESTAMP(MICROS) column as a nanosecond type,
  match the file's isAdjustedToUTC to the requested LTZ/NTZ family so a deliberately
  cross-family explicit read schema (e.g. an NTZ file requested as an LTZ instant) fails
  loudly instead of reinterpreting the values. Row-based via
  isMicrosTimestamp(parquetType, expectedAdjustedToUTC); vectorized via a new
  isTimestampTypeMatched(unit, isAdjustedToUTC) overload. Widening only ever pairs same-family
  types, so this just rejects a mismatched .schema(...).
- Fix the stale getVectorUpdater comment: a TIMESTAMP(MICROS) column is now promoted to nanos
  by the factory rather than failing loudly.

Tests: cross-family rejection (TimestampNanosParquetOpsSuite) and fail-loud cross-family reads
across both readers (ParquetTypeWideningSuite).

Co-authored-by: Isaac <no-reply@databricks.com>

@uros-b uros-b left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this broke a test in ParquetTimestampNanosSuite and CI is red because of that, @stevomitric PTAL and fix accordingly.

This PR intentionally makes reading a TIMESTAMP(MICROS) column under a nanos type succeed, but leaves the pre-existing negative test "SPARK-57102: requesting a nanos type over a non-NANOS Parquet column fails clearly" unchanged. That test writes exactly a TIMESTAMP_NTZ micros column and reads it as TIMESTAMP_NTZ(7) asserting a PARQUET_CONVERSION_FAILURE; with this change the read now succeeds (NTZ-micros -> NTZ-nanos via the new microsAsNanosConverter), so the intercept[SparkException] fails with "Expected exception ... but no exception was thrown".

The test and its now-stale explanatory comment ("the guard requires a NANOS annotation") must be updated to exercise a genuinely-still-unsupported encoding (TIMESTAMP_MILLIS or INT96, both of which still throw here) so the "fails clearly" negative-path coverage is preserved, and CI must go green before merge. The break also indicates no sweep was done for existing tests asserting the old behavior.

…ud test

The "requesting a nanos type over a non-NANOS Parquet column fails clearly" test
wrote an INT64 TIMESTAMP(MICROS) column and asserted that reading it under a nanos
schema throws PARQUET_CONVERSION_FAILURE. That read is now supported (this PR reads
an INT64 TIMESTAMP(MICROS) column as nanos -- the read side of widening TIMESTAMP(6)
to nanosecond precision), so the assertion no longer holds.

Repurpose the test to a still-unsupported non-NANOS encoding (TIMESTAMP(MILLIS)),
which is neither NANOS nor same-family MICROS and so still falls through to the
generic PARQUET_CONVERSION_FAILURE error, preserving the test's intent.

Co-authored-by: Isaac <no-reply@databricks.com>

@stevomitric stevomitric left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this broke a test in ParquetTimestampNanosSuite and CI is red because of that, @stevomitric PTAL and fix accordingly.

Thanks @uros-b for noticing - should be fixed now.

@stevomitric
stevomitric requested a review from uros-b September 6, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants