This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new b33e0348 fix(core): preserve partition-bucket order in scan planning 
(#838)
b33e0348 is described below

commit b33e0348629e52aca713c48e6d212f139a564226
Author: Jingsong Lee <[email protected]>
AuthorDate: Tue Sep 15 14:07:15 2026 +0800

    fix(core): preserve partition-bucket order in scan planning (#838)
---
 crates/paimon/src/table/table_scan.rs | 91 +++++++++++++++++++++++++++++------
 1 file changed, 76 insertions(+), 15 deletions(-)

diff --git a/crates/paimon/src/table/table_scan.rs 
b/crates/paimon/src/table/table_scan.rs
index 502202d2..b48e6229 100644
--- a/crates/paimon/src/table/table_scan.rs
+++ b/crates/paimon/src/table/table_scan.rs
@@ -637,29 +637,23 @@ impl LimitPushdownAccumulator {
     }
 }
 
-type PartitionDataFileGroups = IndexMap<Vec<u8>, IndexMap<i32, (i32, 
Vec<DataFileMeta>)>>;
+type PartitionDataFileGroups = IndexMap<(Vec<u8>, i32), (i32, 
Vec<DataFileMeta>)>;
 type BucketDataFileGroup = ((Vec<u8>, i32), (i32, Vec<DataFileMeta>));
 
 fn group_data_files_by_partition_bucket(entries: Vec<ManifestEntry>) -> 
Vec<BucketDataFileGroup> {
-    let mut partitions = PartitionDataFileGroups::new();
+    // Preserve the first occurrence of each pair across partitions. Nesting by
+    // partition moves later buckets ahead of intervening groups and changes
+    // the physical row positions used by append slices and shards.
+    let mut groups = PartitionDataFileGroups::new();
     for entry in entries {
         let (partition, bucket, total_buckets, file) = entry.into_parts();
-        partitions
-            .entry(partition)
-            .or_default()
-            .entry(bucket)
+        groups
+            .entry((partition, bucket))
             .or_insert_with(|| (total_buckets, Vec::new()))
             .1
             .push(file);
     }
-
-    let mut groups = Vec::new();
-    for (partition, buckets) in partitions {
-        for (bucket, files) in buckets {
-            groups.push(((partition.clone(), bucket), files));
-        }
-    }
-    groups
+    groups.into_iter().collect()
 }
 
 #[derive(Clone, Copy)]
@@ -2662,12 +2656,79 @@ mod tests {
             ordered,
             vec![
                 (b"b".as_slice(), 1, vec!["b-1.parquet", "b-1-next.parquet"]),
-                (b"b".as_slice(), 0, vec!["b-0.parquet"]),
                 (b"a".as_slice(), 0, vec!["a.parquet"]),
+                (b"b".as_slice(), 0, vec!["b-0.parquet"]),
             ]
         );
     }
 
+    #[tokio::test]
+    async fn test_append_plan_keeps_interleaved_partition_bucket_order() {
+        let schema = PaimonSchema::builder()
+            .column("p", DataType::VarChar(VarCharType::default()))
+            .column("id", DataType::Int(IntType::new()))
+            .partition_keys(["p"])
+            .option("bucket", "2")
+            .option("source.split.target-size", "1b")
+            .option("source.split.open-file-cost", "1b")
+            .build()
+            .unwrap();
+        let table = Table::new(
+            FileIOBuilder::new("memory").build().unwrap(),
+            Identifier::new("test_db", "interleaved_groups"),
+            "memory:/interleaved_groups".to_string(),
+            TableSchema::new(0, &schema),
+            None,
+        );
+        let entry = |partition: &str, bucket: i32, name: &str| {
+            let mut row = BinaryRowBuilder::new(1);
+            row.write_string(0, partition);
+            ManifestEntry::new(
+                FileKind::Add,
+                row.build_serialized(),
+                bucket,
+                2,
+                make_evo_file(name, 1, 1, 1, None),
+                2,
+            )
+        };
+        let entries = vec![
+            entry("b", 1, "b-1.parquet"),
+            entry("a", 0, "a-0.parquet"),
+            entry("b", 0, "b-0.parquet"),
+            entry("b", 1, "b-1-next.parquet"),
+        ];
+        for limit in [None, Some(3)] {
+            let scan = PaimonTableScan::new(&table, None, Vec::new(), None, 
limit, None);
+            let plan = scan
+                .plan_snapshot_from_entries(
+                    diff_snapshot(7),
+                    entries.clone(),
+                    None,
+                    None,
+                    None,
+                    None,
+                )
+                .await
+                .unwrap();
+            let names = plan
+                .splits()
+                .iter()
+                .flat_map(|split| split.data_files())
+                .map(|file| file.file_name.as_str())
+                .collect::<Vec<_>>();
+            let expected = [
+                "b-1.parquet",
+                "b-1-next.parquet",
+                "a-0.parquet",
+                "b-0.parquet",
+            ];
+            assert_eq!(names, expected[..limit.unwrap_or(4)]);
+            assert_eq!(plan.snapshot_id(), Some(7));
+            assert!(plan.splits().iter().all(|split| split.total_buckets() == 
2));
+        }
+    }
+
     #[test]
     fn test_merge_manifest_entries_keeps_in_place_upgraded_file() {
         // Reproduces a single-run compaction "upgrade": the SAME file name is

Reply via email to