@@ -35,10 +35,11 @@ pub enum IncrementalScanMode {
3535 /// Resolve to [`Delta`](Self::Delta) when `changelog-producer=none`,
3636 /// otherwise to [`Changelog`](Self::Changelog).
3737 Auto ,
38- /// Diff before/after snapshots .
38+ /// Diff before/after snapshot states for PK tables .
3939 ///
40- /// Not fully implemented in this release; planning returns
41- /// [`Error::Unsupported`](crate::Error::Unsupported).
40+ /// Phase 1 supports only `merge-engine=deduplicate`. Planning compares the
41+ /// full table state at `start_exclusive` vs `end_inclusive` and yields
42+ /// per-(partition, bucket) [`IncrementalSplit::DiffPair`] units.
4243 Diff ,
4344}
4445
@@ -223,9 +224,51 @@ impl<'a> IncrementalScan<'a> {
223224 }
224225
225226 async fn plan_diff ( & self , mode : IncrementalScanMode ) -> crate :: Result < IncrementalPlan > {
226- let _ = mode;
227- Err ( crate :: Error :: Unsupported {
228- message : "Batch incremental Diff scan is not implemented yet" . to_string ( ) ,
229- } )
227+ let core_options = CoreOptions :: new ( self . table . schema ( ) . options ( ) ) ;
228+ if core_options. merge_engine ( ) ? != crate :: spec:: MergeEngine :: Deduplicate {
229+ return Err ( crate :: Error :: Unsupported {
230+ message : "Batch incremental Diff only supports merge-engine=deduplicate in Phase 1"
231+ . to_string ( ) ,
232+ } ) ;
233+ }
234+ let before = self
235+ . snapshot_manager
236+ . get_snapshot ( self . start_exclusive )
237+ . await ?;
238+ let after = self
239+ . snapshot_manager
240+ . get_snapshot ( self . end_inclusive )
241+ . await ?;
242+ let ( before_plan, after_plan) = self . scan . plan_snapshot_diff ( & before, & after) . await ?;
243+
244+ use std:: collections:: BTreeMap ;
245+ type PBKey = ( Vec < u8 > , i32 ) ;
246+
247+ let mut before_map: BTreeMap < PBKey , Vec < DataSplit > > = BTreeMap :: new ( ) ;
248+ for split in before_plan. splits ( ) {
249+ let key = ( split. partition ( ) . to_serialized_bytes ( ) , split. bucket ( ) ) ;
250+ before_map. entry ( key) . or_default ( ) . push ( split. clone ( ) ) ;
251+ }
252+
253+ let mut after_map: BTreeMap < PBKey , Vec < DataSplit > > = BTreeMap :: new ( ) ;
254+ for split in after_plan. splits ( ) {
255+ let key = ( split. partition ( ) . to_serialized_bytes ( ) , split. bucket ( ) ) ;
256+ after_map. entry ( key) . or_default ( ) . push ( split. clone ( ) ) ;
257+ }
258+
259+ let mut keys: std:: collections:: BTreeSet < PBKey > = before_map. keys ( ) . cloned ( ) . collect ( ) ;
260+ keys. extend ( after_map. keys ( ) . cloned ( ) ) ;
261+
262+ let mut splits = Vec :: new ( ) ;
263+ for key in keys {
264+ let before = before_map. remove ( & key) . unwrap_or_default ( ) ;
265+ let after = after_map. remove ( & key) . unwrap_or_default ( ) ;
266+ if before. is_empty ( ) && after. is_empty ( ) {
267+ continue ;
268+ }
269+ splits. push ( IncrementalSplit :: DiffPair { before, after } ) ;
270+ }
271+
272+ Ok ( IncrementalPlan :: new ( mode, splits) )
230273 }
231274}
0 commit comments