gotosocial/vendor/codeberg.org/gruf/go-structr/key.go
kim 7ec1e1332e
[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... 😇

* 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
2024-01-19 12:57:29 +00:00

205 lines
5.1 KiB
Go

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
}