github.com/cockroachdb/pebble@v1.1.2/metamorphic/ops.go (about)

     1  // Copyright 2019 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 metamorphic
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rand"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"io"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/cockroachdb/errors"
    17  	"github.com/cockroachdb/pebble"
    18  	"github.com/cockroachdb/pebble/internal/base"
    19  	"github.com/cockroachdb/pebble/internal/keyspan"
    20  	"github.com/cockroachdb/pebble/internal/private"
    21  	"github.com/cockroachdb/pebble/internal/testkeys"
    22  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider"
    23  	"github.com/cockroachdb/pebble/sstable"
    24  	"github.com/cockroachdb/pebble/vfs"
    25  	"github.com/cockroachdb/pebble/vfs/errorfs"
    26  )
    27  
    28  // op defines the interface for a single operation, such as creating a batch,
    29  // or advancing an iterator.
    30  type op interface {
    31  	String() string
    32  	run(t *test, h historyRecorder)
    33  
    34  	// receiver returns the object ID of the object the operation is performed
    35  	// on. Every operation has a receiver (eg, batch0.Set(...) has `batch0` as
    36  	// its receiver). Receivers are used for synchronization when running with
    37  	// concurrency.
    38  	receiver() objID
    39  
    40  	// syncObjs returns an additional set of object IDs—excluding the
    41  	// receiver—that the operation must synchronize with. At execution time,
    42  	// the operation will run serially with respect to all other operations
    43  	// that return these objects from their own syncObjs or receiver methods.
    44  	syncObjs() objIDSlice
    45  }
    46  
    47  // initOp performs test initialization
    48  type initOp struct {
    49  	batchSlots    uint32
    50  	iterSlots     uint32
    51  	snapshotSlots uint32
    52  }
    53  
    54  func (o *initOp) run(t *test, h historyRecorder) {
    55  	t.batches = make([]*pebble.Batch, o.batchSlots)
    56  	t.iters = make([]*retryableIter, o.iterSlots)
    57  	t.snapshots = make([]readerCloser, o.snapshotSlots)
    58  	h.Recordf("%s", o)
    59  }
    60  
    61  func (o *initOp) String() string {
    62  	return fmt.Sprintf("Init(%d /* batches */, %d /* iters */, %d /* snapshots */)",
    63  		o.batchSlots, o.iterSlots, o.snapshotSlots)
    64  }
    65  
    66  func (o *initOp) receiver() objID      { return dbObjID }
    67  func (o *initOp) syncObjs() objIDSlice { return nil }
    68  
    69  // applyOp models a Writer.Apply operation.
    70  type applyOp struct {
    71  	writerID objID
    72  	batchID  objID
    73  }
    74  
    75  func (o *applyOp) run(t *test, h historyRecorder) {
    76  	b := t.getBatch(o.batchID)
    77  	w := t.getWriter(o.writerID)
    78  	var err error
    79  	if o.writerID.tag() == dbTag && t.testOpts.asyncApplyToDB && t.writeOpts.Sync {
    80  		err = w.(*pebble.DB).ApplyNoSyncWait(b, t.writeOpts)
    81  		if err == nil {
    82  			err = b.SyncWait()
    83  		}
    84  	} else {
    85  		err = w.Apply(b, t.writeOpts)
    86  	}
    87  	h.Recordf("%s // %v", o, err)
    88  	// batch will be closed by a closeOp which is guaranteed to be generated
    89  }
    90  
    91  func (o *applyOp) String() string  { return fmt.Sprintf("%s.Apply(%s)", o.writerID, o.batchID) }
    92  func (o *applyOp) receiver() objID { return o.writerID }
    93  func (o *applyOp) syncObjs() objIDSlice {
    94  	// Apply should not be concurrent with operations that are mutating the
    95  	// batch.
    96  	return []objID{o.batchID}
    97  }
    98  
    99  // checkpointOp models a DB.Checkpoint operation.
   100  type checkpointOp struct {
   101  	// If non-empty, the checkpoint is restricted to these spans.
   102  	spans []pebble.CheckpointSpan
   103  }
   104  
   105  func (o *checkpointOp) run(t *test, h historyRecorder) {
   106  	// TODO(josh): db.Checkpoint does not work with shared storage yet.
   107  	// It would be better to filter out ahead of calling run on the op,
   108  	// by setting the weight that generator.go uses to zero, or similar.
   109  	// But IIUC the ops are shared for ALL the metamorphic test runs, so
   110  	// not sure how to do that easily:
   111  	// https://github.com/cockroachdb/pebble/blob/master/metamorphic/meta.go#L177
   112  	if t.testOpts.sharedStorageEnabled {
   113  		h.Recordf("%s // %v", o, nil)
   114  		return
   115  	}
   116  	var opts []pebble.CheckpointOption
   117  	if len(o.spans) > 0 {
   118  		opts = append(opts, pebble.WithRestrictToSpans(o.spans))
   119  	}
   120  	err := withRetries(func() error {
   121  		return t.db.Checkpoint(o.dir(t.dir, h.op), opts...)
   122  	})
   123  	h.Recordf("%s // %v", o, err)
   124  }
   125  
   126  func (o *checkpointOp) dir(dataDir string, idx int) string {
   127  	return filepath.Join(dataDir, "checkpoints", fmt.Sprintf("op-%06d", idx))
   128  }
   129  
   130  func (o *checkpointOp) String() string {
   131  	var spanStr bytes.Buffer
   132  	for i, span := range o.spans {
   133  		if i > 0 {
   134  			spanStr.WriteString(",")
   135  		}
   136  		fmt.Fprintf(&spanStr, "%q,%q", span.Start, span.End)
   137  	}
   138  	return fmt.Sprintf("db.Checkpoint(%s)", spanStr.String())
   139  }
   140  
   141  func (o *checkpointOp) receiver() objID      { return dbObjID }
   142  func (o *checkpointOp) syncObjs() objIDSlice { return nil }
   143  
   144  // closeOp models a {Batch,Iterator,Snapshot}.Close operation.
   145  type closeOp struct {
   146  	objID objID
   147  }
   148  
   149  func (o *closeOp) run(t *test, h historyRecorder) {
   150  	c := t.getCloser(o.objID)
   151  	if o.objID.tag() == dbTag && t.opts.DisableWAL {
   152  		// Special case: If WAL is disabled, do a flush right before DB Close. This
   153  		// allows us to reuse this run's data directory as initial state for
   154  		// future runs without losing any mutations.
   155  		_ = t.db.Flush()
   156  	}
   157  	t.clearObj(o.objID)
   158  	err := c.Close()
   159  	h.Recordf("%s // %v", o, err)
   160  }
   161  
   162  func (o *closeOp) String() string  { return fmt.Sprintf("%s.Close()", o.objID) }
   163  func (o *closeOp) receiver() objID { return o.objID }
   164  func (o *closeOp) syncObjs() objIDSlice {
   165  	// Synchronize on the database so that we don't close the database before
   166  	// all its iterators, snapshots and batches are closed.
   167  	// TODO(jackson): It would be nice to relax this so that Close calls can
   168  	// execute in parallel.
   169  	if o.objID == dbObjID {
   170  		return nil
   171  	}
   172  	return []objID{dbObjID}
   173  }
   174  
   175  // compactOp models a DB.Compact operation.
   176  type compactOp struct {
   177  	start       []byte
   178  	end         []byte
   179  	parallelize bool
   180  }
   181  
   182  func (o *compactOp) run(t *test, h historyRecorder) {
   183  	err := withRetries(func() error {
   184  		return t.db.Compact(o.start, o.end, o.parallelize)
   185  	})
   186  	h.Recordf("%s // %v", o, err)
   187  }
   188  
   189  func (o *compactOp) String() string {
   190  	return fmt.Sprintf("db.Compact(%q, %q, %t /* parallelize */)", o.start, o.end, o.parallelize)
   191  }
   192  
   193  func (o *compactOp) receiver() objID      { return dbObjID }
   194  func (o *compactOp) syncObjs() objIDSlice { return nil }
   195  
   196  // deleteOp models a Write.Delete operation.
   197  type deleteOp struct {
   198  	writerID objID
   199  	key      []byte
   200  }
   201  
   202  func (o *deleteOp) run(t *test, h historyRecorder) {
   203  	w := t.getWriter(o.writerID)
   204  	var err error
   205  	if t.testOpts.deleteSized && t.isFMV(pebble.FormatDeleteSizedAndObsolete) {
   206  		// Call DeleteSized with a deterministic size derived from the index.
   207  		// The size does not need to be accurate for correctness.
   208  		err = w.DeleteSized(o.key, hashSize(t.idx), t.writeOpts)
   209  	} else {
   210  		err = w.Delete(o.key, t.writeOpts)
   211  	}
   212  	h.Recordf("%s // %v", o, err)
   213  }
   214  
   215  func hashSize(index int) uint32 {
   216  	// Fibonacci hash https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
   217  	return uint32((11400714819323198485 * uint64(index)) % maxValueSize)
   218  }
   219  
   220  func (o *deleteOp) String() string {
   221  	return fmt.Sprintf("%s.Delete(%q)", o.writerID, o.key)
   222  }
   223  func (o *deleteOp) receiver() objID      { return o.writerID }
   224  func (o *deleteOp) syncObjs() objIDSlice { return nil }
   225  
   226  // singleDeleteOp models a Write.SingleDelete operation.
   227  type singleDeleteOp struct {
   228  	writerID           objID
   229  	key                []byte
   230  	maybeReplaceDelete bool
   231  }
   232  
   233  func (o *singleDeleteOp) run(t *test, h historyRecorder) {
   234  	w := t.getWriter(o.writerID)
   235  	var err error
   236  	if t.testOpts.replaceSingleDelete && o.maybeReplaceDelete {
   237  		err = w.Delete(o.key, t.writeOpts)
   238  	} else {
   239  		err = w.SingleDelete(o.key, t.writeOpts)
   240  	}
   241  	// NOTE: even if the SINGLEDEL was replaced with a DELETE, we must still
   242  	// write the former to the history log. The log line will indicate whether
   243  	// or not the delete *could* have been replaced. The OPTIONS file should
   244  	// also be consulted to determine what happened at runtime (i.e. by taking
   245  	// the logical AND).
   246  	h.Recordf("%s // %v", o, err)
   247  }
   248  
   249  func (o *singleDeleteOp) String() string {
   250  	return fmt.Sprintf("%s.SingleDelete(%q, %v /* maybeReplaceDelete */)", o.writerID, o.key, o.maybeReplaceDelete)
   251  }
   252  
   253  func (o *singleDeleteOp) receiver() objID      { return o.writerID }
   254  func (o *singleDeleteOp) syncObjs() objIDSlice { return nil }
   255  
   256  // deleteRangeOp models a Write.DeleteRange operation.
   257  type deleteRangeOp struct {
   258  	writerID objID
   259  	start    []byte
   260  	end      []byte
   261  }
   262  
   263  func (o *deleteRangeOp) run(t *test, h historyRecorder) {
   264  	w := t.getWriter(o.writerID)
   265  	err := w.DeleteRange(o.start, o.end, t.writeOpts)
   266  	h.Recordf("%s // %v", o, err)
   267  }
   268  
   269  func (o *deleteRangeOp) String() string {
   270  	return fmt.Sprintf("%s.DeleteRange(%q, %q)", o.writerID, o.start, o.end)
   271  }
   272  
   273  func (o *deleteRangeOp) receiver() objID      { return o.writerID }
   274  func (o *deleteRangeOp) syncObjs() objIDSlice { return nil }
   275  
   276  // flushOp models a DB.Flush operation.
   277  type flushOp struct {
   278  }
   279  
   280  func (o *flushOp) run(t *test, h historyRecorder) {
   281  	err := t.db.Flush()
   282  	h.Recordf("%s // %v", o, err)
   283  }
   284  
   285  func (o *flushOp) String() string       { return "db.Flush()" }
   286  func (o *flushOp) receiver() objID      { return dbObjID }
   287  func (o *flushOp) syncObjs() objIDSlice { return nil }
   288  
   289  // mergeOp models a Write.Merge operation.
   290  type mergeOp struct {
   291  	writerID objID
   292  	key      []byte
   293  	value    []byte
   294  }
   295  
   296  func (o *mergeOp) run(t *test, h historyRecorder) {
   297  	w := t.getWriter(o.writerID)
   298  	err := w.Merge(o.key, o.value, t.writeOpts)
   299  	h.Recordf("%s // %v", o, err)
   300  }
   301  
   302  func (o *mergeOp) String() string       { return fmt.Sprintf("%s.Merge(%q, %q)", o.writerID, o.key, o.value) }
   303  func (o *mergeOp) receiver() objID      { return o.writerID }
   304  func (o *mergeOp) syncObjs() objIDSlice { return nil }
   305  
   306  // setOp models a Write.Set operation.
   307  type setOp struct {
   308  	writerID objID
   309  	key      []byte
   310  	value    []byte
   311  }
   312  
   313  func (o *setOp) run(t *test, h historyRecorder) {
   314  	w := t.getWriter(o.writerID)
   315  	err := w.Set(o.key, o.value, t.writeOpts)
   316  	h.Recordf("%s // %v", o, err)
   317  }
   318  
   319  func (o *setOp) String() string       { return fmt.Sprintf("%s.Set(%q, %q)", o.writerID, o.key, o.value) }
   320  func (o *setOp) receiver() objID      { return o.writerID }
   321  func (o *setOp) syncObjs() objIDSlice { return nil }
   322  
   323  // rangeKeyDeleteOp models a Write.RangeKeyDelete operation.
   324  type rangeKeyDeleteOp struct {
   325  	writerID objID
   326  	start    []byte
   327  	end      []byte
   328  }
   329  
   330  func (o *rangeKeyDeleteOp) run(t *test, h historyRecorder) {
   331  	w := t.getWriter(o.writerID)
   332  	err := w.RangeKeyDelete(o.start, o.end, t.writeOpts)
   333  	h.Recordf("%s // %v", o, err)
   334  }
   335  
   336  func (o *rangeKeyDeleteOp) String() string {
   337  	return fmt.Sprintf("%s.RangeKeyDelete(%q, %q)", o.writerID, o.start, o.end)
   338  }
   339  
   340  func (o *rangeKeyDeleteOp) receiver() objID      { return o.writerID }
   341  func (o *rangeKeyDeleteOp) syncObjs() objIDSlice { return nil }
   342  
   343  // rangeKeySetOp models a Write.RangeKeySet operation.
   344  type rangeKeySetOp struct {
   345  	writerID objID
   346  	start    []byte
   347  	end      []byte
   348  	suffix   []byte
   349  	value    []byte
   350  }
   351  
   352  func (o *rangeKeySetOp) run(t *test, h historyRecorder) {
   353  	w := t.getWriter(o.writerID)
   354  	err := w.RangeKeySet(o.start, o.end, o.suffix, o.value, t.writeOpts)
   355  	h.Recordf("%s // %v", o, err)
   356  }
   357  
   358  func (o *rangeKeySetOp) String() string {
   359  	return fmt.Sprintf("%s.RangeKeySet(%q, %q, %q, %q)",
   360  		o.writerID, o.start, o.end, o.suffix, o.value)
   361  }
   362  
   363  func (o *rangeKeySetOp) receiver() objID      { return o.writerID }
   364  func (o *rangeKeySetOp) syncObjs() objIDSlice { return nil }
   365  
   366  // rangeKeyUnsetOp models a Write.RangeKeyUnset operation.
   367  type rangeKeyUnsetOp struct {
   368  	writerID objID
   369  	start    []byte
   370  	end      []byte
   371  	suffix   []byte
   372  }
   373  
   374  func (o *rangeKeyUnsetOp) run(t *test, h historyRecorder) {
   375  	w := t.getWriter(o.writerID)
   376  	err := w.RangeKeyUnset(o.start, o.end, o.suffix, t.writeOpts)
   377  	h.Recordf("%s // %v", o, err)
   378  }
   379  
   380  func (o *rangeKeyUnsetOp) String() string {
   381  	return fmt.Sprintf("%s.RangeKeyUnset(%q, %q, %q)",
   382  		o.writerID, o.start, o.end, o.suffix)
   383  }
   384  
   385  func (o *rangeKeyUnsetOp) receiver() objID      { return o.writerID }
   386  func (o *rangeKeyUnsetOp) syncObjs() objIDSlice { return nil }
   387  
   388  // newBatchOp models a Write.NewBatch operation.
   389  type newBatchOp struct {
   390  	batchID objID
   391  }
   392  
   393  func (o *newBatchOp) run(t *test, h historyRecorder) {
   394  	b := t.db.NewBatch()
   395  	t.setBatch(o.batchID, b)
   396  	h.Recordf("%s", o)
   397  }
   398  
   399  func (o *newBatchOp) String() string  { return fmt.Sprintf("%s = db.NewBatch()", o.batchID) }
   400  func (o *newBatchOp) receiver() objID { return dbObjID }
   401  func (o *newBatchOp) syncObjs() objIDSlice {
   402  	// NewBatch should not be concurrent with operations that interact with that
   403  	// same batch.
   404  	return []objID{o.batchID}
   405  }
   406  
   407  // newIndexedBatchOp models a Write.NewIndexedBatch operation.
   408  type newIndexedBatchOp struct {
   409  	batchID objID
   410  }
   411  
   412  func (o *newIndexedBatchOp) run(t *test, h historyRecorder) {
   413  	b := t.db.NewIndexedBatch()
   414  	t.setBatch(o.batchID, b)
   415  	h.Recordf("%s", o)
   416  }
   417  
   418  func (o *newIndexedBatchOp) String() string {
   419  	return fmt.Sprintf("%s = db.NewIndexedBatch()", o.batchID)
   420  }
   421  func (o *newIndexedBatchOp) receiver() objID { return dbObjID }
   422  func (o *newIndexedBatchOp) syncObjs() objIDSlice {
   423  	// NewIndexedBatch should not be concurrent with operations that interact
   424  	// with that same batch.
   425  	return []objID{o.batchID}
   426  }
   427  
   428  // batchCommitOp models a Batch.Commit operation.
   429  type batchCommitOp struct {
   430  	batchID objID
   431  }
   432  
   433  func (o *batchCommitOp) run(t *test, h historyRecorder) {
   434  	b := t.getBatch(o.batchID)
   435  	err := b.Commit(t.writeOpts)
   436  	h.Recordf("%s // %v", o, err)
   437  }
   438  
   439  func (o *batchCommitOp) String() string  { return fmt.Sprintf("%s.Commit()", o.batchID) }
   440  func (o *batchCommitOp) receiver() objID { return o.batchID }
   441  func (o *batchCommitOp) syncObjs() objIDSlice {
   442  	// Synchronize on the database so that NewIters wait for the commit.
   443  	return []objID{dbObjID}
   444  }
   445  
   446  // ingestOp models a DB.Ingest operation.
   447  type ingestOp struct {
   448  	batchIDs []objID
   449  }
   450  
   451  func (o *ingestOp) run(t *test, h historyRecorder) {
   452  	// We can only use apply as an alternative for ingestion if we are ingesting
   453  	// a single batch. If we are ingesting multiple batches, the batches may
   454  	// overlap which would cause ingestion to fail but apply would succeed.
   455  	if t.testOpts.ingestUsingApply && len(o.batchIDs) == 1 {
   456  		id := o.batchIDs[0]
   457  		b := t.getBatch(id)
   458  		iter, rangeDelIter, rangeKeyIter := private.BatchSort(b)
   459  		c, err := o.collapseBatch(t, iter, rangeDelIter, rangeKeyIter)
   460  		if err == nil {
   461  			w := t.getWriter(makeObjID(dbTag, 0))
   462  			err = w.Apply(c, t.writeOpts)
   463  		}
   464  		_ = b.Close()
   465  		_ = c.Close()
   466  		t.clearObj(id)
   467  		h.Recordf("%s // %v", o, err)
   468  		return
   469  	}
   470  
   471  	var paths []string
   472  	var err error
   473  	for i, id := range o.batchIDs {
   474  		b := t.getBatch(id)
   475  		t.clearObj(id)
   476  		path, err2 := o.build(t, h, b, i)
   477  		if err2 != nil {
   478  			h.Recordf("Build(%s) // %v", id, err2)
   479  		}
   480  		err = firstError(err, err2)
   481  		if err2 == nil {
   482  			paths = append(paths, path)
   483  		}
   484  		err = firstError(err, b.Close())
   485  	}
   486  
   487  	err = firstError(err, withRetries(func() error {
   488  		return t.db.Ingest(paths)
   489  	}))
   490  
   491  	h.Recordf("%s // %v", o, err)
   492  }
   493  
   494  func (o *ingestOp) build(t *test, h historyRecorder, b *pebble.Batch, i int) (string, error) {
   495  	rootFS := vfs.Root(t.opts.FS)
   496  	path := rootFS.PathJoin(t.tmpDir, fmt.Sprintf("ext%d", i))
   497  	f, err := rootFS.Create(path)
   498  	if err != nil {
   499  		return "", err
   500  	}
   501  
   502  	iter, rangeDelIter, rangeKeyIter := private.BatchSort(b)
   503  	defer closeIters(iter, rangeDelIter, rangeKeyIter)
   504  
   505  	equal := t.opts.Comparer.Equal
   506  	tableFormat := t.db.FormatMajorVersion().MaxTableFormat()
   507  	w := sstable.NewWriter(
   508  		objstorageprovider.NewFileWritable(f),
   509  		t.opts.MakeWriterOptions(0, tableFormat),
   510  	)
   511  
   512  	var lastUserKey []byte
   513  	for key, value := iter.First(); key != nil; key, value = iter.Next() {
   514  		// Ignore duplicate keys.
   515  		if equal(lastUserKey, key.UserKey) {
   516  			continue
   517  		}
   518  		// NB: We don't have to copy the key or value since we're reading from a
   519  		// batch which doesn't do prefix compression.
   520  		lastUserKey = key.UserKey
   521  
   522  		key.SetSeqNum(base.SeqNumZero)
   523  		if err := w.Add(*key, value.InPlaceValue()); err != nil {
   524  			return "", err
   525  		}
   526  	}
   527  	if err := iter.Close(); err != nil {
   528  		return "", err
   529  	}
   530  	iter = nil
   531  
   532  	if rangeDelIter != nil {
   533  		// NB: The range tombstones have already been fragmented by the Batch.
   534  		for t := rangeDelIter.First(); t != nil; t = rangeDelIter.Next() {
   535  			// NB: We don't have to copy the key or value since we're reading from a
   536  			// batch which doesn't do prefix compression.
   537  			if err := w.DeleteRange(t.Start, t.End); err != nil {
   538  				return "", err
   539  			}
   540  		}
   541  		if err := rangeDelIter.Close(); err != nil {
   542  			return "", err
   543  		}
   544  		rangeDelIter = nil
   545  	}
   546  
   547  	if err := w.Close(); err != nil {
   548  		return "", err
   549  	}
   550  	return path, nil
   551  }
   552  
   553  func (o *ingestOp) receiver() objID { return dbObjID }
   554  func (o *ingestOp) syncObjs() objIDSlice {
   555  	// Ingest should not be concurrent with mutating the batches that will be
   556  	// ingested as sstables.
   557  	return o.batchIDs
   558  }
   559  
   560  func closeIters(
   561  	pointIter base.InternalIterator,
   562  	rangeDelIter keyspan.FragmentIterator,
   563  	rangeKeyIter keyspan.FragmentIterator,
   564  ) {
   565  	if pointIter != nil {
   566  		pointIter.Close()
   567  	}
   568  	if rangeDelIter != nil {
   569  		rangeDelIter.Close()
   570  	}
   571  	if rangeKeyIter != nil {
   572  		rangeKeyIter.Close()
   573  	}
   574  }
   575  
   576  // collapseBatch collapses the mutations in a batch to be equivalent to an
   577  // sstable ingesting those mutations. Duplicate updates to a key are collapsed
   578  // so that only the latest update is performed. All range deletions are
   579  // performed first in the batch to match the semantics of ingestion where a
   580  // range deletion does not delete a point record contained in the sstable.
   581  func (o *ingestOp) collapseBatch(
   582  	t *test, pointIter base.InternalIterator, rangeDelIter, rangeKeyIter keyspan.FragmentIterator,
   583  ) (*pebble.Batch, error) {
   584  	defer closeIters(pointIter, rangeDelIter, rangeKeyIter)
   585  	equal := t.opts.Comparer.Equal
   586  	collapsed := t.db.NewBatch()
   587  
   588  	if rangeDelIter != nil {
   589  		// NB: The range tombstones have already been fragmented by the Batch.
   590  		for t := rangeDelIter.First(); t != nil; t = rangeDelIter.Next() {
   591  			// NB: We don't have to copy the key or value since we're reading from a
   592  			// batch which doesn't do prefix compression.
   593  			if err := collapsed.DeleteRange(t.Start, t.End, nil); err != nil {
   594  				return nil, err
   595  			}
   596  		}
   597  		if err := rangeDelIter.Close(); err != nil {
   598  			return nil, err
   599  		}
   600  		rangeDelIter = nil
   601  	}
   602  
   603  	if pointIter != nil {
   604  		var lastUserKey []byte
   605  		for key, value := pointIter.First(); key != nil; key, value = pointIter.Next() {
   606  			// Ignore duplicate keys.
   607  			if equal(lastUserKey, key.UserKey) {
   608  				continue
   609  			}
   610  			// NB: We don't have to copy the key or value since we're reading from a
   611  			// batch which doesn't do prefix compression.
   612  			lastUserKey = key.UserKey
   613  
   614  			var err error
   615  			switch key.Kind() {
   616  			case pebble.InternalKeyKindDelete:
   617  				err = collapsed.Delete(key.UserKey, nil)
   618  			case pebble.InternalKeyKindDeleteSized:
   619  				v, _ := binary.Uvarint(value.InPlaceValue())
   620  				// Batch.DeleteSized takes just the length of the value being
   621  				// deleted and adds the key's length to derive the overall entry
   622  				// size of the value being deleted. This has already been done
   623  				// to the key we're reading from the batch, so we must subtract
   624  				// the key length from the encoded value before calling
   625  				// collapsed.DeleteSized, which will again add the key length
   626  				// before encoding.
   627  				err = collapsed.DeleteSized(key.UserKey, uint32(v-uint64(len(key.UserKey))), nil)
   628  			case pebble.InternalKeyKindSingleDelete:
   629  				err = collapsed.SingleDelete(key.UserKey, nil)
   630  			case pebble.InternalKeyKindSet:
   631  				err = collapsed.Set(key.UserKey, value.InPlaceValue(), nil)
   632  			case pebble.InternalKeyKindMerge:
   633  				err = collapsed.Merge(key.UserKey, value.InPlaceValue(), nil)
   634  			case pebble.InternalKeyKindLogData:
   635  				err = collapsed.LogData(key.UserKey, nil)
   636  			default:
   637  				err = errors.Errorf("unknown batch record kind: %d", key.Kind())
   638  			}
   639  			if err != nil {
   640  				return nil, err
   641  			}
   642  		}
   643  		if err := pointIter.Close(); err != nil {
   644  			return nil, err
   645  		}
   646  		pointIter = nil
   647  	}
   648  
   649  	return collapsed, nil
   650  }
   651  
   652  func (o *ingestOp) String() string {
   653  	var buf strings.Builder
   654  	buf.WriteString("db.Ingest(")
   655  	for i, id := range o.batchIDs {
   656  		if i > 0 {
   657  			buf.WriteString(", ")
   658  		}
   659  		buf.WriteString(id.String())
   660  	}
   661  	buf.WriteString(")")
   662  	return buf.String()
   663  }
   664  
   665  // getOp models a Reader.Get operation.
   666  type getOp struct {
   667  	readerID objID
   668  	key      []byte
   669  }
   670  
   671  func (o *getOp) run(t *test, h historyRecorder) {
   672  	r := t.getReader(o.readerID)
   673  	var val []byte
   674  	var closer io.Closer
   675  	err := withRetries(func() (err error) {
   676  		val, closer, err = r.Get(o.key)
   677  		return err
   678  	})
   679  	h.Recordf("%s // [%q] %v", o, val, err)
   680  	if closer != nil {
   681  		closer.Close()
   682  	}
   683  }
   684  
   685  func (o *getOp) String() string  { return fmt.Sprintf("%s.Get(%q)", o.readerID, o.key) }
   686  func (o *getOp) receiver() objID { return o.readerID }
   687  func (o *getOp) syncObjs() objIDSlice {
   688  	if o.readerID == dbObjID {
   689  		return nil
   690  	}
   691  	// batch.Get reads through to the current database state.
   692  	return []objID{dbObjID}
   693  }
   694  
   695  // newIterOp models a Reader.NewIter operation.
   696  type newIterOp struct {
   697  	readerID objID
   698  	iterID   objID
   699  	iterOpts
   700  }
   701  
   702  func (o *newIterOp) run(t *test, h historyRecorder) {
   703  	r := t.getReader(o.readerID)
   704  	opts := iterOptions(o.iterOpts)
   705  
   706  	var i *pebble.Iterator
   707  	for {
   708  		i, _ = r.NewIter(opts)
   709  		if err := i.Error(); !errors.Is(err, errorfs.ErrInjected) {
   710  			break
   711  		}
   712  		// close this iter and retry NewIter
   713  		_ = i.Close()
   714  	}
   715  	t.setIter(o.iterID, i)
   716  
   717  	// Trash the bounds to ensure that Pebble doesn't rely on the stability of
   718  	// the user-provided bounds.
   719  	if opts != nil {
   720  		rand.Read(opts.LowerBound[:])
   721  		rand.Read(opts.UpperBound[:])
   722  	}
   723  	h.Recordf("%s // %v", o, i.Error())
   724  }
   725  
   726  func (o *newIterOp) String() string {
   727  	return fmt.Sprintf("%s = %s.NewIter(%q, %q, %d /* key types */, %d, %d, %t /* use L6 filters */, %q /* masking suffix */)",
   728  		o.iterID, o.readerID, o.lower, o.upper, o.keyTypes, o.filterMin, o.filterMax, o.useL6Filters, o.maskSuffix)
   729  }
   730  
   731  func (o *newIterOp) receiver() objID { return o.readerID }
   732  func (o *newIterOp) syncObjs() objIDSlice {
   733  	// Prevent o.iterID ops from running before it exists.
   734  	objs := []objID{o.iterID}
   735  	// If reading through a batch, the new iterator will also observe database
   736  	// state, and we must synchronize on the database state for a consistent
   737  	// view.
   738  	if o.readerID.tag() == batchTag {
   739  		objs = append(objs, dbObjID)
   740  	}
   741  	return objs
   742  }
   743  
   744  // newIterUsingCloneOp models a Iterator.Clone operation.
   745  type newIterUsingCloneOp struct {
   746  	existingIterID objID
   747  	iterID         objID
   748  	refreshBatch   bool
   749  	iterOpts
   750  
   751  	// derivedReaderID is the ID of the underlying reader that backs both the
   752  	// existing iterator and the new iterator. The derivedReaderID is NOT
   753  	// serialized by String and is derived from other operations during parse.
   754  	derivedReaderID objID
   755  }
   756  
   757  func (o *newIterUsingCloneOp) run(t *test, h historyRecorder) {
   758  	iter := t.getIter(o.existingIterID)
   759  	cloneOpts := pebble.CloneOptions{
   760  		IterOptions:      iterOptions(o.iterOpts),
   761  		RefreshBatchView: o.refreshBatch,
   762  	}
   763  	i, err := iter.iter.Clone(cloneOpts)
   764  	if err != nil {
   765  		panic(err)
   766  	}
   767  	t.setIter(o.iterID, i)
   768  	h.Recordf("%s // %v", o, i.Error())
   769  }
   770  
   771  func (o *newIterUsingCloneOp) String() string {
   772  	return fmt.Sprintf("%s = %s.Clone(%t, %q, %q, %d /* key types */, %d, %d, %t /* use L6 filters */, %q /* masking suffix */)",
   773  		o.iterID, o.existingIterID, o.refreshBatch, o.lower, o.upper,
   774  		o.keyTypes, o.filterMin, o.filterMax, o.useL6Filters, o.maskSuffix)
   775  }
   776  
   777  func (o *newIterUsingCloneOp) receiver() objID { return o.existingIterID }
   778  
   779  func (o *newIterUsingCloneOp) syncObjs() objIDSlice {
   780  	objIDs := []objID{o.iterID}
   781  	// If the underlying reader is a batch, we must synchronize with the batch.
   782  	// If refreshBatch=true, synchronizing is necessary to observe all the
   783  	// mutations up to until this op and no more. Even when refreshBatch=false,
   784  	// we must synchronize because iterator construction may access state cached
   785  	// on the indexed batch to avoid refragmenting range tombstones or range
   786  	// keys.
   787  	if o.derivedReaderID.tag() == batchTag {
   788  		objIDs = append(objIDs, o.derivedReaderID)
   789  	}
   790  	return objIDs
   791  }
   792  
   793  // iterSetBoundsOp models an Iterator.SetBounds operation.
   794  type iterSetBoundsOp struct {
   795  	iterID objID
   796  	lower  []byte
   797  	upper  []byte
   798  }
   799  
   800  func (o *iterSetBoundsOp) run(t *test, h historyRecorder) {
   801  	i := t.getIter(o.iterID)
   802  	var lower, upper []byte
   803  	if o.lower != nil {
   804  		lower = append(lower, o.lower...)
   805  	}
   806  	if o.upper != nil {
   807  		upper = append(upper, o.upper...)
   808  	}
   809  	i.SetBounds(lower, upper)
   810  
   811  	// Trash the bounds to ensure that Pebble doesn't rely on the stability of
   812  	// the user-provided bounds.
   813  	rand.Read(lower[:])
   814  	rand.Read(upper[:])
   815  
   816  	h.Recordf("%s // %v", o, i.Error())
   817  }
   818  
   819  func (o *iterSetBoundsOp) String() string {
   820  	return fmt.Sprintf("%s.SetBounds(%q, %q)", o.iterID, o.lower, o.upper)
   821  }
   822  
   823  func (o *iterSetBoundsOp) receiver() objID      { return o.iterID }
   824  func (o *iterSetBoundsOp) syncObjs() objIDSlice { return nil }
   825  
   826  // iterSetOptionsOp models an Iterator.SetOptions operation.
   827  type iterSetOptionsOp struct {
   828  	iterID objID
   829  	iterOpts
   830  
   831  	// derivedReaderID is the ID of the underlying reader that backs the
   832  	// iterator. The derivedReaderID is NOT serialized by String and is derived
   833  	// from other operations during parse.
   834  	derivedReaderID objID
   835  }
   836  
   837  func (o *iterSetOptionsOp) run(t *test, h historyRecorder) {
   838  	i := t.getIter(o.iterID)
   839  
   840  	opts := iterOptions(o.iterOpts)
   841  	if opts == nil {
   842  		opts = &pebble.IterOptions{}
   843  	}
   844  	i.SetOptions(opts)
   845  
   846  	// Trash the bounds to ensure that Pebble doesn't rely on the stability of
   847  	// the user-provided bounds.
   848  	rand.Read(opts.LowerBound[:])
   849  	rand.Read(opts.UpperBound[:])
   850  
   851  	h.Recordf("%s // %v", o, i.Error())
   852  }
   853  
   854  func (o *iterSetOptionsOp) String() string {
   855  	return fmt.Sprintf("%s.SetOptions(%q, %q, %d /* key types */, %d, %d, %t /* use L6 filters */, %q /* masking suffix */)",
   856  		o.iterID, o.lower, o.upper, o.keyTypes, o.filterMin, o.filterMax, o.useL6Filters, o.maskSuffix)
   857  }
   858  
   859  func iterOptions(o iterOpts) *pebble.IterOptions {
   860  	if o.IsZero() {
   861  		return nil
   862  	}
   863  	var lower, upper []byte
   864  	if o.lower != nil {
   865  		lower = append(lower, o.lower...)
   866  	}
   867  	if o.upper != nil {
   868  		upper = append(upper, o.upper...)
   869  	}
   870  	opts := &pebble.IterOptions{
   871  		LowerBound: lower,
   872  		UpperBound: upper,
   873  		KeyTypes:   pebble.IterKeyType(o.keyTypes),
   874  		RangeKeyMasking: pebble.RangeKeyMasking{
   875  			Suffix: o.maskSuffix,
   876  		},
   877  		UseL6Filters: o.useL6Filters,
   878  	}
   879  	if opts.RangeKeyMasking.Suffix != nil {
   880  		opts.RangeKeyMasking.Filter = func() pebble.BlockPropertyFilterMask {
   881  			return sstable.NewTestKeysMaskingFilter()
   882  		}
   883  	}
   884  	if o.filterMax > 0 {
   885  		opts.PointKeyFilters = []pebble.BlockPropertyFilter{
   886  			sstable.NewTestKeysBlockPropertyFilter(o.filterMin, o.filterMax),
   887  		}
   888  		// Enforce the timestamp bounds in SkipPoint, so that the iterator never
   889  		// returns a key outside the filterMin, filterMax bounds. This provides
   890  		// deterministic iteration.
   891  		opts.SkipPoint = func(k []byte) (skip bool) {
   892  			n := testkeys.Comparer.Split(k)
   893  			if n == len(k) {
   894  				// No suffix, don't skip it.
   895  				return false
   896  			}
   897  			v, err := testkeys.ParseSuffix(k[n:])
   898  			if err != nil {
   899  				panic(err)
   900  			}
   901  			ts := uint64(v)
   902  			return ts < o.filterMin || ts >= o.filterMax
   903  		}
   904  	}
   905  	return opts
   906  }
   907  
   908  func (o *iterSetOptionsOp) receiver() objID { return o.iterID }
   909  
   910  func (o *iterSetOptionsOp) syncObjs() objIDSlice {
   911  	if o.derivedReaderID.tag() == batchTag {
   912  		// If the underlying reader is a batch, we must synchronize with the
   913  		// batch so that we observe all the mutations up until this operation
   914  		// and no more.
   915  		return []objID{o.derivedReaderID}
   916  	}
   917  	return nil
   918  }
   919  
   920  // iterSeekGEOp models an Iterator.SeekGE[WithLimit] operation.
   921  type iterSeekGEOp struct {
   922  	iterID objID
   923  	key    []byte
   924  	limit  []byte
   925  
   926  	derivedReaderID objID
   927  }
   928  
   929  func iteratorPos(i *retryableIter) string {
   930  	var buf bytes.Buffer
   931  	fmt.Fprintf(&buf, "%q", i.Key())
   932  	hasPoint, hasRange := i.HasPointAndRange()
   933  	if hasPoint {
   934  		fmt.Fprintf(&buf, ",%q", i.Value())
   935  	} else {
   936  		fmt.Fprint(&buf, ",<no point>")
   937  	}
   938  	if hasRange {
   939  		start, end := i.RangeBounds()
   940  		fmt.Fprintf(&buf, ",[%q,%q)=>{", start, end)
   941  		for i, rk := range i.RangeKeys() {
   942  			if i > 0 {
   943  				fmt.Fprint(&buf, ",")
   944  			}
   945  			fmt.Fprintf(&buf, "%q=%q", rk.Suffix, rk.Value)
   946  		}
   947  		fmt.Fprint(&buf, "}")
   948  	} else {
   949  		fmt.Fprint(&buf, ",<no range>")
   950  	}
   951  	if i.RangeKeyChanged() {
   952  		fmt.Fprint(&buf, "*")
   953  	}
   954  	return buf.String()
   955  }
   956  
   957  func validBoolToStr(valid bool) string {
   958  	return fmt.Sprintf("%t", valid)
   959  }
   960  
   961  func validityStateToStr(validity pebble.IterValidityState) (bool, string) {
   962  	// We can't distinguish between IterExhausted and IterAtLimit in a
   963  	// deterministic manner.
   964  	switch validity {
   965  	case pebble.IterExhausted, pebble.IterAtLimit:
   966  		return false, "invalid"
   967  	case pebble.IterValid:
   968  		return true, "valid"
   969  	default:
   970  		panic("unknown validity")
   971  	}
   972  }
   973  
   974  func (o *iterSeekGEOp) run(t *test, h historyRecorder) {
   975  	i := t.getIter(o.iterID)
   976  	var valid bool
   977  	var validStr string
   978  	if o.limit == nil {
   979  		valid = i.SeekGE(o.key)
   980  		validStr = validBoolToStr(valid)
   981  	} else {
   982  		valid, validStr = validityStateToStr(i.SeekGEWithLimit(o.key, o.limit))
   983  	}
   984  	if valid {
   985  		h.Recordf("%s // [%s,%s] %v", o, validStr, iteratorPos(i), i.Error())
   986  	} else {
   987  		h.Recordf("%s // [%s] %v", o, validStr, i.Error())
   988  	}
   989  }
   990  
   991  func (o *iterSeekGEOp) String() string {
   992  	return fmt.Sprintf("%s.SeekGE(%q, %q)", o.iterID, o.key, o.limit)
   993  }
   994  func (o *iterSeekGEOp) receiver() objID      { return o.iterID }
   995  func (o *iterSeekGEOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
   996  
   997  func onlyBatchIDs(ids ...objID) objIDSlice {
   998  	var ret objIDSlice
   999  	for _, id := range ids {
  1000  		if id.tag() == batchTag {
  1001  			ret = append(ret, id)
  1002  		}
  1003  	}
  1004  	return ret
  1005  }
  1006  
  1007  // iterSeekPrefixGEOp models an Iterator.SeekPrefixGE operation.
  1008  type iterSeekPrefixGEOp struct {
  1009  	iterID objID
  1010  	key    []byte
  1011  
  1012  	derivedReaderID objID
  1013  }
  1014  
  1015  func (o *iterSeekPrefixGEOp) run(t *test, h historyRecorder) {
  1016  	i := t.getIter(o.iterID)
  1017  	valid := i.SeekPrefixGE(o.key)
  1018  	if valid {
  1019  		h.Recordf("%s // [%t,%s] %v", o, valid, iteratorPos(i), i.Error())
  1020  	} else {
  1021  		h.Recordf("%s // [%t] %v", o, valid, i.Error())
  1022  	}
  1023  }
  1024  
  1025  func (o *iterSeekPrefixGEOp) String() string {
  1026  	return fmt.Sprintf("%s.SeekPrefixGE(%q)", o.iterID, o.key)
  1027  }
  1028  func (o *iterSeekPrefixGEOp) receiver() objID      { return o.iterID }
  1029  func (o *iterSeekPrefixGEOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1030  
  1031  // iterSeekLTOp models an Iterator.SeekLT[WithLimit] operation.
  1032  type iterSeekLTOp struct {
  1033  	iterID objID
  1034  	key    []byte
  1035  	limit  []byte
  1036  
  1037  	derivedReaderID objID
  1038  }
  1039  
  1040  func (o *iterSeekLTOp) run(t *test, h historyRecorder) {
  1041  	i := t.getIter(o.iterID)
  1042  	var valid bool
  1043  	var validStr string
  1044  	if o.limit == nil {
  1045  		valid = i.SeekLT(o.key)
  1046  		validStr = validBoolToStr(valid)
  1047  	} else {
  1048  		valid, validStr = validityStateToStr(i.SeekLTWithLimit(o.key, o.limit))
  1049  	}
  1050  	if valid {
  1051  		h.Recordf("%s // [%s,%s] %v", o, validStr, iteratorPos(i), i.Error())
  1052  	} else {
  1053  		h.Recordf("%s // [%s] %v", o, validStr, i.Error())
  1054  	}
  1055  }
  1056  
  1057  func (o *iterSeekLTOp) String() string {
  1058  	return fmt.Sprintf("%s.SeekLT(%q, %q)", o.iterID, o.key, o.limit)
  1059  }
  1060  
  1061  func (o *iterSeekLTOp) receiver() objID      { return o.iterID }
  1062  func (o *iterSeekLTOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1063  
  1064  // iterFirstOp models an Iterator.First operation.
  1065  type iterFirstOp struct {
  1066  	iterID objID
  1067  
  1068  	derivedReaderID objID
  1069  }
  1070  
  1071  func (o *iterFirstOp) run(t *test, h historyRecorder) {
  1072  	i := t.getIter(o.iterID)
  1073  	valid := i.First()
  1074  	if valid {
  1075  		h.Recordf("%s // [%t,%s] %v", o, valid, iteratorPos(i), i.Error())
  1076  	} else {
  1077  		h.Recordf("%s // [%t] %v", o, valid, i.Error())
  1078  	}
  1079  }
  1080  
  1081  func (o *iterFirstOp) String() string       { return fmt.Sprintf("%s.First()", o.iterID) }
  1082  func (o *iterFirstOp) receiver() objID      { return o.iterID }
  1083  func (o *iterFirstOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1084  
  1085  // iterLastOp models an Iterator.Last operation.
  1086  type iterLastOp struct {
  1087  	iterID objID
  1088  
  1089  	derivedReaderID objID
  1090  }
  1091  
  1092  func (o *iterLastOp) run(t *test, h historyRecorder) {
  1093  	i := t.getIter(o.iterID)
  1094  	valid := i.Last()
  1095  	if valid {
  1096  		h.Recordf("%s // [%t,%s] %v", o, valid, iteratorPos(i), i.Error())
  1097  	} else {
  1098  		h.Recordf("%s // [%t] %v", o, valid, i.Error())
  1099  	}
  1100  }
  1101  
  1102  func (o *iterLastOp) String() string       { return fmt.Sprintf("%s.Last()", o.iterID) }
  1103  func (o *iterLastOp) receiver() objID      { return o.iterID }
  1104  func (o *iterLastOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1105  
  1106  // iterNextOp models an Iterator.Next[WithLimit] operation.
  1107  type iterNextOp struct {
  1108  	iterID objID
  1109  	limit  []byte
  1110  
  1111  	derivedReaderID objID
  1112  }
  1113  
  1114  func (o *iterNextOp) run(t *test, h historyRecorder) {
  1115  	i := t.getIter(o.iterID)
  1116  	var valid bool
  1117  	var validStr string
  1118  	if o.limit == nil {
  1119  		valid = i.Next()
  1120  		validStr = validBoolToStr(valid)
  1121  	} else {
  1122  		valid, validStr = validityStateToStr(i.NextWithLimit(o.limit))
  1123  	}
  1124  	if valid {
  1125  		h.Recordf("%s // [%s,%s] %v", o, validStr, iteratorPos(i), i.Error())
  1126  	} else {
  1127  		h.Recordf("%s // [%s] %v", o, validStr, i.Error())
  1128  	}
  1129  }
  1130  
  1131  func (o *iterNextOp) String() string       { return fmt.Sprintf("%s.Next(%q)", o.iterID, o.limit) }
  1132  func (o *iterNextOp) receiver() objID      { return o.iterID }
  1133  func (o *iterNextOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1134  
  1135  // iterNextPrefixOp models an Iterator.NextPrefix operation.
  1136  type iterNextPrefixOp struct {
  1137  	iterID objID
  1138  
  1139  	derivedReaderID objID
  1140  }
  1141  
  1142  func (o *iterNextPrefixOp) run(t *test, h historyRecorder) {
  1143  	i := t.getIter(o.iterID)
  1144  	valid := i.NextPrefix()
  1145  	validStr := validBoolToStr(valid)
  1146  	if valid {
  1147  		h.Recordf("%s // [%s,%s] %v", o, validStr, iteratorPos(i), i.Error())
  1148  	} else {
  1149  		h.Recordf("%s // [%s] %v", o, validStr, i.Error())
  1150  	}
  1151  }
  1152  
  1153  func (o *iterNextPrefixOp) String() string       { return fmt.Sprintf("%s.NextPrefix()", o.iterID) }
  1154  func (o *iterNextPrefixOp) receiver() objID      { return o.iterID }
  1155  func (o *iterNextPrefixOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1156  
  1157  // iterCanSingleDelOp models a call to CanDeterministicallySingleDelete with an
  1158  // Iterator.
  1159  type iterCanSingleDelOp struct {
  1160  	iterID objID
  1161  
  1162  	derivedReaderID objID
  1163  }
  1164  
  1165  func (o *iterCanSingleDelOp) run(t *test, h historyRecorder) {
  1166  	// TODO(jackson): When we perform error injection, we'll need to rethink
  1167  	// this.
  1168  	_, err := pebble.CanDeterministicallySingleDelete(t.getIter(o.iterID).iter)
  1169  	// The return value of CanDeterministicallySingleDelete is dependent on
  1170  	// internal LSM state and non-deterministic, so we don't record it.
  1171  	// Including the operation within the metamorphic test at all helps ensure
  1172  	// that it does not change the result of any other Iterator operation that
  1173  	// should be deterministic, regardless of its own outcome.
  1174  	//
  1175  	// We still record the value of the error because it's deterministic, at
  1176  	// least for now. The possible error cases are:
  1177  	//  - The iterator was already in an error state when the operation ran.
  1178  	//  - The operation is deterministically invalid (like using an InternalNext
  1179  	//    to change directions.)
  1180  	h.Recordf("%s // %v", o, err)
  1181  }
  1182  
  1183  func (o *iterCanSingleDelOp) String() string       { return fmt.Sprintf("%s.InternalNext()", o.iterID) }
  1184  func (o *iterCanSingleDelOp) receiver() objID      { return o.iterID }
  1185  func (o *iterCanSingleDelOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1186  
  1187  // iterPrevOp models an Iterator.Prev[WithLimit] operation.
  1188  type iterPrevOp struct {
  1189  	iterID objID
  1190  	limit  []byte
  1191  
  1192  	derivedReaderID objID
  1193  }
  1194  
  1195  func (o *iterPrevOp) run(t *test, h historyRecorder) {
  1196  	i := t.getIter(o.iterID)
  1197  	var valid bool
  1198  	var validStr string
  1199  	if o.limit == nil {
  1200  		valid = i.Prev()
  1201  		validStr = validBoolToStr(valid)
  1202  	} else {
  1203  		valid, validStr = validityStateToStr(i.PrevWithLimit(o.limit))
  1204  	}
  1205  	if valid {
  1206  		h.Recordf("%s // [%s,%s] %v", o, validStr, iteratorPos(i), i.Error())
  1207  	} else {
  1208  		h.Recordf("%s // [%s] %v", o, validStr, i.Error())
  1209  	}
  1210  }
  1211  
  1212  func (o *iterPrevOp) String() string       { return fmt.Sprintf("%s.Prev(%q)", o.iterID, o.limit) }
  1213  func (o *iterPrevOp) receiver() objID      { return o.iterID }
  1214  func (o *iterPrevOp) syncObjs() objIDSlice { return onlyBatchIDs(o.derivedReaderID) }
  1215  
  1216  // newSnapshotOp models a DB.NewSnapshot operation.
  1217  type newSnapshotOp struct {
  1218  	snapID objID
  1219  	// If nonempty, this snapshot must not be used to read any keys outside of
  1220  	// the provided bounds. This allows some implementations to use 'Eventually
  1221  	// file-only snapshots,' which require bounds.
  1222  	bounds []pebble.KeyRange
  1223  }
  1224  
  1225  func (o *newSnapshotOp) run(t *test, h historyRecorder) {
  1226  	// Fibonacci hash https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
  1227  	if len(o.bounds) > 0 && ((11400714819323198485*uint64(t.idx)*t.testOpts.seedEFOS)>>63) == 1 {
  1228  		s := t.db.NewEventuallyFileOnlySnapshot(o.bounds)
  1229  		t.setSnapshot(o.snapID, s)
  1230  	} else {
  1231  		s := t.db.NewSnapshot()
  1232  		t.setSnapshot(o.snapID, s)
  1233  	}
  1234  	h.Recordf("%s", o)
  1235  }
  1236  
  1237  func (o *newSnapshotOp) String() string {
  1238  	var buf bytes.Buffer
  1239  	fmt.Fprintf(&buf, "%s = db.NewSnapshot(", o.snapID)
  1240  	for i := range o.bounds {
  1241  		if i > 0 {
  1242  			fmt.Fprint(&buf, ", ")
  1243  		}
  1244  		fmt.Fprintf(&buf, "%q, %q", o.bounds[i].Start, o.bounds[i].End)
  1245  	}
  1246  	fmt.Fprint(&buf, ")")
  1247  	return buf.String()
  1248  }
  1249  func (o *newSnapshotOp) receiver() objID      { return dbObjID }
  1250  func (o *newSnapshotOp) syncObjs() objIDSlice { return []objID{o.snapID} }
  1251  
  1252  type dbRatchetFormatMajorVersionOp struct {
  1253  	vers pebble.FormatMajorVersion
  1254  }
  1255  
  1256  func (o *dbRatchetFormatMajorVersionOp) run(t *test, h historyRecorder) {
  1257  	var err error
  1258  	// NB: We no-op the operation if we're already at or above the provided
  1259  	// format major version. Different runs start at different format major
  1260  	// versions, making the presence of an error and the error message itself
  1261  	// non-deterministic if we attempt to upgrade to an older version.
  1262  	//
  1263  	//Regardless, subsequent operations should behave identically, which is what
  1264  	//we're really aiming to test by including this format major version ratchet
  1265  	//operation.
  1266  	if t.db.FormatMajorVersion() < o.vers {
  1267  		err = t.db.RatchetFormatMajorVersion(o.vers)
  1268  	}
  1269  	h.Recordf("%s // %v", o, err)
  1270  }
  1271  
  1272  func (o *dbRatchetFormatMajorVersionOp) String() string {
  1273  	return fmt.Sprintf("db.RatchetFormatMajorVersion(%s)", o.vers)
  1274  }
  1275  func (o *dbRatchetFormatMajorVersionOp) receiver() objID      { return dbObjID }
  1276  func (o *dbRatchetFormatMajorVersionOp) syncObjs() objIDSlice { return nil }
  1277  
  1278  type dbRestartOp struct{}
  1279  
  1280  func (o *dbRestartOp) run(t *test, h historyRecorder) {
  1281  	if err := t.restartDB(); err != nil {
  1282  		h.Recordf("%s // %v", o, err)
  1283  		h.history.err.Store(errors.Wrap(err, "dbRestartOp"))
  1284  	} else {
  1285  		h.Recordf("%s", o)
  1286  	}
  1287  }
  1288  
  1289  func (o *dbRestartOp) String() string       { return "db.Restart()" }
  1290  func (o *dbRestartOp) receiver() objID      { return dbObjID }
  1291  func (o *dbRestartOp) syncObjs() objIDSlice { return nil }
  1292  
  1293  func formatOps(ops []op) string {
  1294  	var buf strings.Builder
  1295  	for _, op := range ops {
  1296  		fmt.Fprintf(&buf, "%s\n", op)
  1297  	}
  1298  	return buf.String()
  1299  }