Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fbc06a2
feat: support Iceberg system functions natively
andygrove Sep 2, 2026
3951f02
test: simplify wrapped remainder in truncate unit test to satisfy clippy
andygrove Sep 2, 2026
8c0d8d6
refactor: simplify Iceberg system function kernels, serde, and tests
andygrove Sep 2, 2026
49868b6
fix: use as_chunks in the Iceberg murmur3 kernel to satisfy clippy 1.98
andygrove Sep 2, 2026
f8db9b5
test: keep multi-byte strings and Long.MinValue out of the partitione…
andygrove Sep 2, 2026
7de2708
test: align the system-function corpus write with Spark 4 datetime de…
andygrove Sep 3, 2026
71e03be
fix: cover the whole date domain in the Iceberg temporal kernels
andygrove Sep 3, 2026
1622503
perf: transform Iceberg dictionary inputs once per distinct value
andygrove Sep 3, 2026
8d898c2
test: pin the Iceberg kernels to iceberg-rust's partition transforms
andygrove Sep 3, 2026
19ca6c3
docs: describe the decimal truncate difference in the Iceberg guide
andygrove Sep 3, 2026
fa0158d
bench: measure the Iceberg system functions natively and against Iceb…
andygrove Sep 3, 2026
3dc1a2d
docs: correct which paths agree on the truncated decimal
andygrove Sep 3, 2026
f4e8e45
test: do not assert an unverified cause for the partition-source excl…
andygrove Sep 3, 2026
ada706b
test: link the upstream iceberg-rust issues from the excluded cases
andygrove Sep 3, 2026
0a57b6a
fix: decline Iceberg's decimal truncate instead of nulling early
andygrove Sep 3, 2026
f466a65
bench: verify outputs and cover nulls in the Iceberg system function …
andygrove Sep 3, 2026
29f69db
Merge branch 'main' into feat/iceberg-system-functions
andygrove Sep 3, 2026
f2f0c70
fix: keep the Iceberg benchmark's row collection compatible with Spar…
andygrove Sep 4, 2026
8c6a064
test: partition the Iceberg write test on the string column again
andygrove Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr_build_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ jobs:
org.apache.comet.CometIcebergRewriteActionSuite
org.apache.comet.CometIcebergWriteActionSuite
org.apache.comet.CometIcebergWriteDetectionSuite
org.apache.comet.CometIcebergSystemFunctionSuite
org.apache.comet.iceberg.IcebergReflectionSuite
org.apache.comet.serde.operator.IcebergWriteProtoTranslationSuite
org.apache.comet.csv.CometCsvNativeReadSuite
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr_build_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ jobs:
org.apache.comet.CometIcebergRewriteActionSuite
org.apache.comet.CometIcebergWriteActionSuite
org.apache.comet.CometIcebergWriteDetectionSuite
org.apache.comet.CometIcebergSystemFunctionSuite
org.apache.comet.iceberg.IcebergReflectionSuite
org.apache.comet.serde.operator.IcebergWriteProtoTranslationSuite
org.apache.comet.csv.CometCsvNativeReadSuite
Expand Down
5 changes: 4 additions & 1 deletion docs/source/user-guide/latest/iceberg-writes.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ per-task Parquet write is delegated to [iceberg-rust](https://github.com/apache/
The native writer must produce the same outcome as iceberg-java — the same Parquet features,
statistics, and manifest metadata — so a write is only eligible when every table property it
depends on is one the native path reproduces exactly, and additionally only when the plan
feeding the write is fully Comet-native. Ineligible writes run through iceberg-java unchanged,
feeding the write is fully Comet-native. For a partitioned table that plan includes the hash
distribution and local sort Iceberg requests on its partition transforms; those stay native
because the transforms themselves have native implementations (see
[Iceberg system functions](iceberg.md)). Ineligible writes run through iceberg-java unchanged,
with the reason reported as a fall-back reason in Comet's extended EXPLAIN output.

**Most Iceberg write settings are not supported.** Detection is an allowlist: a write is
Expand Down
35 changes: 35 additions & 0 deletions docs/source/user-guide/latest/iceberg.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,41 @@ the project, exchange, and sort operators around them stay on the Comet path end
Spark, which forces a columnar-to-row roundtrip and demotes the surrounding shuffle from
`CometExchange` to `CometColumnarExchange`.

### Iceberg system functions

Iceberg's system functions `bucket`, `truncate`, `years`, `months`, `days`, and `hours` (the SQL
form of its partition transforms, for example `SELECT system.bucket(16, id) FROM t`) run natively.
Spark binds them as static invocations of Iceberg's per-type implementations under
`org.apache.iceberg.spark.functions`, and Comet recognizes those classes wherever the expression
appears: in a projection, a filter, a sort key, or the hash partitioning of a shuffle.

The native kernels reproduce Iceberg's Java semantics exactly rather than approximately:

- `bucket` hashes the spec's byte encoding of each value (8-byte little-endian for integers, dates,
and timestamps; UTF-8 for strings; raw bytes for binary; the minimal big-endian two's complement
of the unscaled value for decimals) with 32-bit Murmur3 and masks the sign bit before taking the
modulus.
- `truncate` uses Java's wrapping integer arithmetic and counts code points (not bytes) for
strings. Decimal inputs are the one case that stays with Spark, see below.
- `years`, `months`, `days`, and `hours` are evaluated in UTC regardless of the session timezone
and go negative before the epoch; `days` returns a date, the other three an int. They cover the
whole `DATE` and `TIMESTAMP` domain, as Iceberg's `DateTimeUtil` does.

`truncate` on a `decimal` column falls back to Spark. Truncating a negative decimal grows its
magnitude, so the result can need one more digit than the column's precision allows:
`truncate(10, v)` on a `decimal(18,4)` value of `-99999999999999.9999` is
`-100000000000000.0000`, which has 19 digits. Iceberg's `TruncateDecimal` hands that oversized
value back unchanged and Spark turns it into null only when the row is materialized. An Arrow
`Decimal128(precision, scale)` array has no encoding for that intermediate, so a native kernel
would have to null it during evaluation, which changes what an enclosing predicate or hash sees.
Every other `truncate` input type, and `bucket` on decimals, runs natively.

This matters most for writes. A partitioned table with the default `write.distribution-mode`
(`hash`) is planned with a shuffle and a local sort keyed on the partition transforms, and with
these functions native the whole sub-plan feeding the [native Iceberg writer](iceberg-writes.md)
stays in Comet. A `numBuckets` or `width` argument that is not a positive integer literal makes
the expression fall back to Spark.

### Task input metrics

The native Iceberg reader populates Spark's task-level `inputMetrics.bytesRead` (visible in the Spark UI Stages tab) using the `bytes_read` counter from iceberg-rust's `ScanMetrics`. This counter includes bytes read from both data files and delete files.
Expand Down
Loading
Loading