@@ -856,6 +856,95 @@ func (i *twoLevelIterator[I, PI, D, PD]) First() *base.InternalKV {
856856 return i .skipForward ()
857857}
858858
859+ // FirstWithMeta moves the iterator to the first key/value pair and returns
860+ // both the key/value and the associated metadata. This method is used by
861+ // compaction iterators that need access to tiering metadata without adding
862+ // overhead to the common iteration path.
863+ func (i * twoLevelIterator [I , PI , D , PD ]) FirstWithMeta () (* base.InternalKV , base.KVMeta ) {
864+ i .lastOpWasSeekPrefixGE .Set (false )
865+ // The synthetic key is no longer relevant and must be cleared.
866+ i .secondLevel .synthetic .atSyntheticKey = false
867+
868+ // If we have a lower bound, use SeekGE. Note that in general this is not
869+ // supported usage, except when the lower bound is there because the table is
870+ // virtual.
871+ if i .secondLevel .lower != nil {
872+ kv := i .SeekGE (i .secondLevel .lower , base .SeekGEFlagsNone )
873+ if kv == nil {
874+ return nil , base.KVMeta {}
875+ }
876+ meta := i .extractMetaFromCurrentPosition ()
877+ return kv , meta
878+ }
879+ i .secondLevel .exhaustedBounds = 0
880+ i .secondLevel .err = nil // clear cached iteration error
881+ // Seek optimization only applies until iterator is first positioned after SetBounds.
882+ i .secondLevel .boundsCmp = 0
883+
884+ if ! i .ensureTopLevelIndexLoaded () {
885+ return nil , base.KVMeta {}
886+ }
887+
888+ if ! PI (& i .topLevelIndex ).First () {
889+ return nil , base.KVMeta {}
890+ }
891+ result := i .loadSecondLevelIndexBlock (+ 1 )
892+ if result == loadBlockFailed {
893+ return nil , base.KVMeta {}
894+ }
895+ if result == loadBlockOK {
896+ if ikv := i .secondLevel .First (); ikv != nil {
897+ meta := i .extractMetaFromCurrentPosition ()
898+ return ikv , meta
899+ }
900+ // Else fall through to skipForward.
901+ } else {
902+ // result == loadBlockIrrelevant. Enforce the upper bound here since
903+ // don't want to bother moving to the next entry in the top level index
904+ // if upper bound is already exceeded. Note that the next entry starts
905+ // with keys >= topLevelIndex.Separator() since even though this is the
906+ // block separator, the same user key can span multiple index blocks.
907+ // If upper is exclusive we pass orEqual=true below, else we require the
908+ // separator to be strictly greater than upper.
909+ if i .secondLevel .upper != nil && PI (& i .topLevelIndex ).SeparatorGT (
910+ i .secondLevel .upper , ! i .secondLevel .endKeyInclusive ) {
911+ i .secondLevel .exhaustedBounds = + 1
912+ }
913+ }
914+ // NB: skipForward checks whether exhaustedBounds is already +1.
915+ kv := i .skipForward ()
916+ if kv == nil {
917+ return nil , base.KVMeta {}
918+ }
919+ meta := i .extractMetaFromCurrentPosition ()
920+ return kv , meta
921+ }
922+
923+ // NextWithMeta moves the iterator to the next key/value pair and returns
924+ // both the key/value and the associated metadata. This method is used by
925+ // compaction iterators that need access to tiering metadata without adding
926+ // overhead to the common iteration path.
927+ func (i * twoLevelIterator [I , PI , D , PD ]) NextWithMeta () (* base.InternalKV , base.KVMeta ) {
928+ kv := i .Next ()
929+ if kv == nil {
930+ return nil , base.KVMeta {}
931+ }
932+ meta := i .extractMetaFromCurrentPosition ()
933+ return kv , meta
934+ }
935+
936+ // extractMetaFromCurrentPosition extracts KVMeta from the current iterator position.
937+ // This method delegates to the underlying second level iterator if it supports
938+ // the specialized methods.
939+ func (i * twoLevelIterator [I , PI , D , PD ]) extractMetaFromCurrentPosition () base.KVMeta {
940+ if PD (& i .secondLevel .data ).IsDataInvalidated () {
941+ return base.KVMeta {}
942+ }
943+
944+ // The dataBlockIterator constraint guarantees that PD(&i.secondLevel.data) implements MetaDecoder
945+ return PD (& i .secondLevel .data ).DecodeMeta ()
946+ }
947+
859948// Last implements internalIterator.Last, as documented in the pebble
860949// package. Note that Last only checks the lower bound. It is up to the caller
861950// to ensure that key is less than the upper bound (e.g. via a call to
0 commit comments