summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
blob: 5508936ba5443ffe92c1ee31bb6cb5e962420a1f (plain)
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package result

import (
	"fmt"
	"reflect"
	"strings"
	"sync"
	"unicode"
	"unicode/utf8"

	"codeberg.org/gruf/go-byteutil"
	"codeberg.org/gruf/go-mangler"
)

// structKeys provides convience methods for a list
// of structKey field combinations used for cache keys.
type structKeys []structKey

// get fetches the structKey info for given lookup name (else, panics).
func (sk structKeys) get(name string) *structKey {
	for i := range sk {
		if sk[i].name == name {
			return &sk[i]
		}
	}
	panic("unknown lookup: \"" + name + "\"")
}

// generate will calculate and produce a slice of cache keys the given value
// can be stored under in the, as determined by receiving struct keys.
func (sk structKeys) generate(a any) []cacheKey {
	var keys []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()
	}

	// Acquire buffer
	buf := getBuf()

outer:
	for i := range sk {
		// Reset buffer
		buf.Reset()

		// Append each field value to buffer.
		for _, field := range sk[i].fields {
			fv := v.Field(field.index)
			fi := fv.Interface()

			// Mangle this key part into buffer.
			ok := field.manglePart(buf, fi)

			if !ok {
				// don't generate keys
				// for zero value parts.
				continue outer
			}

			// Append part separator.
			buf.B = append(buf.B, '.')
		}

		// Drop last '.'
		buf.Truncate(1)

		// Append new cached key to slice
		keys = append(keys, cacheKey{
			info: &sk[i],
			key:  string(buf.B), // copy
		})
	}

	// Release buf
	putBuf(buf)

	return keys
}

type cacheKeys []cacheKey

// drop will drop the cachedKey with lookup name from receiving cacheKeys slice.
func (ck *cacheKeys) drop(name string) {
	_ = *ck // move out of loop
	for i := range *ck {
		if (*ck)[i].info.name == name {
			(*ck) = append((*ck)[:i], (*ck)[i+1:]...)
			break
		}
	}
}

// cacheKey represents an actual cached key.
type cacheKey struct {
	// info is a reference to the structKey this
	// cacheKey is representing. This is a shared
	// reference and as such only the structKey.pkeys
	// lookup map is expecting to be modified.
	info *structKey

	// value is the actual string representing
	// this cache key for hashmap lookups.
	key string
}

// structKey represents a list of struct fields
// encompassing a single cache key, the string name
// of the lookup, the lookup map to primary cache
// keys, and the key's possible zero value string.
type structKey struct {
	// name is the provided cache lookup name for
	// this particular struct key, consisting of
	// period ('.') separated struct field names.
	name string

	// unique determines whether this structKey supports
	// multiple or just the singular unique result.
	unique bool

	// fields is a slice of runtime struct field
	// indices, of fields encompassed by this key.
	fields []structField

	// pkeys is a lookup of stored struct key values
	// to the primary cache lookup key (int64). this
	// is protected by the main cache mutex.
	pkeys map[string][]int64
}

// newStructKey will generate a structKey{} information object for user-given lookup
// key information, and the receiving generic paramter's type information. Panics on error.
func newStructKey(lk Lookup, t reflect.Type) structKey {
	var sk structKey

	// Set the lookup name
	sk.name = lk.Name

	// Split dot-separated lookup to get
	// the individual struct field names
	names := strings.Split(lk.Name, ".")

	// Allocate the mangler and field indices slice.
	sk.fields = make([]structField, 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
		sk.fields[i].index = ft.Index[0]

		// Allocate new instance of field
		v := reflect.New(ft.Type)
		v = v.Elem()

		// Fetch mangler for field type.
		sk.fields[i].mangle = mangler.Get(ft.Type)

		if !lk.AllowZero {
			// Append the mangled zero value interface
			zero := sk.fields[i].mangle(nil, v.Interface())
			sk.fields[i].zero = string(zero)
		}
	}

	// Set unique lookup flag.
	sk.unique = !lk.Multi

	// Allocate primary lookup map
	sk.pkeys = make(map[string][]int64)

	return sk
}

// genKey generates a cache key string for given key parts (i.e. serializes them using "go-mangler").
func (sk *structKey) genKey(parts []any) string {
	// Check this expected no. key parts.
	if len(parts) != len(sk.fields) {
		panic(fmt.Sprintf("incorrect no. key parts provided: want=%d received=%d", len(parts), len(sk.fields)))
	}

	// Acquire buffer
	buf := getBuf()
	buf.Reset()

	for i, part := range parts {
		// Mangle this key part into buffer.
		// specifically ignoring whether this
		// is returning a zero value key part.
		_ = sk.fields[i].manglePart(buf, part)

		// Append part separator.
		buf.B = append(buf.B, '.')
	}

	// Drop last '.'
	buf.Truncate(1)

	// Create str copy
	str := string(buf.B)

	// Release buf
	putBuf(buf)

	return str
}

type structField struct {
	// index is the reflect index of this struct field.
	index int

	// zero is the possible zero value for this
	// key part. if set, this will _always_ be
	// non-empty due to how the mangler works.
	//
	// i.e. zero = ""  --> allow zero value keys
	//      zero != "" --> don't allow zero value keys
	zero string

	// mangle is the mangler function for
	// serializing values of this struct field.
	mangle mangler.Mangler
}

// manglePart ...
func (field *structField) manglePart(buf *byteutil.Buffer, part any) bool {
	// Start of part bytes.
	start := len(buf.B)

	// Mangle this key part into buffer.
	buf.B = field.mangle(buf.B, part)

	// End of part bytes.
	end := len(buf.B)

	// Return whether this is zero value.
	return (field.zero == "" ||
		string(buf.B[start:end]) != field.zero)
}

// 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)}
	},
}

// getBuf acquires a byte buffer from memory pool.
func getBuf() *byteutil.Buffer {
	return bufPool.Get().(*byteutil.Buffer)
}

// putBuf replaces a byte buffer back in memory pool.
func putBuf(buf *byteutil.Buffer) {
	if buf.Cap() > int(^uint16(0)) {
		return // drop large bufs
	}
	bufPool.Put(buf)
}