github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/keysutil/keys.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 keysutil 12 13 import ( 14 "fmt" 15 "strings" 16 17 "github.com/cockroachdb/cockroach/pkg/keys" 18 "github.com/cockroachdb/cockroach/pkg/roachpb" 19 "github.com/cockroachdb/errors" 20 ) 21 22 // PrettyScanner implements a partial right inverse to keys.PrettyPrint(): it 23 // takes a key formatted for human consumption and attempts to translate it into 24 // a roachpb.Key. Not all key types are supported, but a function for decoding 25 // the SQL table space can be provided (to replace the weak default one). 26 // 27 // No optimization has been performed. This is intended for use in debugging and 28 // tests only. 29 type PrettyScanner struct { 30 // keyComprehension contains pointers to scanner routines for pretty-printed 31 // keys from different regions of the key space. 32 keyComprehension keys.KeyComprehensionTable 33 // validateRoundTrip, if set, 34 // makes the scanner validate that calling PrettyPrint on the result yields 35 // the scan's input. 36 validateRoundTrip bool 37 } 38 39 // MakePrettyScanner creates a PrettyScanner. 40 // 41 // If tableParser is not nil, it will replace the default function for scanning 42 // pretty-printed keys from the table part of the keys space (i.e. inputs 43 // starting with "/Table"). The supplied function needs to parse the part that 44 // comes after "/Table". 45 func MakePrettyScanner(tableParser keys.KeyParserFunc) PrettyScanner { 46 dict := keys.KeyDict 47 if tableParser != nil { 48 dict = customizeKeyComprehension(dict, tableParser) 49 } 50 return PrettyScanner{ 51 keyComprehension: dict, 52 // If we specified a custom parser, forget about the roundtrip. 53 validateRoundTrip: tableParser == nil, 54 } 55 } 56 57 // customizeKeyComprehension takes as input a KeyComprehensionTable and 58 // overwrites the "pretty scanner" function for the tables key space (i.e. for 59 // keys starting with "/Table"). The modified table is returned. 60 func customizeKeyComprehension( 61 table keys.KeyComprehensionTable, tableParser keys.KeyParserFunc, 62 ) keys.KeyComprehensionTable { 63 // Make a deep copy of the table. 64 cpy := make(keys.KeyComprehensionTable, len(table)) 65 copy(cpy, table) 66 for i := range table { 67 cpy[i].Entries = make([]keys.DictEntry, len(cpy[i].Entries)) 68 copy(cpy[i].Entries, table[i].Entries) 69 } 70 table = cpy 71 72 // Find the part of the table that deals with parsing table data. 73 // We'll perform surgery on it to apply `tableParser`. 74 for i := range table { 75 region := &table[i] 76 if region.Name == "/Table" { 77 if len(region.Entries) != 1 { 78 panic(fmt.Sprintf("expected a single entry under \"/Table\", got: %d", len(region.Entries))) 79 } 80 subRegion := ®ion.Entries[0] 81 subRegion.PSFunc = tableParser 82 return table 83 } 84 } 85 panic("failed to find required \"/Table\" entry") 86 } 87 88 // Scan is a partial right inverse to PrettyPrint: it takes a key formatted for 89 // human consumption and attempts to translate it into a roachpb.Key. Not all 90 // key types are supported and no optimization has been performed. This is 91 // intended for use in debugging and tests only. 92 func (s PrettyScanner) Scan(input string) (_ roachpb.Key, rErr error) { 93 defer func() { 94 if r := recover(); r != nil { 95 if err, ok := r.(error); ok { 96 rErr = err 97 return 98 } 99 rErr = errors.Errorf("%v", r) 100 } 101 }() 102 103 origInput := input 104 var output roachpb.Key 105 106 mkErr := func(err error) (roachpb.Key, error) { 107 if err == nil { 108 err = errIllegalInput 109 } 110 err = errors.Errorf(`can't parse "%s" after reading %s: %s`, 111 input, origInput[:len(origInput)-len(input)], err) 112 return nil, &keys.ErrUglifyUnsupported{Wrapped: err} 113 } 114 115 var entries []keys.DictEntry // nil if not pinned to a subrange 116 outer: 117 for len(input) > 0 { 118 if entries != nil { 119 for _, v := range entries { 120 if strings.HasPrefix(input, v.Name) { 121 input = input[len(v.Name):] 122 if v.PSFunc == nil { 123 return mkErr(nil) 124 } 125 remainder, key := v.PSFunc(input) 126 input = remainder 127 output = append(output, key...) 128 entries = nil 129 continue outer 130 } 131 } 132 return nil, &keys.ErrUglifyUnsupported{ 133 Wrapped: errors.New("known key, but unsupported subtype"), 134 } 135 } 136 for _, v := range keys.ConstKeyDict { 137 if strings.HasPrefix(input, v.Name) { 138 output = append(output, v.Value...) 139 input = input[len(v.Name):] 140 continue outer 141 } 142 } 143 for _, v := range s.keyComprehension { 144 if strings.HasPrefix(input, v.Name) { 145 // No appending to output yet, the dictionary will take care of 146 // it. 147 input = input[len(v.Name):] 148 entries = v.Entries 149 continue outer 150 } 151 } 152 return mkErr(errors.New("can't handle key")) 153 } 154 if s.validateRoundTrip { 155 if out := keys.PrettyPrint(nil /* valDirs */, output); out != origInput { 156 return nil, errors.Errorf("constructed key deviates from original: %s vs %s", out, origInput) 157 } 158 } 159 return output, nil 160 } 161 162 var errIllegalInput = errors.New("illegal input")