diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-cache/v3/result/key.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-cache/v3/result/key.go | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/key.go b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go new file mode 100644 index 000000000..ec58e0ef9 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go @@ -0,0 +1,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)} + }, +} |