-
Notifications
You must be signed in to change notification settings - Fork 0
/
txkey.go
200 lines (178 loc) · 5.86 KB
/
txkey.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
200
// Copyright 2022 Molecula Corp. (DBA FeatureBase).
// SPDX-License-Identifier: Apache-2.0
// Package txkey consolidates in one place the use of keys to index into our
// various storage/txn back-ends. The short_txkey version omits the
// index and shard, since these are implicitly part of our database-per-shard
// in an index scheme. In other words, every database is only in exactly
// one shard of one index already. There is no need to repeat the index
// and shard in these keys.
package short_txkey
import (
"encoding/binary"
"fmt"
)
// FieldView is here to avoid circular import.
type FieldView struct {
Field string
View string
}
func FieldViewFromPrefix(prefix []byte) FieldView {
field, view := SplitPrefix(prefix)
return FieldView{Field: field, View: view}
}
func FieldViewFromFullKey(fullKey []byte) FieldView {
field, view, _ := Split(fullKey)
return FieldView{Field: field, View: view}
}
// Key produces the bytes that we use as a key to query the storage/tx engine.
// The roaringContainerKey argument to Key() is a container key into a roaring Container.
// The return value from Key() is constructed as follows:
//
// ~field;view<ckey#
//
// where ckey is always exactly 8 bytes, uint64 big-endian encoded.
//
// Keys always start with either '~' or '>'. Keys always end with '#'.
// Keys always contain exactly one each of ';' and '<', in that order.
// The field is between the '~' and the ';'. It must be at least 1 byte long.
// The view is between the ';' and the '<'. It must be at least 1 byte long.
// The ckey is the 8 bytes between the '<' and the '#'.
// The Prefix of a key ends at, and includes, the '<'. It is at least 13 bytes long.
// The index, field, and view are not allowed to contain these reserved bytes:
//
// {'~', '>', ';', ':', '<', '#', '$', '%', '^', '(', ')', '*', '!'}
//
// The bytes {'+', '/', '-', '_', '.', and '=' can be used in index, field, and view; to enable
// base-64 encoding.
//
// The shortest possible key is 14 bytes. It would be laid out like this:
//
// ~f;v<12345678#
// 12345678901234
//
// keys starting with '~' are regular value keys.
// keys starting with '>' are symlink keys.
//
// NB must be kept in sync with Prefix() and KeyExtractContainerKey().
func Key(index, field, view string, shard, roaringContainerKey uint64) (r []byte) {
prefix := Prefix(index, field, view, shard)
var ckey [9]byte
binary.BigEndian.PutUint64(ckey[:8], roaringContainerKey)
ckey[8] = byte('#')
return append(prefix, ckey[:]...)
}
// KeyAndPrefix returns the equivalent of Key() and Prefix() calls.
func KeyAndPrefix(index, field, view string, shard, roaringContainerKey uint64) (key, prefix []byte) {
prefix = Prefix(index, field, view, shard)
var ckey [9]byte
binary.BigEndian.PutUint64(ckey[:8], roaringContainerKey)
ckey[8] = byte('#')
key = append(prefix, ckey[:]...)
return
}
var _ = KeyAndPrefix // keep linter happy
func MustValidateKey(bkey []byte) {
n := len(bkey)
if n < 14 {
panic(fmt.Sprintf("bkey too short, must have at least 14 bytes: '%v'", string(bkey)))
}
typ := bkey[0]
if typ != '~' && typ != '>' {
panic(fmt.Sprintf("bkey did not start with '~' for value nor '>' for symlink: '%v'", string(bkey)))
}
if bkey[n-10] != '<' {
panic(fmt.Sprintf("bkey did not have '<' at 9 bytes from the end: '%v'", string(bkey)))
}
if bkey[n-1] != '#' {
panic(fmt.Sprintf("bkey did not end in '#': '%v'", string(bkey)))
}
}
// KeyExtractContainerKey extracts the containerKey from bkey.
// key example: field;view<ckey
// shortest: ~f;v<12345678#
//
// 1234567890123456789012345
// numbering len(bkey) - i:
// 5432109876543210987654321
func KeyExtractContainerKey(bkey []byte) (containerKey uint64) {
n := len(bkey)
MustValidateKey(bkey)
containerKey = binary.BigEndian.Uint64(bkey[(n - 9):(n - 1)])
return
}
func AllShardPrefix(index, field, view string) (r []byte) {
r = make([]byte, 0, 64)
r = append(r, '~')
r = append(r, []byte(field)...)
r = append(r, ';')
r = append(r, []byte(view)...)
r = append(r, '<')
return
}
// Prefix returns everything from Key up to and
// including the '<' byte in a Key. The prefix excludes the roaring container key itself.
// NB must be kept in sync with Key() and KeyExtractContainerKey().
func Prefix(index, field, view string, shard uint64) (r []byte) {
r = make([]byte, 0, 32)
r = append(r, '~')
r = append(r, []byte(field)...)
r = append(r, ';')
r = append(r, []byte(view)...)
r = append(r, '<')
return
}
// IndexOnlyPrefix returns a "~" prefix suitable for DeleteIndex and a key-scan to
// remove all storage. We assume only one index in this database, so delete everything.
func IndexOnlyPrefix(indexName string) (r []byte) {
return []byte("~")
}
// same for deleting a whole field.
func FieldPrefix(index, field string) (r []byte) {
r = make([]byte, 0, 16)
r = append(r, '~')
r = append(r, []byte(field)...)
r = append(r, ';')
return
}
// PrefixFromKey key example: ~field;view<ckey#
//
// n-9 n-1
//
// ... : 01234567 < 01234567 #
//
// view ckey
func PrefixFromKey(bkey []byte) (prefix []byte) {
n := len(bkey)
return bkey[:(n - 9)]
}
func ToString(bkey []byte) (r string) {
field, view, ckey := Split(bkey)
return fmt.Sprintf("fld:'%v';vw:'%v';ckey@%020d", field, view, ckey)
}
func PrefixToString(pre []byte) (r string) {
field, view := SplitPrefix(pre)
return fmt.Sprintf("fld:'%v';vw:'%v';", field, view)
}
func Split(bkey []byte) (field, view string, ckey uint64) {
ckey = KeyExtractContainerKey(bkey)
n := len(bkey)
field, view = SplitPrefix(bkey[:(n - 9)])
return
}
// full key: ~field;view<ckey#
// prefix : ~field;view<
func SplitPrefix(pre []byte) (field, view string) {
n := len(pre)
// prefix: ~field;view<
beg := 1
for i := 1; i < n; i++ {
switch pre[i] {
case ';':
field = string(pre[beg:i])
beg = i + 1
view = string(pre[beg:(n - 1)])
return
}
}
panic(fmt.Sprintf("malformed prefix '%v' / '%#v', could not Split", string(pre), pre))
}