@@ -703,6 +703,70 @@ func (l *levelIter) internalSeekGE(
703703 return l .verify (kv ), kvMeta
704704}
705705
706+ // SeekPrefixGE implements InternalIterator.SeekPrefixGE. It positions the
707+ // iterator at the first key greater than or equal to key across all files in
708+ // the level. It returns the key-value pair at that position, or nil if no such
709+ // key exists.
710+ //
711+ // The prefix argument is passed to each file's iterator for bloom filter
712+ // checking. If a file's bloom filter indicates that prefix is not present, that
713+ // file is skipped. The key argument is used for the actual seek positioning.
714+ //
715+ // # Prefix vs key
716+ //
717+ // The prefix is typically the prefix of key (i.e. Split.Prefix(key) == prefix),
718+ // but this is not required. The only requirement is that prefix be less than or
719+ // equal to Split.Prefix(key). This flexibility is used when a higher-level
720+ // range deletion invalidates keys between prefix and Split.Prefix(key).
721+ //
722+ // For example, consider a SeekPrefixGE for prefix P1. A range deletion from a
723+ // higher level may cover all keys with prefix P1, requiring the merging
724+ // iterator to seek past the tombstone to a key with prefix P3 > P1. In this
725+ // case, the merging iterator calls SeekPrefixGE(P1, keyWithPrefixP3, flags).
726+ // The bloom filter is checked against P1 (the original prefix), while the
727+ // actual seek targets keyWithPrefixP3.
728+ //
729+ // # File positioning and TrySeekUsingNext
730+ //
731+ // The prefix is stored and controls file advancement when the current file is
732+ // exhausted. If the current file's largest key has a prefix greater than the
733+ // seek prefix, the iterator stops rather than advancing to the next file.
734+ //
735+ // This stopping condition is critical for TrySeekUsingNext correctness. Without
736+ // it, the following scenario would produce incorrect results:
737+ //
738+ // 1. SeekPrefixGE(P1, key1) - bloom filter misses on file F, no key found
739+ // 2. Iterator advances to file G (incorrectly, if G's smallest prefix > P1)
740+ // 3. SeekPrefixGE(P2, key2, TrySeekUsingNext) where P1 < P2 < G's smallest prefix
741+ // 4. TrySeekUsingNext starts from G, completely skipping file F which may
742+ // contain keys with prefix P2
743+ //
744+ // The stopping condition prevents step 2: if F's largest prefix > P1, the
745+ // iterator remains at F, allowing the subsequent seek for P2 to correctly
746+ // examine F.
747+ //
748+ // # Return values and iterator state
749+ //
750+ // - If a key is found: returns the key-value pair and positions the iterator
751+ // at that key. The iterator may be positioned in any file that contains
752+ // matching keys.
753+ //
754+ // - If no key is found: returns nil. The iterator is exhausted in the forward
755+ // direction. Subsequent Next() calls will return nil.
756+ //
757+ // # TrySeekUsingNext optimization
758+ //
759+ // The TrySeekUsingNext flag in flags indicates that the caller knows the seek
760+ // key is greater than or equal to the iterator's current position. When set:
761+ //
762+ // - At the file level: the iterator may scan forward through files rather
763+ // than performing a binary search through the file metadata.
764+ //
765+ // - At the sstable level: the optimization is automatically disabled when a
766+ // new file is loaded, since the new file's iterator is not yet positioned.
767+ //
768+ // Note: The caller must ensure that key is greater than or equal to the lower
769+ // bound. SeekPrefixGE checks the upper bound but not the lower bound.
706770func (l * levelIter ) SeekPrefixGE (prefix , key []byte , flags base.SeekGEFlags ) (kv * base.InternalKV ) {
707771 if treesteps .Enabled && treesteps .IsRecording (l ) {
708772 op := treesteps .StartOpf (l , "SeekPrefixGE(%q, %q, %d)" , prefix , key , flags )
0 commit comments