@@ -345,3 +345,110 @@ impl<T: DupSort> DbDupCursorRW<T> for Cursor<RW, T> {
345345 )
346346 }
347347}
348+
349+ #[ cfg( test) ]
350+ mod tests {
351+ use crate :: {
352+ mdbx:: { DatabaseArguments , DatabaseEnv , DatabaseEnvKind } ,
353+ tables:: StorageChangeSets ,
354+ Database ,
355+ } ;
356+ use alloy_primitives:: { address, Address , B256 , U256 } ;
357+ use reth_db_api:: {
358+ cursor:: { DbCursorRO , DbDupCursorRW } ,
359+ models:: { BlockNumberAddress , ClientVersion } ,
360+ table:: TableImporter ,
361+ transaction:: { DbTx , DbTxMut } ,
362+ } ;
363+ use reth_primitives_traits:: StorageEntry ;
364+ use std:: sync:: Arc ;
365+ use tempfile:: TempDir ;
366+
367+ fn create_test_db ( ) -> Arc < DatabaseEnv > {
368+ let path = TempDir :: new ( ) . unwrap ( ) ;
369+ let mut db = DatabaseEnv :: open (
370+ path. path ( ) ,
371+ DatabaseEnvKind :: RW ,
372+ DatabaseArguments :: new ( ClientVersion :: default ( ) ) ,
373+ )
374+ . unwrap ( ) ;
375+ db. create_tables ( ) . unwrap ( ) ;
376+ Arc :: new ( db)
377+ }
378+
379+ #[ test]
380+ fn test_import_table_with_range_works_on_dupsort ( ) {
381+ let addr1 = address ! ( "0000000000000000000000000000000000000001" ) ;
382+ let addr2 = address ! ( "0000000000000000000000000000000000000002" ) ;
383+ let addr3 = address ! ( "0000000000000000000000000000000000000003" ) ;
384+ let source_db = create_test_db ( ) ;
385+ let target_db = create_test_db ( ) ;
386+ let test_data = vec ! [
387+ (
388+ BlockNumberAddress ( ( 100 , addr1) ) ,
389+ StorageEntry { key: B256 :: with_last_byte( 1 ) , value: U256 :: from( 100 ) } ,
390+ ) ,
391+ (
392+ BlockNumberAddress ( ( 100 , addr1) ) ,
393+ StorageEntry { key: B256 :: with_last_byte( 2 ) , value: U256 :: from( 200 ) } ,
394+ ) ,
395+ (
396+ BlockNumberAddress ( ( 100 , addr1) ) ,
397+ StorageEntry { key: B256 :: with_last_byte( 3 ) , value: U256 :: from( 300 ) } ,
398+ ) ,
399+ (
400+ BlockNumberAddress ( ( 101 , addr1) ) ,
401+ StorageEntry { key: B256 :: with_last_byte( 1 ) , value: U256 :: from( 400 ) } ,
402+ ) ,
403+ (
404+ BlockNumberAddress ( ( 101 , addr2) ) ,
405+ StorageEntry { key: B256 :: with_last_byte( 1 ) , value: U256 :: from( 500 ) } ,
406+ ) ,
407+ (
408+ BlockNumberAddress ( ( 101 , addr2) ) ,
409+ StorageEntry { key: B256 :: with_last_byte( 2 ) , value: U256 :: from( 600 ) } ,
410+ ) ,
411+ (
412+ BlockNumberAddress ( ( 102 , addr3) ) ,
413+ StorageEntry { key: B256 :: with_last_byte( 1 ) , value: U256 :: from( 700 ) } ,
414+ ) ,
415+ ] ;
416+
417+ // setup data
418+ let tx = source_db. tx_mut ( ) . unwrap ( ) ;
419+ {
420+ let mut cursor = tx. cursor_dup_write :: < StorageChangeSets > ( ) . unwrap ( ) ;
421+ for ( key, value) in & test_data {
422+ cursor. append_dup ( * key, * value) . unwrap ( ) ;
423+ }
424+ }
425+ tx. commit ( ) . unwrap ( ) ;
426+
427+ // import data from source db to target
428+ let source_tx = source_db. tx ( ) . unwrap ( ) ;
429+ let target_tx = target_db. tx_mut ( ) . unwrap ( ) ;
430+
431+ target_tx
432+ . import_table_with_range :: < StorageChangeSets , _ > (
433+ & source_tx,
434+ Some ( BlockNumberAddress ( ( 100 , Address :: ZERO ) ) ) ,
435+ BlockNumberAddress ( ( 102 , Address :: repeat_byte ( 0xff ) ) ) ,
436+ )
437+ . unwrap ( ) ;
438+ target_tx. commit ( ) . unwrap ( ) ;
439+
440+ // fetch all data from target db
441+ let verify_tx = target_db. tx ( ) . unwrap ( ) ;
442+ let mut cursor = verify_tx. cursor_dup_read :: < StorageChangeSets > ( ) . unwrap ( ) ;
443+ let copied: Vec < _ > = cursor. walk ( None ) . unwrap ( ) . collect :: < Result < Vec < _ > , _ > > ( ) . unwrap ( ) ;
444+
445+ // verify each entry matches the test data
446+ assert_eq ! ( copied. len( ) , test_data. len( ) , "Should copy all entries including duplicates" ) ;
447+ for ( ( copied_key, copied_value) , ( expected_key, expected_value) ) in
448+ copied. iter ( ) . zip ( test_data. iter ( ) )
449+ {
450+ assert_eq ! ( copied_key, expected_key) ;
451+ assert_eq ! ( copied_value, expected_value) ;
452+ }
453+ }
454+ }
0 commit comments