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 }