LCOV - code coverage report
Current view: top level - pebble/internal/keyspan - bounded.go (source / functions) Hit Total Coverage
Test: 2023-09-09 08:15Z 1efa535d - tests only.lcov Lines: 132 136 97.1 %
Date: 2023-09-09 08:16:07 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2022 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 keyspan
       6             : 
       7             : import "github.com/cockroachdb/pebble/internal/base"
       8             : 
       9             : // TODO(jackson): Consider removing this type and adding bounds enforcement
      10             : // directly to the MergingIter. This type is probably too lightweight to warrant
      11             : // its own type, but for now we implement it separately for expediency.
      12             : 
      13             : // boundedIterPos records the position of the BoundedIter relative to the
      14             : // underlying iterator's position. It's used to avoid Next/Prev-ing the iterator
      15             : // if there can't possibly be another span within bounds, because the current
      16             : // span overlaps the bound.
      17             : //
      18             : // Imagine bounds [a,c) and an iterator that seeks to a span [b,d). The span
      19             : // [b,d) overlaps some portion of the iterator bounds, so the iterator must
      20             : // return it. If the iterator is subsequently Nexted, Next can tell that the
      21             : // iterator is exhausted without advancing the underlying iterator because the
      22             : // current span's end bound of d is ≥ the upper bound of c. In this case, the
      23             : // bounded iterator returns nil and records i.pos as posAtUpperLimit to remember
      24             : // that the underlying iterator position does not match the current BoundedIter
      25             : // position.
      26             : type boundedIterPos int8
      27             : 
      28             : const (
      29             :         posAtLowerLimit boundedIterPos = -1
      30             :         posAtIterSpan   boundedIterPos = 0
      31             :         posAtUpperLimit boundedIterPos = +1
      32             : )
      33             : 
      34             : // BoundedIter implements FragmentIterator and enforces bounds.
      35             : //
      36             : // Like the point InternalIterator interface, the bounded iterator's forward
      37             : // positioning routines (SeekGE, First, and Next) only check the upper bound.
      38             : // The reverse positioning routines (SeekLT, Last, and Prev) only check the
      39             : // lower bound. It is up to the caller to ensure that the forward positioning
      40             : // routines respect the lower bound and the reverse positioning routines respect
      41             : // the upper bound (i.e. calling SeekGE instead of First if there is a lower
      42             : // bound, and SeekLT instead of Last if there is an upper bound).
      43             : //
      44             : // When the hasPrefix parameter indicates that the iterator is in prefix
      45             : // iteration mode, BoundedIter elides any spans that do not overlap with the
      46             : // prefix's keyspace. In prefix iteration mode, reverse iteration is disallowed,
      47             : // except for an initial SeekLT with a seek key greater than or equal to the
      48             : // prefix. In prefix iteration mode, the first seek must position the iterator
      49             : // at or immediately before the first fragment covering a key greater than or
      50             : // equal to the prefix.
      51             : type BoundedIter struct {
      52             :         iter      FragmentIterator
      53             :         iterSpan  *Span
      54             :         cmp       base.Compare
      55             :         split     base.Split
      56             :         lower     []byte
      57             :         upper     []byte
      58             :         hasPrefix *bool
      59             :         prefix    *[]byte
      60             :         pos       boundedIterPos
      61             : }
      62             : 
      63             : // Init initializes the bounded iterator.
      64             : //
      65             : // In addition to the iterator bounds, Init takes pointers to a boolean
      66             : // indicating whether the iterator is in prefix iteration mode and the prefix
      67             : // key if it is. This is used to exclude spans that are outside the iteration
      68             : // prefix.
      69             : //
      70             : // hasPrefix and prefix are allowed to be nil, however if hasPrefix != nil,
      71             : // prefix must also not be nil.
      72             : func (i *BoundedIter) Init(
      73             :         cmp base.Compare,
      74             :         split base.Split,
      75             :         iter FragmentIterator,
      76             :         lower, upper []byte,
      77             :         hasPrefix *bool,
      78             :         prefix *[]byte,
      79           1 : ) {
      80           1 :         *i = BoundedIter{
      81           1 :                 iter:      iter,
      82           1 :                 cmp:       cmp,
      83           1 :                 split:     split,
      84           1 :                 lower:     lower,
      85           1 :                 upper:     upper,
      86           1 :                 hasPrefix: hasPrefix,
      87           1 :                 prefix:    prefix,
      88           1 :         }
      89           1 : }
      90             : 
      91             : var _ FragmentIterator = (*BoundedIter)(nil)
      92             : 
      93             : // Seek calls.
      94             : //
      95             : // Seek calls check iterator bounds in the direction of the seek. Additionally,
      96             : // if the iterator is in prefix iteration mode, seek calls check both start and
      97             : // end bounds against the prefix's bounds. We check both bounds for defense in
      98             : // depth. This optimization has been a source of various bugs due to various
      99             : // other prefix iteration optimizations that can result in seek keys that don't
     100             : // respect the prefix bounds.
     101             : 
     102             : // SeekGE implements FragmentIterator.
     103           1 : func (i *BoundedIter) SeekGE(key []byte) *Span {
     104           1 :         s := i.iter.SeekGE(key)
     105           1 :         s = i.checkPrefixSpanStart(s)
     106           1 :         s = i.checkPrefixSpanEnd(s)
     107           1 :         return i.checkForwardBound(s)
     108           1 : }
     109             : 
     110             : // SeekLT implements FragmentIterator.
     111           1 : func (i *BoundedIter) SeekLT(key []byte) *Span {
     112           1 :         s := i.iter.SeekLT(key)
     113           1 :         s = i.checkPrefixSpanStart(s)
     114           1 :         s = i.checkPrefixSpanEnd(s)
     115           1 :         return i.checkBackwardBound(s)
     116           1 : }
     117             : 
     118             : // First implements FragmentIterator.
     119           1 : func (i *BoundedIter) First() *Span {
     120           1 :         s := i.iter.First()
     121           1 :         s = i.checkPrefixSpanStart(s)
     122           1 :         return i.checkForwardBound(s)
     123           1 : }
     124             : 
     125             : // Last implements FragmentIterator.
     126           1 : func (i *BoundedIter) Last() *Span {
     127           1 :         s := i.iter.Last()
     128           1 :         s = i.checkPrefixSpanEnd(s)
     129           1 :         return i.checkBackwardBound(s)
     130           1 : }
     131             : 
     132             : // Next implements FragmentIterator.
     133           1 : func (i *BoundedIter) Next() *Span {
     134           1 :         switch i.pos {
     135           1 :         case posAtLowerLimit:
     136           1 :                 // The BoundedIter had previously returned nil, because it knew from
     137           1 :                 // i.iterSpan's bounds that there was no previous span. To Next, we only
     138           1 :                 // need to return the current iter span and reset i.pos to reflect that
     139           1 :                 // we're no longer positioned at the limit.
     140           1 :                 i.pos = posAtIterSpan
     141           1 :                 return i.iterSpan
     142           1 :         case posAtIterSpan:
     143           1 :                 // If the span at the underlying iterator position extends to or beyond the
     144           1 :                 // upper bound, we can avoid advancing because the next span is necessarily
     145           1 :                 // out of bounds.
     146           1 :                 if i.iterSpan != nil && i.upper != nil && i.cmp(i.iterSpan.End, i.upper) >= 0 {
     147           1 :                         i.pos = posAtUpperLimit
     148           1 :                         return nil
     149           1 :                 }
     150             :                 // Similarly, if the span extends to the next prefix and we're in prefix
     151             :                 // iteration mode, we can avoid advancing.
     152           1 :                 if i.iterSpan != nil && i.hasPrefix != nil && *i.hasPrefix {
     153           1 :                         ei := i.split(i.iterSpan.End)
     154           1 :                         if i.cmp(i.iterSpan.End[:ei], *i.prefix) > 0 {
     155           1 :                                 i.pos = posAtUpperLimit
     156           1 :                                 return nil
     157           1 :                         }
     158             :                 }
     159           1 :                 return i.checkForwardBound(i.checkPrefixSpanStart(i.iter.Next()))
     160           1 :         case posAtUpperLimit:
     161           1 :                 // Already exhausted.
     162           1 :                 return nil
     163           0 :         default:
     164           0 :                 panic("unreachable")
     165             :         }
     166             : }
     167             : 
     168             : // Prev implements FragmentIterator.
     169           1 : func (i *BoundedIter) Prev() *Span {
     170           1 :         switch i.pos {
     171           1 :         case posAtLowerLimit:
     172           1 :                 // Already exhausted.
     173           1 :                 return nil
     174           1 :         case posAtIterSpan:
     175           1 :                 // If the span at the underlying iterator position extends to or beyond
     176           1 :                 // the lower bound, we can avoid advancing because the previous span is
     177           1 :                 // necessarily out of bounds.
     178           1 :                 if i.iterSpan != nil && i.lower != nil && i.cmp(i.iterSpan.Start, i.lower) <= 0 {
     179           1 :                         i.pos = posAtLowerLimit
     180           1 :                         return nil
     181           1 :                 }
     182             :                 // Similarly, if the span extends to or beyond the current prefix and
     183             :                 // we're in prefix iteration mode, we can avoid advancing.
     184           1 :                 if i.iterSpan != nil && i.hasPrefix != nil && *i.hasPrefix {
     185           1 :                         si := i.split(i.iterSpan.Start)
     186           1 :                         if i.cmp(i.iterSpan.Start[:si], *i.prefix) < 0 {
     187           1 :                                 i.pos = posAtLowerLimit
     188           1 :                                 return nil
     189           1 :                         }
     190             :                 }
     191           1 :                 return i.checkBackwardBound(i.checkPrefixSpanEnd(i.iter.Prev()))
     192           1 :         case posAtUpperLimit:
     193           1 :                 // The BoundedIter had previously returned nil, because it knew from
     194           1 :                 // i.iterSpan's bounds that there was no next span. To Prev, we only
     195           1 :                 // need to return the current iter span and reset i.pos to reflect that
     196           1 :                 // we're no longer positioned at the limit.
     197           1 :                 i.pos = posAtIterSpan
     198           1 :                 return i.iterSpan
     199           0 :         default:
     200           0 :                 panic("unreachable")
     201             :         }
     202             : }
     203             : 
     204             : // Error implements FragmentIterator.
     205           1 : func (i *BoundedIter) Error() error {
     206           1 :         return i.iter.Error()
     207           1 : }
     208             : 
     209             : // Close implements FragmentIterator.
     210           1 : func (i *BoundedIter) Close() error {
     211           1 :         return i.iter.Close()
     212           1 : }
     213             : 
     214             : // SetBounds modifies the FragmentIterator's bounds.
     215           1 : func (i *BoundedIter) SetBounds(lower, upper []byte) {
     216           1 :         i.lower, i.upper = lower, upper
     217           1 : }
     218             : 
     219           1 : func (i *BoundedIter) checkPrefixSpanStart(span *Span) *Span {
     220           1 :         // Compare to the prefix's bounds, if in prefix iteration mode.
     221           1 :         if span != nil && i.hasPrefix != nil && *i.hasPrefix {
     222           1 :                 si := i.split(span.Start)
     223           1 :                 if i.cmp(span.Start[:si], *i.prefix) > 0 {
     224           1 :                         // This span starts at a prefix that sorts after our current prefix.
     225           1 :                         span = nil
     226           1 :                 }
     227             :         }
     228           1 :         return span
     229             : }
     230             : 
     231             : // checkForwardBound enforces the upper bound, returning nil if the provided
     232             : // span is wholly outside the upper bound. It also updates i.pos and i.iterSpan
     233             : // to reflect the new iterator position.
     234           1 : func (i *BoundedIter) checkForwardBound(span *Span) *Span {
     235           1 :         // Compare to the upper bound.
     236           1 :         if span != nil && i.upper != nil && i.cmp(span.Start, i.upper) >= 0 {
     237           1 :                 span = nil
     238           1 :         }
     239           1 :         i.iterSpan = span
     240           1 :         if i.pos != posAtIterSpan {
     241           1 :                 i.pos = posAtIterSpan
     242           1 :         }
     243           1 :         return span
     244             : }
     245             : 
     246           1 : func (i *BoundedIter) checkPrefixSpanEnd(span *Span) *Span {
     247           1 :         // Compare to the prefix's bounds, if in prefix iteration mode.
     248           1 :         if span != nil && i.hasPrefix != nil && *i.hasPrefix && i.cmp(span.End, *i.prefix) <= 0 {
     249           1 :                 // This span ends before the current prefix.
     250           1 :                 span = nil
     251           1 :         }
     252           1 :         return span
     253             : }
     254             : 
     255             : // checkBackward enforces the lower bound, returning nil if the provided span is
     256             : // wholly outside the lower bound.  It also updates i.pos and i.iterSpan to
     257             : // reflect the new iterator position.
     258           1 : func (i *BoundedIter) checkBackwardBound(span *Span) *Span {
     259           1 :         // Compare to the lower bound.
     260           1 :         if span != nil && i.lower != nil && i.cmp(span.End, i.lower) <= 0 {
     261           1 :                 span = nil
     262           1 :         }
     263           1 :         i.iterSpan = span
     264           1 :         if i.pos != posAtIterSpan {
     265           1 :                 i.pos = posAtIterSpan
     266           1 :         }
     267           1 :         return span
     268             : }

Generated by: LCOV version 1.14