diff options
author | 2024-01-19 12:57:29 +0000 | |
---|---|---|
committer | 2024-01-19 12:57:29 +0000 | |
commit | 7ec1e1332e7d04e74451acef18b41f389722b698 (patch) | |
tree | 9c69eca7fc664ab5564279a2e065dfd5c2ddd17b /vendor/codeberg.org/gruf/go-structr/key.go | |
parent | [chore] chore rationalise http return codes for activitypub handlers (#2540) (diff) | |
download | gotosocial-7ec1e1332e7d04e74451acef18b41f389722b698.tar.xz |
[performance] overhaul struct (+ result) caching library for simplicity, performance and multiple-result lookups (#2535)
* rewrite cache library as codeberg.org/gruf/go-structr, implement in gotosocial
* use actual go-structr release version (not just commit hash)
* revert go toolchain changes (damn you go for auto changing this)
* fix go mod woes
* ensure %w is used in calls to errs.Appendf()
* fix error checking
* fix possible panic
* remove unnecessary start/stop functions, move to main Cache{} struct, add note regarding which caches require start/stop
* fix copy-paste artifact... :innocent:
* fix all comment copy-paste artifacts
* remove dropID() function, now we can just use slices.DeleteFunc()
* use util.Deduplicate() instead of collate(), move collate to util
* move orderByIDs() to util package and "generify"
* add a util.DeleteIf() function, use this to delete entries on failed population
* use slices.DeleteFunc() instead of util.DeleteIf() (i had the logic mixed up in my head somehow lol)
* add note about how collate differs from deduplicate
Diffstat (limited to 'vendor/codeberg.org/gruf/go-structr/key.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-structr/key.go | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-structr/key.go b/vendor/codeberg.org/gruf/go-structr/key.go new file mode 100644 index 000000000..557a5f033 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-structr/key.go @@ -0,0 +1,204 @@ +package structr + +import ( + "reflect" + "strings" + + "codeberg.org/gruf/go-byteutil" + "codeberg.org/gruf/go-mangler" +) + +// KeyGen is the underlying index key generator +// used within Index, and therefore Cache itself. +type KeyGen[StructType any] struct { + + // fields contains our representation of + // the struct fields contained in the + // creation of keys by this generator. + fields []structfield + + // zero specifies whether zero + // value fields are permitted. + zero bool +} + +// NewKeyGen returns a new initialized KeyGen for the receiving generic +// parameter type, comprising of the given field strings, and whether to +// allow zero values to be included within generated output strings. +func NewKeyGen[T any](fields []string, allowZero bool) KeyGen[T] { + var kgen KeyGen[T] + + // Preallocate expected struct field slice. + kgen.fields = make([]structfield, len(fields)) + + // Get the reflected struct ptr type. + t := reflect.TypeOf((*T)(nil)).Elem() + + for i, fieldName := range fields { + // Split name to account for nesting. + names := strings.Split(fieldName, ".") + + // Look for a usable struct field from type. + sfield, ok := findField(t, names, allowZero) + if !ok { + panicf("failed finding field: %s", fieldName) + } + + // Set parsed struct field. + kgen.fields[i] = sfield + } + + // Set config flags. + kgen.zero = allowZero + + return kgen +} + +// FromParts generates key string from individual key parts. +func (kgen *KeyGen[T]) FromParts(parts ...any) (key string, ok bool) { + buf := getBuf() + if ok = kgen.AppendFromParts(buf, parts...); ok { + key = string(buf.B) + } + putBuf(buf) + return +} + +// FromValue generates key string from a value, via reflection. +func (kgen *KeyGen[T]) FromValue(value T) (key string, ok bool) { + buf := getBuf() + rvalue := reflect.ValueOf(value) + if ok = kgen.appendFromRValue(buf, rvalue); ok { + key = string(buf.B) + } + putBuf(buf) + return +} + +// AppendFromParts generates key string into provided buffer, from individual key parts. +func (kgen *KeyGen[T]) AppendFromParts(buf *byteutil.Buffer, parts ...any) bool { + if len(parts) != len(kgen.fields) { + // User must provide correct number of parts for key. + panicf("incorrect number key parts: want=%d received=%d", + len(parts), + len(kgen.fields), + ) + } + + if kgen.zero { + // Zero values are permitted, + // mangle all values and ignore + // zero value return booleans. + for i, part := range parts { + + // Mangle this value into buffer. + _ = kgen.fields[i].Mangle(buf, part) + + // Append part separator. + buf.B = append(buf.B, '.') + } + } else { + // Zero values are NOT permitted. + for i, part := range parts { + + // Mangle this value into buffer. + z := kgen.fields[i].Mangle(buf, part) + + if z { + // The value was zero for + // this type, return early. + return false + } + + // Append part separator. + buf.B = append(buf.B, '.') + } + } + + // Drop the last separator. + buf.B = buf.B[:len(buf.B)-1] + + return true +} + +// AppendFromValue generates key string into provided buffer, from a value via reflection. +func (kgen *KeyGen[T]) AppendFromValue(buf *byteutil.Buffer, value T) bool { + return kgen.appendFromRValue(buf, reflect.ValueOf(value)) +} + +// appendFromRValue is the underlying generator function for the exported ___FromValue() functions, +// accepting a reflected input. We do not expose this as the reflected value is EXPECTED to be right. +func (kgen *KeyGen[T]) appendFromRValue(buf *byteutil.Buffer, rvalue reflect.Value) bool { + // Follow any ptrs leading to value. + for rvalue.Kind() == reflect.Pointer { + rvalue = rvalue.Elem() + } + + if kgen.zero { + // Zero values are permitted, + // mangle all values and ignore + // zero value return booleans. + for i := range kgen.fields { + + // Get the reflect value's field at idx. + fv := rvalue.FieldByIndex(kgen.fields[i].index) + fi := fv.Interface() + + // Mangle this value into buffer. + _ = kgen.fields[i].Mangle(buf, fi) + + // Append part separator. + buf.B = append(buf.B, '.') + } + } else { + // Zero values are NOT permitted. + for i := range kgen.fields { + + // Get the reflect value's field at idx. + fv := rvalue.FieldByIndex(kgen.fields[i].index) + fi := fv.Interface() + + // Mangle this value into buffer. + z := kgen.fields[i].Mangle(buf, fi) + + if z { + // The value was zero for + // this type, return early. + return false + } + + // Append part separator. + buf.B = append(buf.B, '.') + } + } + + // Drop the last separator. + buf.B = buf.B[:len(buf.B)-1] + + return true +} + +type structfield struct { + // index is the reflected index + // of this field (this takes into + // account struct nesting). + index []int + + // zero is the possible mangled + // zero value for this field. + zero string + + // mangler is the mangler function for + // serializing values of this field. + mangler mangler.Mangler +} + +// Mangle mangles the given value, using the determined type-appropriate +// field's type. The returned boolean indicates whether this is a zero value. +func (f *structfield) Mangle(buf *byteutil.Buffer, value any) (isZero bool) { + s := len(buf.B) // start pos. + buf.B = f.mangler(buf.B, value) + e := len(buf.B) // end pos. + isZero = (f.zero == string(buf.B[s:e])) + return +} |