@@ -534,6 +534,232 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
534534 delete db;
535535}
536536
537+ // Helper function to generate zero-padded keys
538+ // e.g., MakeKey("a", 5) -> "a05", MakeKey("b", 42) -> "b42"
539+ static std::string MakeKey (const std::string& prefix, int index) {
540+ return prefix + (index < 10 ? " 0" : " " ) + std::to_string (index);
541+ }
542+
543+ TEST_F (CompactFilesTest, TrivialMoveNonOverlappingFiles) {
544+ Options options;
545+ options.create_if_missing = true ;
546+ options.disable_auto_compactions = true ;
547+ options.compression = kNoCompression ;
548+ options.level_compaction_dynamic_level_bytes = false ;
549+
550+ DB * db = nullptr ;
551+ ASSERT_OK (DestroyDB (db_name_, options));
552+ Status s = DB::Open (options, db_name_, &db);
553+ ASSERT_OK (s);
554+ ASSERT_NE (db, nullptr );
555+
556+ // Create 3 non-overlapping files in L0
557+ // File 1: keys [a00-a99]
558+ for (int i = 0 ; i < 100 ; i++) {
559+ std::string key = MakeKey (" a" , i);
560+ ASSERT_OK (db->Put (WriteOptions (), key, " value_" + key));
561+ }
562+ ASSERT_OK (db->Flush (FlushOptions ()));
563+
564+ // File 2: keys [b00-b99]
565+ for (int i = 0 ; i < 100 ; i++) {
566+ std::string key = MakeKey (" b" , i);
567+ ASSERT_OK (db->Put (WriteOptions (), key, " value_" + key));
568+ }
569+ ASSERT_OK (db->Flush (FlushOptions ()));
570+
571+ // File 3: keys [c00-c99]
572+ for (int i = 0 ; i < 100 ; i++) {
573+ std::string key = MakeKey (" c" , i);
574+ ASSERT_OK (db->Put (WriteOptions (), key, " value_" + key));
575+ }
576+ ASSERT_OK (db->Flush (FlushOptions ()));
577+
578+ // Verify files are in L0
579+ ColumnFamilyMetaData meta;
580+ db->GetColumnFamilyMetaData (&meta);
581+ ASSERT_EQ (meta.levels [0 ].files .size (), 3 );
582+ ASSERT_EQ (meta.levels [1 ].files .size (), 0 );
583+
584+ // Get L0 files
585+ std::vector<std::string> l0_files;
586+ for (const auto & file : meta.levels [0 ].files ) {
587+ l0_files.push_back (file.db_path + " /" + file.name );
588+ }
589+
590+ CompactionOptions compact_option;
591+ compact_option.allow_trivial_move = true ;
592+ // Compact all L0 files to L1 (non-overlapping in L1)
593+ ASSERT_OK (db->CompactFiles (compact_option, l0_files, 1 ));
594+
595+ // Verify files are now in L1
596+ db->GetColumnFamilyMetaData (&meta);
597+ ASSERT_EQ (meta.levels [0 ].files .size (), 0 );
598+ ASSERT_EQ (meta.levels [1 ].files .size (), 3 );
599+
600+ // Get the first file from L1 (should be the one with keys a00-a99)
601+ std::string l1_file_to_move;
602+ std::vector<std::string> l1_files_to_move_later;
603+ uint64_t l1_file_number = 0 ;
604+ for (const auto & file : meta.levels [1 ].files ) {
605+ if (file.smallestkey [0 ] == ' a' ) {
606+ l1_file_to_move = file.db_path + " /" + file.name ;
607+ l1_file_number = file.file_number ;
608+ } else {
609+ l1_files_to_move_later.push_back (file.db_path + " /" + file.name );
610+ }
611+ }
612+ ASSERT_FALSE (l1_file_to_move.empty ());
613+
614+ // Set up sync point to verify trivial move path is taken
615+ bool trivial_move_executed = false ;
616+ SyncPoint::GetInstance ()->SetCallBack (
617+ " DBImpl::CompactFilesImpl:TrivialMove" ,
618+ [&](void * /* arg*/ ) { trivial_move_executed = true ; });
619+ SyncPoint::GetInstance ()->EnableProcessing ();
620+
621+ // Move the file from L1 to L6 - this should be a trivial move
622+ // because the file doesn't overlap with anything in L6
623+ std::vector<std::string> files_to_move = {l1_file_to_move};
624+ ASSERT_OK (db->CompactFiles (compact_option, files_to_move, 6 ));
625+
626+ // Verify trivial move was executed
627+ ASSERT_TRUE (trivial_move_executed);
628+
629+ SyncPoint::GetInstance ()->DisableProcessing ();
630+ SyncPoint::GetInstance ()->ClearAllCallBacks ();
631+
632+ // Verify the file is now in L6
633+ db->GetColumnFamilyMetaData (&meta);
634+ ASSERT_EQ (meta.levels [1 ].files .size (), 2 ); // Two files remain in L1
635+ ASSERT_EQ (meta.levels [6 ].files .size (), 1 ); // One file in L6
636+
637+ // Verify it's the correct file in L6
638+ bool found_file_in_l6 = false ;
639+ for (const auto & file : meta.levels [6 ].files ) {
640+ if (file.file_number == l1_file_number) {
641+ found_file_in_l6 = true ;
642+ // Verify key range hasn't changed
643+ ASSERT_EQ (file.smallestkey [0 ], ' a' );
644+ ASSERT_EQ (file.largestkey [0 ], ' a' );
645+ break ;
646+ }
647+ }
648+ ASSERT_TRUE (found_file_in_l6);
649+
650+ // Move the other 2 files from L1 to L6, with allow_trivial_move set to false.
651+ // This will trigger a normal compaction, so the 2 files will be compacted
652+ // into a single file in L6.
653+ ASSERT_OK (db->CompactFiles (CompactionOptions (), l1_files_to_move_later, 6 ));
654+
655+ // Verify files in L6
656+ db->GetColumnFamilyMetaData (&meta);
657+ ASSERT_EQ (meta.levels [1 ].files .size (), 0 ); // Zero files remain in L1
658+ ASSERT_EQ (meta.levels [6 ].files .size (), 2 ); // Two file in L6
659+
660+ // Verify data integrity - all keys should still be readable
661+ for (int i = 0 ; i < 100 ; i++) {
662+ std::string key = MakeKey (" a" , i);
663+ std::string value;
664+ ASSERT_OK (db->Get (ReadOptions (), key, &value));
665+ ASSERT_EQ (value, " value_" + key);
666+ }
667+
668+ delete db;
669+ }
670+
671+ TEST_F (CompactFilesTest, TrivialMoveBlockedByOverlap) {
672+ Options options;
673+ options.create_if_missing = true ;
674+ options.disable_auto_compactions = true ;
675+ options.compression = kNoCompression ;
676+ options.level_compaction_dynamic_level_bytes = false ;
677+ options.num_levels = 7 ;
678+
679+ DB * db = nullptr ;
680+ ASSERT_OK (DestroyDB (db_name_, options));
681+ Status s = DB::Open (options, db_name_, &db);
682+ ASSERT_OK (s);
683+ ASSERT_NE (db, nullptr );
684+
685+ // Create a file in L6 with keys [m00-m99] (wide range)
686+ for (int i = 0 ; i < 100 ; i++) {
687+ std::string key = MakeKey (" m" , i);
688+ ASSERT_OK (db->Put (WriteOptions (), key, " value_" + key));
689+ }
690+ ASSERT_OK (db->Flush (FlushOptions ()));
691+
692+ // Get L0 file
693+ ColumnFamilyMetaData meta;
694+ db->GetColumnFamilyMetaData (&meta);
695+ std::vector<std::string> l0_files;
696+ for (const auto & file : meta.levels [0 ].files ) {
697+ l0_files.push_back (file.db_path + " /" + file.name );
698+ }
699+
700+ CompactionOptions compact_option;
701+ compact_option.allow_trivial_move = true ;
702+
703+ // Move to L6
704+ ASSERT_OK (db->CompactFiles (compact_option, l0_files, 6 ));
705+
706+ // Now create a file in L1 with overlapping keys [m50-m60]
707+ for (int i = 50 ; i <= 60 ; i++) {
708+ std::string key = " m" + std::to_string (i);
709+ ASSERT_OK (db->Put (WriteOptions (), key, " updated_value_" + key));
710+ }
711+ ASSERT_OK (db->Flush (FlushOptions ()));
712+
713+ // Get the L0 file
714+ db->GetColumnFamilyMetaData (&meta);
715+ std::vector<std::string> l0_files_2;
716+ for (const auto & file : meta.levels [0 ].files ) {
717+ l0_files_2.push_back (file.db_path + " /" + file.name );
718+ }
719+
720+ // Move to L1
721+ ASSERT_OK (db->CompactFiles (compact_option, l0_files_2, 1 ));
722+
723+ // Get the L1 file
724+ db->GetColumnFamilyMetaData (&meta);
725+ ASSERT_EQ (meta.levels [1 ].files .size (), 1 );
726+ std::string l1_file =
727+ meta.levels [1 ].files [0 ].db_path + " /" + meta.levels [1 ].files [0 ].name ;
728+
729+ // Set up sync point to verify full compaction path is taken
730+ bool trivial_move_executed = false ;
731+ SyncPoint::GetInstance ()->SetCallBack (
732+ " DBImpl::CompactFilesImpl:TrivialMove" ,
733+ [&](void * /* arg*/ ) { trivial_move_executed = true ; });
734+ SyncPoint::GetInstance ()->EnableProcessing ();
735+
736+ // Try to move from L1 to L6 - this should NOT be a trivial move
737+ // because the file overlaps with the existing file in L6
738+ ASSERT_OK (db->CompactFiles (compact_option, {l1_file}, 6 ));
739+
740+ // Verify trivial move was NOT executed (full compaction happened)
741+ ASSERT_FALSE (trivial_move_executed);
742+
743+ SyncPoint::GetInstance ()->DisableProcessing ();
744+ SyncPoint::GetInstance ()->ClearAllCallBacks ();
745+
746+ // Verify the result - should have merged data in L6
747+ db->GetColumnFamilyMetaData (&meta);
748+ ASSERT_EQ (meta.levels [1 ].files .size (), 0 ); // L1 should be empty
749+ // L6 should have the merged file (may be 1 file if merged, or 2 if not)
750+ ASSERT_GE (meta.levels [6 ].files .size (), 1 );
751+
752+ // Verify updated values are present
753+ for (int i = 50 ; i <= 60 ; i++) {
754+ std::string key = " m" + std::to_string (i);
755+ std::string value;
756+ ASSERT_OK (db->Get (ReadOptions (), key, &value));
757+ ASSERT_EQ (value, " updated_value_" + key);
758+ }
759+
760+ delete db;
761+ }
762+
537763} // namespace ROCKSDB_NAMESPACE
538764
539765int main (int argc, char ** argv) {
0 commit comments