This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 04ce1e6e2e [core] Never prune postpone buckets in BucketSelector
(#10085)
04ce1e6e2e is described below
commit 04ce1e6e2e3fd22bca355cea64dee578048af011
Author: YangJie <[email protected]>
AuthorDate: Thu Sep 24 02:40:29 2026 -0400
[core] Never prune postpone buckets in BucketSelector (#10085)
---
.../apache/paimon/operation/BucketSelector.java | 7 +++++
.../paimon/operation/BucketSelectorTest.java | 34 ++++++++++++++++++++++
2 files changed, 41 insertions(+)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/BucketSelector.java
b/paimon-core/src/main/java/org/apache/paimon/operation/BucketSelector.java
index ee4c1c1ec2..0eeb0b314e 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/BucketSelector.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/BucketSelector.java
@@ -84,6 +84,13 @@ public class BucketSelector implements ManifestBucketFilter {
@Override
public boolean test(BinaryRow partition, Integer bucket, Integer
numBucket) {
+ if (bucket == null || bucket < 0 || numBucket == null || numBucket <=
0) {
+ // Postpone buckets (negative, see BucketMode#POSTPONE_BUCKET)
hold pending rows
+ // whose bucket is not yet assigned, so bucket keys cannot prune
them; the same
+ // guard mayContain applies at the manifest level. A non-positive
bucket count
+ // carries no bucket information either.
+ return true;
+ }
return partitionSelectors
.computeIfAbsent(partition, this::createPartitionSelector)
.map(selector -> selector.test(bucket, numBucket))
diff --git
a/paimon-core/src/test/java/org/apache/paimon/operation/BucketSelectorTest.java
b/paimon-core/src/test/java/org/apache/paimon/operation/BucketSelectorTest.java
index cd5208753f..16da67c541 100644
---
a/paimon-core/src/test/java/org/apache/paimon/operation/BucketSelectorTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/operation/BucketSelectorTest.java
@@ -60,6 +60,40 @@ public class BucketSelectorTest {
assertThat(selected).hasSize(1);
}
+ @Test
+ public void testPostponeBucketIsNeverPruned() {
+ RowType rowType = DataTypes.ROW(DataTypes.FIELD(0, "k",
DataTypes.INT()));
+ RowType partType = RowType.of();
+ RowType bucketKeyType = DataTypes.ROW(DataTypes.FIELD(0, "k",
DataTypes.INT()));
+ PredicateBuilder pb = new PredicateBuilder(rowType);
+ BucketSelector selector =
+ new BucketSelector(
+ pb.equal(0, 5),
+ BucketFunctionType.DEFAULT,
+ rowType,
+ partType,
+ bucketKeyType);
+
+ // postpone entries carry bucket -2 and no bucket count; their pending
rows cannot
+ // be pruned by bucket keys, so the selector must keep them, also when
composed
+ // into the entry-level BucketFilter without onlyReadRealBuckets
(streaming reads)
+ assertThat(selector.test(BinaryRow.EMPTY_ROW, -2, -2)).isTrue();
+ BucketFilter entryFilter = BucketFilter.create(false, null, null,
selector);
+ assertThat(entryFilter.test(BinaryRow.EMPTY_ROW, -2, -2)).isTrue();
+
+ // a non-positive bucket count carries no bucket information, so it
cannot be pruned
+ // either, and the selector must not divide by that count
+ assertThat(selector.test(BinaryRow.EMPTY_ROW, 3, 0)).isTrue();
+
+ // real buckets are still pruned: exactly the selected bucket passes
+ Set<Integer> selected = selectedBuckets(selector, BinaryRow.EMPTY_ROW,
NUM_BUCKETS);
+ assertThat(selected).hasSize(1);
+ for (int b = 0; b < NUM_BUCKETS; b++) {
+ assertThat(selector.test(BinaryRow.EMPTY_ROW, b, NUM_BUCKETS))
+ .isEqualTo(selected.contains(b));
+ }
+ }
+
@Test
public void testManifestBucketRange() {
RowType rowType = DataTypes.ROW(DataTypes.FIELD(0, "k",
DataTypes.INT()));