LCOV - code coverage report
Current view: top level - pebble/metamorphic - parser.go (source / functions) Hit Total Coverage
Test: 2024-05-13 08:16Z 5ee10bd4 - tests + meta.lcov Lines: 443 478 92.7 %
Date: 2024-05-13 08:17:09 Functions: 0 0 -

          Line data    Source code
       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             :         "fmt"
      10             :         "go/scanner"
      11             :         "go/token"
      12             :         "reflect"
      13             :         "slices"
      14             :         "strconv"
      15             : 
      16             :         "github.com/cockroachdb/errors"
      17             :         "github.com/cockroachdb/pebble"
      18             : )
      19             : 
      20             : type methodInfo struct {
      21             :         constructor func() op
      22             :         validTags   uint32
      23             : }
      24             : 
      25           2 : func makeMethod(i interface{}, tags ...objTag) *methodInfo {
      26           2 :         var validTags uint32
      27           2 :         for _, tag := range tags {
      28           2 :                 validTags |= 1 << tag
      29           2 :         }
      30             : 
      31           2 :         t := reflect.TypeOf(i)
      32           2 :         return &methodInfo{
      33           2 :                 constructor: func() op {
      34           2 :                         return reflect.New(t).Interface().(op)
      35           2 :                 },
      36             :                 validTags: validTags,
      37             :         }
      38             : }
      39             : 
      40             : // args returns the receiverID, targetID and arguments for the op. The
      41             : // receiverID is the ID of the object the op will be applied to. The targetID
      42             : // is the ID of the object for assignment. If the method does not return a new
      43             : // object, then targetID will be nil.
      44             : //
      45             : // The argument list returns pointers to operation fields that map to arguments
      46             : // for the operation. The last argument can be a pointer to a slice,
      47             : // corresponding to a variable number of arguments.
      48           2 : func opArgs(op op) (receiverID *objID, targetID *objID, args []interface{}) {
      49           2 :         switch t := op.(type) {
      50           2 :         case *applyOp:
      51           2 :                 return &t.writerID, nil, []interface{}{&t.batchID}
      52           2 :         case *checkpointOp:
      53           2 :                 return &t.dbID, nil, []interface{}{&t.spans}
      54           2 :         case *closeOp:
      55           2 :                 return &t.objID, nil, nil
      56           2 :         case *compactOp:
      57           2 :                 return &t.dbID, nil, []interface{}{&t.start, &t.end, &t.parallelize}
      58           2 :         case *batchCommitOp:
      59           2 :                 return &t.batchID, nil, nil
      60           2 :         case *dbRatchetFormatMajorVersionOp:
      61           2 :                 return &t.dbID, nil, []interface{}{&t.vers}
      62           2 :         case *dbRestartOp:
      63           2 :                 return &t.dbID, nil, nil
      64           2 :         case *deleteOp:
      65           2 :                 return &t.writerID, nil, []interface{}{&t.key}
      66           2 :         case *deleteRangeOp:
      67           2 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end}
      68           2 :         case *downloadOp:
      69           2 :                 return &t.dbID, nil, []interface{}{&t.spans}
      70           2 :         case *iterFirstOp:
      71           2 :                 return &t.iterID, nil, nil
      72           2 :         case *flushOp:
      73           2 :                 return &t.db, nil, nil
      74           2 :         case *getOp:
      75           2 :                 return &t.readerID, nil, []interface{}{&t.key}
      76           2 :         case *ingestOp:
      77           2 :                 return &t.dbID, nil, []interface{}{&t.batchIDs}
      78           2 :         case *ingestAndExciseOp:
      79           2 :                 return &t.dbID, nil, []interface{}{&t.batchID, &t.exciseStart, &t.exciseEnd, &t.sstContainsExciseTombstone}
      80           2 :         case *ingestExternalFilesOp:
      81           2 :                 return &t.dbID, nil, []interface{}{&t.objs}
      82           2 :         case *initOp:
      83           2 :                 return nil, nil, []interface{}{&t.dbSlots, &t.batchSlots, &t.iterSlots, &t.snapshotSlots, &t.externalObjSlots}
      84           2 :         case *iterLastOp:
      85           2 :                 return &t.iterID, nil, nil
      86           2 :         case *logDataOp:
      87           2 :                 return &t.writerID, nil, []interface{}{&t.data}
      88           2 :         case *mergeOp:
      89           2 :                 return &t.writerID, nil, []interface{}{&t.key, &t.value}
      90           2 :         case *newBatchOp:
      91           2 :                 return &t.dbID, &t.batchID, nil
      92           2 :         case *newIndexedBatchOp:
      93           2 :                 return &t.dbID, &t.batchID, nil
      94           2 :         case *newIterOp:
      95           2 :                 return &t.readerID, &t.iterID, []interface{}{&t.lower, &t.upper, &t.keyTypes, &t.filterMin, &t.filterMax, &t.useL6Filters, &t.maskSuffix}
      96           2 :         case *newIterUsingCloneOp:
      97           2 :                 return &t.existingIterID, &t.iterID, []interface{}{&t.refreshBatch, &t.lower, &t.upper, &t.keyTypes, &t.filterMin, &t.filterMax, &t.useL6Filters, &t.maskSuffix}
      98           2 :         case *newSnapshotOp:
      99           2 :                 return &t.dbID, &t.snapID, []interface{}{&t.bounds}
     100           2 :         case *newExternalObjOp:
     101           2 :                 return &t.batchID, &t.externalObjID, nil
     102           2 :         case *iterNextOp:
     103           2 :                 return &t.iterID, nil, []interface{}{&t.limit}
     104           2 :         case *iterNextPrefixOp:
     105           2 :                 return &t.iterID, nil, nil
     106           2 :         case *iterCanSingleDelOp:
     107           2 :                 return &t.iterID, nil, []interface{}{}
     108           2 :         case *iterPrevOp:
     109           2 :                 return &t.iterID, nil, []interface{}{&t.limit}
     110           2 :         case *iterSeekLTOp:
     111           2 :                 return &t.iterID, nil, []interface{}{&t.key, &t.limit}
     112           2 :         case *iterSeekGEOp:
     113           2 :                 return &t.iterID, nil, []interface{}{&t.key, &t.limit}
     114           2 :         case *iterSeekPrefixGEOp:
     115           2 :                 return &t.iterID, nil, []interface{}{&t.key}
     116           2 :         case *setOp:
     117           2 :                 return &t.writerID, nil, []interface{}{&t.key, &t.value}
     118           2 :         case *iterSetBoundsOp:
     119           2 :                 return &t.iterID, nil, []interface{}{&t.lower, &t.upper}
     120           2 :         case *iterSetOptionsOp:
     121           2 :                 return &t.iterID, nil, []interface{}{&t.lower, &t.upper, &t.keyTypes, &t.filterMin, &t.filterMax, &t.useL6Filters, &t.maskSuffix}
     122           2 :         case *singleDeleteOp:
     123           2 :                 return &t.writerID, nil, []interface{}{&t.key, &t.maybeReplaceDelete}
     124           2 :         case *rangeKeyDeleteOp:
     125           2 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end}
     126           2 :         case *rangeKeySetOp:
     127           2 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end, &t.suffix, &t.value}
     128           2 :         case *rangeKeyUnsetOp:
     129           2 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end, &t.suffix}
     130           2 :         case *replicateOp:
     131           2 :                 return &t.source, nil, []interface{}{&t.dest, &t.start, &t.end}
     132             :         }
     133           0 :         panic(fmt.Sprintf("unsupported op type: %T", op))
     134             : }
     135             : 
     136             : var methods = map[string]*methodInfo{
     137             :         "Apply":                     makeMethod(applyOp{}, dbTag, batchTag),
     138             :         "Checkpoint":                makeMethod(checkpointOp{}, dbTag),
     139             :         "Clone":                     makeMethod(newIterUsingCloneOp{}, iterTag),
     140             :         "Close":                     makeMethod(closeOp{}, dbTag, batchTag, iterTag, snapTag),
     141             :         "Commit":                    makeMethod(batchCommitOp{}, batchTag),
     142             :         "Compact":                   makeMethod(compactOp{}, dbTag),
     143             :         "Delete":                    makeMethod(deleteOp{}, dbTag, batchTag),
     144             :         "DeleteRange":               makeMethod(deleteRangeOp{}, dbTag, batchTag),
     145             :         "Download":                  makeMethod(downloadOp{}, dbTag),
     146             :         "First":                     makeMethod(iterFirstOp{}, iterTag),
     147             :         "Flush":                     makeMethod(flushOp{}, dbTag),
     148             :         "Get":                       makeMethod(getOp{}, dbTag, batchTag, snapTag),
     149             :         "Ingest":                    makeMethod(ingestOp{}, dbTag),
     150             :         "IngestAndExcise":           makeMethod(ingestAndExciseOp{}, dbTag),
     151             :         "IngestExternalFiles":       makeMethod(ingestExternalFilesOp{}, dbTag),
     152             :         "Init":                      makeMethod(initOp{}, dbTag),
     153             :         "Last":                      makeMethod(iterLastOp{}, iterTag),
     154             :         "LogData":                   makeMethod(logDataOp{}, dbTag, batchTag),
     155             :         "Merge":                     makeMethod(mergeOp{}, dbTag, batchTag),
     156             :         "NewBatch":                  makeMethod(newBatchOp{}, dbTag),
     157             :         "NewIndexedBatch":           makeMethod(newIndexedBatchOp{}, dbTag),
     158             :         "NewIter":                   makeMethod(newIterOp{}, dbTag, batchTag, snapTag),
     159             :         "NewSnapshot":               makeMethod(newSnapshotOp{}, dbTag),
     160             :         "NewExternalObj":            makeMethod(newExternalObjOp{}, batchTag),
     161             :         "Next":                      makeMethod(iterNextOp{}, iterTag),
     162             :         "NextPrefix":                makeMethod(iterNextPrefixOp{}, iterTag),
     163             :         "InternalNext":              makeMethod(iterCanSingleDelOp{}, iterTag),
     164             :         "Prev":                      makeMethod(iterPrevOp{}, iterTag),
     165             :         "RangeKeyDelete":            makeMethod(rangeKeyDeleteOp{}, dbTag, batchTag),
     166             :         "RangeKeySet":               makeMethod(rangeKeySetOp{}, dbTag, batchTag),
     167             :         "RangeKeyUnset":             makeMethod(rangeKeyUnsetOp{}, dbTag, batchTag),
     168             :         "RatchetFormatMajorVersion": makeMethod(dbRatchetFormatMajorVersionOp{}, dbTag),
     169             :         "Replicate":                 makeMethod(replicateOp{}, dbTag),
     170             :         "Restart":                   makeMethod(dbRestartOp{}, dbTag),
     171             :         "SeekGE":                    makeMethod(iterSeekGEOp{}, iterTag),
     172             :         "SeekLT":                    makeMethod(iterSeekLTOp{}, iterTag),
     173             :         "SeekPrefixGE":              makeMethod(iterSeekPrefixGEOp{}, iterTag),
     174             :         "Set":                       makeMethod(setOp{}, dbTag, batchTag),
     175             :         "SetBounds":                 makeMethod(iterSetBoundsOp{}, iterTag),
     176             :         "SetOptions":                makeMethod(iterSetOptionsOp{}, iterTag),
     177             :         "SingleDelete":              makeMethod(singleDeleteOp{}, dbTag, batchTag),
     178             : }
     179             : 
     180             : type parser struct {
     181             :         opts parserOpts
     182             :         fset *token.FileSet
     183             :         s    scanner.Scanner
     184             :         objs map[objID]bool
     185             : }
     186             : 
     187             : type parserOpts struct {
     188             :         allowUndefinedObjs bool
     189             : }
     190             : 
     191           2 : func parse(src []byte, opts parserOpts) (_ []op, err error) {
     192           2 :         // Various bits of magic incantation to set up a scanner for Go compatible
     193           2 :         // syntax. We arranged for the textual format of ops (e.g. op.String()) to
     194           2 :         // look like Go which allows us to use the Go scanner for parsing.
     195           2 :         p := &parser{
     196           2 :                 opts: opts,
     197           2 :                 fset: token.NewFileSet(),
     198           2 :                 objs: map[objID]bool{makeObjID(dbTag, 1): true, makeObjID(dbTag, 2): true},
     199           2 :         }
     200           2 :         file := p.fset.AddFile("", -1, len(src))
     201           2 :         p.s.Init(file, src, nil /* no error handler */, 0)
     202           2 :         return p.parse()
     203           2 : }
     204             : 
     205           2 : func (p *parser) parse() (_ []op, err error) {
     206           2 :         defer func() {
     207           2 :                 if r := recover(); r != nil {
     208           1 :                         var ok bool
     209           1 :                         if err, ok = r.(error); ok {
     210           1 :                                 return
     211           1 :                         }
     212           0 :                         err = errors.Errorf("%v", r)
     213             :                 }
     214             :         }()
     215             : 
     216           2 :         var ops []op
     217           2 :         for {
     218           2 :                 op := p.parseOp()
     219           2 :                 if op == nil {
     220           2 :                         computeDerivedFields(ops)
     221           2 :                         return ops, nil
     222           2 :                 }
     223           2 :                 ops = append(ops, op)
     224             :         }
     225             : }
     226             : 
     227           2 : func (p *parser) parseOp() op {
     228           2 :         destPos, destTok, destLit := p.s.Scan()
     229           2 :         if destTok == token.EOF {
     230           2 :                 return nil
     231           2 :         }
     232           2 :         if destTok != token.IDENT {
     233           1 :                 panic(p.errorf(destPos, "unexpected token: %s %q", destTok, destLit))
     234             :         }
     235           2 :         if destLit == "Init" {
     236           2 :                 // <op>(<args>)
     237           2 :                 return p.makeOp(destLit, makeObjID(dbTag, 1), 0, destPos)
     238           2 :         }
     239             : 
     240           2 :         destID := p.parseObjID(destPos, destLit)
     241           2 : 
     242           2 :         pos, tok, lit := p.s.Scan()
     243           2 :         switch tok {
     244           2 :         case token.PERIOD:
     245           2 :                 // <obj>.<op>(<args>)
     246           2 :                 if !p.objs[destID] {
     247           1 :                         if p.opts.allowUndefinedObjs {
     248           1 :                                 p.objs[destID] = true
     249           1 :                         } else {
     250           0 :                                 panic(p.errorf(destPos, "unknown object: %s", destID))
     251             :                         }
     252             :                 }
     253           2 :                 _, methodLit := p.scanToken(token.IDENT)
     254           2 :                 return p.makeOp(methodLit, destID, 0, destPos)
     255             : 
     256           2 :         case token.ASSIGN:
     257           2 :                 // <obj> = <obj>.<op>(<args>)
     258           2 :                 srcPos, srcLit := p.scanToken(token.IDENT)
     259           2 :                 srcID := p.parseObjID(srcPos, srcLit)
     260           2 :                 if !p.objs[srcID] {
     261           0 :                         if p.opts.allowUndefinedObjs {
     262           0 :                                 p.objs[srcID] = true
     263           0 :                         } else {
     264           0 :                                 panic(p.errorf(srcPos, "unknown object %q", srcLit))
     265             :                         }
     266             :                 }
     267           2 :                 p.scanToken(token.PERIOD)
     268           2 :                 _, methodLit := p.scanToken(token.IDENT)
     269           2 :                 p.objs[destID] = true
     270           2 :                 return p.makeOp(methodLit, srcID, destID, srcPos)
     271             :         }
     272           0 :         panic(p.errorf(pos, "unexpected token: %q", p.tokenf(tok, lit)))
     273             : }
     274             : 
     275           2 : func (p *parser) parseObjID(pos token.Pos, str string) objID {
     276           2 :         id, err := parseObjID(str)
     277           2 :         if err != nil {
     278           1 :                 panic(p.errorf(pos, "%s", err))
     279             :         }
     280           2 :         return id
     281             : }
     282             : 
     283           2 : func unquoteBytes(lit string) []byte {
     284           2 :         s, err := strconv.Unquote(lit)
     285           2 :         if err != nil {
     286           0 :                 panic(err)
     287             :         }
     288           2 :         if len(s) == 0 {
     289           2 :                 return nil
     290           2 :         }
     291           2 :         return []byte(s)
     292             : }
     293             : 
     294           2 : func (p *parser) parseArgs(op op, methodName string, args []interface{}) {
     295           2 :         pos, list := p.parseList()
     296           2 :         p.scanToken(token.SEMICOLON)
     297           2 : 
     298           2 :         // The last argument can have variable length.
     299           2 :         var varArg interface{}
     300           2 :         if len(args) > 0 {
     301           2 :                 switch args[len(args)-1].(type) {
     302           2 :                 case *[]objID, *[]pebble.KeyRange, *[]pebble.CheckpointSpan, *[]pebble.DownloadSpan, *[]externalObjWithBounds:
     303           2 :                         varArg = args[len(args)-1]
     304           2 :                         args = args[:len(args)-1]
     305             :                 }
     306             :         }
     307             : 
     308           2 :         if len(list) < len(args) {
     309           1 :                 panic(p.errorf(pos, "%s: not enough arguments", methodName))
     310             :         }
     311           2 :         if len(list) > len(args) && varArg == nil {
     312           0 :                 panic(p.errorf(pos, "%s: too many arguments", methodName))
     313             :         }
     314             : 
     315           2 :         for i := range args {
     316           2 :                 elem := list[i]
     317           2 :                 switch t := args[i].(type) {
     318           2 :                 case *uint32:
     319           2 :                         elem.expectToken(p, token.INT)
     320           2 :                         val, err := strconv.ParseUint(elem.lit, 10, 32)
     321           2 :                         if err != nil {
     322           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     323             :                         }
     324           2 :                         *t = uint32(val)
     325             : 
     326           2 :                 case *uint64:
     327           2 :                         elem.expectToken(p, token.INT)
     328           2 :                         val, err := strconv.ParseUint(elem.lit, 10, 64)
     329           2 :                         if err != nil {
     330           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     331             :                         }
     332           2 :                         *t = val
     333             : 
     334           2 :                 case *[]byte:
     335           2 :                         elem.expectToken(p, token.STRING)
     336           2 :                         *t = unquoteBytes(elem.lit)
     337             : 
     338           2 :                 case *bool:
     339           2 :                         elem.expectToken(p, token.IDENT)
     340           2 :                         b, err := strconv.ParseBool(elem.lit)
     341           2 :                         if err != nil {
     342           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     343             :                         }
     344           2 :                         *t = b
     345             : 
     346           2 :                 case *objID:
     347           2 :                         elem.expectToken(p, token.IDENT)
     348           2 :                         *t = p.parseObjID(elem.pos, elem.lit)
     349             : 
     350           2 :                 case *pebble.FormatMajorVersion:
     351           2 :                         elem.expectToken(p, token.INT)
     352           2 :                         val, err := strconv.ParseUint(elem.lit, 10, 64)
     353           2 :                         if err != nil {
     354           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     355             :                         }
     356           2 :                         *t = pebble.FormatMajorVersion(val)
     357             : 
     358           0 :                 default:
     359           0 :                         panic(p.errorf(pos, "%s: unsupported arg[%d] type: %T", methodName, i, args[i]))
     360             :                 }
     361             :         }
     362             : 
     363           2 :         if varArg != nil {
     364           2 :                 list = list[len(args):]
     365           2 :                 switch t := varArg.(type) {
     366           2 :                 case *[]objID:
     367           2 :                         *t = p.parseObjIDs(list)
     368           2 :                 case *[]pebble.KeyRange:
     369           2 :                         *t = p.parseKeyRanges(list)
     370           2 :                 case *[]pebble.CheckpointSpan:
     371           2 :                         *t = p.parseCheckpointSpans(list)
     372           2 :                 case *[]pebble.DownloadSpan:
     373           2 :                         *t = p.parseDownloadSpans(list)
     374           2 :                 case *[]externalObjWithBounds:
     375           2 :                         *t = p.parseExternalObjsWithBounds(list)
     376           0 :                 default:
     377           0 :                         // We already checked for these types when we set varArgs.
     378           0 :                         panic("unreachable")
     379             :                 }
     380             :         }
     381             : }
     382             : 
     383             : type listElem struct {
     384             :         pos token.Pos
     385             :         tok token.Token
     386             :         lit string
     387             : }
     388             : 
     389           2 : func (e listElem) expectToken(p *parser, expTok token.Token) {
     390           2 :         if e.tok != expTok {
     391           0 :                 panic(p.errorf(e.pos, "unexpected token: %q", p.tokenf(e.tok, e.lit)))
     392             :         }
     393             : }
     394             : 
     395             : // pop checks that the first element of the list matches the expected token,
     396             : // removes it from the list and returns the literal.
     397           2 : func (p *parser) pop(list *[]listElem, expTok token.Token) string {
     398           2 :         (*list)[0].expectToken(p, expTok)
     399           2 :         lit := (*list)[0].lit
     400           2 :         (*list) = (*list)[1:]
     401           2 :         return lit
     402           2 : }
     403             : 
     404             : // parseKeyRange parses an arbitrary number of comma separated STRING/IDENT/INT
     405             : // tokens surrounded by parens.
     406           2 : func (p *parser) parseList() (token.Pos, []listElem) {
     407           2 :         p.scanToken(token.LPAREN)
     408           2 :         var list []listElem
     409           2 :         for {
     410           2 :                 pos, tok, lit := p.s.Scan()
     411           2 :                 if len(list) == 0 && tok == token.RPAREN {
     412           2 :                         return pos, nil
     413           2 :                 }
     414             : 
     415           2 :                 switch tok {
     416           2 :                 case token.STRING, token.IDENT, token.INT:
     417           2 :                         list = append(list, listElem{
     418           2 :                                 pos: pos,
     419           2 :                                 tok: tok,
     420           2 :                                 lit: lit,
     421           2 :                         })
     422           2 :                         pos, tok, lit = p.s.Scan()
     423           2 :                         switch tok {
     424           2 :                         case token.COMMA:
     425           2 :                                 continue
     426           2 :                         case token.RPAREN:
     427           2 :                                 return pos, list
     428             :                         }
     429             :                 }
     430           0 :                 panic(p.errorf(pos, "unexpected token: %q", p.tokenf(tok, lit)))
     431             :         }
     432             : }
     433             : 
     434           2 : func (p *parser) parseObjIDs(list []listElem) []objID {
     435           2 :         res := make([]objID, len(list))
     436           2 :         for i, elem := range list {
     437           2 :                 res[i] = p.parseObjID(elem.pos, elem.lit)
     438           2 :         }
     439           2 :         return res
     440             : }
     441             : 
     442           2 : func (p *parser) parseKeys(list []listElem) [][]byte {
     443           2 :         res := make([][]byte, len(list))
     444           2 :         for i := range res {
     445           2 :                 res[i] = unquoteBytes(p.pop(&list, token.STRING))
     446           2 :         }
     447           2 :         return res
     448             : }
     449             : 
     450           2 : func (p *parser) parseKeyRanges(list []listElem) []pebble.KeyRange {
     451           2 :         keys := p.parseKeys(list)
     452           2 :         if len(keys)%2 != 0 {
     453           0 :                 panic(p.errorf(list[0].pos, "expected even number of keys"))
     454             :         }
     455           2 :         res := make([]pebble.KeyRange, len(keys)/2)
     456           2 :         for i := range res {
     457           2 :                 res[i].Start = keys[2*i]
     458           2 :                 res[i].End = keys[2*i+1]
     459           2 :         }
     460           2 :         return res
     461             : }
     462             : 
     463           2 : func (p *parser) parseCheckpointSpans(list []listElem) []pebble.CheckpointSpan {
     464           2 :         keys := p.parseKeys(list)
     465           2 :         if len(keys)%2 == 1 {
     466           0 :                 panic(p.errorf(list[0].pos, "expected even number of keys"))
     467             :         }
     468           2 :         if len(keys) == 0 {
     469           2 :                 // Necessary for round-trip tests which differentiate between nil and empty slice.
     470           2 :                 return nil
     471           2 :         }
     472           2 :         res := make([]pebble.CheckpointSpan, len(keys)/2)
     473           2 :         for i := range res {
     474           2 :                 res[i] = pebble.CheckpointSpan{
     475           2 :                         Start: keys[i*2],
     476           2 :                         End:   keys[i*2+1],
     477           2 :                 }
     478           2 :         }
     479           2 :         return res
     480             : }
     481             : 
     482           2 : func (p *parser) parseDownloadSpans(list []listElem) []pebble.DownloadSpan {
     483           2 :         if len(list)%3 != 0 {
     484           0 :                 panic(p.errorf(list[0].pos, "expected 3k args"))
     485             :         }
     486           2 :         res := make([]pebble.DownloadSpan, len(list)/3)
     487           2 :         for i := range res {
     488           2 :                 res[i] = pebble.DownloadSpan{
     489           2 :                         StartKey:               unquoteBytes(p.pop(&list, token.STRING)),
     490           2 :                         EndKey:                 unquoteBytes(p.pop(&list, token.STRING)),
     491           2 :                         ViaBackingFileDownload: p.pop(&list, token.IDENT) == "true",
     492           2 :                 }
     493           2 :         }
     494           2 :         return res
     495             : }
     496             : 
     497           2 : func (p *parser) parseExternalObjsWithBounds(list []listElem) []externalObjWithBounds {
     498           2 :         const numArgs = 5
     499           2 :         if len(list)%5 != 0 {
     500           0 :                 panic(p.errorf(list[0].pos, "expected number of arguments to be multiple of %d", numArgs))
     501             :         }
     502           2 :         objs := make([]externalObjWithBounds, len(list)/numArgs)
     503           2 :         for i := range objs {
     504           2 :                 pos := list[0].pos
     505           2 :                 objs[i] = externalObjWithBounds{
     506           2 :                         externalObjID: p.parseObjID(pos, p.pop(&list, token.IDENT)),
     507           2 :                         bounds: pebble.KeyRange{
     508           2 :                                 Start: unquoteBytes(p.pop(&list, token.STRING)),
     509           2 :                                 End:   unquoteBytes(p.pop(&list, token.STRING)),
     510           2 :                         },
     511           2 :                 }
     512           2 :                 if syntheticSuffix := unquoteBytes(p.pop(&list, token.STRING)); len(syntheticSuffix) > 0 {
     513           2 :                         objs[i].syntheticSuffix = syntheticSuffix
     514           2 :                 }
     515             : 
     516           2 :                 syntheticPrefix := unquoteBytes(p.pop(&list, token.STRING))
     517           2 :                 if len(syntheticPrefix) > 0 {
     518           2 :                         if !bytes.HasPrefix(objs[i].bounds.Start, syntheticPrefix) {
     519           0 :                                 panic(fmt.Sprintf("invalid synthetic prefix %q %q", objs[i].bounds.Start, syntheticPrefix))
     520             :                         }
     521           2 :                         if !bytes.HasPrefix(objs[i].bounds.End, syntheticPrefix) {
     522           0 :                                 panic(fmt.Sprintf("invalid synthetic prefix %q %q", objs[i].bounds.End, syntheticPrefix))
     523             :                         }
     524           2 :                         objs[i].syntheticPrefix = syntheticPrefix
     525             :                 }
     526             :         }
     527           2 :         return objs
     528             : }
     529             : 
     530           2 : func (p *parser) scanToken(expected token.Token) (pos token.Pos, lit string) {
     531           2 :         pos, tok, lit := p.s.Scan()
     532           2 :         if tok != expected {
     533           0 :                 panic(p.errorf(pos, "unexpected token: %q", p.tokenf(tok, lit)))
     534             :         }
     535           2 :         return pos, lit
     536             : }
     537             : 
     538           2 : func (p *parser) makeOp(methodName string, receiverID, targetID objID, pos token.Pos) op {
     539           2 :         info := methods[methodName]
     540           2 :         if info == nil {
     541           1 :                 panic(p.errorf(pos, "unknown op %s.%s", receiverID, methodName))
     542             :         }
     543           2 :         if info.validTags&(1<<receiverID.tag()) == 0 {
     544           1 :                 panic(p.errorf(pos, "%s.%s: %s is not a method on %s",
     545           1 :                         receiverID, methodName, methodName, receiverID))
     546             :         }
     547             : 
     548           2 :         op := info.constructor()
     549           2 :         receiver, target, args := opArgs(op)
     550           2 : 
     551           2 :         // The form of an operation is:
     552           2 :         //   [target =] receiver.method(args)
     553           2 :         //
     554           2 :         // The receiver is the object the operation will be called on, which can be
     555           2 :         // any valid ID. Certain operations such as Ingest are only valid on the DB
     556           2 :         // object. That is indicated by opArgs returning a nil receiver.
     557           2 :         if receiver != nil {
     558           2 :                 *receiver = receiverID
     559           2 :         } else if receiverID.tag() != dbTag {
     560           0 :                 panic(p.errorf(pos, "unknown op %s.%s", receiverID, methodName))
     561             :         }
     562             : 
     563             :         // The target is the object that will be assigned the result of an object
     564             :         // creation operation such as newBatchOp or newIterOp.
     565           2 :         if target != nil {
     566           2 :                 // It is invalid to not have a targetID for a method which generates a new
     567           2 :                 // object.
     568           2 :                 if targetID == 0 {
     569           1 :                         panic(p.errorf(pos, "assignment expected for %s.%s", receiverID, methodName))
     570             :                 }
     571             :                 // It is invalid to try to assign to the DB object.
     572           2 :                 if targetID.tag() == dbTag {
     573           0 :                         panic(p.errorf(pos, "cannot use %s as target of assignment", targetID))
     574             :                 }
     575           2 :                 *target = targetID
     576           2 :         } else if targetID != 0 {
     577           1 :                 panic(p.errorf(pos, "cannot use %s.%s in assignment", receiverID, methodName))
     578             :         }
     579             : 
     580           2 :         p.parseArgs(op, methodName, args)
     581           2 :         return op
     582             : }
     583             : 
     584           0 : func (p *parser) tokenf(tok token.Token, lit string) string {
     585           0 :         if tok.IsLiteral() {
     586           0 :                 return lit
     587           0 :         }
     588           0 :         return tok.String()
     589             : }
     590             : 
     591           1 : func (p *parser) errorf(pos token.Pos, format string, args ...interface{}) error {
     592           1 :         return errors.New("metamorphic test internal error: " + p.fset.Position(pos).String() + ": " + fmt.Sprintf(format, args...))
     593           1 : }
     594             : 
     595             : // computeDerivedFields makes one pass through the provided operations, filling
     596             : // any derived fields. This pass must happen before execution because concurrent
     597             : // execution depends on these fields.
     598           2 : func computeDerivedFields(ops []op) {
     599           2 :         iterToReader := make(map[objID]objID)
     600           2 :         objToDB := make(map[objID]objID)
     601           2 :         for i := range ops {
     602           2 :                 switch v := ops[i].(type) {
     603           2 :                 case *newSnapshotOp:
     604           2 :                         objToDB[v.snapID] = v.dbID
     605           2 :                 case *newIterOp:
     606           2 :                         iterToReader[v.iterID] = v.readerID
     607           2 :                         dbReaderID := v.readerID
     608           2 :                         if dbReaderID.tag() != dbTag {
     609           2 :                                 dbReaderID = objToDB[dbReaderID]
     610           2 :                         }
     611           2 :                         objToDB[v.iterID] = dbReaderID
     612           2 :                         v.derivedDBID = dbReaderID
     613           2 :                 case *newIterUsingCloneOp:
     614           2 :                         v.derivedReaderID = iterToReader[v.existingIterID]
     615           2 :                         iterToReader[v.iterID] = v.derivedReaderID
     616           2 :                         objToDB[v.iterID] = objToDB[v.existingIterID]
     617           2 :                 case *iterSetOptionsOp:
     618           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     619           2 :                 case *iterFirstOp:
     620           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     621           2 :                 case *iterLastOp:
     622           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     623           2 :                 case *iterSeekGEOp:
     624           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     625           2 :                 case *iterSeekPrefixGEOp:
     626           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     627           2 :                 case *iterSeekLTOp:
     628           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     629           2 :                 case *iterNextOp:
     630           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     631           2 :                 case *iterNextPrefixOp:
     632           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     633           2 :                 case *iterCanSingleDelOp:
     634           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     635           2 :                 case *iterPrevOp:
     636           2 :                         v.derivedReaderID = iterToReader[v.iterID]
     637           2 :                 case *newBatchOp:
     638           2 :                         objToDB[v.batchID] = v.dbID
     639           2 :                 case *newIndexedBatchOp:
     640           2 :                         objToDB[v.batchID] = v.dbID
     641           2 :                 case *applyOp:
     642           2 :                         if derivedDBID, ok := objToDB[v.batchID]; ok && v.writerID.tag() != dbTag {
     643           2 :                                 objToDB[v.writerID] = derivedDBID
     644           2 :                         }
     645           2 :                 case *getOp:
     646           2 :                         if derivedDBID, ok := objToDB[v.readerID]; ok {
     647           2 :                                 v.derivedDBID = derivedDBID
     648           2 :                         }
     649           2 :                 case *batchCommitOp:
     650           2 :                         v.dbID = objToDB[v.batchID]
     651           2 :                 case *closeOp:
     652           2 :                         if v.objID.tag() == dbTag {
     653           2 :                                 // Find all objects that use this db.
     654           2 :                                 v.affectedObjects = nil
     655           2 :                                 for obj, db := range objToDB {
     656           2 :                                         if db == v.objID {
     657           2 :                                                 v.affectedObjects = append(v.affectedObjects, obj)
     658           2 :                                         }
     659             :                                 }
     660             :                                 // Sort so the output is deterministic.
     661           2 :                                 slices.Sort(v.affectedObjects)
     662           2 :                         } else if dbID, ok := objToDB[v.objID]; ok {
     663           2 :                                 v.affectedObjects = objIDSlice{dbID}
     664           2 :                         }
     665           2 :                 case *dbRestartOp:
     666           2 :                         // Find all objects that use this db.
     667           2 :                         v.affectedObjects = nil
     668           2 :                         for obj, db := range objToDB {
     669           2 :                                 if db == v.dbID {
     670           2 :                                         v.affectedObjects = append(v.affectedObjects, obj)
     671           2 :                                 }
     672             :                         }
     673             :                         // Sort so the output is deterministic.
     674           2 :                         slices.Sort(v.affectedObjects)
     675           2 :                 case *ingestOp:
     676           2 :                         v.derivedDBIDs = make([]objID, len(v.batchIDs))
     677           2 :                         for i := range v.batchIDs {
     678           2 :                                 v.derivedDBIDs[i] = objToDB[v.batchIDs[i]]
     679           2 :                         }
     680           2 :                 case *ingestAndExciseOp:
     681           2 :                         v.derivedDBID = objToDB[v.batchID]
     682           2 :                 case *deleteOp:
     683           2 :                         derivedDBID := v.writerID
     684           2 :                         if v.writerID.tag() != dbTag {
     685           2 :                                 derivedDBID = objToDB[v.writerID]
     686           2 :                         }
     687           2 :                         v.derivedDBID = derivedDBID
     688             :                 }
     689             :         }
     690             : }

Generated by: LCOV version 1.14