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