github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/storage/mvcc_history_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package storage
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"io"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    23  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    24  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    25  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    26  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    27  	"github.com/cockroachdb/cockroach/pkg/util/uint128"
    28  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    29  	"github.com/cockroachdb/datadriven"
    30  	"github.com/cockroachdb/errors"
    31  )
    32  
    33  // TestMVCCHistories verifies that sequences of MVCC reads and writes
    34  // perform properly.
    35  //
    36  // The input files use the following DSL:
    37  //
    38  // txn_begin      t=<name> [ts=<int>[,<int>]]
    39  // txn_remove     t=<name>
    40  // txn_restart    t=<name>
    41  // txn_update     t=<name> t2=<name>
    42  // txn_step       t=<name> [n=<int>]
    43  // txn_advance    t=<name> ts=<int>[,<int>]
    44  // txn_status     t=<name> status=<txnstatus>
    45  //
    46  // resolve_intent t=<name> k=<key> [status=<txnstatus>]
    47  // check_intent   k=<key> [none]
    48  //
    49  // cput      [t=<name>] [ts=<int>[,<int>]] [resolve [status=<txnstatus>]] k=<key> v=<string> [raw] [cond=<string>]
    50  // del       [t=<name>] [ts=<int>[,<int>]] [resolve [status=<txnstatus>]] k=<key>
    51  // get       [t=<name>] [ts=<int>[,<int>]] [resolve [status=<txnstatus>]] k=<key> [inconsistent] [tombstones] [failOnMoreRecent]
    52  // increment [t=<name>] [ts=<int>[,<int>]] [resolve [status=<txnstatus>]] k=<key> [inc=<val>]
    53  // put       [t=<name>] [ts=<int>[,<int>]] [resolve [status=<txnstatus>]] k=<key> v=<string> [raw]
    54  // scan      [t=<name>] [ts=<int>[,<int>]] [resolve [status=<txnstatus>]] k=<key> [end=<key>] [inconsistent] [tombstones] [reverse] [failOnMoreRecent]
    55  //
    56  // merge     [ts=<int>[,<int>]] k=<key> v=<string> [raw]
    57  //
    58  // clear_range    k=<key> end=<key>
    59  //
    60  // Where `<key>` can be a simple string, or a string
    61  // prefixed by the following characters:
    62  //
    63  // - `=foo` means exactly key `foo`
    64  // - `+foo` means `Key(foo).Next()`
    65  // - `-foo` means `Key(foo).PrefixEnd()`
    66  //
    67  // Additionally, the pseudo-command `with` enables sharing
    68  // a group of arguments between multiple commands, for example:
    69  //   with t=A
    70  //     txn_begin
    71  //     with k=a
    72  //       put v=b
    73  //       resolve_intent
    74  // Really means:
    75  //   txn_begin          t=A
    76  //   put v=b        k=a t=A
    77  //   resolve_intent k=a t=A
    78  //
    79  func TestMVCCHistories(t *testing.T) {
    80  	defer leaktest.AfterTest(t)()
    81  
    82  	ctx := context.Background()
    83  	for _, engineImpl := range mvccEngineImpls {
    84  		t.Run(engineImpl.name, func(t *testing.T) {
    85  
    86  			// Everything reads/writes under the same prefix.
    87  			key := roachpb.Key("")
    88  			span := roachpb.Span{Key: key, EndKey: key.PrefixEnd()}
    89  
    90  			datadriven.Walk(t, "testdata/mvcc_histories", func(t *testing.T, path string) {
    91  				// We start from a clean slate in every test file.
    92  				engine := engineImpl.create()
    93  				defer engine.Close()
    94  
    95  				reportDataEntries := func(buf *bytes.Buffer) error {
    96  					hasData := false
    97  					err := engine.Iterate(
    98  						span.Key,
    99  						span.EndKey,
   100  						func(r MVCCKeyValue) (bool, error) {
   101  							hasData = true
   102  							if r.Key.Timestamp.IsEmpty() {
   103  								// Meta is at timestamp zero.
   104  								meta := enginepb.MVCCMetadata{}
   105  								if err := protoutil.Unmarshal(r.Value, &meta); err != nil {
   106  									fmt.Fprintf(buf, "meta: %v -> error decoding proto from %v: %v\n", r.Key, r.Value, err)
   107  								} else {
   108  									fmt.Fprintf(buf, "meta: %v -> %+v\n", r.Key, &meta)
   109  								}
   110  							} else {
   111  								fmt.Fprintf(buf, "data: %v -> %s\n", r.Key, roachpb.Value{RawBytes: r.Value}.PrettyPrint())
   112  							}
   113  							return false, nil
   114  						})
   115  					if !hasData {
   116  						buf.WriteString("<no data>\n")
   117  					}
   118  					return err
   119  				}
   120  
   121  				e := newEvalCtx(ctx, engine)
   122  
   123  				datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
   124  					// We'll be overriding cmd/cmdargs below, because the
   125  					// datadriven reader does not know about sub-commands.
   126  					defer func(pos, cmd string, cmdArgs []datadriven.CmdArg) {
   127  						d.Pos = pos
   128  						d.Cmd = cmd
   129  						d.CmdArgs = cmdArgs
   130  					}(d.Pos, d.Cmd, d.CmdArgs)
   131  					// The various evalCtx helpers want access to the current test
   132  					// and testdata structs.
   133  					e.t = t
   134  					e.td = d
   135  
   136  					switch d.Cmd {
   137  					case "skip":
   138  						if len(d.CmdArgs) == 0 || d.CmdArgs[0].Key == engineImpl.name {
   139  							e.t.Skip("skipped")
   140  						}
   141  						return d.Expected
   142  					case "run":
   143  						// Syntax: run [trace] [error]
   144  						// (other words - in particular "ok" - are accepted but ignored)
   145  						//
   146  						// "run" executes a script of zero or more operations from
   147  						// the commands library defined below.
   148  						// It stops upon the first error encountered, if any.
   149  						//
   150  						// Options:
   151  						// "trace" means detail each operation in the output.
   152  						// "error" means expect an error to occur. The specific error type/
   153  						// message to expect is spelled out in the expected output.
   154  						//
   155  						trace := false
   156  						if e.hasArg("trace") {
   157  							trace = true
   158  						}
   159  						expectError := false
   160  						if e.hasArg("error") {
   161  							expectError = true
   162  						}
   163  
   164  						// buf will accumulate the actual output, which the
   165  						// datadriven driver will use to compare to the expected
   166  						// output.
   167  						var buf bytes.Buffer
   168  						e.results.buf = &buf
   169  
   170  						// foundErr remembers which error was last encountered while
   171  						// executing the script under "run".
   172  						var foundErr error
   173  
   174  						// pos is the original <file>:<lineno> prefix computed by
   175  						// datadriven. It points to the top "run" command itself.
   176  						// We editing d.Pos in-place below by extending `pos` upon
   177  						// each new line of the script.
   178  						pos := d.Pos
   179  
   180  						// dataChange indicates whether some command in the script
   181  						// has modified the stored data. When this becomes true, the
   182  						// current content of storage is printed in the results
   183  						// buffer at the end.
   184  						dataChange := false
   185  						// txnChange indicates whether some command has modified
   186  						// a transaction object. When set, the last modified txn
   187  						// object is reported in the result buffer at the end.
   188  						txnChange := false
   189  
   190  						reportResults := func(printTxn, printData bool) {
   191  							if printTxn && e.results.txn != nil {
   192  								fmt.Fprintf(&buf, "txn: %v\n", e.results.txn)
   193  							}
   194  							if printData {
   195  								err := reportDataEntries(&buf)
   196  								if err != nil {
   197  									if foundErr == nil {
   198  										// Handle the error below.
   199  										foundErr = err
   200  									} else {
   201  										fmt.Fprintf(&buf, "error reading data: (%T:) %v\n", err, err)
   202  									}
   203  								}
   204  							}
   205  						}
   206  
   207  						// sharedCmdArgs is updated by "with" pseudo-commands,
   208  						// to pre-populate common arguments for the following
   209  						// indented commands.
   210  						var sharedCmdArgs []datadriven.CmdArg
   211  
   212  						// The lines of the script under "run".
   213  						lines := strings.Split(d.Input, "\n")
   214  						for i, line := range lines {
   215  							if short := strings.TrimSpace(line); short == "" || strings.HasPrefix(short, "#") {
   216  								// Comment or empty line. Do nothing.
   217  								continue
   218  							}
   219  
   220  							// Compute a line prefix, to clarify error message. We
   221  							// prefix a newline character because some text editor do
   222  							// not know how to jump to the location of an error if
   223  							// there are multiple file:line prefixes on the same line.
   224  							d.Pos = fmt.Sprintf("\n%s: (+%d)", pos, i+1)
   225  
   226  							// Trace the execution in testing.T, to clarify where we
   227  							// are in case an error occurs.
   228  							e.t.Logf("%s: %s", d.Pos, line)
   229  
   230  							// Decompose the current script line.
   231  							var err error
   232  							d.Cmd, d.CmdArgs, err = datadriven.ParseLine(line)
   233  							if err != nil {
   234  								e.t.Fatalf("%s: %v", d.Pos, err)
   235  							}
   236  
   237  							// Expand "with" commands:
   238  							//   with t=A
   239  							//       txn_begin
   240  							//       resolve_intent k=a
   241  							// is equivalent to:
   242  							//   txn_begin      t=A
   243  							//   resolve_intent k=a t=A
   244  							isIndented := strings.TrimLeft(line, " \t") != line
   245  							if d.Cmd == "with" {
   246  								if !isIndented {
   247  									// Reset shared args.
   248  									sharedCmdArgs = d.CmdArgs
   249  								} else {
   250  									// Prefix shared args. We use prefix so that the
   251  									// innermost "with" can override/shadow the outermost
   252  									// "with".
   253  									sharedCmdArgs = append(d.CmdArgs, sharedCmdArgs...)
   254  								}
   255  								continue
   256  							} else if isIndented {
   257  								// line is indented. Inherit arguments.
   258  								if len(sharedCmdArgs) == 0 {
   259  									// sanity check.
   260  									e.Fatalf("indented command without prior 'with': %s", line)
   261  								}
   262  								// We prepend the args that are provided on the command
   263  								// itself so it's possible to override those provided
   264  								// via "with".
   265  								d.CmdArgs = append(d.CmdArgs, sharedCmdArgs...)
   266  							} else {
   267  								// line is not indented. Clear shared arguments.
   268  								sharedCmdArgs = nil
   269  							}
   270  
   271  							cmd := e.getCmd()
   272  							txnChange = txnChange || cmd.typ == typTxnUpdate
   273  							dataChange = dataChange || cmd.typ == typDataUpdate
   274  
   275  							if trace {
   276  								// If tracing is also requested by the datadriven input,
   277  								// we'll trace the statement in the actual results too.
   278  								fmt.Fprintf(&buf, ">> %s", d.Cmd)
   279  								for i := range d.CmdArgs {
   280  									fmt.Fprintf(&buf, " %s", &d.CmdArgs[i])
   281  								}
   282  								buf.WriteByte('\n')
   283  							}
   284  
   285  							// Run the command.
   286  							foundErr = cmd.fn(e)
   287  
   288  							if trace {
   289  								// If tracing is enabled, we report the intermediate results
   290  								// after each individual step in the script.
   291  								// This may modify foundErr too.
   292  								reportResults(cmd.typ == typTxnUpdate, cmd.typ == typDataUpdate)
   293  							}
   294  
   295  							if foundErr != nil {
   296  								// An error occurred. Stop the script prematurely.
   297  								break
   298  							}
   299  						}
   300  						// End of script.
   301  
   302  						if !trace {
   303  							// If we were not tracing, no results were printed yet. Do it now.
   304  							if txnChange || dataChange {
   305  								buf.WriteString(">> at end:\n")
   306  							}
   307  							reportResults(txnChange, dataChange)
   308  						}
   309  
   310  						signalError := e.t.Errorf
   311  						if txnChange || dataChange {
   312  							// We can't recover from an error and continue
   313  							// to proceed further tests, because the state
   314  							// may have changed from what the test may be expecting.
   315  							signalError = e.t.Fatalf
   316  						}
   317  
   318  						// Check for errors.
   319  						if foundErr == nil && expectError {
   320  							signalError("%s: expected error, got success", d.Pos)
   321  							return d.Expected
   322  						} else if foundErr != nil {
   323  							if expectError {
   324  								fmt.Fprintf(&buf, "error: (%T:) %v\n", foundErr, foundErr)
   325  							} else /* !expectError */ {
   326  								signalError("%s: expected success, found: (%T:) %v", d.Pos, foundErr, foundErr)
   327  								return d.Expected
   328  							}
   329  						}
   330  
   331  						// We're done. Report the actual results and errors to the
   332  						// datadriven executor.
   333  						return buf.String()
   334  
   335  					default:
   336  						e.t.Errorf("%s: unknown command: %s", d.Pos, d.Cmd)
   337  						return d.Expected
   338  					}
   339  				})
   340  			})
   341  		})
   342  	}
   343  }
   344  
   345  // getCmd retrieves the cmd entry for the current script line.
   346  func (e *evalCtx) getCmd() cmd {
   347  	e.t.Helper()
   348  	c, ok := commands[e.td.Cmd]
   349  	if !ok {
   350  		e.Fatalf("unknown command: %s", e.td.Cmd)
   351  	}
   352  	return c
   353  }
   354  
   355  // cmd represents one supported script command.
   356  type cmd struct {
   357  	typ cmdType
   358  	fn  func(e *evalCtx) error
   359  }
   360  
   361  type cmdType int
   362  
   363  const (
   364  	typReadOnly cmdType = iota
   365  	typTxnUpdate
   366  	typDataUpdate
   367  )
   368  
   369  // commands is the list of all supported script commands.
   370  var commands = map[string]cmd{
   371  	"txn_advance":     {typTxnUpdate, cmdTxnAdvance},
   372  	"txn_begin":       {typTxnUpdate, cmdTxnBegin},
   373  	"txn_ignore_seqs": {typTxnUpdate, cmdTxnIgnoreSeqs},
   374  	"txn_remove":      {typTxnUpdate, cmdTxnRemove},
   375  	"txn_restart":     {typTxnUpdate, cmdTxnRestart},
   376  	"txn_status":      {typTxnUpdate, cmdTxnSetStatus},
   377  	"txn_step":        {typTxnUpdate, cmdTxnStep},
   378  	"txn_update":      {typTxnUpdate, cmdTxnUpdate},
   379  
   380  	"resolve_intent": {typDataUpdate, cmdResolveIntent},
   381  	// TODO(nvanbenschoten): test "resolve_intent_range".
   382  	"check_intent": {typReadOnly, cmdCheckIntent},
   383  
   384  	"clear_range": {typDataUpdate, cmdClearRange},
   385  	"cput":        {typDataUpdate, cmdCPut},
   386  	"del":         {typDataUpdate, cmdDelete},
   387  	"get":         {typReadOnly, cmdGet},
   388  	"increment":   {typDataUpdate, cmdIncrement},
   389  	"merge":       {typDataUpdate, cmdMerge},
   390  	"put":         {typDataUpdate, cmdPut},
   391  	"scan":        {typReadOnly, cmdScan},
   392  }
   393  
   394  func cmdTxnAdvance(e *evalCtx) error {
   395  	txn := e.getTxn(mandatory)
   396  	ts := e.getTs(txn)
   397  	if ts.Less(txn.ReadTimestamp) {
   398  		e.Fatalf("cannot advance txn to earlier (%s) than its ReadTimestamp (%s)",
   399  			ts, txn.ReadTimestamp)
   400  	}
   401  	txn.WriteTimestamp = ts
   402  	e.results.txn = txn
   403  	return nil
   404  }
   405  
   406  func cmdTxnBegin(e *evalCtx) error {
   407  	var txnName string
   408  	e.scanArg("t", &txnName)
   409  	ts := e.getTs(nil)
   410  	key := roachpb.KeyMin
   411  	if e.hasArg("k") {
   412  		key = e.getKey()
   413  	}
   414  	txn, err := e.newTxn(txnName, ts, key)
   415  	e.results.txn = txn
   416  	return err
   417  }
   418  
   419  func cmdTxnIgnoreSeqs(e *evalCtx) error {
   420  	txn := e.getTxn(mandatory)
   421  	seql := e.getList("seqs")
   422  	is := []enginepb.IgnoredSeqNumRange{}
   423  	for _, s := range seql {
   424  		parts := strings.Split(s, "-")
   425  		if len(parts) != 2 {
   426  			e.Fatalf("syntax error: expected 'a-b', got: '%s'", s)
   427  		}
   428  		a, err := strconv.ParseInt(parts[0], 10, 32)
   429  		if err != nil {
   430  			e.Fatalf("%v", err)
   431  		}
   432  		b, err := strconv.ParseInt(parts[1], 10, 32)
   433  		if err != nil {
   434  			e.Fatalf("%v", err)
   435  		}
   436  		is = append(is, enginepb.IgnoredSeqNumRange{Start: enginepb.TxnSeq(a), End: enginepb.TxnSeq(b)})
   437  	}
   438  	txn.IgnoredSeqNums = is
   439  	e.results.txn = txn
   440  	return nil
   441  }
   442  
   443  func cmdTxnRemove(e *evalCtx) error {
   444  	txn := e.getTxn(mandatory)
   445  	delete(e.txns, txn.Name)
   446  	e.results.txn = nil
   447  	return nil
   448  }
   449  
   450  func cmdTxnRestart(e *evalCtx) error {
   451  	txn := e.getTxn(mandatory)
   452  	ts := e.getTs(txn)
   453  	up := roachpb.NormalUserPriority
   454  	tp := enginepb.MinTxnPriority
   455  	txn.Restart(up, tp, ts)
   456  	e.results.txn = txn
   457  	return nil
   458  }
   459  
   460  func cmdTxnSetStatus(e *evalCtx) error {
   461  	txn := e.getTxn(mandatory)
   462  	status := e.getTxnStatus()
   463  	txn.Status = status
   464  	e.results.txn = txn
   465  	return nil
   466  }
   467  
   468  func cmdTxnStep(e *evalCtx) error {
   469  	txn := e.getTxn(mandatory)
   470  	n := 1
   471  	if e.hasArg("seq") {
   472  		e.scanArg("seq", &n)
   473  		txn.Sequence = enginepb.TxnSeq(n)
   474  	} else {
   475  		if e.hasArg("n") {
   476  			e.scanArg("n", &n)
   477  		}
   478  		txn.Sequence += enginepb.TxnSeq(n)
   479  	}
   480  	e.results.txn = txn
   481  	return nil
   482  }
   483  
   484  func cmdTxnUpdate(e *evalCtx) error {
   485  	txn := e.getTxn(mandatory)
   486  	var txnName2 string
   487  	e.scanArg("t2", &txnName2)
   488  	txn2, err := e.lookupTxn(txnName2)
   489  	if err != nil {
   490  		e.Fatalf("%v", err)
   491  	}
   492  	txn.Update(txn2)
   493  	e.results.txn = txn
   494  	return nil
   495  }
   496  
   497  func cmdResolveIntent(e *evalCtx) error {
   498  	txn := e.getTxn(mandatory)
   499  	key := e.getKey()
   500  	status := e.getTxnStatus()
   501  	return e.resolveIntent(e.engine, key, txn, status)
   502  }
   503  
   504  func (e *evalCtx) resolveIntent(
   505  	rw ReadWriter, key roachpb.Key, txn *roachpb.Transaction, resolveStatus roachpb.TransactionStatus,
   506  ) error {
   507  	intent := roachpb.MakeLockUpdate(txn, roachpb.Span{Key: key})
   508  	intent.Status = resolveStatus
   509  	_, err := MVCCResolveWriteIntent(e.ctx, rw, nil, intent)
   510  	return err
   511  }
   512  
   513  func cmdCheckIntent(e *evalCtx) error {
   514  	key := e.getKey()
   515  	wantIntent := true
   516  	if e.hasArg("none") {
   517  		wantIntent = false
   518  	}
   519  	metaKey := mvccKey(key)
   520  	var meta enginepb.MVCCMetadata
   521  	ok, _, _, err := e.engine.GetProto(metaKey, &meta)
   522  	if err != nil {
   523  		return err
   524  	}
   525  	if !ok && wantIntent {
   526  		return errors.Newf("meta: %v -> expected intent, found none", key)
   527  	}
   528  	if ok {
   529  		fmt.Fprintf(e.results.buf, "meta: %v -> %+v\n", key, &meta)
   530  		if !wantIntent {
   531  			return errors.Newf("meta: %v -> expected no intent, found one", key)
   532  		}
   533  	}
   534  	return nil
   535  }
   536  
   537  func cmdClearRange(e *evalCtx) error {
   538  	key, endKey := e.getKeyRange()
   539  	return e.engine.ClearRange(
   540  		MVCCKey{Key: key},
   541  		MVCCKey{Key: endKey},
   542  	)
   543  }
   544  
   545  func cmdCPut(e *evalCtx) error {
   546  	txn := e.getTxn(optional)
   547  	ts := e.getTs(txn)
   548  
   549  	key := e.getKey()
   550  	val := e.getVal()
   551  	// Condition val is optional.
   552  	var expVal *roachpb.Value
   553  	if e.hasArg("cond") {
   554  		rexpVal := e.getValInternal("cond")
   555  		expVal = &rexpVal
   556  	}
   557  	behavior := CPutFailIfMissing
   558  	if e.hasArg("allow_missing") {
   559  		behavior = CPutAllowIfMissing
   560  	}
   561  	resolve, resolveStatus := e.getResolve()
   562  
   563  	return e.withWriter("cput", func(rw ReadWriter) error {
   564  		if err := MVCCConditionalPut(e.ctx, rw, nil, key, ts, val, expVal, behavior, txn); err != nil {
   565  			return err
   566  		}
   567  		if resolve {
   568  			return e.resolveIntent(rw, key, txn, resolveStatus)
   569  		}
   570  		return nil
   571  	})
   572  }
   573  
   574  func cmdDelete(e *evalCtx) error {
   575  	txn := e.getTxn(optional)
   576  	key := e.getKey()
   577  	ts := e.getTs(txn)
   578  	resolve, resolveStatus := e.getResolve()
   579  	return e.withWriter("del", func(rw ReadWriter) error {
   580  		if err := MVCCDelete(e.ctx, rw, nil, key, ts, txn); err != nil {
   581  			return err
   582  		}
   583  		if resolve {
   584  			return e.resolveIntent(rw, key, txn, resolveStatus)
   585  		}
   586  		return nil
   587  	})
   588  }
   589  
   590  func cmdGet(e *evalCtx) error {
   591  	txn := e.getTxn(optional)
   592  	key := e.getKey()
   593  	ts := e.getTs(txn)
   594  	opts := MVCCGetOptions{Txn: txn}
   595  	if e.hasArg("inconsistent") {
   596  		opts.Inconsistent = true
   597  		opts.Txn = nil
   598  	}
   599  	if e.hasArg("tombstones") {
   600  		opts.Tombstones = true
   601  	}
   602  	if e.hasArg("failOnMoreRecent") {
   603  		opts.FailOnMoreRecent = true
   604  	}
   605  	val, intent, err := MVCCGet(e.ctx, e.engine, key, ts, opts)
   606  	// NB: the error is returned below. This ensures the test can
   607  	// ascertain no result is populated in the intent when an error
   608  	// occurs.
   609  	if intent != nil {
   610  		fmt.Fprintf(e.results.buf, "get: %v -> intent {%s}\n", key, intent.Txn)
   611  	}
   612  	if val != nil {
   613  		fmt.Fprintf(e.results.buf, "get: %v -> %v @%v\n", key, val.PrettyPrint(), val.Timestamp)
   614  	} else {
   615  		fmt.Fprintf(e.results.buf, "get: %v -> <no data>\n", key)
   616  	}
   617  	return err
   618  }
   619  
   620  func cmdIncrement(e *evalCtx) error {
   621  	txn := e.getTxn(optional)
   622  	ts := e.getTs(txn)
   623  
   624  	key := e.getKey()
   625  	inc := int64(1)
   626  	if e.hasArg("inc") {
   627  		var incI int
   628  		e.scanArg("inc", &incI)
   629  		inc = int64(incI)
   630  	}
   631  
   632  	resolve, resolveStatus := e.getResolve()
   633  
   634  	return e.withWriter("increment", func(rw ReadWriter) error {
   635  		curVal, err := MVCCIncrement(e.ctx, rw, nil, key, ts, txn, inc)
   636  		if err != nil {
   637  			return err
   638  		}
   639  		fmt.Fprintf(e.results.buf, "inc: current value = %d\n", curVal)
   640  		if resolve {
   641  			return e.resolveIntent(rw, key, txn, resolveStatus)
   642  		}
   643  		return nil
   644  	})
   645  }
   646  
   647  func cmdMerge(e *evalCtx) error {
   648  	key := e.getKey()
   649  	var value string
   650  	e.scanArg("v", &value)
   651  	var val roachpb.Value
   652  	if e.hasArg("raw") {
   653  		val.RawBytes = []byte(value)
   654  	} else {
   655  		val.SetString(value)
   656  	}
   657  	ts := e.getTs(nil)
   658  	return e.withWriter("merge", func(rw ReadWriter) error {
   659  		return MVCCMerge(e.ctx, rw, nil, key, ts, val)
   660  	})
   661  }
   662  
   663  func cmdPut(e *evalCtx) error {
   664  	txn := e.getTxn(optional)
   665  	ts := e.getTs(txn)
   666  
   667  	key := e.getKey()
   668  	val := e.getVal()
   669  
   670  	resolve, resolveStatus := e.getResolve()
   671  
   672  	return e.withWriter("put", func(rw ReadWriter) error {
   673  		if err := MVCCPut(e.ctx, rw, nil, key, ts, val, txn); err != nil {
   674  			return err
   675  		}
   676  		if resolve {
   677  			return e.resolveIntent(rw, key, txn, resolveStatus)
   678  		}
   679  		return nil
   680  	})
   681  }
   682  
   683  func cmdScan(e *evalCtx) error {
   684  	txn := e.getTxn(optional)
   685  	key, endKey := e.getKeyRange()
   686  	ts := e.getTs(txn)
   687  	opts := MVCCScanOptions{Txn: txn}
   688  	if e.hasArg("inconsistent") {
   689  		opts.Inconsistent = true
   690  		opts.Txn = nil
   691  	}
   692  	if e.hasArg("tombstones") {
   693  		opts.Tombstones = true
   694  	}
   695  	if e.hasArg("reverse") {
   696  		opts.Reverse = true
   697  	}
   698  	if e.hasArg("failOnMoreRecent") {
   699  		opts.FailOnMoreRecent = true
   700  	}
   701  	if e.hasArg("max") {
   702  		var n int
   703  		e.scanArg("max", &n)
   704  		opts.MaxKeys = int64(n)
   705  	}
   706  	if key := "targetbytes"; e.hasArg(key) {
   707  		var tb int
   708  		e.scanArg(key, &tb)
   709  		opts.TargetBytes = int64(tb)
   710  	}
   711  	res, err := MVCCScan(e.ctx, e.engine, key, endKey, ts, opts)
   712  	// NB: the error is returned below. This ensures the test can
   713  	// ascertain no result is populated in the intents when an error
   714  	// occurs.
   715  	for _, intent := range res.Intents {
   716  		fmt.Fprintf(e.results.buf, "scan: %v -> intent {%s}\n", key, intent.Txn)
   717  	}
   718  	for _, val := range res.KVs {
   719  		fmt.Fprintf(e.results.buf, "scan: %v -> %v @%v\n", val.Key, val.Value.PrettyPrint(), val.Value.Timestamp)
   720  	}
   721  	if res.ResumeSpan != nil {
   722  		fmt.Fprintf(e.results.buf, "scan: resume span [%s,%s)\n", res.ResumeSpan.Key, res.ResumeSpan.EndKey)
   723  	}
   724  	if opts.TargetBytes > 0 {
   725  		fmt.Fprintf(e.results.buf, "scan: %d bytes (target %d)\n", res.NumBytes, opts.TargetBytes)
   726  	}
   727  	if len(res.KVs) == 0 {
   728  		fmt.Fprintf(e.results.buf, "scan: %v-%v -> <no data>\n", key, endKey)
   729  	}
   730  	return err
   731  }
   732  
   733  // evalCtx stored the current state of the environment of a running
   734  // script.
   735  type evalCtx struct {
   736  	results struct {
   737  		buf io.Writer
   738  		txn *roachpb.Transaction
   739  	}
   740  	ctx        context.Context
   741  	engine     Engine
   742  	t          *testing.T
   743  	td         *datadriven.TestData
   744  	txns       map[string]*roachpb.Transaction
   745  	txnCounter uint128.Uint128
   746  }
   747  
   748  func newEvalCtx(ctx context.Context, engine Engine) *evalCtx {
   749  	return &evalCtx{
   750  		ctx:        ctx,
   751  		engine:     engine,
   752  		txns:       make(map[string]*roachpb.Transaction),
   753  		txnCounter: uint128.FromInts(0, 1),
   754  	}
   755  }
   756  
   757  func (e *evalCtx) getTxnStatus() roachpb.TransactionStatus {
   758  	status := roachpb.COMMITTED
   759  	if e.hasArg("status") {
   760  		var sn string
   761  		e.scanArg("status", &sn)
   762  		s, ok := roachpb.TransactionStatus_value[sn]
   763  		if !ok {
   764  			e.Fatalf("invalid status: %s", sn)
   765  		}
   766  		status = roachpb.TransactionStatus(s)
   767  	}
   768  	return status
   769  }
   770  
   771  func (e *evalCtx) scanArg(key string, dests ...interface{}) {
   772  	e.t.Helper()
   773  	e.td.ScanArgs(e.t, key, dests...)
   774  }
   775  
   776  func (e *evalCtx) hasArg(key string) bool {
   777  	for _, c := range e.td.CmdArgs {
   778  		if c.Key == key {
   779  			return true
   780  		}
   781  	}
   782  	return false
   783  }
   784  
   785  func (e *evalCtx) Fatalf(format string, args ...interface{}) {
   786  	e.t.Helper()
   787  	e.td.Fatalf(e.t, format, args...)
   788  }
   789  
   790  func (e *evalCtx) getResolve() (bool, roachpb.TransactionStatus) {
   791  	e.t.Helper()
   792  	if !e.hasArg("resolve") {
   793  		return false, roachpb.PENDING
   794  	}
   795  	return true, e.getTxnStatus()
   796  }
   797  
   798  func (e *evalCtx) getTs(txn *roachpb.Transaction) hlc.Timestamp {
   799  	var ts hlc.Timestamp
   800  	if txn != nil {
   801  		ts = txn.ReadTimestamp
   802  	}
   803  	if !e.hasArg("ts") {
   804  		return ts
   805  	}
   806  	var tsS string
   807  	e.scanArg("ts", &tsS)
   808  	parts := strings.Split(tsS, ",")
   809  
   810  	// Find the wall time part.
   811  	tsW, err := strconv.ParseInt(parts[0], 10, 64)
   812  	if err != nil {
   813  		e.Fatalf("%v", err)
   814  	}
   815  	ts.WallTime = tsW
   816  
   817  	// Find the logical part, if there is one.
   818  	var tsL int64
   819  	if len(parts) > 1 {
   820  		tsL, err = strconv.ParseInt(parts[1], 10, 32)
   821  		if err != nil {
   822  			e.Fatalf("%v", err)
   823  		}
   824  	}
   825  	ts.Logical = int32(tsL)
   826  	return ts
   827  }
   828  
   829  type optArg int
   830  
   831  const (
   832  	optional optArg = iota
   833  	mandatory
   834  )
   835  
   836  func (e *evalCtx) getList(argName string) []string {
   837  	for _, c := range e.td.CmdArgs {
   838  		if c.Key == argName {
   839  			return c.Vals
   840  		}
   841  	}
   842  	e.Fatalf("missing argument: %s", argName)
   843  	return nil
   844  }
   845  
   846  func (e *evalCtx) getTxn(opt optArg) *roachpb.Transaction {
   847  	e.t.Helper()
   848  	if opt == optional && (e.hasArg("notxn") || !e.hasArg("t")) {
   849  		return nil
   850  	}
   851  	var txnName string
   852  	e.scanArg("t", &txnName)
   853  	txn, err := e.lookupTxn(txnName)
   854  	if err != nil {
   855  		e.Fatalf("%v", err)
   856  	}
   857  	return txn
   858  }
   859  
   860  func (e *evalCtx) withWriter(cmd string, fn func(_ ReadWriter) error) error {
   861  	var rw ReadWriter
   862  	rw = e.engine
   863  	var batch Batch
   864  	if e.hasArg("batched") {
   865  		batch = e.engine.NewBatch()
   866  		defer batch.Close()
   867  		rw = batch
   868  	}
   869  	origErr := fn(rw)
   870  	if batch != nil {
   871  		batchStatus := "non-empty"
   872  		if batch.Empty() {
   873  			batchStatus = "empty"
   874  		}
   875  		fmt.Fprintf(e.results.buf, "%s: batch after write is %s\n", cmd, batchStatus)
   876  	}
   877  	if origErr != nil {
   878  		return origErr
   879  	}
   880  	if batch != nil {
   881  		return batch.Commit(true)
   882  	}
   883  	return nil
   884  }
   885  
   886  func (e *evalCtx) getVal() roachpb.Value { return e.getValInternal("v") }
   887  func (e *evalCtx) getValInternal(argName string) roachpb.Value {
   888  	var value string
   889  	e.scanArg(argName, &value)
   890  	var val roachpb.Value
   891  	if e.hasArg("raw") {
   892  		val.RawBytes = []byte(value)
   893  	} else {
   894  		val.SetString(value)
   895  	}
   896  	return val
   897  }
   898  
   899  func (e *evalCtx) getKey() roachpb.Key {
   900  	e.t.Helper()
   901  	var keyS string
   902  	e.scanArg("k", &keyS)
   903  	return toKey(keyS)
   904  }
   905  
   906  func (e *evalCtx) getKeyRange() (sk, ek roachpb.Key) {
   907  	e.t.Helper()
   908  	var keyS string
   909  	e.scanArg("k", &keyS)
   910  	sk = toKey(keyS)
   911  	ek = sk.Next()
   912  	if e.hasArg("end") {
   913  		var endKeyS string
   914  		e.scanArg("end", &endKeyS)
   915  		ek = toKey(endKeyS)
   916  	}
   917  	return sk, ek
   918  }
   919  
   920  func (e *evalCtx) newTxn(
   921  	txnName string, ts hlc.Timestamp, key roachpb.Key,
   922  ) (*roachpb.Transaction, error) {
   923  	if _, ok := e.txns[txnName]; ok {
   924  		e.Fatalf("txn %s already open", txnName)
   925  	}
   926  	txn := &roachpb.Transaction{
   927  		TxnMeta: enginepb.TxnMeta{
   928  			ID:             uuid.FromUint128(e.txnCounter),
   929  			Key:            []byte(key),
   930  			WriteTimestamp: ts,
   931  			Sequence:       0,
   932  		},
   933  		Name:                    txnName,
   934  		DeprecatedOrigTimestamp: ts,
   935  		ReadTimestamp:           ts,
   936  		Status:                  roachpb.PENDING,
   937  	}
   938  	e.txnCounter = e.txnCounter.Add(1)
   939  	e.txns[txnName] = txn
   940  	return txn, nil
   941  }
   942  
   943  func (e *evalCtx) lookupTxn(txnName string) (*roachpb.Transaction, error) {
   944  	txn, ok := e.txns[txnName]
   945  	if !ok {
   946  		e.Fatalf("txn %s not open", txnName)
   947  	}
   948  	return txn, nil
   949  }
   950  
   951  func toKey(s string) roachpb.Key {
   952  	switch {
   953  	case len(s) > 0 && s[0] == '+':
   954  		return roachpb.Key(s[1:]).Next()
   955  	case len(s) > 0 && s[0] == '=':
   956  		return roachpb.Key(s[1:])
   957  	case len(s) > 0 && s[0] == '-':
   958  		return roachpb.Key(s[1:]).PrefixEnd()
   959  	default:
   960  		return roachpb.Key(s)
   961  	}
   962  }