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
|
package result
import (
"reflect"
"strings"
"sync"
"unicode"
"unicode/utf8"
"codeberg.org/gruf/go-byteutil"
"codeberg.org/gruf/go-mangler"
)
// structKeys provides convience methods for a list
// of struct field combinations used for cache keys.
type structKeys []keyFields
// get fetches the key-fields for given lookup (else, panics).
func (sk structKeys) get(lookup string) *keyFields {
for i := range sk {
if sk[i].lookup == lookup {
return &sk[i]
}
}
panic("unknown lookup: \"" + lookup + "\"")
}
// generate will calculate the value string for each required
// cache key as laid-out by the receiving structKeys{}.
func (sk structKeys) generate(a any) []cacheKey {
// Get reflected value in order
// to access the struct fields
v := reflect.ValueOf(a)
// Iteratively deref pointer value
for v.Kind() == reflect.Pointer {
if v.IsNil() {
panic("nil ptr")
}
v = v.Elem()
}
// Preallocate expected slice of keys
keys := make([]cacheKey, len(sk))
// Acquire byte buffer
buf := bufpool.Get().(*byteutil.Buffer)
defer bufpool.Put(buf)
for i := range sk {
// Reset buffer
buf.B = buf.B[:0]
// Set the key-fields reference
keys[i].fields = &sk[i]
// Calculate cache-key value
keys[i].populate(buf, v)
}
return keys
}
// cacheKey represents an actual cache key.
type cacheKey struct {
// value is the actual string representing
// this cache key for hashmap lookups.
value string
// fieldsRO is a read-only slice (i.e. we should
// NOT be modifying them, only using for reference)
// of struct fields encapsulated by this cache key.
fields *keyFields
}
// populate will calculate the cache key's value string for given
// value's reflected information. Passed encoder is for string building.
func (k *cacheKey) populate(buf *byteutil.Buffer, v reflect.Value) {
// Append each field value to buffer.
for _, idx := range k.fields.fields {
fv := v.Field(idx)
fi := fv.Interface()
buf.B = mangler.Append(buf.B, fi)
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Create string copy from buf
k.value = string(buf.B)
}
// keyFields represents a list of struct fields
// encompassed in a single cache key, the string name
// of the lookup, and the lookup map to primary keys.
type keyFields struct {
// lookup is the calculated (well, provided)
// cache key lookup, consisting of dot sep'd
// struct field names.
lookup string
// fields is a slice of runtime struct field
// indices, of the fields encompassed by this key.
fields []int
// pkeys is a lookup of stored struct key values
// to the primary cache lookup key (int64).
pkeys map[string]int64
}
// populate will populate this keyFields{} object's .fields member by determining
// the field names from the given lookup, and querying given reflected type to get
// the runtime field indices for each of the fields. this speeds-up future value lookups.
func (kf *keyFields) populate(t reflect.Type) {
// Split dot-separated lookup to get
// the individual struct field names
names := strings.Split(kf.lookup, ".")
if len(names) == 0 {
panic("no key fields specified")
}
// Pre-allocate slice of expected length
kf.fields = make([]int, len(names))
for i, name := range names {
// Get field info for given name
ft, ok := t.FieldByName(name)
if !ok {
panic("no field found for name: \"" + name + "\"")
}
// Check field is usable
if !isExported(name) {
panic("field must be exported")
}
// Set the runtime field index
kf.fields[i] = ft.Index[0]
}
}
// genkey generates a cache key for given key values.
func genkey(parts ...any) string {
if len(parts) < 1 {
// Panic to prevent annoying usecase
// where user forgets to pass lookup
// and instead only passes a key part,
// e.g. cache.Get("key")
// which then always returns false.
panic("no key parts provided")
}
// Acquire buffer and reset
buf := bufpool.Get().(*byteutil.Buffer)
defer bufpool.Put(buf)
buf.Reset()
// Encode each key part
for _, part := range parts {
buf.B = mangler.Append(buf.B, part)
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Return string copy
return string(buf.B)
}
// isExported checks whether function name is exported.
func isExported(fnName string) bool {
r, _ := utf8.DecodeRuneInString(fnName)
return unicode.IsUpper(r)
}
// bufpool provides a memory pool of byte
// buffers use when encoding key types.
var bufpool = sync.Pool{
New: func() any {
return &byteutil.Buffer{B: make([]byte, 0, 512)}
},
}
|