Skip to content

[#11936] fix(spark-connector): reject sub-microsecond timestamp precision#11941

Draft
nevzheng wants to merge 2 commits into
apache:mainfrom
nevzheng:spark-timestamp-precision-guard
Draft

[#11936] fix(spark-connector): reject sub-microsecond timestamp precision#11941
nevzheng wants to merge 2 commits into
apache:mainfrom
nevzheng:spark-timestamp-precision-guard

Conversation

@nevzheng

@nevzheng nevzheng commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Stacked on #11938 (Iceberg V3 nanosecond timestamps). That PR makes Gravitino surface Iceberg
timestamp_ns / timestamptz_ns columns as timestamp(9) / timestamp_tz(9). This PR makes the
Spark connector handle that precision safely. Please review/merge after #11938; the first commit
here is #11938 and will drop out once it lands (I'll rebase).

What changes were proposed in this pull request?

The Spark connector maps every Gravitino timestamp[_tz] to Spark's microsecond timestamp regardless
of precision, so a timestamp(9) is silently truncated to microseconds. Spark itself has no
released nanosecond timestamp type — the nanosecond types in
SPARK-57454 are preview-only and flag-gated
(spark.sql.timestampNanosTypes.enabled), and both TimestampType and TimestampNTZType are
microsecond precision.
So there is no lossless target; the connector should reject a precision it cannot represent rather
than drop digits silently.

This adds a single precision guard (precision() > 6 is rejected) and routes every Spark type
converter through it via a shared convertTimestamp seam, so it cannot be bypassed:

  • SparkTypeConverter / SparkTypeConverter34 (iceberg, paimon).
  • SparkJdbcTypeConverter / SparkJdbcTypeConverter34 (jdbc).
  • SparkHiveTypeConverter / SparkHiveTypeConverter34 (hive).

Precision 6 and below (and unset) are unchanged.

Draft status: the guard currently covers the base converters (SparkTypeConverter /
SparkTypeConverter34, i.e. the iceberg/paimon paths). The jdbc and hive converters override the
timestamp mapping and are being moved onto the shared convertTimestamp seam so the guard applies
there too — that is the remaining work before this leaves draft.

Why are the changes needed?

Once #11938 lands, reading an Iceberg V3 timestamptz_ns table through the Spark connector would
otherwise present a microsecond column and silently discard the sub-microsecond digits. Failing
loudly is the correct behavior until Spark ships a stable nanosecond type (at which point a
spark-4.x converter can map it natively).

Does this PR introduce any user-facing change?

Yes. The Spark connector now throws UnsupportedOperationException when asked to represent a
timestamp[_tz] with precision greater than microsecond (> 6), instead of silently truncating it.
Precision 6 and below is unchanged.

How was this patch tested?

  • Unit tests asserting timestamp[_tz] at precision 7/8/9 throws and precision unset/0/6 maps, across
    the base, jdbc, and hive converters (zoned and TIMESTAMP_NTZ paths), for Spark 3.3/3.4/3.5.

nevzheng and others added 2 commits July 7, 2026 23:21
Map Iceberg V3's timestamp_ns / timestamptz_ns to Gravitino's existing
timestamp[_tz](9) instead of ExternalType("TIMESTAMP_NANO").

- FromIcebergType: TIMESTAMP_NANO -> TimestampType.with[out]TimeZone(9),
  honoring shouldAdjustToUTC().
- ToIcebergType: precision 9 -> TimestampNanoType.with[out]Zone(); precision
  6 / unset still map to the microsecond TimestampType; any other precision
  is rejected (Iceberg cannot represent it).

The conversion is backend-agnostic: it produces TimestampNanoType for any
backend. Whether that persists depends on the backend's own Iceberg glue -
the REST/JDBC path handles it; Iceberg's HiveSchemaUtil has no nanosecond arm
yet, so a Hive-backed create throws from within Iceberg (not from Gravitino),
and it will start working with no change here once Iceberg adds that arm.

Tests:
- Unit (TestConvertUtil): both directions, both zones, a precision-6
  regression, and the reject case for an unsupported precision.
- Integration (CatalogIcebergBaseIT): a REST-backed round-trip creating a
  format-version-3 table with timestamp_ns / timestamptz_ns columns and
  asserting the native round-trip plus the underlying Iceberg schema.

The Iceberg<->Gravitino mapping is bijective (precision 9 + the time-zone flag
fully determine the nanosecond type), so no distinct timestamp_ns token is
needed. No changes to the core type model, JSON serde, Python client, or
OpenAPI: timestamp(9) is already a first-class Gravitino type. Discussed in
apache#11936.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… precision

Spark timestamps are microsecond precision (the nanosecond types in
SPARK-57454 are preview-only and flag-gated), so the connector's toSparkType
now rejects a Gravitino timestamp[_tz] with precision > 6 instead of silently
truncating it to microseconds. This matters now that Iceberg V3 tables load
nanosecond columns as timestamp[_tz](9).

- SparkTypeConverter / SparkTypeConverter34: validate precision <= 6 in the
  zoned and TIMESTAMP_NTZ branches before mapping (3.5 inherits via the 3.4
  converters).
- Tests: precision unset/0/6 map; precision 7/8/9 throw, for both the zoned
  and TIMESTAMP_NTZ paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant