LCOV - code coverage report
Current view: top level - pebble - range_keys.go (source / functions) Hit Total Coverage
Test: 2024-06-14 08:16Z 29f4bd63 - meta test only.lcov Lines: 274 324 84.6 %
Date: 2024-06-14 08:16:56 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : package pebble
       6             : 
       7             : import (
       8             :         "context"
       9             : 
      10             :         "github.com/cockroachdb/errors"
      11             :         "github.com/cockroachdb/pebble/internal/base"
      12             :         "github.com/cockroachdb/pebble/internal/invariants"
      13             :         "github.com/cockroachdb/pebble/internal/keyspan"
      14             :         "github.com/cockroachdb/pebble/internal/manifest"
      15             :         "github.com/cockroachdb/pebble/sstable"
      16             : )
      17             : 
      18             : // constructRangeKeyIter constructs the range-key iterator stack, populating
      19             : // i.rangeKey.rangeKeyIter with the resulting iterator.
      20           1 : func (i *Iterator) constructRangeKeyIter() {
      21           1 :         i.rangeKey.rangeKeyIter = i.rangeKey.iterConfig.Init(
      22           1 :                 &i.comparer, i.seqNum, i.opts.LowerBound, i.opts.UpperBound,
      23           1 :                 &i.hasPrefix, &i.prefixOrFullSeekKey, false /* internalKeys */, &i.rangeKey.rangeKeyBuffers.internal)
      24           1 : 
      25           1 :         if i.opts.DebugRangeKeyStack {
      26           0 :                 // The default logger is preferable to i.opts.getLogger(), at least in the
      27           0 :                 // metamorphic test.
      28           0 :                 i.rangeKey.rangeKeyIter = keyspan.InjectLogging(i.rangeKey.rangeKeyIter, base.DefaultLogger)
      29           0 :         }
      30             : 
      31             :         // If there's an indexed batch with range keys, include it.
      32           1 :         if i.batch != nil {
      33           1 :                 if i.batch.index == nil {
      34           0 :                         // This isn't an indexed batch. We shouldn't have gotten this far.
      35           0 :                         panic(errors.AssertionFailedf("creating an iterator over an unindexed batch"))
      36           1 :                 } else {
      37           1 :                         // Only include the batch's range key iterator if it has any keys.
      38           1 :                         // NB: This can force reconstruction of the rangekey iterator stack
      39           1 :                         // in SetOptions if subsequently range keys are added. See
      40           1 :                         // SetOptions.
      41           1 :                         if i.batch.countRangeKeys > 0 {
      42           0 :                                 i.batch.initRangeKeyIter(&i.opts, &i.batchRangeKeyIter, i.batchSeqNum)
      43           0 :                                 i.rangeKey.iterConfig.AddLevel(&i.batchRangeKeyIter)
      44           0 :                         }
      45             :                 }
      46             :         }
      47             : 
      48           1 :         if !i.batchOnlyIter {
      49           1 :                 // Next are the flushables: memtables and large batches.
      50           1 :                 if i.readState != nil {
      51           1 :                         for j := len(i.readState.memtables) - 1; j >= 0; j-- {
      52           1 :                                 mem := i.readState.memtables[j]
      53           1 :                                 // We only need to read from memtables which contain sequence numbers older
      54           1 :                                 // than seqNum.
      55           1 :                                 if logSeqNum := mem.logSeqNum; logSeqNum >= i.seqNum {
      56           1 :                                         continue
      57             :                                 }
      58           1 :                                 if rki := mem.newRangeKeyIter(&i.opts); rki != nil {
      59           1 :                                         i.rangeKey.iterConfig.AddLevel(rki)
      60           1 :                                 }
      61             :                         }
      62             :                 }
      63             : 
      64           1 :                 current := i.version
      65           1 :                 if current == nil {
      66           1 :                         current = i.readState.current
      67           1 :                 }
      68             :                 // Next are the file levels: L0 sub-levels followed by lower levels.
      69             : 
      70             :                 // Add file-specific iterators for L0 files containing range keys. We
      71             :                 // maintain a separate manifest.LevelMetadata for each level containing only
      72             :                 // files that contain range keys, however we don't compute a separate
      73             :                 // L0Sublevels data structure too.
      74             :                 //
      75             :                 // We first use L0's LevelMetadata to peek and see whether L0 contains any
      76             :                 // range keys at all. If it does, we create a range key level iterator per
      77             :                 // level that contains range keys using the information from L0Sublevels.
      78             :                 // Some sublevels may not contain any range keys, and we need to iterate
      79             :                 // through the fileMetadata to determine that. Since L0's file count should
      80             :                 // not significantly exceed ~1000 files (see L0CompactionFileThreshold),
      81             :                 // this should be okay.
      82           1 :                 if !current.RangeKeyLevels[0].Empty() {
      83           1 :                         // L0 contains at least 1 file containing range keys.
      84           1 :                         // Add level iterators for the L0 sublevels, iterating from newest to
      85           1 :                         // oldest.
      86           1 :                         for j := len(current.L0SublevelFiles) - 1; j >= 0; j-- {
      87           1 :                                 iter := current.L0SublevelFiles[j].Iter()
      88           1 :                                 if !containsAnyRangeKeys(iter) {
      89           1 :                                         continue
      90             :                                 }
      91             : 
      92           1 :                                 li := i.rangeKey.iterConfig.NewLevelIter()
      93           1 :                                 li.Init(
      94           1 :                                         i.opts.SpanIterOptions(),
      95           1 :                                         i.cmp,
      96           1 :                                         i.newIterRangeKey,
      97           1 :                                         iter.Filter(manifest.KeyTypeRange),
      98           1 :                                         manifest.L0Sublevel(j),
      99           1 :                                         manifest.KeyTypeRange,
     100           1 :                                 )
     101           1 :                                 i.rangeKey.iterConfig.AddLevel(li)
     102             :                         }
     103             :                 }
     104             : 
     105             :                 // Add level iterators for the non-empty non-L0 levels.
     106           1 :                 for level := 1; level < len(current.RangeKeyLevels); level++ {
     107           1 :                         if current.RangeKeyLevels[level].Empty() {
     108           1 :                                 continue
     109             :                         }
     110           1 :                         li := i.rangeKey.iterConfig.NewLevelIter()
     111           1 :                         spanIterOpts := i.opts.SpanIterOptions()
     112           1 :                         li.Init(spanIterOpts, i.cmp, i.newIterRangeKey, current.RangeKeyLevels[level].Iter(),
     113           1 :                                 manifest.Level(level), manifest.KeyTypeRange)
     114           1 :                         i.rangeKey.iterConfig.AddLevel(li)
     115             :                 }
     116             :         }
     117             : }
     118             : 
     119           1 : func containsAnyRangeKeys(iter manifest.LevelIterator) bool {
     120           1 :         for f := iter.First(); f != nil; f = iter.Next() {
     121           1 :                 if f.HasRangeKeys {
     122           1 :                         return true
     123           1 :                 }
     124             :         }
     125           1 :         return false
     126             : }
     127             : 
     128             : // Range key masking
     129             : //
     130             : // Pebble iterators may be configured such that range keys with suffixes mask
     131             : // point keys with lower suffixes. The intended use is implementing a MVCC
     132             : // delete range operation using range keys, when suffixes are MVCC timestamps.
     133             : //
     134             : // To enable masking, the user populates the IterOptions's RangeKeyMasking
     135             : // field. The Suffix field configures which range keys act as masks. The
     136             : // intended use is to hold a MVCC read timestamp. When implementing a MVCC
     137             : // delete range operation, only range keys that are visible at the read
     138             : // timestamp should be visible. If a range key has a suffix ≤
     139             : // RangeKeyMasking.Suffix, it acts as a mask.
     140             : //
     141             : // Range key masking is facilitated by the keyspan.InterleavingIter. The
     142             : // interleaving iterator interleaves range keys and point keys during combined
     143             : // iteration. During user iteration, the interleaving iterator is configured
     144             : // with a keyspan.SpanMask, implemented by the rangeKeyMasking struct below.
     145             : // The SpanMask interface defines two methods: SpanChanged and SkipPoint.
     146             : //
     147             : // SpanChanged is used to keep the current mask up-to-date. Whenever the point
     148             : // iterator has stepped into or out of the bounds of a range key, the
     149             : // interleaving iterator invokes SpanChanged passing the current covering range
     150             : // key. The below rangeKeyMasking implementation scans the range keys looking
     151             : // for the range key with the largest suffix that's still ≤ the suffix supplied
     152             : // to IterOptions.RangeKeyMasking.Suffix (the "read timestamp"). If it finds a
     153             : // range key that meets the condition, the range key should act as a mask. The
     154             : // span and the relevant range key's suffix are saved.
     155             : //
     156             : // The above ensures that `rangeKeyMasking.maskActiveSuffix` always contains the
     157             : // current masking suffix such that any point keys with lower suffixes should be
     158             : // skipped.
     159             : //
     160             : // There are two ways in which masked point keys are skipped.
     161             : //
     162             : //   1. Interleaving iterator SkipPoint
     163             : //
     164             : // Whenever the interleaving iterator encounters a point key that falls within
     165             : // the bounds of a range key, it invokes SkipPoint. The interleaving iterator
     166             : // guarantees that the SpanChanged method described above has already been
     167             : // invoked with the covering range key. The below rangeKeyMasking implementation
     168             : // of SkipPoint splits the key into prefix and suffix, compares the suffix to
     169             : // the `maskActiveSuffix` updated by SpanChanged and returns true if
     170             : // suffix(point) < maskActiveSuffix.
     171             : //
     172             : // The SkipPoint logic is sufficient to ensure that the Pebble iterator filters
     173             : // out all masked point keys. However, it requires the iterator read each masked
     174             : // point key. For broad range keys that mask many points, this may be expensive.
     175             : //
     176             : //   2. Block property filter
     177             : //
     178             : // For more efficient handling of braad range keys that mask many points, the
     179             : // IterOptions.RangeKeyMasking field has an optional Filter option. This Filter
     180             : // field takes a superset of the block-property filter interface, adding a
     181             : // method to dynamically configure the filter's filtering criteria.
     182             : //
     183             : // To make use of the Filter option, the user is required to define and
     184             : // configure a block-property collector that collects a property containing at
     185             : // least the maximum suffix of a key within a block.
     186             : //
     187             : // When the SpanChanged method described above is invoked, rangeKeyMasking also
     188             : // reconfigures the user-provided filter. It invokes a SetSuffix method,
     189             : // providing the `maskActiveSuffix`, requesting that from now on the
     190             : // block-property filter return Intersects()=false for any properties indicating
     191             : // that a block contains exclusively keys with suffixes greater than the
     192             : // provided suffix.
     193             : //
     194             : // Note that unlike other block-property filters, the filter used for masking
     195             : // must not apply across the entire keyspace. It must only filter blocks that
     196             : // lie within the bounds of the range key that set the mask suffix. To
     197             : // accommodate this, rangeKeyMasking implements a special interface:
     198             : // sstable.BoundLimitedBlockPropertyFilter. This interface extends the block
     199             : // property filter interface with two new methods: KeyIsWithinLowerBound and
     200             : // KeyIsWithinUpperBound. The rangeKeyMasking type wraps the user-provided block
     201             : // property filter, implementing these two methods and overriding Intersects to
     202             : // always return true if there is no active mask.
     203             : //
     204             : // The logic to ensure that a mask block-property filter is only applied within
     205             : // the bounds of the masking range key is subtle. The interleaving iterator
     206             : // guarantees that it never invokes SpanChanged until the point iterator is
     207             : // positioned within the range key. During forward iteration, this guarantees
     208             : // that any block that a sstable reader might attempt to load contains only keys
     209             : // greater than or equal to the range key's lower bound. During backward
     210             : // iteration, it provides the analagous guarantee on the range key's upper
     211             : // bound.
     212             : //
     213             : // The above ensures that an sstable reader only needs to verify that a block
     214             : // that it skips meets the opposite bound. This is where the
     215             : // KeyIsWithinLowerBound and KeyIsWithinUpperBound methods are used. When an
     216             : // sstable iterator is configured with a BoundLimitedBlockPropertyFilter, it
     217             : // checks for intersection with the block-property filter before every block
     218             : // load, like ordinary block-property filters. However, if the bound-limited
     219             : // block property filter indicates that it does NOT intersect, the filter's
     220             : // relevant KeyIsWithin{Lower,Upper}Bound method is queried, using a block
     221             : // index separator as the bound. If the method indicates that the provided index
     222             : // separator does not fall within the range key bounds, the no-intersection
     223             : // result is ignored, and the block is read.
     224             : 
     225             : type rangeKeyMasking struct {
     226             :         cmp    base.Compare
     227             :         split  base.Split
     228             :         filter BlockPropertyFilterMask
     229             :         // maskActiveSuffix holds the suffix of a range key currently acting as a
     230             :         // mask, hiding point keys with suffixes greater than it. maskActiveSuffix
     231             :         // is only ever non-nil if IterOptions.RangeKeyMasking.Suffix is non-nil.
     232             :         // maskActiveSuffix is updated whenever the iterator passes over a new range
     233             :         // key. The maskActiveSuffix should only be used if maskSpan is non-nil.
     234             :         //
     235             :         // See SpanChanged.
     236             :         maskActiveSuffix []byte
     237             :         // maskSpan holds the span from which the active mask suffix was extracted.
     238             :         // The span is used for bounds comparisons, to ensure that a range-key mask
     239             :         // is not applied beyond the bounds of the range key.
     240             :         maskSpan *keyspan.Span
     241             :         parent   *Iterator
     242             : }
     243             : 
     244           1 : func (m *rangeKeyMasking) init(parent *Iterator, cmp base.Compare, split base.Split) {
     245           1 :         m.cmp = cmp
     246           1 :         m.split = split
     247           1 :         if parent.opts.RangeKeyMasking.Filter != nil {
     248           1 :                 m.filter = parent.opts.RangeKeyMasking.Filter()
     249           1 :         }
     250           1 :         m.parent = parent
     251             : }
     252             : 
     253             : // SpanChanged implements the keyspan.SpanMask interface, used during range key
     254             : // iteration.
     255           1 : func (m *rangeKeyMasking) SpanChanged(s *keyspan.Span) {
     256           1 :         if s == nil && m.maskSpan == nil {
     257           1 :                 return
     258           1 :         }
     259           1 :         m.maskSpan = nil
     260           1 :         m.maskActiveSuffix = m.maskActiveSuffix[:0]
     261           1 : 
     262           1 :         // Find the smallest suffix of a range key contained within the Span,
     263           1 :         // excluding suffixes less than m.opts.RangeKeyMasking.Suffix.
     264           1 :         if s != nil {
     265           1 :                 m.parent.rangeKey.stale = true
     266           1 :                 if m.parent.opts.RangeKeyMasking.Suffix != nil {
     267           1 :                         for j := range s.Keys {
     268           1 :                                 if s.Keys[j].Suffix == nil {
     269           0 :                                         continue
     270             :                                 }
     271           1 :                                 if m.cmp(s.Keys[j].Suffix, m.parent.opts.RangeKeyMasking.Suffix) < 0 {
     272           1 :                                         continue
     273             :                                 }
     274           1 :                                 if len(m.maskActiveSuffix) == 0 || m.cmp(m.maskActiveSuffix, s.Keys[j].Suffix) > 0 {
     275           1 :                                         m.maskSpan = s
     276           1 :                                         m.maskActiveSuffix = append(m.maskActiveSuffix[:0], s.Keys[j].Suffix...)
     277           1 :                                 }
     278             :                         }
     279             :                 }
     280             :         }
     281             : 
     282           1 :         if m.maskSpan != nil && m.parent.opts.RangeKeyMasking.Filter != nil {
     283           1 :                 // Update the  block-property filter to filter point keys with suffixes
     284           1 :                 // greater than m.maskActiveSuffix.
     285           1 :                 err := m.filter.SetSuffix(m.maskActiveSuffix)
     286           1 :                 if err != nil {
     287           0 :                         m.parent.err = err
     288           0 :                 }
     289             :         }
     290             :         // If no span is active, we leave the inner block-property filter configured
     291             :         // with its existing suffix. That's okay, because Intersects calls are first
     292             :         // evaluated by iteratorRangeKeyState.Intersects, which considers all blocks
     293             :         // as intersecting if there's no active mask.
     294             : }
     295             : 
     296             : // SkipPoint implements the keyspan.SpanMask interface, used during range key
     297             : // iteration. Whenever a point key is covered by a non-empty Span, the
     298             : // interleaving iterator invokes SkipPoint. This function is responsible for
     299             : // performing range key masking.
     300             : //
     301             : // If a non-nil IterOptions.RangeKeyMasking.Suffix is set, range key masking is
     302             : // enabled. Masking hides point keys, transparently skipping over the keys.
     303             : // Whether or not a point key is masked is determined by comparing the point
     304             : // key's suffix, the overlapping span's keys' suffixes, and the user-configured
     305             : // IterOption's RangeKeyMasking.Suffix. When configured with a masking threshold
     306             : // _t_, and there exists a span with suffix _r_ covering a point key with suffix
     307             : // _p_, and
     308             : //
     309             : //      _t_ ≤ _r_ < _p_
     310             : //
     311             : // then the point key is elided. Consider the following rendering, where using
     312             : // integer suffixes with higher integers sort before suffixes with lower
     313             : // integers, (for example @7 ≤ @6 < @5):
     314             : //
     315             : //           ^
     316             : //        @9 |        •―――――――――――――――○ [e,m)@9
     317             : //      s  8 |                      • l@8
     318             : //      u  7 |------------------------------------ @7 RangeKeyMasking.Suffix
     319             : //      f  6 |      [h,q)@6 •―――――――――――――――――○            (threshold)
     320             : //      f  5 |              • h@5
     321             : //      f  4 |                          • n@4
     322             : //      i  3 |          •―――――――――――○ [f,l)@3
     323             : //      x  2 |  • b@2
     324             : //         1 |
     325             : //         0 |___________________________________
     326             : //            a b c d e f g h i j k l m n o p q
     327             : //
     328             : // An iterator scanning the entire keyspace with the masking threshold set to @7
     329             : // will observe point keys b@2 and l@8. The span keys [h,q)@6 and [f,l)@3 serve
     330             : // as masks, because cmp(@6,@7) ≥ 0 and cmp(@3,@7) ≥ 0. The span key [e,m)@9
     331             : // does not serve as a mask, because cmp(@9,@7) < 0.
     332             : //
     333             : // Although point l@8 falls within the user key bounds of [e,m)@9, [e,m)@9 is
     334             : // non-masking due to its suffix. The point key l@8 also falls within the user
     335             : // key bounds of [h,q)@6, but since cmp(@6,@8) ≥ 0, l@8 is unmasked.
     336             : //
     337             : // Invariant: The userKey is within the user key bounds of the span most
     338             : // recently provided to `SpanChanged`.
     339           1 : func (m *rangeKeyMasking) SkipPoint(userKey []byte) bool {
     340           1 :         m.parent.stats.RangeKeyStats.ContainedPoints++
     341           1 :         if m.maskSpan == nil {
     342           1 :                 // No range key is currently acting as a mask, so don't skip.
     343           1 :                 return false
     344           1 :         }
     345             :         // Range key masking is enabled and the current span includes a range key
     346             :         // that is being used as a mask. (NB: SpanChanged already verified that the
     347             :         // range key's suffix is ≥ RangeKeyMasking.Suffix).
     348             :         //
     349             :         // This point key falls within the bounds of the range key (guaranteed by
     350             :         // the InterleavingIter). Skip the point key if the range key's suffix is
     351             :         // greater than the point key's suffix.
     352           1 :         pointSuffix := userKey[m.split(userKey):]
     353           1 :         if len(pointSuffix) > 0 && m.cmp(m.maskActiveSuffix, pointSuffix) < 0 {
     354           1 :                 m.parent.stats.RangeKeyStats.SkippedPoints++
     355           1 :                 return true
     356           1 :         }
     357           1 :         return false
     358             : }
     359             : 
     360             : // The iteratorRangeKeyState type implements the sstable package's
     361             : // BoundLimitedBlockPropertyFilter interface in order to use block property
     362             : // filters for range key masking. The iteratorRangeKeyState implementation wraps
     363             : // the block-property filter provided in Options.RangeKeyMasking.Filter.
     364             : //
     365             : // Using a block-property filter for range-key masking requires limiting the
     366             : // filter's effect to the bounds of the range key currently acting as a mask.
     367             : // Consider the range key [a,m)@10, and an iterator positioned just before the
     368             : // below block, bounded by index separators `c` and `z`:
     369             : //
     370             : //                c                          z
     371             : //         x      |  c@9 c@5 c@1 d@7 e@4 y@4 | ...
     372             : //      iter pos
     373             : //
     374             : // The next block cannot be skipped, despite the range key suffix @10 is greater
     375             : // than all the block's keys' suffixes, because it contains a key (y@4) outside
     376             : // the bounds of the range key.
     377             : //
     378             : // This extended BoundLimitedBlockPropertyFilter interface adds two new methods,
     379             : // KeyIsWithinLowerBound and KeyIsWithinUpperBound, for testing whether a
     380             : // particular block is within bounds.
     381             : //
     382             : // The iteratorRangeKeyState implements these new methods by first checking if
     383             : // the iterator is currently positioned within a range key. If not, the provided
     384             : // key is considered out-of-bounds. If the iterator is positioned within a range
     385             : // key, it compares the corresponding range key bound.
     386             : var _ sstable.BoundLimitedBlockPropertyFilter = (*rangeKeyMasking)(nil)
     387             : 
     388             : // Name implements the limitedBlockPropertyFilter interface defined in the
     389             : // sstable package by passing through to the user-defined block property filter.
     390           1 : func (m *rangeKeyMasking) Name() string {
     391           1 :         return m.filter.Name()
     392           1 : }
     393             : 
     394             : // Intersects implements the limitedBlockPropertyFilter interface defined in the
     395             : // sstable package by passing the intersection decision to the user-provided
     396             : // block property filter only if a range key is covering the current iterator
     397             : // position.
     398           1 : func (m *rangeKeyMasking) Intersects(prop []byte) (bool, error) {
     399           1 :         if m.maskSpan == nil {
     400           1 :                 // No span is actively masking.
     401           1 :                 return true, nil
     402           1 :         }
     403           1 :         return m.filter.Intersects(prop)
     404             : }
     405             : 
     406           1 : func (m *rangeKeyMasking) SyntheticSuffixIntersects(prop []byte, suffix []byte) (bool, error) {
     407           1 :         if m.maskSpan == nil {
     408           1 :                 // No span is actively masking.
     409           1 :                 return true, nil
     410           1 :         }
     411           1 :         return m.filter.SyntheticSuffixIntersects(prop, suffix)
     412             : }
     413             : 
     414             : // KeyIsWithinLowerBound implements the limitedBlockPropertyFilter interface
     415             : // defined in the sstable package. It's used to restrict the masking block
     416             : // property filter to only applying within the bounds of the active range key.
     417           1 : func (m *rangeKeyMasking) KeyIsWithinLowerBound(key []byte) bool {
     418           1 :         // Invariant: m.maskSpan != nil
     419           1 :         //
     420           1 :         // The provided `key` is an inclusive lower bound of the block we're
     421           1 :         // considering skipping.
     422           1 :         return m.cmp(m.maskSpan.Start, key) <= 0
     423           1 : }
     424             : 
     425             : // KeyIsWithinUpperBound implements the limitedBlockPropertyFilter interface
     426             : // defined in the sstable package. It's used to restrict the masking block
     427             : // property filter to only applying within the bounds of the active range key.
     428           1 : func (m *rangeKeyMasking) KeyIsWithinUpperBound(key []byte) bool {
     429           1 :         // Invariant: m.maskSpan != nil
     430           1 :         //
     431           1 :         // The provided `key` is an *inclusive* upper bound of the block we're
     432           1 :         // considering skipping, so the range key's end must be strictly greater
     433           1 :         // than the block bound for the block to be within bounds.
     434           1 :         return m.cmp(m.maskSpan.End, key) > 0
     435           1 : }
     436             : 
     437             : // lazyCombinedIter implements the internalIterator interface, wrapping a
     438             : // pointIter. It requires the pointIter's the levelIters be configured with
     439             : // pointers to its combinedIterState. When the levelIter observes a file
     440             : // containing a range key, the lazyCombinedIter constructs the combined
     441             : // range+point key iterator stack and switches to it.
     442             : type lazyCombinedIter struct {
     443             :         // parent holds a pointer to the root *pebble.Iterator containing this
     444             :         // iterator. It's used to mutate the internalIterator in use when switching
     445             :         // to combined iteration.
     446             :         parent            *Iterator
     447             :         pointIter         internalIterator
     448             :         combinedIterState combinedIterState
     449             : }
     450             : 
     451             : // combinedIterState encapsulates the current state of combined iteration.
     452             : // Various low-level iterators (mergingIter, leveliter) hold pointers to the
     453             : // *pebble.Iterator's combinedIterState. This allows them to check whether or
     454             : // not they must monitor for files containing range keys (!initialized), or not.
     455             : //
     456             : // When !initialized, low-level iterators watch for files containing range keys.
     457             : // When one is discovered, they set triggered=true and key to the smallest
     458             : // (forward direction) or largest (reverse direction) range key that's been
     459             : // observed.
     460             : type combinedIterState struct {
     461             :         // key holds the smallest (forward direction) or largest (backward
     462             :         // direction) user key from a range key bound discovered during the iterator
     463             :         // operation that triggered the switch to combined iteration.
     464             :         //
     465             :         // Slices stored here must be stable. This is possible because callers pass
     466             :         // a Smallest/Largest bound from a fileMetadata, which are immutable. A key
     467             :         // slice's bytes must not be overwritten.
     468             :         key         []byte
     469             :         triggered   bool
     470             :         initialized bool
     471             : }
     472             : 
     473             : // Assert that *lazyCombinedIter implements internalIterator.
     474             : var _ internalIterator = (*lazyCombinedIter)(nil)
     475             : 
     476             : // initCombinedIteration is invoked after a pointIter positioning operation
     477             : // resulted in i.combinedIterState.triggered=true.
     478             : //
     479             : // The `dir` parameter is `+1` or `-1` indicating forward iteration or backward
     480             : // iteration respectively.
     481             : //
     482             : // The `pointKey` and `pointValue` parameters provide the new point key-value
     483             : // pair that the iterator was just positioned to. The combined iterator should
     484             : // be seeded with this point key-value pair and return the smaller (forward
     485             : // iteration) or largest (backward iteration) of the two.
     486             : //
     487             : // The `seekKey` parameter is non-nil only if the iterator operation that
     488             : // triggered the switch to combined iteration was a SeekGE, SeekPrefixGE or
     489             : // SeekLT. It provides the seek key supplied and is used to seek the range-key
     490             : // iterator using the same key. This is necessary for SeekGE/SeekPrefixGE
     491             : // operations that land in the middle of a range key and must truncate to the
     492             : // user-provided seek key.
     493             : func (i *lazyCombinedIter) initCombinedIteration(
     494             :         dir int8, pointKV *base.InternalKV, seekKey []byte,
     495           1 : ) *base.InternalKV {
     496           1 :         // Invariant: i.parent.rangeKey is nil.
     497           1 :         // Invariant: !i.combinedIterState.initialized.
     498           1 :         if invariants.Enabled {
     499           1 :                 if i.combinedIterState.initialized {
     500           0 :                         panic("pebble: combined iterator already initialized")
     501             :                 }
     502           1 :                 if i.parent.rangeKey != nil {
     503           0 :                         panic("pebble: iterator already has a range-key iterator stack")
     504             :                 }
     505             :         }
     506             : 
     507             :         // We need to determine the key to seek the range key iterator to. If
     508             :         // seekKey is not nil, the user-initiated operation that triggered the
     509             :         // switch to combined iteration was itself a seek, and we can use that key.
     510             :         // Otherwise, a First/Last or relative positioning operation triggered the
     511             :         // switch to combined iteration.
     512             :         //
     513             :         // The levelIter that observed a file containing range keys populated
     514             :         // combinedIterState.key with the smallest (forward) or largest (backward)
     515             :         // range key it observed. If multiple levelIters observed files with range
     516             :         // keys during the same operation on the mergingIter, combinedIterState.key
     517             :         // is the smallest [during forward iteration; largest in reverse iteration]
     518             :         // such key.
     519           1 :         if seekKey == nil {
     520           1 :                 // Use the levelIter-populated key.
     521           1 :                 seekKey = i.combinedIterState.key
     522           1 : 
     523           1 :                 // We may need to adjust the levelIter-populated seek key to the
     524           1 :                 // surfaced point key. If the key observed is beyond [in the iteration
     525           1 :                 // direction] the current point key, there may still exist a range key
     526           1 :                 // at an earlier key. Consider the following example:
     527           1 :                 //
     528           1 :                 //   L5:  000003:[bar.DEL.5, foo.RANGEKEYSET.9]
     529           1 :                 //   L6:  000001:[bar.SET.2] 000002:[bax.RANGEKEYSET.8]
     530           1 :                 //
     531           1 :                 // A call to First() seeks the levels to files L5.000003 and L6.000001.
     532           1 :                 // The L5 levelIter observes that L5.000003 contains the range key with
     533           1 :                 // start key `foo`, and triggers a switch to combined iteration, setting
     534           1 :                 // `combinedIterState.key` = `foo`.
     535           1 :                 //
     536           1 :                 // The L6 levelIter did not observe the true first range key
     537           1 :                 // (bax.RANGEKEYSET.8), because it appears in a later sstable. When the
     538           1 :                 // combined iterator is initialized, the range key iterator must be
     539           1 :                 // seeked to a key that will find `bax`. To accomplish this, we seek the
     540           1 :                 // key instead to `bar`. It is guaranteed that no range key exists
     541           1 :                 // earlier than `bar`, otherwise a levelIter would've observed it and
     542           1 :                 // set `combinedIterState.key` to its start key.
     543           1 :                 if pointKV != nil {
     544           1 :                         if dir == +1 && i.parent.cmp(i.combinedIterState.key, pointKV.K.UserKey) > 0 {
     545           1 :                                 seekKey = pointKV.K.UserKey
     546           1 :                         } else if dir == -1 && i.parent.cmp(seekKey, pointKV.K.UserKey) < 0 {
     547           1 :                                 seekKey = pointKV.K.UserKey
     548           1 :                         }
     549             :                 }
     550             :         }
     551             : 
     552             :         // An operation on the point iterator observed a file containing range keys,
     553             :         // so we must switch to combined interleaving iteration. First, construct
     554             :         // the range key iterator stack. It must not exist, otherwise we'd already
     555             :         // be performing combined iteration.
     556           1 :         i.parent.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
     557           1 :         i.parent.rangeKey.init(i.parent.comparer.Compare, i.parent.comparer.Split, &i.parent.opts)
     558           1 :         i.parent.constructRangeKeyIter()
     559           1 : 
     560           1 :         // Initialize the Iterator's interleaving iterator.
     561           1 :         i.parent.rangeKey.iiter.Init(
     562           1 :                 &i.parent.comparer, i.parent.pointIter, i.parent.rangeKey.rangeKeyIter,
     563           1 :                 keyspan.InterleavingIterOpts{
     564           1 :                         Mask:       &i.parent.rangeKeyMasking,
     565           1 :                         LowerBound: i.parent.opts.LowerBound,
     566           1 :                         UpperBound: i.parent.opts.UpperBound,
     567           1 :                 })
     568           1 : 
     569           1 :         // Set the parent's primary iterator to point to the combined, interleaving
     570           1 :         // iterator that's now initialized with our current state.
     571           1 :         i.parent.iter = &i.parent.rangeKey.iiter
     572           1 :         i.combinedIterState.initialized = true
     573           1 :         i.combinedIterState.key = nil
     574           1 : 
     575           1 :         // All future iterator operations will go directly through the combined
     576           1 :         // iterator.
     577           1 :         //
     578           1 :         // Initialize the interleaving iterator. We pass the point key-value pair so
     579           1 :         // that the interleaving iterator knows where the point iterator is
     580           1 :         // positioned. Additionally, we pass the seek key to which the range-key
     581           1 :         // iterator should be seeked in order to initialize its position.
     582           1 :         //
     583           1 :         // In the forward direction (invert for backwards), the seek key is a key
     584           1 :         // guaranteed to find the smallest range key that's greater than the last
     585           1 :         // key the iterator returned. The range key may be less than pointKV, in
     586           1 :         // which case the range key will be interleaved next instead of the point
     587           1 :         // key.
     588           1 :         if dir == +1 {
     589           1 :                 var prefix []byte
     590           1 :                 if i.parent.hasPrefix {
     591           1 :                         prefix = i.parent.prefixOrFullSeekKey
     592           1 :                 }
     593           1 :                 return i.parent.rangeKey.iiter.InitSeekGE(prefix, seekKey, pointKV)
     594             :         }
     595           1 :         return i.parent.rangeKey.iiter.InitSeekLT(seekKey, pointKV)
     596             : }
     597             : 
     598           1 : func (i *lazyCombinedIter) SeekGE(key []byte, flags base.SeekGEFlags) *base.InternalKV {
     599           1 :         if i.combinedIterState.initialized {
     600           0 :                 return i.parent.rangeKey.iiter.SeekGE(key, flags)
     601           0 :         }
     602           1 :         kv := i.pointIter.SeekGE(key, flags)
     603           1 :         if i.combinedIterState.triggered {
     604           1 :                 return i.initCombinedIteration(+1, kv, key)
     605           1 :         }
     606           1 :         return kv
     607             : }
     608             : 
     609             : func (i *lazyCombinedIter) SeekPrefixGE(
     610             :         prefix, key []byte, flags base.SeekGEFlags,
     611           1 : ) *base.InternalKV {
     612           1 :         if i.combinedIterState.initialized {
     613           0 :                 return i.parent.rangeKey.iiter.SeekPrefixGE(prefix, key, flags)
     614           0 :         }
     615           1 :         kv := i.pointIter.SeekPrefixGE(prefix, key, flags)
     616           1 :         if i.combinedIterState.triggered {
     617           1 :                 return i.initCombinedIteration(+1, kv, key)
     618           1 :         }
     619           1 :         return kv
     620             : }
     621             : 
     622           1 : func (i *lazyCombinedIter) SeekLT(key []byte, flags base.SeekLTFlags) *base.InternalKV {
     623           1 :         if i.combinedIterState.initialized {
     624           0 :                 return i.parent.rangeKey.iiter.SeekLT(key, flags)
     625           0 :         }
     626           1 :         kv := i.pointIter.SeekLT(key, flags)
     627           1 :         if i.combinedIterState.triggered {
     628           1 :                 return i.initCombinedIteration(-1, kv, key)
     629           1 :         }
     630           1 :         return kv
     631             : }
     632             : 
     633           1 : func (i *lazyCombinedIter) First() *base.InternalKV {
     634           1 :         if i.combinedIterState.initialized {
     635           0 :                 return i.parent.rangeKey.iiter.First()
     636           0 :         }
     637           1 :         kv := i.pointIter.First()
     638           1 :         if i.combinedIterState.triggered {
     639           1 :                 return i.initCombinedIteration(+1, kv, nil)
     640           1 :         }
     641           1 :         return kv
     642             : }
     643             : 
     644           1 : func (i *lazyCombinedIter) Last() *base.InternalKV {
     645           1 :         if i.combinedIterState.initialized {
     646           0 :                 return i.parent.rangeKey.iiter.Last()
     647           0 :         }
     648           1 :         kv := i.pointIter.Last()
     649           1 :         if i.combinedIterState.triggered {
     650           1 :                 return i.initCombinedIteration(-1, kv, nil)
     651           1 :         }
     652           1 :         return kv
     653             : }
     654             : 
     655           1 : func (i *lazyCombinedIter) Next() *base.InternalKV {
     656           1 :         if i.combinedIterState.initialized {
     657           0 :                 return i.parent.rangeKey.iiter.Next()
     658           0 :         }
     659           1 :         kv := i.pointIter.Next()
     660           1 :         if i.combinedIterState.triggered {
     661           1 :                 return i.initCombinedIteration(+1, kv, nil)
     662           1 :         }
     663           1 :         return kv
     664             : }
     665             : 
     666           1 : func (i *lazyCombinedIter) NextPrefix(succKey []byte) *base.InternalKV {
     667           1 :         if i.combinedIterState.initialized {
     668           0 :                 return i.parent.rangeKey.iiter.NextPrefix(succKey)
     669           0 :         }
     670           1 :         kv := i.pointIter.NextPrefix(succKey)
     671           1 :         if i.combinedIterState.triggered {
     672           0 :                 return i.initCombinedIteration(+1, kv, nil)
     673           0 :         }
     674           1 :         return kv
     675             : }
     676             : 
     677           1 : func (i *lazyCombinedIter) Prev() *base.InternalKV {
     678           1 :         if i.combinedIterState.initialized {
     679           0 :                 return i.parent.rangeKey.iiter.Prev()
     680           0 :         }
     681           1 :         kv := i.pointIter.Prev()
     682           1 :         if i.combinedIterState.triggered {
     683           1 :                 return i.initCombinedIteration(-1, kv, nil)
     684           1 :         }
     685           1 :         return kv
     686             : }
     687             : 
     688           1 : func (i *lazyCombinedIter) Error() error {
     689           1 :         if i.combinedIterState.initialized {
     690           0 :                 return i.parent.rangeKey.iiter.Error()
     691           0 :         }
     692           1 :         return i.pointIter.Error()
     693             : }
     694             : 
     695           1 : func (i *lazyCombinedIter) Close() error {
     696           1 :         if i.combinedIterState.initialized {
     697           0 :                 return i.parent.rangeKey.iiter.Close()
     698           0 :         }
     699           1 :         return i.pointIter.Close()
     700             : }
     701             : 
     702           1 : func (i *lazyCombinedIter) SetBounds(lower, upper []byte) {
     703           1 :         if i.combinedIterState.initialized {
     704           0 :                 i.parent.rangeKey.iiter.SetBounds(lower, upper)
     705           0 :                 return
     706           0 :         }
     707           1 :         i.pointIter.SetBounds(lower, upper)
     708             : }
     709             : 
     710           0 : func (i *lazyCombinedIter) SetContext(ctx context.Context) {
     711           0 :         if i.combinedIterState.initialized {
     712           0 :                 i.parent.rangeKey.iiter.SetContext(ctx)
     713           0 :                 return
     714           0 :         }
     715           0 :         i.pointIter.SetContext(ctx)
     716             : }
     717             : 
     718           0 : func (i *lazyCombinedIter) String() string {
     719           0 :         if i.combinedIterState.initialized {
     720           0 :                 return i.parent.rangeKey.iiter.String()
     721           0 :         }
     722           0 :         return i.pointIter.String()
     723             : }

Generated by: LCOV version 1.14