Truncate transform diverges from Iceberg Java's TruncateUtil for widths above 2^30, and panics in debug builds at the integer minimum
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
crates/iceberg/src/transform/truncate.rs から始めて、truncate_i32、truncate_i64、truncate_decimal_i128 を調査します。リストされている大きな幅と整数の最小値のケースを debug ビルドと release ビルドで再現し、その後、結果を Apache Iceberg Java TruncateUtil の例と比較します。報告された境界値についてカーネルが Java と一致し、debug ビルドで panic が発生しなければ完了です。
索引モデルが issue の本文から書いたものです。
説明
Apache Iceberg Rust version
main @ 3d84c81353b1b23b6e4ae8eea8f8a021cc6927a7
Describe the bug
Truncate::transform in crates/iceberg/src/transform/truncate.rs does not reproduce Iceberg Java's TruncateUtil arithmetic. There are two distinct problems.
1. truncate_i32 diverges from Java for widths above 2^30
fn truncate_i32(v: i32, width: i32) -> i32 {
v - v.rem_euclid(width)
}
Java computes the positive remainder with an expression that is allowed to overflow:
// org.apache.iceberg.util.TruncateUtil
public static int truncateInt(int width, int value) {
return value - (((value % width) + width) % width);
}
(value % width) + width overflows int whenever value % width > Integer.MAX_VALUE - width, which is reachable for any width > 2^30. rem_euclid never overflows, so the two produce different partition values. Only positive values with a large width are affected; negative values and widths at or below 2^30 agree.
Truncate.get(int width) in Iceberg Java only validates width > 0, so these widths are accepted by the spec. Whether Java's overflow is desirable is beside the point — it is the value iceberg-java writes into the partition tuple, so a Rust writer that computes something else produces a table the Java reader partitions differently.
2. Overflow panics in debug builds at the integer minimum
fn truncate_i64(v: i64, width: i64) -> i64 {
v - (((v % width) + width) % width)
}
For v = i64::MIN, width = 1000 the remainder is 192 and v - 192 underflows. With debug_assertions on this panics (attempt to subtract with overflow); in a release build it wraps and matches Java exactly. truncate_i32 has the same shape at i32::MIN, and truncate_decimal_i128 uses the same unchecked subtraction (unreachable for a 38-digit Iceberg decimal, but the same pattern).
A panic here aborts the write task, so a debug or test build of any downstream writer fails on data that Java handles.
To Reproduce
Values below were produced by running TruncateUtil from apache-iceberg 1.11.0 on JDK 17 against the Rust expressions:
| width | value | Java TruncateUtil.truncateInt |
iceberg-rust truncate_i32 |
|---|---|---|---|
| 2000000000 | 1500000000 | -2000000000 | 0 |
| 2147483647 | 2147483646 | -2147483647 | 0 |
| 1073741824 | 1500000000 | 1073741824 | 1073741824 |
| 1000 | 2147483647 | 2147483000 | 2147483000 |
For problem 2, in a debug build:
let t = create_transform_function(&Transform::Truncate(1000)).unwrap();
t.transform(Arc::new(Int64Array::from(vec![i64::MIN]))).unwrap(); // panics
Expected behavior
Both kernels bit-identical to Java in debug and release. Wrapping arithmetic gives that:
fn truncate_i32(v: i32, w: i32) -> i32 {
v.wrapping_sub((v % w).wrapping_add(w) % w)
}
fn truncate_i64(v: i64, w: i64) -> i64 {
v.wrapping_sub((v % w).wrapping_add(w) % w)
}
That fixes both problems at once: it reproduces Java's overflow for large widths and removes the debug panic. The alternative for problem 1 is to reject width > 2^30 at construction, which overlaps with #2473, but that would reject specs iceberg-java accepts.
Willingness to contribute
I would be willing to contribute a fix for this bug with guidance from the Iceberg community.
How this was found. Apache DataFusion Comet is adding native kernels for Iceberg's Spark system functions (apache/datafusion-comet#5638). On a partitioned write the sort key comes from those kernels (Java semantics) while the partition values the clustered writer groups by come from iceberg-rust's transforms, and the clustered writer fails at runtime with The input is not sorted! Cannot write to partition that was previously closed when the two disagree. Cross-checking the two implementations over boundary inputs is what surfaced this.
This report was drafted with LLM assistance (Claude Code); the divergences were verified against Iceberg Java on a JVM.
- 主要言語
- Rust
- スター
- 1.4k
- フォーク
- 574
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 66
環境構築
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/iceberg-rust のほかの issue
-
datafusion
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
apache/iceberg-rust#3297 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
apache/iceberg-rust#3285 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
apache/iceberg-rust#3280 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
apache/iceberg-rust#3234 · リアクション 2 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
apache/iceberg-rust#3229 ·
メンテナーはふだん 1 日以内に返信
apache/iceberg-rust の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
trezor/trezor-firmware#7997 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
smol-machines/smolvm#1489 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信