From b79bdae83684c810be5884fc89fae25bef93eff4 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Sun, 20 Mar 2022 15:17:07 +0000 Subject: [PATCH] storage: add MVCC range tombstone handling for scans and gets This patch adds MVCC range tombstone handling for scans and gets. In the basic case, this simply means that point keys below an MVCC range tombstone are not visible. When tombstones are requested by the caller, the MVCC range tombstones themselves are never exposed, to avoid having to explicitly handle these throughout the codebase. Instead, synthetic MVCC point tombstones are emitted at the start of MVCC range tombstones and wherever they overlap a point key (above and below). Additionally, point gets return synthetic point tombstones even if no existing point key exists. The point tombstone synthesis is implemented by a new `pointSynthesizingIterator`, an `MVCCIterator` implementation that wraps an existing `MVCCIterator`. This allows e.g. `pebbleMVCCScanner` to remain unchanged, and automatically take MVCC range tombstones into account for e.g. conflict/uncertainty checks. Point tombstone synthesis must be enabled even when the caller has not requested tombstones, because they must always be taken into account for conflict/uncertainty checks. However, in these cases we enable range key masking below the read timestamp, omitting any covered points since these are no longer needed. Release note: None --- pkg/storage/BUILD.bazel | 1 + pkg/storage/mvcc.go | 82 +- pkg/storage/mvcc_history_test.go | 5 +- pkg/storage/mvcc_key.go | 6 + pkg/storage/point_synthesizing_iter.go | 614 ++++++++++ .../mvcc_histories/range_key_point_synthesis | 1042 +++++++++++++++++ .../mvcc_histories/range_tombstone_gets | 305 +++++ .../mvcc_histories/range_tombstone_scans | 287 +++++ 8 files changed, 2313 insertions(+), 29 deletions(-) create mode 100644 pkg/storage/point_synthesizing_iter.go create mode 100644 pkg/storage/testdata/mvcc_histories/range_key_point_synthesis create mode 100644 pkg/storage/testdata/mvcc_histories/range_tombstone_gets create mode 100644 pkg/storage/testdata/mvcc_histories/range_tombstone_scans diff --git a/pkg/storage/BUILD.bazel b/pkg/storage/BUILD.bazel index 631f6767f05f..5b394749baa8 100644 --- a/pkg/storage/BUILD.bazel +++ b/pkg/storage/BUILD.bazel @@ -28,6 +28,7 @@ go_library( "pebble_iterator.go", "pebble_merge.go", "pebble_mvcc_scanner.go", + "point_synthesizing_iter.go", "replicas_storage.go", "resource_limiter.go", "row_counter.go", diff --git a/pkg/storage/mvcc.go b/pkg/storage/mvcc.go index d08670868eb1..63801a9091ec 100644 --- a/pkg/storage/mvcc.go +++ b/pkg/storage/mvcc.go @@ -689,12 +689,34 @@ func (opts *MVCCGetOptions) validate() error { return nil } -func newMVCCIterator(reader Reader, inlineMeta bool, opts IterOptions) MVCCIterator { +// newMVCCIterator is a convenience function that sets up a suitable iterator +// for internal MVCC operations. In particular, this never exposes range keys +// (i.e. MVCC range tombstones), but instead synthesizes MVCC point tombstones +// around existing point keys that overlap an MVCC range tombstone, as well as +// at the start of MVCC range tombstones. It does this even if tombstones are +// not returned, since they must be visible to conflict/uncertainty checks. +func newMVCCIterator( + reader Reader, timestamp hlc.Timestamp, returnTombstones bool, opts IterOptions, +) MVCCIterator { + if opts.KeyTypes != IterKeyTypePointsOnly { + panic(errors.AssertionFailedf("can't request range keys")) + } + opts.KeyTypes = IterKeyTypePointsAndRanges + // Disable separated intents if reading inline. iterKind := MVCCKeyAndIntentsIterKind - if inlineMeta { + if timestamp.IsEmpty() { iterKind = MVCCKeyIterKind } - return reader.NewMVCCIterator(iterKind, opts) + // If we're not returning tombstones, enable range key masking. + if !returnTombstones && opts.RangeKeyMaskingBelow.IsEmpty() { + opts.RangeKeyMaskingBelow = timestamp + } + // Prefix iterators are generally used for point operations (e.g. gets or puts + // as opposed to scans). For these, we also synthesize point tombstones for + // MVCC range tombstones at a SeekGE key, to emit them even if no existing + // point key exists -- this is necessary for conflict/uncertainty checks. + emitOnSeekGE := opts.Prefix + return newPointSynthesizingIter(reader.NewMVCCIterator(iterKind, opts), emitOnSeekGE) } // MVCCGet returns the most recent value for the specified key whose timestamp @@ -703,7 +725,10 @@ func newMVCCIterator(reader Reader, inlineMeta bool, opts IterOptions) MVCCItera // // In tombstones mode, if the most recent value is a deletion tombstone, the // result will be a non-nil roachpb.Value whose RawBytes field is nil. -// Otherwise, a deletion tombstone results in a nil roachpb.Value. +// Otherwise, a deletion tombstone results in a nil roachpb.Value. MVCC range +// tombstones will be emitted as synthetic point tombstones (these synthetic +// point tombstones may not be visible to a scan if there is no existing point +// key at this key). // // In inconsistent mode, if an intent is encountered, it will be placed in the // dedicated return parameter. By contrast, in consistent mode, an intent will @@ -724,7 +749,7 @@ func newMVCCIterator(reader Reader, inlineMeta bool, opts IterOptions) MVCCItera func MVCCGet( ctx context.Context, reader Reader, key roachpb.Key, timestamp hlc.Timestamp, opts MVCCGetOptions, ) (*roachpb.Value, *roachpb.Intent, error) { - iter := newMVCCIterator(reader, timestamp.IsEmpty(), IterOptions{Prefix: true}) + iter := newMVCCIterator(reader, timestamp, opts.Tombstones, IterOptions{Prefix: true}) defer iter.Close() value, intent, err := mvccGet(ctx, iter, key, timestamp, opts) return value.ToPointer(), intent, err @@ -1063,10 +1088,7 @@ func MVCCDelete( timestamp hlc.Timestamp, txn *roachpb.Transaction, ) error { - iter := newMVCCIterator(rw, timestamp.IsEmpty(), IterOptions{ - KeyTypes: IterKeyTypePointsAndRanges, - Prefix: true, - }) + iter := newMVCCIterator(rw, timestamp, false /* returnTombstones */, IterOptions{Prefix: true}) defer iter.Close() return mvccPutUsingIter(ctx, rw, iter, ms, key, timestamp, noValue, txn, nil /* valueFn */) @@ -1687,10 +1709,7 @@ func MVCCIncrement( txn *roachpb.Transaction, inc int64, ) (int64, error) { - iter := newMVCCIterator(rw, timestamp.IsEmpty(), IterOptions{ - KeyTypes: IterKeyTypePointsAndRanges, - Prefix: true, - }) + iter := newMVCCIterator(rw, timestamp, false /* returnTombstones */, IterOptions{Prefix: true}) defer iter.Close() var int64Val int64 @@ -1762,10 +1781,7 @@ func MVCCConditionalPut( allowIfDoesNotExist CPutMissingBehavior, txn *roachpb.Transaction, ) error { - iter := newMVCCIterator(rw, timestamp.IsEmpty(), IterOptions{ - KeyTypes: IterKeyTypePointsAndRanges, - Prefix: true, - }) + iter := newMVCCIterator(rw, timestamp, false /* returnTombstones */, IterOptions{Prefix: true}) defer iter.Close() return mvccConditionalPutUsingIter(ctx, rw, iter, ms, key, timestamp, value, expVal, allowIfDoesNotExist, txn) @@ -1843,10 +1859,7 @@ func MVCCInitPut( failOnTombstones bool, txn *roachpb.Transaction, ) error { - iter := newMVCCIterator(rw, timestamp.IsEmpty(), IterOptions{ - KeyTypes: IterKeyTypePointsAndRanges, - Prefix: true, - }) + iter := newMVCCIterator(rw, timestamp, false /* returnTombstones */, IterOptions{Prefix: true}) defer iter.Close() return mvccInitPutUsingIter(ctx, rw, iter, ms, key, timestamp, value, failOnTombstones, txn) } @@ -2221,7 +2234,7 @@ func MVCCDeleteRange( buf := newPutBuffer() defer buf.release() - iter := newMVCCIterator(rw, timestamp.IsEmpty(), IterOptions{Prefix: true}) + iter := newMVCCIterator(rw, timestamp, false /* returnTombstones */, IterOptions{Prefix: true}) defer iter.Close() var keys []roachpb.Key @@ -2741,7 +2754,10 @@ type MVCCScanResult struct { // In tombstones mode, if the most recent value for a key is a deletion // tombstone, the scan result will contain a roachpb.KeyValue for that key whose // RawBytes field is nil. Otherwise, the key-value pair will be omitted from the -// result entirely. +// result entirely. For MVCC range tombstones, synthetic point tombstones are +// returned at the start of the range tombstone and when then overlap a point +// key (note that this may emit spurious point tombstones at the MVCCScan start +// key if it truncates an MVCC range tombstone). // // When scanning inconsistently, any encountered intents will be placed in the // dedicated result parameter. By contrast, when scanning consistently, any @@ -2764,7 +2780,10 @@ func MVCCScan( timestamp hlc.Timestamp, opts MVCCScanOptions, ) (MVCCScanResult, error) { - iter := newMVCCIterator(reader, timestamp.IsEmpty(), IterOptions{LowerBound: key, UpperBound: endKey}) + iter := newMVCCIterator(reader, timestamp, opts.Tombstones, IterOptions{ + LowerBound: key, + UpperBound: endKey, + }) defer iter.Close() return mvccScanToKvs(ctx, iter, key, endKey, timestamp, opts) } @@ -2777,7 +2796,10 @@ func MVCCScanToBytes( timestamp hlc.Timestamp, opts MVCCScanOptions, ) (MVCCScanResult, error) { - iter := newMVCCIterator(reader, timestamp.IsEmpty(), IterOptions{LowerBound: key, UpperBound: endKey}) + iter := newMVCCIterator(reader, timestamp, opts.Tombstones, IterOptions{ + LowerBound: key, + UpperBound: endKey, + }) defer iter.Close() return mvccScanToBytes(ctx, iter, key, endKey, timestamp, opts) } @@ -2811,7 +2833,9 @@ func MVCCScanAsTxn( // the reverse flag is set, the iterator will be moved in reverse order. If the // scan options specify an inconsistent scan, all "ignored" intents will be // returned. In consistent mode, intents are only ever returned as part of a -// WriteIntentError. +// WriteIntentError. In Tombstones mode, MVCC range tombstones are emitted as +// synthetic point tombstones around overlapping point keys and the start of +// MVCC range tombstones. func MVCCIterate( ctx context.Context, reader Reader, @@ -2820,8 +2844,10 @@ func MVCCIterate( opts MVCCScanOptions, f func(roachpb.KeyValue) error, ) ([]roachpb.Intent, error) { - iter := newMVCCIterator( - reader, timestamp.IsEmpty(), IterOptions{LowerBound: key, UpperBound: endKey}) + iter := newMVCCIterator(reader, timestamp, opts.Tombstones, IterOptions{ + LowerBound: key, + UpperBound: endKey, + }) defer iter.Close() var intents []roachpb.Intent diff --git a/pkg/storage/mvcc_history_test.go b/pkg/storage/mvcc_history_test.go index b0cf29329d91..cc8d1cf06d70 100644 --- a/pkg/storage/mvcc_history_test.go +++ b/pkg/storage/mvcc_history_test.go @@ -70,7 +70,7 @@ import ( // put_rangekey k= end= ts=[,] // scan [t=] [ts=[,]] [resolve [status=]] k= [end=] [inconsistent] [tombstones] [reverse] [failOnMoreRecent] [localUncertaintyLimit=[,]] [globalUncertaintyLimit=[,]] [max=] [targetbytes=] [avoidExcess] [allowEmpty] // -// iter [k=] [end=] [kind=key|keyAndIntents] [types=pointsOnly|pointsWithRanges|pointsAndRanges|rangesOnly] [maskBelow=[,]] +// iter [k=] [end=] [kind=key|keyAndIntents] [types=pointsOnly|pointsWithRanges|pointsAndRanges|rangesOnly] [pointSynthesis [emitOnSeekGE]] [maskBelow=[,]] // iter_seek_ge k= [ts=[,]] // iter_seek_lt k= [ts=[,]] // iter_seek_intent_ge k= txn= @@ -1002,6 +1002,9 @@ func cmdIterNew(e *evalCtx) error { MVCCIterator: r.NewMVCCIterator(kind, opts), closeReader: closeReader, } + if e.hasArg("pointSynthesis") { + e.iter = newPointSynthesizingIter(e.iter, e.hasArg("emitOnSeekGE")) + } return nil } diff --git a/pkg/storage/mvcc_key.go b/pkg/storage/mvcc_key.go index 2452b2b4e79b..7b8e63612bce 100644 --- a/pkg/storage/mvcc_key.go +++ b/pkg/storage/mvcc_key.go @@ -62,6 +62,12 @@ func (k MVCCKey) Next() MVCCKey { } } +// Clone returns a copy of the key. +func (k MVCCKey) Clone() MVCCKey { + k.Key = k.Key.Clone() + return k +} + // Compare returns -1 if this key is less than the given key, 0 if they're // equal, or 1 if this is greater. Comparison is by key,timestamp, where larger // timestamps sort before smaller ones except empty ones which sort first (like diff --git a/pkg/storage/point_synthesizing_iter.go b/pkg/storage/point_synthesizing_iter.go new file mode 100644 index 000000000000..3b3e8c09cdab --- /dev/null +++ b/pkg/storage/point_synthesizing_iter.go @@ -0,0 +1,614 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package storage + +import ( + "sort" + + "github.com/cockroachdb/cockroach/pkg/roachpb" + "github.com/cockroachdb/cockroach/pkg/storage/enginepb" + "github.com/cockroachdb/cockroach/pkg/util" + "github.com/cockroachdb/cockroach/pkg/util/hlc" + "github.com/cockroachdb/cockroach/pkg/util/protoutil" + "github.com/cockroachdb/cockroach/pkg/util/uuid" + "github.com/cockroachdb/errors" +) + +// pointSynthesizingIter wraps an MVCCIterator, and synthesizes MVCC point +// tombstones for MVCC range tombstones above/below existing point keys, and at +// the start keys of MVCC range tombstones (truncated to iterator bounds). If +// emitOnSeekGE is set, it will also synthesize point tombstones around the +// SeekGE key if it does not have an existing point key. +// +// It does not emit MVCC range keys at all, since these would appear to conflict +// with the synthesized point keys. +type pointSynthesizingIter struct { + iter MVCCIterator + + // emitOnSeekGE will synthesize point tombstones for the SeekGE seek key if it + // overlaps with a range tombstone even if no point key exists. The primary + // use-case is to synthesize point tombstones for e.g. an MVCCGet that does + // not match a point key but overlaps a range key, which is necessary for + // conflict checks. + // + // This is optional, because e.g. pebbleMVCCScanner often uses seeks as an + // optimization to skip over old versions of a key, and we don't want to keep + // synthesizing point tombstones every time it skips ahead. + // + // TODO(erikgrinaker): This could instead check for prefix iterators, or a + // separate SeekPrefixGE() method, but we don't currently have APIs for it. + emitOnSeekGE bool + + // rangeKeys contains the timestamps of MVCC range tombstones at the current + // key position, for which point tombstones will be synthesized. + rangeKeys []hlc.Timestamp + + // rangeKeysPos is the current key (along the rangeKeys span) that points will + // be synthesized for. It is only set if rangeKeys is non-empty, and may + // differ from the underlying iterator position. See atPoint for details. + rangeKeysPos roachpb.Key + + // rangeKeysIdx is the rangeKeys index of the current/pending range tombstone + // to synthesize a point for. See atPoint for details. + rangeKeysIdx int + + // rangeKeysStart contains the start key of the current rangeKeys stack. It is + // only used to memoize rangeKeys for adjacent keys. + rangeKeysStart roachpb.Key + + // atPoint is true if the synthesizing iterator is positioned on a real point + // key in the underlying iterator. In that case, rangeKeysIdx points to a + // range key following the point key, or beyond the slice bounds when there + // are no further range keys at this key position. + // + // When false, the synthesizing iterator is positioned on a synthetic point + // tombstone given by rangeKeysIdx. In that case, the underlying iterator + // points to a following key (either a different version of the current key or + // a different point/range key) unless it is exhausted. + // + // The relative positioning is mirrored in the forward and reverse direction. + // For example, when atPoint is true and rangeKeys are exhausted, rangeKeysIdx + // will be len(rangeKeys) in the forward direction and -1 in the reverse + // direction. Similarly, the underlying iterator is always >= rangeKeysPos in + // the forward direction and <= in reverse. + // + // See also assertInvariants() which asserts positioning invariants. + atPoint bool + + // reverse is true when the current iterator direction is in reverse, i.e. + // following a SeekLT or Prev call. + reverse bool +} + +var _ MVCCIterator = new(pointSynthesizingIter) + +// newPointSynthesizingIter creates a new pointSynthesizingIter. +func newPointSynthesizingIter(iter MVCCIterator, emitOnSeekGE bool) *pointSynthesizingIter { + return &pointSynthesizingIter{ + iter: iter, + emitOnSeekGE: emitOnSeekGE, + } +} + +// updateRangeKeys updates i.rangeKeys and related fields with range keys from +// the underlying iterator. rangeKeysIdx is reset to the first/last range key. +func (i *pointSynthesizingIter) updateRangeKeys() { + if _, hasRange := i.iter.HasPointAndRange(); hasRange { + i.rangeKeysPos = append(i.rangeKeysPos[:0], i.iter.UnsafeKey().Key...) + if rangeStart, _ := i.iter.RangeBounds(); !rangeStart.Equal(i.rangeKeysStart) { + i.rangeKeys = i.rangeKeys[:0] + for _, rk := range i.iter.RangeKeys() { + i.rangeKeys = append(i.rangeKeys, rk.Timestamp) + } + i.rangeKeysStart = append(i.rangeKeysStart[:0], rangeStart...) + } + } else if len(i.rangeKeys) != 0 { + i.rangeKeys = i.rangeKeys[:0] + i.rangeKeysPos = i.rangeKeysPos[:0] + i.rangeKeysStart = i.rangeKeysStart[:0] + } + if !i.reverse { + i.rangeKeysIdx = 0 + } else { + i.rangeKeysIdx = len(i.rangeKeys) - 1 // NB: -1 is correct with no range keys + } +} + +// updateAtPoint updates i.atPoint according to whether the synthesizing +// iterator is positioned on the real point key in the underlying iterator. +// Requires i.rangeKeys to have been positioned first. +func (i *pointSynthesizingIter) updateAtPoint() { + if hasPoint, _ := i.iter.HasPointAndRange(); !hasPoint { + i.atPoint = false + } else if len(i.rangeKeys) == 0 { + i.atPoint = true + } else if point := i.iter.UnsafeKey(); !point.Key.Equal(i.rangeKeysPos) { + i.atPoint = false + } else if !i.reverse { + i.atPoint = i.rangeKeysIdx >= len(i.rangeKeys) || + point.Timestamp.IsEmpty() || + i.rangeKeys[i.rangeKeysIdx].LessEq(point.Timestamp) + } else { + i.atPoint = i.rangeKeysIdx < 0 || (point.Timestamp.IsSet() && + point.Timestamp.LessEq(i.rangeKeys[i.rangeKeysIdx])) + } +} + +// updatePosition updates the synthesizing iterator with the position of the +// underlying iterator. This may step the underlying iterator to position it +// correctly relative to bare range keys. +func (i *pointSynthesizingIter) updatePosition() { + if !i.reverse { + i.updateRangeKeys() + // If we're on a bare range key in the forward direction, we populate the + // range keys but then step iter ahead before updating the point position. + // The next position may be a point key colocated with the current range key + // position, which must be interleaved with the synthetic points. + if hasPoint, hasRange := i.iter.HasPointAndRange(); hasRange && !hasPoint { + i.iter.Next() + } + i.updateAtPoint() + } else { + // If we're on a bare range key in the reverse direction, and we've already + // emitted synthetic points for this key (as evidenced by rangeKeysPos), + // then we skip over the bare range key to avoid duplicates. + if hasPoint, hasRange := i.iter.HasPointAndRange(); hasRange && !hasPoint { + if i.iter.UnsafeKey().Key.Equal(i.rangeKeysPos) { + i.iter.Prev() + } + } + i.updateRangeKeys() + i.updateAtPoint() + } +} + +// SeekGE implements MVCCIterator. +func (i *pointSynthesizingIter) SeekGE(key MVCCKey) { + i.reverse = false + i.iter.SeekGE(key) + + // If we land in the middle of a bare range key and emitOnSeekGE is disabled, + // then skip over it to the next point/range key. However, if we're seeking to + // a specific version and don't find an older point key at the seek key, then + // we also need to peek backwards for an existing point key above us, which + // would mandate that we synthesize point keys here after all. + // + // TODO(erikgrinaker): It might be faster to first do an unversioned seek to + // look for previous points and then a versioned seek. + var positioned bool + if !i.emitOnSeekGE { + if hasPoint, hasRange := i.iter.HasPointAndRange(); hasRange && !hasPoint { + if rangeStart, _ := i.iter.RangeBounds(); !rangeStart.Equal(i.iter.UnsafeKey().Key) { + i.iter.Next() + + if key.Timestamp.IsSet() { + ok, err := i.iter.Valid() + if err == nil && (!ok || !key.Key.Equal(i.iter.UnsafeKey().Key)) { + i.iter.Prev() + if hasP, _ := i.iter.HasPointAndRange(); hasP && key.Key.Equal(i.iter.UnsafeKey().Key) { + i.updateRangeKeys() + positioned = true + } + i.iter.Next() + } + } + } + } + } + + if !positioned { + i.updateRangeKeys() + + // If we're now at a bare range key, it must either be at the start of it, + // or in the middle with emitOnSeekGE enabled. In either case, we want to + // move the iterator ahead to look for a point key that may be colocated + // with the start/seek key in order to interleave it. + if hasPoint, hasRange := i.iter.HasPointAndRange(); hasRange && !hasPoint { + i.iter.Next() + } + } + + // If we're seeking to a specific version, skip newer range tombstones. + if len(i.rangeKeys) > 0 && key.Timestamp.IsSet() && key.Key.Equal(i.rangeKeysPos) { + i.rangeKeysIdx = sort.Search(len(i.rangeKeys), func(idx int) bool { + return i.rangeKeys[idx].LessEq(key.Timestamp) + }) + } + + i.updateAtPoint() + + // It's possible that we seeked past all of the range key versions. In this + // case, we have to reposition on the next key (current iter key). + if !i.atPoint && i.rangeKeysIdx >= len(i.rangeKeys) { + i.updatePosition() + } +} + +// SeekIntentGE implements MVCCIterator. +func (i *pointSynthesizingIter) SeekIntentGE(key roachpb.Key, txnUUID uuid.UUID) { + i.reverse = false + i.iter.SeekIntentGE(key, txnUUID) + + // If we land in the middle of a bare range key and emitOnSeekGE is disabled, + // then skip over it to the next point/range key. + if !i.emitOnSeekGE { + if hasPoint, hasRange := i.iter.HasPointAndRange(); hasRange && !hasPoint { + if rangeStart, _ := i.iter.RangeBounds(); !rangeStart.Equal(i.iter.UnsafeKey().Key) { + i.iter.Next() + } + } + } + + i.updatePosition() +} + +// Next implements MVCCIterator. +func (i *pointSynthesizingIter) Next() { + // When changing direction, flip the relative positioning with iter. + if i.reverse { + i.reverse = false + if !i.atPoint && len(i.rangeKeys) == 0 { // iterator was exhausted + i.iter.Next() + i.updatePosition() + return + } else if i.atPoint { + i.rangeKeysIdx++ + } else { + i.iter.Next() + } + } + + // Step off the current point, either real or synthetic. + if i.atPoint { + i.iter.Next() + } else { + i.rangeKeysIdx++ + } + i.updateAtPoint() + + // If we've exhausted the current range keys, update with the underlying + // iterator position (which must now be at a later key). + if !i.atPoint && i.rangeKeysIdx >= len(i.rangeKeys) { + i.updatePosition() + } +} + +// NextKey implements MVCCIterator. +func (i *pointSynthesizingIter) NextKey() { + // When changing direction, flip the relative positioning with iter. + if i.reverse { + i.reverse = false + if !i.atPoint { + i.iter.Next() + } + } + // Don't call NextKey() if the underlying iterator is already on the next key. + if i.atPoint || i.rangeKeysPos.Equal(i.iter.UnsafeKey().Key) { + i.iter.NextKey() + } + i.updatePosition() +} + +// SeekLT implements MVCCIterator. +func (i *pointSynthesizingIter) SeekLT(key MVCCKey) { + i.reverse = true + i.iter.SeekLT(key) + + // If we did a versioned seek and find a range key that overlaps the seek key, + // we may have skipped over existing point key versions of the seek key. These + // would mandate that we synthesize point keys for the seek key after all, so + // we peek ahead to check for them. + // + // TODO(erikgrinaker): It might be faster to do an unversioned seek from the + // next key first to look for points. + var positioned bool + if key.Timestamp.IsSet() { + if hasPoint, hasRange := i.iter.HasPointAndRange(); hasRange { + if !hasPoint || !i.iter.UnsafeKey().Key.Equal(key.Key) { + if _, rangeEnd := i.iter.RangeBounds(); key.Key.Compare(rangeEnd) < 0 { + i.iter.Next() + if hasP, _ := i.iter.HasPointAndRange(); hasP && i.iter.UnsafeKey().Key.Equal(key.Key) { + i.updateRangeKeys() + positioned = true + } + i.iter.Prev() + } + } + } + } + + if !positioned { + i.updateRangeKeys() + } + + // If we're seeking to a specific version, skip over older range tombstones. + if key.Timestamp.IsSet() && key.Key.Equal(i.rangeKeysPos) { + i.rangeKeysIdx = sort.Search(len(i.rangeKeys), func(idx int) bool { + return i.rangeKeys[idx].LessEq(key.Timestamp) + }) - 1 + } + + i.updateAtPoint() + + // It's possible that we seeked past all of the range key versions. In this + // case, we have to reposition on the previous key (current iter key). + if !i.atPoint && i.rangeKeysIdx < 0 { + i.updatePosition() + } +} + +// Prev implements MVCCIterator. +func (i *pointSynthesizingIter) Prev() { + // When changing direction, flip the relative positioning with iter. + if !i.reverse { + i.reverse = true + if !i.atPoint && len(i.rangeKeys) == 0 { // iterator was exhausted + i.iter.Prev() + i.updatePosition() + return + } else if i.atPoint { + i.rangeKeysIdx-- + } else { + i.iter.Prev() + } + } + + // Step off the current point key (real or synthetic). + if i.atPoint { + i.iter.Prev() + } else { + i.rangeKeysIdx-- + } + i.updateAtPoint() + + // If we've exhausted the current range keys, and we're not positioned on a + // point key at the current range key position, then update with the + // underlying iter position (which must be before the current key). + if i.rangeKeysIdx < 0 && (!i.atPoint || !i.rangeKeysPos.Equal(i.iter.UnsafeKey().Key)) { + i.updatePosition() + } +} + +// Valid implements MVCCIterator. +func (i *pointSynthesizingIter) Valid() (bool, error) { + if util.RaceEnabled { + if err := i.assertInvariants(); err != nil { + panic(err) + } + } + if !i.atPoint && i.rangeKeysIdx >= 0 && i.rangeKeysIdx < len(i.rangeKeys) { + return true, nil // on synthetic point tombstone + } + return i.iter.Valid() +} + +// Key implements MVCCIterator. +func (i *pointSynthesizingIter) Key() MVCCKey { + return i.UnsafeKey().Clone() +} + +// UnsafeKey implements MVCCIterator. +func (i *pointSynthesizingIter) UnsafeKey() MVCCKey { + if i.atPoint { + return i.iter.UnsafeKey() + } + if i.rangeKeysIdx >= len(i.rangeKeys) || i.rangeKeysIdx < 0 { + return MVCCKey{} + } + return MVCCKey{ + Key: i.rangeKeysPos, + Timestamp: i.rangeKeys[i.rangeKeysIdx], + } +} + +// UnsafeRawKey implements MVCCIterator. +func (i *pointSynthesizingIter) UnsafeRawKey() []byte { + if i.atPoint { + return i.iter.UnsafeRawKey() + } + return EncodeMVCCKeyPrefix(i.rangeKeysPos) +} + +// UnsafeRawMVCCKey implements MVCCIterator. +func (i *pointSynthesizingIter) UnsafeRawMVCCKey() []byte { + if i.atPoint { + return i.iter.UnsafeRawMVCCKey() + } + return EncodeMVCCKey(i.UnsafeKey()) +} + +// Value implements MVCCIterator. +func (i *pointSynthesizingIter) Value() []byte { + if v := i.UnsafeValue(); v != nil { + return append([]byte(nil), v...) + } + return nil +} + +// UnsafeValue implements MVCCIterator. +func (i *pointSynthesizingIter) UnsafeValue() []byte { + if i.atPoint { + return i.iter.UnsafeValue() + } + return nil +} + +// ValueProto implements MVCCIterator. +func (i *pointSynthesizingIter) ValueProto(msg protoutil.Message) error { + if i.atPoint { + return i.iter.ValueProto(msg) + } + // Tombstones have no value, but ValueProto() also resets the message. + msg.Reset() + return nil +} + +// HasPointAndRange implements MVCCIterator. +func (i *pointSynthesizingIter) HasPointAndRange() (bool, bool) { + ok, err := i.Valid() + return ok && err == nil, false +} + +// RangeBounds implements MVCCIterator. +func (i *pointSynthesizingIter) RangeBounds() (roachpb.Key, roachpb.Key) { + return nil, nil +} + +// RangeKeys implements MVCCIterator. +func (i *pointSynthesizingIter) RangeKeys() []MVCCRangeKey { + return nil +} + +// Close implements MVCCIterator. +func (i *pointSynthesizingIter) Close() { + i.iter.Close() +} + +// ComputeStats implements MVCCIterator. +func (i *pointSynthesizingIter) ComputeStats( + start, end roachpb.Key, nowNanos int64, +) (enginepb.MVCCStats, error) { + return i.iter.ComputeStats(start, end, nowNanos) +} + +// FindSplitKey implements MVCCIterator. +func (i *pointSynthesizingIter) FindSplitKey( + start, end, minSplitKey roachpb.Key, targetSize int64, +) (MVCCKey, error) { + return i.iter.FindSplitKey(start, end, minSplitKey, targetSize) +} + +// Stats implements MVCCIterator. +func (i *pointSynthesizingIter) Stats() IteratorStats { + return i.iter.Stats() +} + +// SupportsPrev implements MVCCIterator. +func (i *pointSynthesizingIter) SupportsPrev() bool { + return i.iter.SupportsPrev() +} + +// assertInvariants asserts iterator invariants. +func (i *pointSynthesizingIter) assertInvariants() error { + // If the underlying iterator has errored, make sure we're not positioned on a + // synthetic point such that Valid() will surface the error. + if _, err := i.iter.Valid(); err != nil { + if !i.atPoint && i.rangeKeysIdx >= 0 && i.rangeKeysIdx < len(i.rangeKeys) { + return errors.NewAssertionErrorWithWrappedErrf(err, "iterator error with synthetic point %s", + i.rangeKeysPos) + } + return nil + } + + // When atPoint is true, the underlying iterator must be valid and on a point. + if i.atPoint { + if ok, _ := i.iter.Valid(); !ok { + return errors.AssertionFailedf("atPoint with invalid iter") + } + if hasPoint, _ := i.iter.HasPointAndRange(); !hasPoint { + return errors.AssertionFailedf("atPoint at non-point position %s", i.iter.UnsafeKey()) + } + } + + // rangeKeysIdx is never more than 1 outside of the slice bounds, and the + // excess depends on the direction: len(rangeKeys) in the forward direction, + // -1 in the reverse. + if i.rangeKeysIdx < 0 || i.rangeKeysIdx >= len(i.rangeKeys) { + if (!i.reverse && i.rangeKeysIdx != len(i.rangeKeys)) || (i.reverse && i.rangeKeysIdx != -1) { + return errors.AssertionFailedf("invalid rangeKeysIdx %d with length %d and reverse=%t", + i.rangeKeysIdx, len(i.rangeKeys), i.reverse) + } + } + + // If rangeKeys is empty, atPoint is true unless exhausted and other state is + // cleared. In this case, there's nothing more to check. + if len(i.rangeKeys) == 0 { + if ok, _ := i.iter.Valid(); ok && !i.atPoint { + return errors.AssertionFailedf("no rangeKeys nor atPoint") + } + if len(i.rangeKeysPos) > 0 { + return errors.AssertionFailedf("no rangeKeys but rangeKeysPos %s", i.rangeKeysPos) + } + if len(i.rangeKeysStart) > 0 { + return errors.AssertionFailedf("no rangeKeys but rangeKeysStart %s", i.rangeKeysStart) + } + return nil + } + + // rangeKeysStart must be set, and rangeKeysPos must be at or after it. This + // implies that rangeKeysPos must also be set. + if len(i.rangeKeysStart) == 0 { + return errors.AssertionFailedf("no rangeKeysStart at %s", i.iter.UnsafeKey()) + } + if i.rangeKeysPos.Compare(i.rangeKeysStart) < 0 { + return errors.AssertionFailedf("rangeKeysPos %s not after rangeKeysStart %s", + i.rangeKeysPos, i.rangeKeysStart) + } + + // rangeKeysIdx must be valid if we're not on a point. + if !i.atPoint && (i.rangeKeysIdx < 0 || i.rangeKeysIdx >= len(i.rangeKeys)) { + return errors.AssertionFailedf("not atPoint with invalid rangeKeysIdx %d at %s", + i.rangeKeysIdx, i.rangeKeysPos) + } + + // If the underlying iterator is exhausted, then there's nothing more to + // check. We must either be on a synthetic point key or exhausted iterator. + if ok, _ := i.iter.Valid(); !ok { + return nil + } + + // We now have range keys and a non-exhausted iterator. Check their relative + // positioning as minimum and maximum iter keys (in MVCC order). We can assume + // that overlapping range keys and point keys don't have the same timestamp, + // since this is enforced by MVCC mutations. + var minKey, maxKey MVCCKey + + // The iterator should never lag behind the range key position. + if !i.reverse { + minKey = MVCCKey{Key: i.rangeKeysPos} + } else { + maxKey = MVCCKey{Key: i.rangeKeysPos, Timestamp: hlc.MinTimestamp} + } + + // If we're not at a real point, then the iterator must be ahead of the + // current synthesized point. If we are on a point, then it must lie between + // the surrounding range keys (if they exist). + minIdx, maxIdx := -1, -1 + if !i.atPoint { + if !i.reverse { + minIdx = i.rangeKeysIdx + } else { + maxIdx = i.rangeKeysIdx + } + } else if !i.reverse { + minIdx = i.rangeKeysIdx - 1 + maxIdx = i.rangeKeysIdx + } else { + minIdx = i.rangeKeysIdx + maxIdx = i.rangeKeysIdx + 1 + } + if minIdx >= 0 && minIdx < len(i.rangeKeys) { + minKey = MVCCKey{Key: i.rangeKeysPos, Timestamp: i.rangeKeys[minIdx]} + } + if maxIdx >= 0 && maxIdx < len(i.rangeKeys) { + maxKey = MVCCKey{Key: i.rangeKeysPos, Timestamp: i.rangeKeys[maxIdx]} + } + + iterKey := i.iter.Key() + if minKey.Key != nil && iterKey.Compare(minKey) < 0 { + return errors.AssertionFailedf("iter %s below minimum key %s", iterKey, minKey) + } + if maxKey.Key != nil && iterKey.Compare(maxKey) > 0 { + return errors.AssertionFailedf("iter %s above maximum key %s", iterKey, maxKey) + } + + return nil +} diff --git a/pkg/storage/testdata/mvcc_histories/range_key_point_synthesis b/pkg/storage/testdata/mvcc_histories/range_key_point_synthesis new file mode 100644 index 000000000000..3b8c97243afd --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_key_point_synthesis @@ -0,0 +1,1042 @@ +# Tests pointSynthesizingIter. +# +# Sets up following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 7 [d7] [j7] +# 6 f6 +# 5 o---------------o k5 o-----------o +# 4 x x d4 f4 g4 +# 3 o-------o e3 o-------oh3 o---o +# 2 a2 f2 g2 +# 1 o-------------------o o-----------o +# a b c d e f g h i j k l m n o p +# +run ok +put_rangekey k=a end=f ts=1 +put_rangekey k=h end=k ts=1 +put_rangekey k=b end=d ts=3 +put_rangekey k=n end=o ts=3 +put_rangekey k=l end=o ts=5 +put k=a ts=2 v=a2 +del k=a ts=4 +del k=b ts=4 +put k=d ts=4 v=d4 +put k=e ts=3 v=e3 +put k=f ts=2 v=f2 +put k=g ts=2 v=g2 +put_rangekey k=f end=h ts=3 +put k=f ts=4 v=f4 +put k=f ts=6 v=f6 +put k=g ts=4 v=g4 +put_rangekey k=c end=g ts=5 +put k=h ts=3 v=h3 +put k=k ts=5 v=k5 +with t=A + txn_begin ts=7 + put k=d v=d7 + put k=j v=j7 +---- +>> at end: +txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=7.000000000,0 wto=false gul=0,0 +rangekey: {a-b}/[1.000000000,0] +rangekey: {b-c}/[3.000000000,0 1.000000000,0] +rangekey: {c-d}/[5.000000000,0 3.000000000,0 1.000000000,0] +rangekey: {d-f}/[5.000000000,0 1.000000000,0] +rangekey: {f-g}/[5.000000000,0 3.000000000,0] +rangekey: {g-h}/[3.000000000,0] +rangekey: {h-k}/[1.000000000,0] +rangekey: {l-n}/[5.000000000,0] +rangekey: {n-o}/[5.000000000,0 3.000000000,0] +data: "a"/4.000000000,0 -> / +data: "a"/2.000000000,0 -> /BYTES/a2 +data: "b"/4.000000000,0 -> / +meta: "d"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "d"/7.000000000,0 -> /BYTES/d7 +data: "d"/4.000000000,0 -> /BYTES/d4 +data: "e"/3.000000000,0 -> /BYTES/e3 +data: "f"/6.000000000,0 -> /BYTES/f6 +data: "f"/4.000000000,0 -> /BYTES/f4 +data: "f"/2.000000000,0 -> /BYTES/f2 +data: "g"/4.000000000,0 -> /BYTES/g4 +data: "g"/2.000000000,0 -> /BYTES/g2 +data: "h"/3.000000000,0 -> /BYTES/h3 +meta: "j"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "j"/7.000000000,0 -> /BYTES/j7 +data: "k"/5.000000000,0 -> /BYTES/k5 + +# Iterate across the entire span, forward and reverse. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=a +iter_scan +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_scan: "a"/4.000000000,0=/ +iter_scan: "a"/2.000000000,0=/BYTES/a2 +iter_scan: "a"/1.000000000,0=/ +iter_scan: "b"/4.000000000,0=/ +iter_scan: "b"/3.000000000,0=/ +iter_scan: "b"/1.000000000,0=/ +iter_scan: "c"/5.000000000,0=/ +iter_scan: "c"/3.000000000,0=/ +iter_scan: "c"/1.000000000,0=/ +iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "d"/7.000000000,0=/BYTES/d7 +iter_scan: "d"/5.000000000,0=/ +iter_scan: "d"/4.000000000,0=/BYTES/d4 +iter_scan: "d"/1.000000000,0=/ +iter_scan: "e"/5.000000000,0=/ +iter_scan: "e"/3.000000000,0=/BYTES/e3 +iter_scan: "e"/1.000000000,0=/ +iter_scan: "f"/6.000000000,0=/BYTES/f6 +iter_scan: "f"/5.000000000,0=/ +iter_scan: "f"/4.000000000,0=/BYTES/f4 +iter_scan: "f"/3.000000000,0=/ +iter_scan: "f"/2.000000000,0=/BYTES/f2 +iter_scan: "g"/4.000000000,0=/BYTES/g4 +iter_scan: "g"/3.000000000,0=/ +iter_scan: "g"/2.000000000,0=/BYTES/g2 +iter_scan: "h"/3.000000000,0=/BYTES/h3 +iter_scan: "h"/1.000000000,0=/ +iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "j"/7.000000000,0=/BYTES/j7 +iter_scan: "j"/1.000000000,0=/ +iter_scan: "k"/5.000000000,0=/BYTES/k5 +iter_scan: "l"/5.000000000,0=/ +iter_scan: "n"/5.000000000,0=/ +iter_scan: "n"/3.000000000,0=/ +iter_scan: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=z +iter_scan reverse +---- +iter_seek_lt: "n"/3.000000000,0=/ +iter_scan: "n"/3.000000000,0=/ +iter_scan: "n"/5.000000000,0=/ +iter_scan: "l"/5.000000000,0=/ +iter_scan: "k"/5.000000000,0=/BYTES/k5 +iter_scan: "j"/1.000000000,0=/ +iter_scan: "j"/7.000000000,0=/BYTES/j7 +iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "h"/1.000000000,0=/ +iter_scan: "h"/3.000000000,0=/BYTES/h3 +iter_scan: "g"/2.000000000,0=/BYTES/g2 +iter_scan: "g"/3.000000000,0=/ +iter_scan: "g"/4.000000000,0=/BYTES/g4 +iter_scan: "f"/2.000000000,0=/BYTES/f2 +iter_scan: "f"/3.000000000,0=/ +iter_scan: "f"/4.000000000,0=/BYTES/f4 +iter_scan: "f"/5.000000000,0=/ +iter_scan: "f"/6.000000000,0=/BYTES/f6 +iter_scan: "e"/1.000000000,0=/ +iter_scan: "e"/3.000000000,0=/BYTES/e3 +iter_scan: "e"/5.000000000,0=/ +iter_scan: "d"/1.000000000,0=/ +iter_scan: "d"/4.000000000,0=/BYTES/d4 +iter_scan: "d"/5.000000000,0=/ +iter_scan: "d"/7.000000000,0=/BYTES/d7 +iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "c"/1.000000000,0=/ +iter_scan: "c"/3.000000000,0=/ +iter_scan: "c"/5.000000000,0=/ +iter_scan: "b"/1.000000000,0=/ +iter_scan: "b"/3.000000000,0=/ +iter_scan: "b"/4.000000000,0=/ +iter_scan: "a"/1.000000000,0=/ +iter_scan: "a"/2.000000000,0=/BYTES/a2 +iter_scan: "a"/4.000000000,0=/ +iter_scan: . + +# Iterate across the entire span using NextKey(). +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=a +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +iter_next_key +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_next_key: "b"/4.000000000,0=/ +iter_next_key: "c"/5.000000000,0=/ +iter_next_key: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_next_key: "e"/5.000000000,0=/ +iter_next_key: "f"/6.000000000,0=/BYTES/f6 +iter_next_key: "g"/4.000000000,0=/BYTES/g4 +iter_next_key: "h"/3.000000000,0=/BYTES/h3 +iter_next_key: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_next_key: "k"/5.000000000,0=/BYTES/k5 +iter_next_key: "l"/5.000000000,0=/ +iter_next_key: "n"/5.000000000,0=/ +iter_next_key: . + +# Unversioned seeks. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=a +iter_seek_ge k=b +iter_seek_ge k=c +iter_seek_ge k=d +iter_seek_ge k=e +iter_seek_ge k=f +iter_seek_ge k=g +iter_seek_ge k=h +iter_seek_ge k=i +iter_seek_ge k=j +iter_seek_ge k=k +iter_seek_ge k=l +iter_seek_ge k=m +iter_seek_ge k=n +iter_seek_ge k=o +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_seek_ge: "b"/4.000000000,0=/ +iter_seek_ge: "c"/5.000000000,0=/ +iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "e"/5.000000000,0=/ +iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 +iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 +iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 +iter_seek_ge: "l"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: . + +run ok +iter_new types=pointsAndRanges pointSynthesis emitOnSeekGE +iter_seek_ge k=a +iter_seek_ge k=b +iter_seek_ge k=c +iter_seek_ge k=d +iter_seek_ge k=e +iter_seek_ge k=f +iter_seek_ge k=g +iter_seek_ge k=h +iter_seek_ge k=i +iter_seek_ge k=j +iter_seek_ge k=k +iter_seek_ge k=l +iter_seek_ge k=m +iter_seek_ge k=n +iter_seek_ge k=o +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_seek_ge: "b"/4.000000000,0=/ +iter_seek_ge: "c"/5.000000000,0=/ +iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "e"/5.000000000,0=/ +iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 +iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 +iter_seek_ge: "i"/1.000000000,0=/ +iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 +iter_seek_ge: "l"/5.000000000,0=/ +iter_seek_ge: "m"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=o +iter_seek_lt k=n +iter_seek_lt k=m +iter_seek_lt k=l +iter_seek_lt k=k +iter_seek_lt k=j +iter_seek_lt k=i +iter_seek_lt k=h +iter_seek_lt k=g +iter_seek_lt k=f +iter_seek_lt k=e +iter_seek_lt k=d +iter_seek_lt k=c +iter_seek_lt k=b +iter_seek_lt k=a +---- +iter_seek_lt: "n"/3.000000000,0=/ +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 +iter_seek_lt: "j"/1.000000000,0=/ +iter_seek_lt: "h"/1.000000000,0=/ +iter_seek_lt: "h"/1.000000000,0=/ +iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 +iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 +iter_seek_lt: "e"/1.000000000,0=/ +iter_seek_lt: "d"/1.000000000,0=/ +iter_seek_lt: "c"/1.000000000,0=/ +iter_seek_lt: "b"/1.000000000,0=/ +iter_seek_lt: "a"/1.000000000,0=/ +iter_seek_lt: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_intent_ge k=a txn=A +iter_seek_intent_ge k=b txn=A +iter_seek_intent_ge k=c txn=A +iter_seek_intent_ge k=d txn=A +iter_seek_intent_ge k=e txn=A +iter_seek_intent_ge k=f txn=A +iter_seek_intent_ge k=g txn=A +iter_seek_intent_ge k=h txn=A +iter_seek_intent_ge k=i txn=A +iter_seek_intent_ge k=j txn=A +iter_seek_intent_ge k=k txn=A +iter_seek_intent_ge k=l txn=A +iter_seek_intent_ge k=m txn=A +iter_seek_intent_ge k=n txn=A +iter_seek_intent_ge k=o txn=A +---- +iter_seek_intent_ge: "a"/4.000000000,0=/ +iter_seek_intent_ge: "b"/4.000000000,0=/ +iter_seek_intent_ge: "c"/5.000000000,0=/ +iter_seek_intent_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_intent_ge: "e"/5.000000000,0=/ +iter_seek_intent_ge: "f"/6.000000000,0=/BYTES/f6 +iter_seek_intent_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_intent_ge: "h"/3.000000000,0=/BYTES/h3 +iter_seek_intent_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_intent_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_intent_ge: "k"/5.000000000,0=/BYTES/k5 +iter_seek_intent_ge: "l"/5.000000000,0=/ +iter_seek_intent_ge: "n"/5.000000000,0=/ +iter_seek_intent_ge: "n"/5.000000000,0=/ +iter_seek_intent_ge: . + +run ok +iter_new types=pointsAndRanges pointSynthesis emitOnSeekGE +iter_seek_intent_ge k=a txn=A +iter_seek_intent_ge k=b txn=A +iter_seek_intent_ge k=c txn=A +iter_seek_intent_ge k=d txn=A +iter_seek_intent_ge k=e txn=A +iter_seek_intent_ge k=f txn=A +iter_seek_intent_ge k=g txn=A +iter_seek_intent_ge k=h txn=A +iter_seek_intent_ge k=i txn=A +iter_seek_intent_ge k=j txn=A +iter_seek_intent_ge k=k txn=A +iter_seek_intent_ge k=l txn=A +iter_seek_intent_ge k=m txn=A +iter_seek_intent_ge k=n txn=A +iter_seek_intent_ge k=o txn=A +---- +iter_seek_intent_ge: "a"/4.000000000,0=/ +iter_seek_intent_ge: "b"/4.000000000,0=/ +iter_seek_intent_ge: "c"/5.000000000,0=/ +iter_seek_intent_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_intent_ge: "e"/5.000000000,0=/ +iter_seek_intent_ge: "f"/6.000000000,0=/BYTES/f6 +iter_seek_intent_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_intent_ge: "h"/3.000000000,0=/BYTES/h3 +iter_seek_intent_ge: "i"/1.000000000,0=/ +iter_seek_intent_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_intent_ge: "k"/5.000000000,0=/BYTES/k5 +iter_seek_intent_ge: "l"/5.000000000,0=/ +iter_seek_intent_ge: "m"/5.000000000,0=/ +iter_seek_intent_ge: "n"/5.000000000,0=/ +iter_seek_intent_ge: . + +# Versioned seeks. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=a ts=5 +iter_seek_ge k=a ts=4 +iter_seek_ge k=a ts=3 +iter_seek_ge k=a ts=2 +iter_seek_ge k=a ts=1 +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_seek_ge: "a"/4.000000000,0=/ +iter_seek_ge: "a"/2.000000000,0=/BYTES/a2 +iter_seek_ge: "a"/2.000000000,0=/BYTES/a2 +iter_seek_ge: "a"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=b ts=5 +iter_seek_ge k=b ts=4 +iter_seek_ge k=b ts=3 +iter_seek_ge k=b ts=2 +iter_seek_ge k=b ts=1 +---- +iter_seek_ge: "b"/4.000000000,0=/ +iter_seek_ge: "b"/4.000000000,0=/ +iter_seek_ge: "b"/3.000000000,0=/ +iter_seek_ge: "b"/1.000000000,0=/ +iter_seek_ge: "b"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=c ts=6 +iter_seek_ge k=c ts=5 +iter_seek_ge k=c ts=4 +iter_seek_ge k=c ts=3 +iter_seek_ge k=c ts=2 +iter_seek_ge k=c ts=1 +---- +iter_seek_ge: "c"/5.000000000,0=/ +iter_seek_ge: "c"/5.000000000,0=/ +iter_seek_ge: "c"/3.000000000,0=/ +iter_seek_ge: "c"/3.000000000,0=/ +iter_seek_ge: "c"/1.000000000,0=/ +iter_seek_ge: "c"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=d ts=0 +iter_seek_ge k=d ts=8 +iter_seek_ge k=d ts=7 +iter_seek_ge k=d ts=6 +iter_seek_ge k=d ts=5 +iter_seek_ge k=d ts=4 +iter_seek_ge k=d ts=3 +iter_seek_ge k=d ts=2 +iter_seek_ge k=d ts=1 +---- +iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "d"/7.000000000,0=/BYTES/d7 +iter_seek_ge: "d"/7.000000000,0=/BYTES/d7 +iter_seek_ge: "d"/5.000000000,0=/ +iter_seek_ge: "d"/5.000000000,0=/ +iter_seek_ge: "d"/4.000000000,0=/BYTES/d4 +iter_seek_ge: "d"/1.000000000,0=/ +iter_seek_ge: "d"/1.000000000,0=/ +iter_seek_ge: "d"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=e ts=6 +iter_seek_ge k=e ts=5 +iter_seek_ge k=e ts=4 +iter_seek_ge k=e ts=3 +iter_seek_ge k=e ts=2 +iter_seek_ge k=e ts=1 +---- +iter_seek_ge: "e"/5.000000000,0=/ +iter_seek_ge: "e"/5.000000000,0=/ +iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 +iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 +iter_seek_ge: "e"/1.000000000,0=/ +iter_seek_ge: "e"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=f ts=7 +iter_seek_ge k=f ts=6 +iter_seek_ge k=f ts=5 +iter_seek_ge k=f ts=4 +iter_seek_ge k=f ts=3 +iter_seek_ge k=f ts=2 +iter_seek_ge k=f ts=1 +---- +iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 +iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 +iter_seek_ge: "f"/5.000000000,0=/ +iter_seek_ge: "f"/4.000000000,0=/BYTES/f4 +iter_seek_ge: "f"/3.000000000,0=/ +iter_seek_ge: "f"/2.000000000,0=/BYTES/f2 +iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=g ts=6 +iter_seek_ge k=g ts=5 +iter_seek_ge k=g ts=4 +iter_seek_ge k=g ts=3 +iter_seek_ge k=g ts=2 +iter_seek_ge k=g ts=1 +---- +iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 +iter_seek_ge: "g"/3.000000000,0=/ +iter_seek_ge: "g"/2.000000000,0=/BYTES/g2 +iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=h ts=4 +iter_seek_ge k=h ts=3 +iter_seek_ge k=h ts=2 +iter_seek_ge k=h ts=1 +---- +iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 +iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 +iter_seek_ge: "h"/1.000000000,0=/ +iter_seek_ge: "h"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=i ts=2 +iter_seek_ge k=i ts=1 +---- +iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=j ts=8 +iter_seek_ge k=j ts=7 +iter_seek_ge k=j ts=6 +iter_seek_ge k=j ts=1 +---- +iter_seek_ge: "j"/7.000000000,0=/BYTES/j7 +iter_seek_ge: "j"/7.000000000,0=/BYTES/j7 +iter_seek_ge: "j"/1.000000000,0=/ +iter_seek_ge: "j"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=k ts=6 +iter_seek_ge k=k ts=5 +iter_seek_ge k=k ts=4 +---- +iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 +iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 +iter_seek_ge: "l"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=l ts=6 +iter_seek_ge k=l ts=5 +iter_seek_ge k=l ts=4 +---- +iter_seek_ge: "l"/5.000000000,0=/ +iter_seek_ge: "l"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=m ts=6 +iter_seek_ge k=m ts=5 +iter_seek_ge k=m ts=4 +---- +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=n ts=6 +iter_seek_ge k=n ts=5 +iter_seek_ge k=n ts=4 +iter_seek_ge k=n ts=3 +iter_seek_ge k=n ts=2 +---- +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/3.000000000,0=/ +iter_seek_ge: "n"/3.000000000,0=/ +iter_seek_ge: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=o ts=6 +iter_seek_ge k=o ts=5 +iter_seek_ge k=o ts=4 +iter_seek_ge k=o ts=3 +---- +iter_seek_ge: . +iter_seek_ge: . +iter_seek_ge: . +iter_seek_ge: . + +# Versioned seeks with emitOnSeekGE. +run ok +iter_new types=pointsAndRanges pointSynthesis emitOnSeekGE +iter_seek_ge k=l ts=6 +iter_seek_ge k=l ts=5 +iter_seek_ge k=l ts=4 +---- +iter_seek_ge: "l"/5.000000000,0=/ +iter_seek_ge: "l"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis emitOnSeekGE +iter_seek_ge k=m ts=6 +iter_seek_ge k=m ts=5 +iter_seek_ge k=m ts=4 +---- +iter_seek_ge: "m"/5.000000000,0=/ +iter_seek_ge: "m"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis emitOnSeekGE +iter_seek_ge k=n ts=6 +iter_seek_ge k=n ts=5 +iter_seek_ge k=n ts=4 +iter_seek_ge k=n ts=3 +---- +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/5.000000000,0=/ +iter_seek_ge: "n"/3.000000000,0=/ +iter_seek_ge: "n"/3.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis emitOnSeekGE +iter_seek_ge k=o ts=6 +iter_seek_ge k=o ts=5 +iter_seek_ge k=o ts=4 +---- +iter_seek_ge: . +iter_seek_ge: . +iter_seek_ge: . + +# Versioned reverse seeks. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=a ts=1 +iter_seek_lt k=a ts=2 +iter_seek_lt k=a ts=3 +iter_seek_lt k=a ts=4 +iter_seek_lt k=a ts=5 +---- +iter_seek_lt: "a"/2.000000000,0=/BYTES/a2 +iter_seek_lt: "a"/4.000000000,0=/ +iter_seek_lt: "a"/4.000000000,0=/ +iter_seek_lt: . +iter_seek_lt: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=b ts=1 +iter_seek_lt k=b ts=2 +iter_seek_lt k=b ts=3 +iter_seek_lt k=b ts=4 +iter_seek_lt k=b ts=5 +---- +iter_seek_lt: "b"/3.000000000,0=/ +iter_seek_lt: "b"/3.000000000,0=/ +iter_seek_lt: "b"/4.000000000,0=/ +iter_seek_lt: "a"/1.000000000,0=/ +iter_seek_lt: "a"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=c ts=1 +iter_seek_lt k=c ts=2 +iter_seek_lt k=c ts=3 +iter_seek_lt k=c ts=4 +iter_seek_lt k=c ts=5 +iter_seek_lt k=c ts=6 +---- +iter_seek_lt: "c"/3.000000000,0=/ +iter_seek_lt: "c"/3.000000000,0=/ +iter_seek_lt: "c"/5.000000000,0=/ +iter_seek_lt: "c"/5.000000000,0=/ +iter_seek_lt: "b"/1.000000000,0=/ +iter_seek_lt: "b"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=d ts=1 +iter_seek_lt k=d ts=2 +iter_seek_lt k=d ts=3 +iter_seek_lt k=d ts=4 +iter_seek_lt k=d ts=5 +iter_seek_lt k=d ts=6 +iter_seek_lt k=d ts=7 +iter_seek_lt k=d ts=8 +---- +iter_seek_lt: "d"/4.000000000,0=/BYTES/d4 +iter_seek_lt: "d"/4.000000000,0=/BYTES/d4 +iter_seek_lt: "d"/4.000000000,0=/BYTES/d4 +iter_seek_lt: "d"/5.000000000,0=/ +iter_seek_lt: "d"/7.000000000,0=/BYTES/d7 +iter_seek_lt: "d"/7.000000000,0=/BYTES/d7 +iter_seek_lt: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_lt: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=e ts=1 +iter_seek_lt k=e ts=2 +iter_seek_lt k=e ts=3 +iter_seek_lt k=e ts=4 +iter_seek_lt k=e ts=5 +iter_seek_lt k=e ts=6 +---- +iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 +iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 +iter_seek_lt: "e"/5.000000000,0=/ +iter_seek_lt: "e"/5.000000000,0=/ +iter_seek_lt: "d"/1.000000000,0=/ +iter_seek_lt: "d"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=f ts=1 +iter_seek_lt k=f ts=2 +iter_seek_lt k=f ts=3 +iter_seek_lt k=f ts=4 +iter_seek_lt k=f ts=5 +iter_seek_lt k=f ts=6 +iter_seek_lt k=f ts=7 +---- +iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 +iter_seek_lt: "f"/3.000000000,0=/ +iter_seek_lt: "f"/4.000000000,0=/BYTES/f4 +iter_seek_lt: "f"/5.000000000,0=/ +iter_seek_lt: "f"/6.000000000,0=/BYTES/f6 +iter_seek_lt: "e"/1.000000000,0=/ +iter_seek_lt: "e"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=g ts=1 +iter_seek_lt k=g ts=2 +iter_seek_lt k=g ts=3 +iter_seek_lt k=g ts=4 +iter_seek_lt k=g ts=5 +iter_seek_lt k=g ts=6 +---- +iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 +iter_seek_lt: "g"/3.000000000,0=/ +iter_seek_lt: "g"/4.000000000,0=/BYTES/g4 +iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 +iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 +iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=h ts=1 +iter_seek_lt k=h ts=2 +iter_seek_lt k=h ts=3 +iter_seek_lt k=h ts=4 +---- +iter_seek_lt: "h"/3.000000000,0=/BYTES/h3 +iter_seek_lt: "h"/3.000000000,0=/BYTES/h3 +iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 +iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=i ts=1 +iter_seek_lt k=i ts=2 +---- +iter_seek_lt: "h"/1.000000000,0=/ +iter_seek_lt: "h"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=j ts=1 +iter_seek_lt k=j ts=6 +iter_seek_lt k=j ts=7 +iter_seek_lt k=j ts=8 +---- +iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 +iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 +iter_seek_lt: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_seek_lt: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=k ts=1 +iter_seek_lt k=k ts=4 +iter_seek_lt k=k ts=5 +iter_seek_lt k=k ts=6 +---- +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 +iter_seek_lt: "j"/1.000000000,0=/ +iter_seek_lt: "j"/1.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=l ts=4 +iter_seek_lt k=l ts=5 +iter_seek_lt k=l ts=6 +---- +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=l ts=4 +iter_seek_lt k=l ts=5 +iter_seek_lt k=l ts=6 +---- +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 +iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=m ts=4 +iter_seek_lt k=m ts=5 +iter_seek_lt k=m ts=6 +---- +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "l"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=n ts=2 +iter_seek_lt k=n ts=3 +iter_seek_lt k=n ts=4 +iter_seek_lt k=n ts=5 +iter_seek_lt k=n ts=6 +---- +iter_seek_lt: "n"/3.000000000,0=/ +iter_seek_lt: "n"/5.000000000,0=/ +iter_seek_lt: "n"/5.000000000,0=/ +iter_seek_lt: "l"/5.000000000,0=/ +iter_seek_lt: "l"/5.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=o ts=1 +---- +iter_seek_lt: "n"/3.000000000,0=/ + +# Seeks with opposite scans. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=d +iter_scan reverse +---- +iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "c"/1.000000000,0=/ +iter_scan: "c"/3.000000000,0=/ +iter_scan: "c"/5.000000000,0=/ +iter_scan: "b"/1.000000000,0=/ +iter_scan: "b"/3.000000000,0=/ +iter_scan: "b"/4.000000000,0=/ +iter_scan: "a"/1.000000000,0=/ +iter_scan: "a"/2.000000000,0=/BYTES/a2 +iter_scan: "a"/4.000000000,0=/ +iter_scan: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=d ts=5 +iter_scan reverse +---- +iter_seek_ge: "d"/5.000000000,0=/ +iter_scan: "d"/5.000000000,0=/ +iter_scan: "d"/7.000000000,0=/BYTES/d7 +iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "c"/1.000000000,0=/ +iter_scan: "c"/3.000000000,0=/ +iter_scan: "c"/5.000000000,0=/ +iter_scan: "b"/1.000000000,0=/ +iter_scan: "b"/3.000000000,0=/ +iter_scan: "b"/4.000000000,0=/ +iter_scan: "a"/1.000000000,0=/ +iter_scan: "a"/2.000000000,0=/BYTES/a2 +iter_scan: "a"/4.000000000,0=/ +iter_scan: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=g +iter_scan +---- +iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 +iter_scan: "f"/2.000000000,0=/BYTES/f2 +iter_scan: "g"/4.000000000,0=/BYTES/g4 +iter_scan: "g"/3.000000000,0=/ +iter_scan: "g"/2.000000000,0=/BYTES/g2 +iter_scan: "h"/3.000000000,0=/BYTES/h3 +iter_scan: "h"/1.000000000,0=/ +iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "j"/7.000000000,0=/BYTES/j7 +iter_scan: "j"/1.000000000,0=/ +iter_scan: "k"/5.000000000,0=/BYTES/k5 +iter_scan: "l"/5.000000000,0=/ +iter_scan: "n"/5.000000000,0=/ +iter_scan: "n"/3.000000000,0=/ +iter_scan: . + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=g ts=2 +iter_scan +---- +iter_seek_lt: "g"/3.000000000,0=/ +iter_scan: "g"/3.000000000,0=/ +iter_scan: "g"/2.000000000,0=/BYTES/g2 +iter_scan: "h"/3.000000000,0=/BYTES/h3 +iter_scan: "h"/1.000000000,0=/ +iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +iter_scan: "j"/7.000000000,0=/BYTES/j7 +iter_scan: "j"/1.000000000,0=/ +iter_scan: "k"/5.000000000,0=/BYTES/k5 +iter_scan: "l"/5.000000000,0=/ +iter_scan: "n"/5.000000000,0=/ +iter_scan: "n"/3.000000000,0=/ +iter_scan: . + +# Try some direction changes. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=e ts=4 +iter_prev +iter_next +iter_next +iter_prev +---- +iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 +iter_prev: "e"/5.000000000,0=/ +iter_next: "e"/3.000000000,0=/BYTES/e3 +iter_next: "e"/1.000000000,0=/ +iter_prev: "e"/3.000000000,0=/BYTES/e3 + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=e ts=4 +iter_next +iter_prev +iter_prev +iter_next +---- +iter_seek_lt: "e"/5.000000000,0=/ +iter_next: "e"/3.000000000,0=/BYTES/e3 +iter_prev: "e"/5.000000000,0=/ +iter_prev: "d"/1.000000000,0=/ +iter_next: "e"/5.000000000,0=/ + +run ok +iter_new kind=keys types=pointsAndRanges pointSynthesis +iter_seek_ge k=e ts=4 +iter_prev +iter_prev +iter_next_key +iter_next +iter_next_key +iter_prev +iter_prev +iter_next_key +iter_next +---- +iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 +iter_prev: "e"/5.000000000,0=/ +iter_prev: "d"/1.000000000,0=/ +iter_next_key: "e"/5.000000000,0=/ +iter_next: "e"/3.000000000,0=/BYTES/e3 +iter_next_key: "f"/6.000000000,0=/BYTES/f6 +iter_prev: "e"/1.000000000,0=/ +iter_prev: "e"/3.000000000,0=/BYTES/e3 +iter_next_key: "f"/6.000000000,0=/BYTES/f6 +iter_next: "f"/5.000000000,0=/ + +run ok +iter_new kind=keys types=pointsAndRanges pointSynthesis +iter_seek_ge k=k ts=4 +iter_next_key +iter_prev +iter_next_key +iter_next +iter_prev +---- +iter_seek_ge: "l"/5.000000000,0=/ +iter_next_key: "n"/5.000000000,0=/ +iter_prev: "l"/5.000000000,0=/ +iter_next_key: "n"/5.000000000,0=/ +iter_next: "n"/3.000000000,0=/ +iter_prev: "n"/5.000000000,0=/ + +run ok +iter_new kind=keys types=pointsAndRanges pointSynthesis +iter_seek_ge k=e ts=3 +iter_prev +iter_next_key +---- +iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 +iter_prev: "e"/5.000000000,0=/ +iter_next_key: "f"/6.000000000,0=/BYTES/f6 + +# Exhausting the iterator then reversing should work in both directions, +# both after a seek and after a step. +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=a +iter_next +---- +iter_seek_lt: . +iter_next: "a"/4.000000000,0=/ + +run ok +iter_new kind=keys types=pointsAndRanges pointSynthesis +iter_seek_lt k=a +iter_next_key +---- +iter_seek_lt: . +iter_next_key: "a"/4.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=z +iter_prev +---- +iter_seek_ge: . +iter_prev: "n"/3.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_lt k=z +iter_next +iter_next +iter_prev +---- +iter_seek_lt: "n"/3.000000000,0=/ +iter_next: . +iter_next: . +iter_prev: "n"/3.000000000,0=/ + +run ok +iter_new kind=keys types=pointsAndRanges pointSynthesis +iter_seek_lt k=z +iter_next_key +iter_next_key +iter_prev +---- +iter_seek_lt: "n"/3.000000000,0=/ +iter_next_key: . +iter_next_key: . +iter_prev: "n"/3.000000000,0=/ + +run ok +iter_new types=pointsAndRanges pointSynthesis +iter_seek_ge k=a +iter_prev +iter_prev +iter_next +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_prev: . +iter_prev: . +iter_next: "a"/4.000000000,0=/ + +run ok +iter_new kind=keys types=pointsAndRanges pointSynthesis +iter_seek_ge k=a +iter_prev +iter_prev +iter_next_key +---- +iter_seek_ge: "a"/4.000000000,0=/ +iter_prev: . +iter_prev: . +iter_next_key: "a"/4.000000000,0=/ diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets new file mode 100644 index 000000000000..c02913612b86 --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets @@ -0,0 +1,305 @@ +# Tests MVCC gets across range tombstones. +# +# Sets up following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 6 [e6] +# 5 f5 +# 4 o-----------------------o +# 3 x d3 f3 +# 2 o---------------o h2 +# 1 a1 x c1 f1 +# a b c d e f g h i +# +run ok +put k=a ts=1 v=a1 +del k=b ts=1 +put k=c ts=1 v=c1 +put k=f ts=1 v=f1 +del_range_ts k=a end=e ts=2 +del k=a ts=3 +put k=d ts=3 v=d3 +put k=f ts=3 v=f3 +put k=h ts=2 v=h2 +del_range_ts k=c end=i ts=4 +put k=f ts=5 v=f5 +with t=A + txn_begin ts=6 + put k=e v=e6 +---- +>> at end: +txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=6.000000000,0 wto=false gul=0,0 +rangekey: {a-c}/[2.000000000,0] +rangekey: {c-e}/[4.000000000,0 2.000000000,0] +rangekey: {e-i}/[4.000000000,0] +data: "a"/3.000000000,0 -> / +data: "a"/1.000000000,0 -> /BYTES/a1 +data: "b"/1.000000000,0 -> / +data: "c"/1.000000000,0 -> /BYTES/c1 +data: "d"/3.000000000,0 -> /BYTES/d3 +meta: "e"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} ts=6.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "e"/6.000000000,0 -> /BYTES/e6 +data: "f"/5.000000000,0 -> /BYTES/f5 +data: "f"/3.000000000,0 -> /BYTES/f3 +data: "f"/1.000000000,0 -> /BYTES/f1 +data: "h"/2.000000000,0 -> /BYTES/h2 + +# Run gets for all keys and all timestamps. +run ok +get k=a ts=1 +get k=a ts=2 +get k=a ts=3 +get k=a ts=1 tombstones +get k=a ts=2 tombstones +get k=a ts=3 tombstones +---- +get: "a" -> /BYTES/a1 @1.000000000,0 +get: "a" -> +get: "a" -> +get: "a" -> /BYTES/a1 @1.000000000,0 +get: "a" -> / @2.000000000,0 +get: "a" -> / @3.000000000,0 + +run ok +get k=b ts=1 +get k=b ts=2 +get k=b ts=3 +get k=b ts=1 tombstones +get k=b ts=2 tombstones +get k=b ts=3 tombstones +---- +get: "b" -> +get: "b" -> +get: "b" -> +get: "b" -> / @1.000000000,0 +get: "b" -> / @2.000000000,0 +get: "b" -> / @2.000000000,0 + +run ok +get k=c ts=1 +get k=c ts=2 +get k=c ts=3 +get k=c ts=4 +get k=c ts=5 +get k=c ts=1 tombstones +get k=c ts=2 tombstones +get k=c ts=3 tombstones +get k=c ts=4 tombstones +get k=c ts=5 tombstones +---- +get: "c" -> /BYTES/c1 @1.000000000,0 +get: "c" -> +get: "c" -> +get: "c" -> +get: "c" -> +get: "c" -> /BYTES/c1 @1.000000000,0 +get: "c" -> / @2.000000000,0 +get: "c" -> / @2.000000000,0 +get: "c" -> / @4.000000000,0 +get: "c" -> / @4.000000000,0 + +run ok +get k=d ts=1 +get k=d ts=2 +get k=d ts=3 +get k=d ts=4 +get k=d ts=5 +get k=d ts=1 tombstones +get k=d ts=2 tombstones +get k=d ts=3 tombstones +get k=d ts=4 tombstones +get k=d ts=5 tombstones +---- +get: "d" -> +get: "d" -> +get: "d" -> /BYTES/d3 @3.000000000,0 +get: "d" -> +get: "d" -> +get: "d" -> +get: "d" -> / @2.000000000,0 +get: "d" -> /BYTES/d3 @3.000000000,0 +get: "d" -> / @4.000000000,0 +get: "d" -> / @4.000000000,0 + +run ok +get k=e ts=3 inconsistent +get k=e ts=4 inconsistent +get k=e ts=5 inconsistent +get k=e ts=6 inconsistent +get k=e ts=3 tombstones inconsistent +get k=e ts=4 tombstones inconsistent +get k=e ts=5 tombstones inconsistent +get k=e ts=6 tombstones inconsistent +---- +get: "e" -> +get: "e" -> +get: "e" -> +get: "e" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +get: "e" -> +get: "e" -> +get: "e" -> / @4.000000000,0 +get: "e" -> / @4.000000000,0 +get: "e" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +get: "e" -> / @4.000000000,0 + +run ok +get k=g ts=3 +get k=g ts=4 +get k=g ts=5 +get k=g ts=3 tombstones +get k=g ts=4 tombstones +get k=g ts=5 tombstones +---- +get: "g" -> +get: "g" -> +get: "g" -> +get: "g" -> +get: "g" -> / @4.000000000,0 +get: "g" -> / @4.000000000,0 + +run ok +get k=h ts=1 +get k=h ts=2 +get k=h ts=3 +get k=h ts=4 +get k=h ts=5 +get k=h ts=1 tombstones +get k=h ts=2 tombstones +get k=h ts=3 tombstones +get k=h ts=4 tombstones +get k=h ts=5 tombstones +---- +get: "h" -> +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> +get: "h" -> +get: "h" -> +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> / @4.000000000,0 +get: "h" -> / @4.000000000,0 + +# failOnMoreRecent: c +run error +get k=c ts=1 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 1.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=c ts=2 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 2.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=c ts=3 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=c ts=4 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +get k=c ts=5 failOnMoreRecent +---- +get: "c" -> + +# failOnMoreRecent: e +run error +get k=e ts=3 failOnMoreRecent +---- +get: "e" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +get k=e ts=4 failOnMoreRecent +---- +get: "e" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +get k=e ts=5 failOnMoreRecent +---- +get: "e" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +# failOnMoreRecent: g +run error +get k=g ts=3 failOnMoreRecent +---- +get: "g" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=g ts=4 failOnMoreRecent +---- +get: "g" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +get k=g ts=5 failOnMoreRecent +---- +get: "g" -> + + +# globalUncertaintyLimit: b-d +run ok +get k=g ts=3 globalUncertaintyLimit=3 +---- +get: "g" -> + +run error +get k=g ts=3 globalUncertaintyLimit=4 +---- +get: "g" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 3.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +get k=g ts=4 globalUncertaintyLimit=5 +---- +get: "g" -> + +# globalUncertaintyLimit: g +run ok +get k=g ts=1 globalUncertaintyLimit=1 +---- +get: "g" -> + +run ok +get k=g ts=1 globalUncertaintyLimit=3 +---- +get: "g" -> + +run error +get k=g ts=1 globalUncertaintyLimit=4 +---- +get: "g" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +get k=g ts=4 globalUncertaintyLimit=5 +---- +get: "g" -> + +# globalUncertaintyLimit: h +run ok +get k=h ts=2 globalUncertaintyLimit=2 +---- +get: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +get k=h ts=2 globalUncertaintyLimit=3 +---- +get: "h" -> /BYTES/h2 @2.000000000,0 + +run error +get k=h ts=2 globalUncertaintyLimit=4 +---- +get: "h" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 2.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans new file mode 100644 index 000000000000..ca4b5b0d29bc --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans @@ -0,0 +1,287 @@ +# Tests MVCC scans across range tombstones. +# +# Sets up following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 6 [e6] +# 5 f5 +# 4 o-----------------------o +# 3 x d3 f3 +# 2 o---------------o h2 +# 1 a1 x c1 f1 +# a b c d e f g h i +# +run ok +put k=a ts=1 v=a1 +del k=b ts=1 +put k=c ts=1 v=c1 +put k=f ts=1 v=f1 +del_range_ts k=a end=e ts=2 +del k=a ts=3 +put k=d ts=3 v=d3 +put k=f ts=3 v=f3 +put k=h ts=2 v=h2 +del_range_ts k=c end=i ts=4 +put k=f ts=5 v=f5 +with t=A + txn_begin ts=6 + put k=e v=e6 +---- +>> at end: +txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=6.000000000,0 wto=false gul=0,0 +rangekey: {a-c}/[2.000000000,0] +rangekey: {c-e}/[4.000000000,0 2.000000000,0] +rangekey: {e-i}/[4.000000000,0] +data: "a"/3.000000000,0 -> / +data: "a"/1.000000000,0 -> /BYTES/a1 +data: "b"/1.000000000,0 -> / +data: "c"/1.000000000,0 -> /BYTES/c1 +data: "d"/3.000000000,0 -> /BYTES/d3 +meta: "e"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} ts=6.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "e"/6.000000000,0 -> /BYTES/e6 +data: "f"/5.000000000,0 -> /BYTES/f5 +data: "f"/3.000000000,0 -> /BYTES/f3 +data: "f"/1.000000000,0 -> /BYTES/f1 +data: "h"/2.000000000,0 -> /BYTES/h2 + +# Run non-tombstone scans at all timestamps. +run ok +scan k=a end=z ts=1 +---- +scan: "a" -> /BYTES/a1 @1.000000000,0 +scan: "c" -> /BYTES/c1 @1.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 + +run ok +scan k=a end=z ts=2 +---- +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 +---- +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=4 +---- +scan: "a"-"z" -> + +run ok +scan k=a end=z ts=5 +---- +scan: "f" -> /BYTES/f5 @5.000000000,0 + +run ok +scan k=a end=z ts=6 inconsistent +---- +scan: "a" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +scan: "f" -> /BYTES/f5 @5.000000000,0 + +# Run tombstone scans at all timestamps. +run ok +scan k=a end=z ts=1 tombstones +---- +scan: "a" -> /BYTES/a1 @1.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "c" -> /BYTES/c1 @1.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 + +run ok +scan k=a end=z ts=2 tombstones +---- +scan: "a" -> / @2.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "d" -> / @2.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 tombstones +---- +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=4 tombstones +---- +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @4.000000000,0 +scan: "d" -> / @4.000000000,0 +scan: "e" -> / @4.000000000,0 +scan: "f" -> / @4.000000000,0 +scan: "h" -> / @4.000000000,0 + +run ok +scan k=a end=z ts=5 tombstones +---- +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @4.000000000,0 +scan: "d" -> / @4.000000000,0 +scan: "e" -> / @4.000000000,0 +scan: "f" -> /BYTES/f5 @5.000000000,0 +scan: "h" -> / @4.000000000,0 + +run ok +scan k=a end=z ts=6 tombstones inconsistent +---- +scan: "a" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @4.000000000,0 +scan: "d" -> / @4.000000000,0 +scan: "e" -> / @4.000000000,0 +scan: "f" -> /BYTES/f5 @5.000000000,0 +scan: "h" -> / @4.000000000,0 + +# failOnMoreRecent: a-d +run error +scan k=a end=d ts=3 failOnMoreRecent +---- +scan: "a"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "a" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=a end=d ts=4 failOnMoreRecent +---- +scan: "a"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +scan k=a end=d ts=5 failOnMoreRecent +---- +scan: "a"-"d" -> + +# failOnMoreRecent: c-d +run error +scan k=c end=d ts=1 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 1.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=c end=d ts=2 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 2.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=c end=d ts=3 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=c end=d ts=4 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +scan k=c end=d ts=5 failOnMoreRecent +---- +scan: "c"-"d" -> + +# failOnMoreRecent: e-f +run error +scan k=e end=f ts=3 failOnMoreRecent +---- +scan: "e"-"f" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +scan k=e end=f ts=4 failOnMoreRecent +---- +scan: "e"-"f" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +scan k=e end=f ts=5 failOnMoreRecent +---- +scan: "e"-"f" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +# failOnMoreRecent: g-h +run error +scan k=g end=h ts=3 failOnMoreRecent +---- +scan: "g"-"h" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=g end=h ts=4 failOnMoreRecent +---- +scan: "g"-"h" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +scan k=g end=h ts=5 failOnMoreRecent +---- +scan: "g"-"h" -> + + +# globalUncertaintyLimit: b-d +run ok +scan k=g end=h ts=3 globalUncertaintyLimit=3 +---- +scan: "g"-"h" -> + +run error +scan k=g end=h ts=3 globalUncertaintyLimit=4 +---- +scan: "g"-"h" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 3.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +scan k=g end=h ts=4 globalUncertaintyLimit=5 +---- +scan: "g"-"h" -> + +# globalUncertaintyLimit: g-h +run ok +scan k=g end=h ts=1 globalUncertaintyLimit=1 +---- +scan: "g"-"h" -> + +run ok +scan k=g end=h ts=1 globalUncertaintyLimit=3 +---- +scan: "g"-"h" -> + +run error +scan k=g end=h ts=1 globalUncertaintyLimit=4 +---- +scan: "g"-"h" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +scan k=g end=h ts=4 globalUncertaintyLimit=5 +---- +scan: "g"-"h" -> + +# globalUncertaintyLimit: h-i +run ok +scan k=h end=i ts=2 globalUncertaintyLimit=2 +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=h end=i ts=2 globalUncertaintyLimit=3 +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run error +scan k=h end=i ts=2 globalUncertaintyLimit=4 +---- +scan: "h"-"i" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 2.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: []