github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/keys/printer.go (about) 1 // Copyright 2015 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 keys 12 13 import ( 14 "bytes" 15 "fmt" 16 "strconv" 17 "strings" 18 19 "github.com/cockroachdb/cockroach/pkg/roachpb" 20 "github.com/cockroachdb/cockroach/pkg/util/encoding" 21 "github.com/cockroachdb/cockroach/pkg/util/uuid" 22 "github.com/cockroachdb/errors" 23 ) 24 25 // PrettyPrintTimeseriesKey is a hook for pretty printing a timeseries key. The 26 // timeseries key prefix will already have been stripped off. 27 var PrettyPrintTimeseriesKey func(key roachpb.Key) string 28 29 // DictEntry contains info on pretty-printing and pretty-scanning keys in a 30 // region of the key space. 31 type DictEntry struct { 32 Name string 33 prefix roachpb.Key 34 // print the key's pretty value, key has been removed prefix data 35 ppFunc func(valDirs []encoding.Direction, key roachpb.Key) string 36 // PSFunc parses the relevant prefix of the input into a roachpb.Key, 37 // returning the remainder and the key corresponding to the consumed prefix of 38 // 'input'. Allowed to panic on errors. 39 PSFunc KeyParserFunc 40 } 41 42 // KeyParserFunc is a function able to reverse pretty-printed keys. 43 type KeyParserFunc func(input string) (string, roachpb.Key) 44 45 func parseUnsupported(_ string) (string, roachpb.Key) { 46 panic(&ErrUglifyUnsupported{}) 47 } 48 49 // KeyComprehensionTable contains information about how to decode pretty-printed 50 // keys, split by key spans. 51 type KeyComprehensionTable []struct { 52 Name string 53 start roachpb.Key 54 end roachpb.Key 55 Entries []DictEntry 56 } 57 58 var ( 59 // ConstKeyDict translates some pretty-printed keys. 60 ConstKeyDict = []struct { 61 Name string 62 Value roachpb.Key 63 }{ 64 {"/Max", MaxKey}, 65 {"/Min", MinKey}, 66 {"/Meta1/Max", Meta1KeyMax}, 67 {"/Meta2/Max", Meta2KeyMax}, 68 } 69 70 // KeyDict drives the pretty-printing and pretty-scanning of the key space. 71 KeyDict = KeyComprehensionTable{ 72 {Name: "/Local", start: localPrefix, end: LocalMax, Entries: []DictEntry{ 73 {Name: "/Store", prefix: roachpb.Key(localStorePrefix), 74 ppFunc: localStoreKeyPrint, PSFunc: localStoreKeyParse}, 75 {Name: "/RangeID", prefix: roachpb.Key(LocalRangeIDPrefix), 76 ppFunc: localRangeIDKeyPrint, PSFunc: localRangeIDKeyParse}, 77 {Name: "/Range", prefix: LocalRangePrefix, ppFunc: localRangeKeyPrint, 78 PSFunc: parseUnsupported}, 79 }}, 80 {Name: "/Meta1", start: Meta1Prefix, end: Meta1KeyMax, Entries: []DictEntry{ 81 {Name: "", prefix: Meta1Prefix, ppFunc: print, 82 PSFunc: func(input string) (string, roachpb.Key) { 83 input = mustShiftSlash(input) 84 unq, err := strconv.Unquote(input) 85 if err != nil { 86 panic(err) 87 } 88 if len(unq) == 0 { 89 return "", Meta1Prefix 90 } 91 return "", RangeMetaKey(RangeMetaKey(MustAddr( 92 roachpb.Key(unq)))).AsRawKey() 93 }, 94 }}, 95 }, 96 {Name: "/Meta2", start: Meta2Prefix, end: Meta2KeyMax, Entries: []DictEntry{ 97 {Name: "", prefix: Meta2Prefix, ppFunc: print, 98 PSFunc: func(input string) (string, roachpb.Key) { 99 input = mustShiftSlash(input) 100 unq, err := strconv.Unquote(input) 101 if err != nil { 102 panic(&ErrUglifyUnsupported{err}) 103 } 104 if len(unq) == 0 { 105 return "", Meta2Prefix 106 } 107 return "", RangeMetaKey(MustAddr(roachpb.Key(unq))).AsRawKey() 108 }, 109 }}, 110 }, 111 {Name: "/System", start: SystemPrefix, end: SystemMax, Entries: []DictEntry{ 112 {Name: "/NodeLiveness", prefix: NodeLivenessPrefix, 113 ppFunc: decodeKeyPrint, 114 PSFunc: parseUnsupported, 115 }, 116 {Name: "/NodeLivenessMax", prefix: NodeLivenessKeyMax, 117 ppFunc: decodeKeyPrint, 118 PSFunc: parseUnsupported, 119 }, 120 {Name: "/StatusNode", prefix: StatusNodePrefix, 121 ppFunc: decodeKeyPrint, 122 PSFunc: parseUnsupported, 123 }, 124 {Name: "/tsd", prefix: TimeseriesPrefix, 125 ppFunc: timeseriesKeyPrint, 126 PSFunc: parseUnsupported, 127 }, 128 }}, 129 {Name: "/NamespaceTable", start: NamespaceTableMin, end: NamespaceTableMax, Entries: []DictEntry{ 130 {Name: "", prefix: nil, ppFunc: decodeKeyPrint, PSFunc: parseUnsupported}, 131 }}, 132 {Name: "/Table", start: TableDataMin, end: TableDataMax, Entries: []DictEntry{ 133 {Name: "", prefix: nil, ppFunc: decodeKeyPrint, PSFunc: tableKeyParse}, 134 }}, 135 {Name: "/Tenant", start: TenantTableDataMin, end: TenantTableDataMax, Entries: []DictEntry{ 136 {Name: "", prefix: nil, ppFunc: tenantKeyPrint, PSFunc: tenantKeyParse}, 137 }}, 138 } 139 140 // keyofKeyDict means the key of suffix which is itself a key, 141 // should recursively pretty print it, see issue #3228 142 keyOfKeyDict = []struct { 143 name string 144 prefix []byte 145 }{ 146 {name: "/Meta2", prefix: Meta2Prefix}, 147 {name: "/Meta1", prefix: Meta1Prefix}, 148 } 149 150 rangeIDSuffixDict = []struct { 151 name string 152 suffix []byte 153 ppFunc func(key roachpb.Key) string 154 psFunc func(rangeID roachpb.RangeID, input string) (string, roachpb.Key) 155 }{ 156 {name: "AbortSpan", suffix: LocalAbortSpanSuffix, ppFunc: abortSpanKeyPrint, psFunc: abortSpanKeyParse}, 157 {name: "RangeTombstone", suffix: LocalRangeTombstoneSuffix}, 158 {name: "RaftHardState", suffix: LocalRaftHardStateSuffix}, 159 {name: "RangeAppliedState", suffix: LocalRangeAppliedStateSuffix}, 160 {name: "RaftAppliedIndex", suffix: LocalRaftAppliedIndexLegacySuffix}, 161 {name: "LeaseAppliedIndex", suffix: LocalLeaseAppliedIndexLegacySuffix}, 162 {name: "RaftLog", suffix: LocalRaftLogSuffix, 163 ppFunc: raftLogKeyPrint, 164 psFunc: raftLogKeyParse, 165 }, 166 {name: "RaftTruncatedState", suffix: LocalRaftTruncatedStateLegacySuffix}, 167 {name: "RangeLastReplicaGCTimestamp", suffix: LocalRangeLastReplicaGCTimestampSuffix}, 168 {name: "RangeLease", suffix: LocalRangeLeaseSuffix}, 169 {name: "RangeStats", suffix: LocalRangeStatsLegacySuffix}, 170 {name: "RangeLastGC", suffix: LocalRangeLastGCSuffix}, 171 } 172 173 rangeSuffixDict = []struct { 174 name string 175 suffix []byte 176 atEnd bool 177 }{ 178 {name: "RangeDescriptor", suffix: LocalRangeDescriptorSuffix, atEnd: true}, 179 {name: "Transaction", suffix: LocalTransactionSuffix, atEnd: false}, 180 {name: "QueueLastProcessed", suffix: LocalQueueLastProcessedSuffix, atEnd: false}, 181 } 182 ) 183 184 var constSubKeyDict = []struct { 185 name string 186 key roachpb.RKey 187 }{ 188 {"/storeIdent", localStoreIdentSuffix}, 189 {"/gossipBootstrap", localStoreGossipSuffix}, 190 {"/clusterVersion", localStoreClusterVersionSuffix}, 191 {"/suggestedCompaction", localStoreSuggestedCompactionSuffix}, 192 } 193 194 func suggestedCompactionKeyPrint(key roachpb.Key) string { 195 start, end, err := DecodeStoreSuggestedCompactionKey(key) 196 if err != nil { 197 return fmt.Sprintf("<invalid: %s>", err) 198 } 199 return fmt.Sprintf("{%s-%s}", start, end) 200 } 201 202 func localStoreKeyPrint(_ []encoding.Direction, key roachpb.Key) string { 203 for _, v := range constSubKeyDict { 204 if bytes.HasPrefix(key, v.key) { 205 if v.key.Equal(localStoreSuggestedCompactionSuffix) { 206 return v.name + "/" + suggestedCompactionKeyPrint( 207 append(roachpb.Key(nil), append(localStorePrefix, key...)...), 208 ) 209 } 210 return v.name 211 } 212 } 213 214 return fmt.Sprintf("%q", []byte(key)) 215 } 216 217 func localStoreKeyParse(input string) (remainder string, output roachpb.Key) { 218 for _, s := range constSubKeyDict { 219 if strings.HasPrefix(input, s.name) { 220 if s.key.Equal(localStoreSuggestedCompactionSuffix) { 221 panic(&ErrUglifyUnsupported{errors.New("cannot parse suggested compaction key")}) 222 } 223 output = MakeStoreKey(s.key, nil) 224 return 225 } 226 } 227 input = mustShiftSlash(input) 228 slashPos := strings.IndexByte(input, '/') 229 if slashPos < 0 { 230 slashPos = len(input) 231 } 232 remainder = input[slashPos:] // `/something/else` -> `/else` 233 output = roachpb.Key(input[:slashPos]) 234 return 235 } 236 237 const strTable = "/Table/" 238 const strSystemConfigSpan = "SystemConfigSpan" 239 const strSystemConfigSpanStart = "Start" 240 241 func tenantKeyParse(input string) (remainder string, output roachpb.Key) { 242 input = mustShiftSlash(input) 243 slashPos := strings.Index(input, "/") 244 if slashPos < 0 { 245 slashPos = len(input) 246 } 247 remainder = input[slashPos:] // `/something/else` -> `/else` 248 tenantIDStr := input[:slashPos] 249 tenantID, err := strconv.ParseUint(tenantIDStr, 10, 64) 250 if err != nil { 251 panic(&ErrUglifyUnsupported{err}) 252 } 253 output = MakeTenantPrefix(roachpb.MakeTenantID(tenantID)) 254 if strings.HasPrefix(remainder, strTable) { 255 var indexKey roachpb.Key 256 remainder = remainder[len(strTable)-1:] 257 remainder, indexKey = tableKeyParse(remainder) 258 output = append(output, indexKey...) 259 } 260 return remainder, output 261 } 262 263 func tableKeyParse(input string) (remainder string, output roachpb.Key) { 264 input = mustShiftSlash(input) 265 slashPos := strings.Index(input, "/") 266 if slashPos < 0 { 267 slashPos = len(input) 268 } 269 remainder = input[slashPos:] // `/something/else` -> `/else` 270 tableIDStr := input[:slashPos] 271 if tableIDStr == strSystemConfigSpan { 272 if remainder[1:] == strSystemConfigSpanStart { 273 remainder = "" 274 } 275 output = SystemConfigSpan.Key 276 return 277 } 278 tableID, err := strconv.ParseUint(tableIDStr, 10, 32) 279 if err != nil { 280 panic(&ErrUglifyUnsupported{err}) 281 } 282 output = encoding.EncodeUvarintAscending(nil /* key */, tableID) 283 if remainder != "" { 284 var indexKey roachpb.Key 285 remainder, indexKey = tableIndexParse(remainder) 286 output = append(output, indexKey...) 287 } 288 return remainder, output 289 } 290 291 // tableIndexParse parses an index id out of the input and returns the remainder. 292 // The input is expected to be of the form "/<index id>[/...]". 293 func tableIndexParse(input string) (string, roachpb.Key) { 294 input = mustShiftSlash(input) 295 slashPos := strings.Index(input, "/") 296 if slashPos < 0 { 297 // We accept simply "/<id>"; if there's no further slashes, the whole string 298 // has to be the index id. 299 slashPos = len(input) 300 } 301 remainder := input[slashPos:] // `/something/else` -> `/else` 302 indexIDStr := input[:slashPos] 303 indexID, err := strconv.ParseUint(indexIDStr, 10, 32) 304 if err != nil { 305 panic(&ErrUglifyUnsupported{err}) 306 } 307 output := encoding.EncodeUvarintAscending(nil /* key */, indexID) 308 return remainder, output 309 } 310 311 const strLogIndex = "/logIndex:" 312 313 func raftLogKeyParse(rangeID roachpb.RangeID, input string) (string, roachpb.Key) { 314 if !strings.HasPrefix(input, strLogIndex) { 315 panic("expected log index") 316 } 317 input = input[len(strLogIndex):] 318 index, err := strconv.ParseUint(input, 10, 64) 319 if err != nil { 320 panic(err) 321 } 322 return "", RaftLogKey(rangeID, index) 323 } 324 325 func raftLogKeyPrint(key roachpb.Key) string { 326 var logIndex uint64 327 var err error 328 key, logIndex, err = encoding.DecodeUint64Ascending(key) 329 if err != nil { 330 return fmt.Sprintf("/err<%v:%q>", err, []byte(key)) 331 } 332 333 return fmt.Sprintf("%s%d", strLogIndex, logIndex) 334 } 335 336 func mustShiftSlash(in string) string { 337 slash, out := mustShift(in) 338 if slash != "/" { 339 panic("expected /: " + in) 340 } 341 return out 342 } 343 344 func mustShift(in string) (first, remainder string) { 345 if len(in) == 0 { 346 panic("premature end of string") 347 } 348 return in[:1], in[1:] 349 } 350 351 func localRangeIDKeyParse(input string) (remainder string, key roachpb.Key) { 352 var rangeID int64 353 var err error 354 input = mustShiftSlash(input) 355 if endPos := strings.IndexByte(input, '/'); endPos > 0 { 356 rangeID, err = strconv.ParseInt(input[:endPos], 10, 64) 357 if err != nil { 358 panic(err) 359 } 360 input = input[endPos:] 361 } else { 362 panic(errors.Errorf("illegal RangeID: %q", input)) 363 } 364 input = mustShiftSlash(input) 365 var infix string 366 infix, input = mustShift(input) 367 var replicated bool 368 switch { 369 case bytes.Equal(localRangeIDUnreplicatedInfix, []byte(infix)): 370 case bytes.Equal(LocalRangeIDReplicatedInfix, []byte(infix)): 371 replicated = true 372 default: 373 panic(errors.Errorf("invalid infix: %q", infix)) 374 } 375 376 input = mustShiftSlash(input) 377 // Get the suffix. 378 var suffix roachpb.RKey 379 for _, s := range rangeIDSuffixDict { 380 if strings.HasPrefix(input, s.name) { 381 input = input[len(s.name):] 382 if s.psFunc != nil { 383 remainder, key = s.psFunc(roachpb.RangeID(rangeID), input) 384 return 385 } 386 suffix = roachpb.RKey(s.suffix) 387 break 388 } 389 } 390 maker := makeRangeIDUnreplicatedKey 391 if replicated { 392 maker = makeRangeIDReplicatedKey 393 } 394 if suffix != nil { 395 if input != "" { 396 panic(&ErrUglifyUnsupported{errors.New("nontrivial detail")}) 397 } 398 var detail roachpb.RKey 399 // TODO(tschottdorf): can't do this, init cycle: 400 // detail, err := UglyPrint(input) 401 // if err != nil { 402 // return "", nil, err 403 // } 404 remainder = "" 405 key = maker(roachpb.RangeID(rangeID), suffix, detail) 406 return 407 } 408 panic(&ErrUglifyUnsupported{errors.New("unhandled general range key")}) 409 } 410 411 func localRangeIDKeyPrint(valDirs []encoding.Direction, key roachpb.Key) string { 412 var buf bytes.Buffer 413 if encoding.PeekType(key) != encoding.Int { 414 return fmt.Sprintf("/err<%q>", []byte(key)) 415 } 416 417 // Get the rangeID. 418 key, i, err := encoding.DecodeVarintAscending(key) 419 if err != nil { 420 return fmt.Sprintf("/err<%v:%q>", err, []byte(key)) 421 } 422 423 fmt.Fprintf(&buf, "/%d", i) 424 425 // Print and remove the rangeID infix specifier. 426 if len(key) != 0 { 427 fmt.Fprintf(&buf, "/%s", string(key[0])) 428 key = key[1:] 429 } 430 431 // Get the suffix. 432 hasSuffix := false 433 for _, s := range rangeIDSuffixDict { 434 if bytes.HasPrefix(key, s.suffix) { 435 fmt.Fprintf(&buf, "/%s", s.name) 436 key = key[len(s.suffix):] 437 if s.ppFunc != nil && len(key) != 0 { 438 fmt.Fprintf(&buf, "%s", s.ppFunc(key)) 439 return buf.String() 440 } 441 hasSuffix = true 442 break 443 } 444 } 445 446 // Get the encode values. 447 if hasSuffix { 448 fmt.Fprintf(&buf, "%s", decodeKeyPrint(valDirs, key)) 449 } else { 450 fmt.Fprintf(&buf, "%q", []byte(key)) 451 } 452 453 return buf.String() 454 } 455 456 func localRangeKeyPrint(valDirs []encoding.Direction, key roachpb.Key) string { 457 var buf bytes.Buffer 458 459 for _, s := range rangeSuffixDict { 460 if s.atEnd { 461 if bytes.HasSuffix(key, s.suffix) { 462 key = key[:len(key)-len(s.suffix)] 463 _, decodedKey, err := encoding.DecodeBytesAscending([]byte(key), nil) 464 if err != nil { 465 fmt.Fprintf(&buf, "%s/%s", decodeKeyPrint(valDirs, key), s.name) 466 } else { 467 fmt.Fprintf(&buf, "%s/%s", roachpb.Key(decodedKey), s.name) 468 } 469 return buf.String() 470 } 471 } else { 472 begin := bytes.Index(key, s.suffix) 473 if begin > 0 { 474 addrKey := key[:begin] 475 _, decodedAddrKey, err := encoding.DecodeBytesAscending([]byte(addrKey), nil) 476 if err != nil { 477 fmt.Fprintf(&buf, "%s/%s", decodeKeyPrint(valDirs, addrKey), s.name) 478 } else { 479 fmt.Fprintf(&buf, "%s/%s", roachpb.Key(decodedAddrKey), s.name) 480 } 481 if bytes.Equal(s.suffix, LocalTransactionSuffix) { 482 txnID, err := uuid.FromBytes(key[(begin + len(s.suffix)):]) 483 if err != nil { 484 return fmt.Sprintf("/%q/err:%v", key, err) 485 } 486 fmt.Fprintf(&buf, "/%q", txnID) 487 } else { 488 id := key[(begin + len(s.suffix)):] 489 fmt.Fprintf(&buf, "/%q", []byte(id)) 490 } 491 return buf.String() 492 } 493 } 494 } 495 496 _, decodedKey, err := encoding.DecodeBytesAscending([]byte(key), nil) 497 if err != nil { 498 fmt.Fprintf(&buf, "%s", decodeKeyPrint(valDirs, key)) 499 } else { 500 fmt.Fprintf(&buf, "%s", roachpb.Key(decodedKey)) 501 } 502 503 return buf.String() 504 } 505 506 // ErrUglifyUnsupported is returned when UglyPrint doesn't know how to process a 507 // key. 508 type ErrUglifyUnsupported struct { 509 Wrapped error 510 } 511 512 func (euu *ErrUglifyUnsupported) Error() string { 513 return fmt.Sprintf("unsupported pretty key: %v", euu.Wrapped) 514 } 515 516 func abortSpanKeyParse(rangeID roachpb.RangeID, input string) (string, roachpb.Key) { 517 var err error 518 input = mustShiftSlash(input) 519 _, input = mustShift(input[:len(input)-1]) 520 if len(input) != len(uuid.UUID{}.String()) { 521 panic(&ErrUglifyUnsupported{errors.New("txn id not available")}) 522 } 523 id, err := uuid.FromString(input) 524 if err != nil { 525 panic(&ErrUglifyUnsupported{err}) 526 } 527 return "", AbortSpanKey(rangeID, id) 528 } 529 530 func abortSpanKeyPrint(key roachpb.Key) string { 531 _, id, err := encoding.DecodeBytesAscending([]byte(key), nil) 532 if err != nil { 533 return fmt.Sprintf("/%q/err:%v", key, err) 534 } 535 536 txnID, err := uuid.FromBytes(id) 537 if err != nil { 538 return fmt.Sprintf("/%q/err:%v", key, err) 539 } 540 541 return fmt.Sprintf("/%q", txnID) 542 } 543 544 func print(_ []encoding.Direction, key roachpb.Key) string { 545 return fmt.Sprintf("/%q", []byte(key)) 546 } 547 548 func decodeKeyPrint(valDirs []encoding.Direction, key roachpb.Key) string { 549 if key.Equal(SystemConfigSpan.Key) { 550 return "/SystemConfigSpan/Start" 551 } 552 return encoding.PrettyPrintValue(valDirs, key, "/") 553 } 554 555 func timeseriesKeyPrint(_ []encoding.Direction, key roachpb.Key) string { 556 return PrettyPrintTimeseriesKey(key) 557 } 558 559 func tenantKeyPrint(valDirs []encoding.Direction, key roachpb.Key) string { 560 key, tID, err := DecodeTenantPrefix(key) 561 if err != nil { 562 return fmt.Sprintf("/err:%v", err) 563 } 564 if len(key) == 0 { 565 return fmt.Sprintf("/%s", tID) 566 } 567 return fmt.Sprintf("/%s%s", tID, key.StringWithDirs(valDirs, 0)) 568 } 569 570 // prettyPrintInternal parse key with prefix in KeyDict. 571 // For table keys, valDirs correspond to the encoding direction of each encoded 572 // value in key. 573 // If valDirs is unspecified, the default encoding direction for each value 574 // type is used (see encoding.go:prettyPrintFirstValue). 575 // If the key doesn't match any prefix in KeyDict, return its byte value with 576 // quotation and false, or else return its human readable value and true. 577 func prettyPrintInternal(valDirs []encoding.Direction, key roachpb.Key, quoteRawKeys bool) string { 578 for _, k := range ConstKeyDict { 579 if key.Equal(k.Value) { 580 return k.Name 581 } 582 } 583 584 helper := func(key roachpb.Key) (string, bool) { 585 var b strings.Builder 586 for _, k := range KeyDict { 587 if key.Compare(k.start) >= 0 && (k.end == nil || key.Compare(k.end) <= 0) { 588 b.WriteString(k.Name) 589 if k.end != nil && k.end.Compare(key) == 0 { 590 b.WriteString("/Max") 591 return b.String(), true 592 } 593 594 hasPrefix := false 595 for _, e := range k.Entries { 596 if bytes.HasPrefix(key, e.prefix) { 597 hasPrefix = true 598 key = key[len(e.prefix):] 599 b.WriteString(e.Name) 600 b.WriteString(e.ppFunc(valDirs, key)) 601 break 602 } 603 } 604 if !hasPrefix { 605 key = key[len(k.start):] 606 if quoteRawKeys { 607 b.WriteByte('/') 608 b.WriteByte('"') 609 } 610 b.Write([]byte(key)) 611 if quoteRawKeys { 612 b.WriteByte('"') 613 } 614 } 615 616 return b.String(), true 617 } 618 } 619 620 if quoteRawKeys { 621 return fmt.Sprintf("%q", []byte(key)), false 622 } 623 return fmt.Sprintf("%s", []byte(key)), false 624 } 625 626 for _, k := range keyOfKeyDict { 627 if bytes.HasPrefix(key, k.prefix) { 628 key = key[len(k.prefix):] 629 str, formatted := helper(key) 630 if formatted { 631 return k.name + str 632 } 633 return k.name + "/" + str 634 } 635 } 636 str, _ := helper(key) 637 return str 638 } 639 640 // PrettyPrint prints the key in a human readable format, see TestPrettyPrint. 641 // The output does not indicate whether a key is part of the replicated or un- 642 // replicated keyspace. 643 // 644 // valDirs correspond to the encoding direction of each encoded value in key. 645 // For example, table keys could have column values encoded in ascending or 646 // descending directions. 647 // If valDirs is unspecified, the default encoding direction for each value 648 // type is used (see encoding.go:prettyPrintFirstValue). 649 // 650 // See keysutil.UglyPrint() for an inverse. 651 func PrettyPrint(valDirs []encoding.Direction, key roachpb.Key) string { 652 return prettyPrintInternal(valDirs, key, true /* quoteRawKeys */) 653 } 654 655 func init() { 656 roachpb.PrettyPrintKey = PrettyPrint 657 roachpb.PrettyPrintRange = PrettyPrintRange 658 } 659 660 // MassagePrettyPrintedSpanForTest does some transformations on pretty-printed spans and keys: 661 // - if dirs is not nil, replace all ints with their ones' complement for 662 // descendingly-encoded columns. 663 // - strips line numbers from error messages. 664 func MassagePrettyPrintedSpanForTest(span string, dirs []encoding.Direction) string { 665 var r string 666 colIdx := -1 667 for i := 0; i < len(span); i++ { 668 if dirs != nil { 669 var d int 670 if _, err := fmt.Sscanf(span[i:], "%d", &d); err != nil { 671 // We've managed to consume an int. 672 dir := dirs[colIdx] 673 i += len(strconv.Itoa(d)) - 1 674 x := d 675 if dir == encoding.Descending { 676 x = ^x 677 } 678 r += strconv.Itoa(x) 679 continue 680 } 681 } 682 r += string(span[i]) 683 switch span[i] { 684 case '/': 685 colIdx++ 686 case '-', ' ': 687 // We're switching from the start constraints to the end constraints, 688 // or starting another span. 689 colIdx = -1 690 } 691 } 692 return r 693 } 694 695 // PrettyPrintRange pretty prints a compact representation of a key range. The 696 // output is of the form: 697 // commonPrefix{remainingStart-remainingEnd} 698 // If the end key is empty, the outut is of the form: 699 // start 700 // It prints at most maxChars, truncating components as needed. See 701 // TestPrettyPrintRange for some examples. 702 func PrettyPrintRange(start, end roachpb.Key, maxChars int) string { 703 var b bytes.Buffer 704 if maxChars < 8 { 705 maxChars = 8 706 } 707 prettyStart := prettyPrintInternal(nil /* valDirs */, start, false /* quoteRawKeys */) 708 if len(end) == 0 { 709 if len(prettyStart) <= maxChars { 710 return prettyStart 711 } 712 b.WriteString(prettyStart[:maxChars-1]) 713 b.WriteRune('…') 714 return b.String() 715 } 716 prettyEnd := prettyPrintInternal(nil /* valDirs */, end, false /* quoteRawKeys */) 717 i := 0 718 // Find the common prefix. 719 for ; i < len(prettyStart) && i < len(prettyEnd) && prettyStart[i] == prettyEnd[i]; i++ { 720 } 721 // If we don't have space for at least '{a…-b…}' after the prefix, only print 722 // the prefix (or part of it). 723 if i > maxChars-7 { 724 if i > maxChars-1 { 725 i = maxChars - 1 726 } 727 b.WriteString(prettyStart[:i]) 728 b.WriteRune('…') 729 return b.String() 730 } 731 b.WriteString(prettyStart[:i]) 732 remaining := (maxChars - i - 3) / 2 733 734 printTrunc := func(b *bytes.Buffer, what string, maxChars int) { 735 if len(what) <= maxChars { 736 b.WriteString(what) 737 } else { 738 b.WriteString(what[:maxChars-1]) 739 b.WriteRune('…') 740 } 741 } 742 743 b.WriteByte('{') 744 printTrunc(&b, prettyStart[i:], remaining) 745 b.WriteByte('-') 746 printTrunc(&b, prettyEnd[i:], remaining) 747 b.WriteByte('}') 748 749 return b.String() 750 }