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 := &region.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")