/
key.go
199 lines (184 loc) · 5.53 KB
/
key.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0.
package utils
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"strings"
"github.com/pingcap/errors"
"github.com/pingcap/log"
berrors "github.com/wuhuizuo/tidb6/br/pkg/errors"
"github.com/wuhuizuo/tidb6/br/pkg/logutil"
"github.com/wuhuizuo/tidb6/kv"
"go.uber.org/zap"
)
// ParseKey parse key by given format.
func ParseKey(format, key string) ([]byte, error) {
switch format {
case "raw":
return []byte(key), nil
case "escaped":
return unescapedKey(key)
case "hex":
key, err := hex.DecodeString(key)
if err != nil {
return nil, errors.Trace(err)
}
return key, nil
}
return nil, errors.Annotate(berrors.ErrInvalidArgument, "unknown format")
}
// Ref PD: https://github.com/pingcap/pd/blob/master/tools/pd-ctl/pdctl/command/region_command.go#L334
func unescapedKey(text string) ([]byte, error) {
var buf []byte
r := bytes.NewBuffer([]byte(text))
for {
c, err := r.ReadByte()
if err != nil {
if errors.Cause(err) != io.EOF { // nolint:errorlint
return nil, errors.Trace(err)
}
break
}
if c != '\\' {
buf = append(buf, c)
continue
}
n := r.Next(1)
if len(n) == 0 {
return nil, io.EOF
}
// See: https://golang.org/ref/spec#Rune_literals
if idx := strings.IndexByte(`abfnrtv\'"`, n[0]); idx != -1 {
buf = append(buf, []byte("\a\b\f\n\r\t\v\\'\"")[idx])
continue
}
switch n[0] {
case 'x':
fmt.Sscanf(string(r.Next(2)), "%02x", &c)
buf = append(buf, c)
default:
n = append(n, r.Next(2)...)
_, err := fmt.Sscanf(string(n), "%03o", &c)
if err != nil {
return nil, errors.Trace(err)
}
buf = append(buf, c)
}
}
return buf, nil
}
// CompareEndKey compared two keys that BOTH represent the EXCLUSIVE ending of some range. An empty end key is the very
// end, so an empty key is greater than any other keys.
// Please note that this function is not applicable if any one argument is not an EXCLUSIVE ending of a range.
func CompareEndKey(a, b []byte) int {
// NOTE: maybe CompareBytesExt(a, true, b, true)?
if len(a) == 0 {
if len(b) == 0 {
return 0
}
return 1
}
if len(b) == 0 {
return -1
}
return bytes.Compare(a, b)
}
// CompareBytesExt compare two byte sequences.
// different from `bytes.Compare`, we can provide whether to treat the key as inf when meet empty key to this.
func CompareBytesExt(a []byte, aEmptyAsInf bool, b []byte, bEmptyAsInf bool) int {
// Inf = Inf
if len(a) == 0 && aEmptyAsInf && len(b) == 0 && bEmptyAsInf {
return 0
}
// Inf > anything
if len(a) == 0 && aEmptyAsInf {
return 1
}
// anything < Inf
if len(b) == 0 && bEmptyAsInf {
return -1
}
return bytes.Compare(a, b)
}
type failedToClampReason int
const (
successClamp failedToClampReason = iota
// ToClamp : |_________|
// Range: |______|
leftNotOverlapped
// ToClamp : |_________|
// Range: |______|
rightNotOverlapped
buggyUnknown
)
func clampInOneRange(rng kv.KeyRange, clampIn kv.KeyRange) (kv.KeyRange, failedToClampReason) {
possibleFailureReason := buggyUnknown
if CompareBytesExt(rng.StartKey, false, clampIn.StartKey, false) < 0 {
rng.StartKey = clampIn.StartKey
possibleFailureReason = leftNotOverlapped
}
if CompareBytesExt(rng.EndKey, true, clampIn.EndKey, true) > 0 {
rng.EndKey = clampIn.EndKey
possibleFailureReason = rightNotOverlapped
}
// We treat empty region as "failed" too.
if CompareBytesExt(rng.StartKey, false, rng.EndKey, true) >= 0 {
return kv.KeyRange{}, possibleFailureReason
}
return rng, successClamp
}
// CloneSlice sallowly clones a slice.
func CloneSlice[T any](s []T) []T {
r := make([]T, len(s))
copy(r, s)
return r
}
// IntersectAll returns the intersect of two set of segments.
// OWNERSHIP INFORMATION:
// For running faster, this function would MUTATE the input slice. (i.e. takes its ownership.)
// (But it is promised that this function won't change the `start key` and `end key` slice)
// If you want to use the input slice after, call `CloneSlice` over arguments before passing them.
//
// You can treat "set of segments" as points maybe not adjacent.
// in this way, IntersectAll(s1, s2) = { point | point in both s1 and s2 }
// Example:
// ranges: |___________| |________________|
// toClampIn: |_____| |____| |________________|
// result: |_____| |_| |______________|
// we are assuming the arguments are sorted by the start key and no overlaps.
// you can call spans.Collapse to get key ranges fits this requirements.
// Note: this algorithm is pretty like the `checkIntervalIsSubset`, can we get them together?
func IntersectAll(s1 []kv.KeyRange, s2 []kv.KeyRange) []kv.KeyRange {
currentClamping := 0
currentClampTarget := 0
rs := make([]kv.KeyRange, 0, len(s1))
for currentClampTarget < len(s2) && currentClamping < len(s1) {
cin := s2[currentClampTarget]
crg := s1[currentClamping]
rng, result := clampInOneRange(crg, cin)
switch result {
case successClamp:
rs = append(rs, rng)
if CompareBytesExt(crg.EndKey, true, cin.EndKey, true) <= 0 {
currentClamping++
} else {
// Not fully consumed the clamped range.
s1[currentClamping].StartKey = cin.EndKey
}
case leftNotOverlapped:
currentClamping++
case rightNotOverlapped:
currentClampTarget++
case buggyUnknown:
log.L().DPanic("Unreachable path reached",
zap.Stringer("over-ranges", logutil.StringifyKeys(s1)),
zap.Stringer("clamp-into", logutil.StringifyKeys(s2)),
zap.Stringer("current-clamping", logutil.StringifyRange(crg)),
zap.Stringer("current-target", logutil.StringifyRange(cin)),
)
}
}
return rs
}