Support Java-compatible bitmap global index#478
Conversation
leaves12138
left a comment
There was a problem hiding this comment.
I checked the bitmap global index changes against Java's BitmapGlobalIndexFormat, BitmapIndexReader, and SortedFileGlobalIndexReader. The default no-compression file layout looks aligned: magic/version/footer length and big-endian footer fields, null/non-null bitmap placement, dictionary/index block varint encoding, serialized-key ordering, raw roaring bitmap blocks, and the 5-byte compressed-block trailer/CRC all match the Java format. The Java-generated golden bitmap fixture is a useful addition.
No blocker from my side. Two non-blocking compatibility boundaries are worth documenting/tracking:
- Java can write bitmap dictionary/index blocks with
lz4orlzoviabitmap-index.compression, while the Rust reader currently supports onlynoneandzstd. - Java's fallback dictionary scan limit is based on the total selected index file size and applies to range/between fallback scans as well. Rust currently checks the limit per index file, applies it only to
ends_with/contains/residuallike, and returns full indexed coverage when a fallback scan is skipped. This is safe with residual row filters, but it is not identical to Java's pruning/performance behavior.
leaves12138
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-checked the bitmap global index format and fallback behavior against the Java implementation. The fallback-budget change looks aligned now, but I found one remaining Java compatibility issue in decimal key serialization.
Java's KeySerializer.DecimalSerializer decides whether a decimal is compact from the column DecimalType precision captured by KeySerializer.create(dataField.type()), not from the literal object's own precision. The Rust implementation currently uses Datum::Decimal.precision in serialize_datum, so a predicate literal whose datum precision differs from the column precision can be encoded differently from Java. For example, for a DECIMAL(20, 0) column with a literal datum carrying precision 10, Java writes the non-compact BigInteger.toByteArray() form, while Rust currently writes the compact little-endian i64 form.
Suggested fix:
diff --git a/crates/paimon/src/btree/key_serde.rs b/crates/paimon/src/btree/key_serde.rs
@@
Datum::Decimal {
unscaled,
precision,
..
} => {
- if *precision <= DECIMAL_COMPACT_PRECISION {
+ let key_precision = match data_type {
+ DataType::Decimal(decimal_type) => decimal_type.precision(),
+ _ => *precision,
+ };
+ if key_precision <= DECIMAL_COMPACT_PRECISION {
(*unscaled as i64).to_le_bytes().to_vec()
} else {
encode_java_big_integer_i128(*unscaled)
}
}Please also add a regression test where the literal datum precision differs from the column decimal precision, covering both compact and non-compact column types.
I applied this fix locally and verified it with:
cargo test -p paimon key_serde --libcargo test -p paimon bitmap --libcargo test -p paimon fallback_scan --libcargo test -p paimon-datafusion --test procedures global_index -- --nocapture
I could not push the fix directly to this PR branch from the current token because the head branch is in the author's fork and this token has no push permission there.
leaves12138
left a comment
There was a problem hiding this comment.
Looks good to me. I re-checked the Java-compatible bitmap global index implementation against the Java format and query behavior, including the fallback-budget behavior and the decimal key serialization fix.
Verified locally with:
cargo fmt --all -- --checkcargo test -p paimon key_serde --libcargo test -p paimon bitmap --libcargo test -p paimon fallback_scan --libcargo test -p paimon-datafusion --test procedures global_index -- --nocapture
The incremental gap computation and overlap guard in the sorted global-index builder's execute() hard-coded the btree index type. Since apache#478 the same builder also builds bitmap indexes (via normalize_sorted_global_index_type), so a bitmap build was computing its gap over BTREE coverage: if a btree index already covered the same field, the bitmap build would treat those rows as already indexed and skip them, producing an incomplete or empty bitmap index. Pass the builder's resolved index_type instead, so bitmap builds are incremental over bitmap coverage. Adds bitmap no-op / incremental / btree-coexistence tests (the coexistence test fails if the type is hard-coded).
The incremental gap computation and overlap guard in the sorted global-index builder's execute() hard-coded the btree index type. Since apache#478 the same builder also builds bitmap indexes (via normalize_sorted_global_index_type), so a bitmap build was computing its gap over BTREE coverage: if a btree index already covered the same field, the bitmap build would treat those rows as already indexed and skip them, producing an incomplete or empty bitmap index. Pass the builder's resolved index_type instead, so bitmap builds are incremental over bitmap coverage. Adds bitmap no-op / incremental / btree-coexistence tests (the coexistence test fails if the type is hard-coded).
…480) The incremental gap computation and overlap guard in the sorted global-index builder's execute() hard-coded the btree index type. Since #478 the same builder also builds bitmap indexes (via normalize_sorted_global_index_type), so a bitmap build was computing its gap over BTREE coverage: if a btree index already covered the same field, the bitmap build would treat those rows as already indexed and skip them, producing an incomplete or empty bitmap index. Pass the builder's resolved index_type instead, so bitmap builds are incremental over bitmap coverage. Adds bitmap no-op / incremental / btree-coexistence tests (the coexistence test fails if the type is hard-coded).
Summary
Adds Java-compatible bitmap global index support alongside the existing BTree global index path. The PR also wires sorted global-index reads, build/drop procedures, fallback scans, and table options so BTree and Bitmap behavior stay aligned with Java Paimon.
Changes
.indexand.meta) generated by Java Paimon'sBitmapGlobalIndexer, and use them in scanner compatibility tests.index_type => 'bitmap'.btree-index.fallback-scan-max-sizeandbitmap-index.fallback-scan-max-size, both defaulting to256mb.Testing
cargo test -p paimon spec::core_options::testscargo test -p paimon table::global_index_scanner::testscargo test -p paimon btree::testscargo test -p paimon-datafusion --test procedurescargo fmt --checkgit diff --checkNotes
No migration is required. The new fallback scan table options use Java-compatible names rather than Rust-only aliases.