summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-structr
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/gruf/go-structr')
-rw-r--r--vendor/codeberg.org/gruf/go-structr/README.md70
-rw-r--r--vendor/codeberg.org/gruf/go-structr/cache.go83
-rw-r--r--vendor/codeberg.org/gruf/go-structr/hash.go370
-rw-r--r--vendor/codeberg.org/gruf/go-structr/hasher.go176
-rw-r--r--vendor/codeberg.org/gruf/go-structr/index.go26
-rw-r--r--vendor/codeberg.org/gruf/go-structr/key.go204
-rw-r--r--vendor/codeberg.org/gruf/go-structr/util.go50
7 files changed, 680 insertions, 299 deletions
diff --git a/vendor/codeberg.org/gruf/go-structr/README.md b/vendor/codeberg.org/gruf/go-structr/README.md
index e2a9bdc15..125b20090 100644
--- a/vendor/codeberg.org/gruf/go-structr/README.md
+++ b/vendor/codeberg.org/gruf/go-structr/README.md
@@ -2,4 +2,74 @@
A performant struct caching library with automated indexing by arbitrary combinations of fields, including support for negative results (errors!). An example use case is in database lookups.
+Some example code of how you can use `go-structr` in your application:
+```golang
+type Cached struct {
+ Username string
+ Domain string
+ URL string
+ CountryCode int
+}
+
+var c structr.Cache[*Cached]
+
+c.Init(structr.Config[*Cached]{
+
+ // Fields this cached struct type
+ // will be indexed and stored under.
+ Indices: []structr.IndexConfig{
+ {Fields: "Username,Domain", AllowZero: true},
+ {Fields: "URL"},
+ {Fields: "CountryCode", Multiple: true},
+ },
+
+ // Maximum LRU cache size before
+ // new entries cause evictions.
+ MaxSize: 1000,
+
+ // User provided value copy function to
+ // reduce need for reflection + ensure
+ // concurrency safety for returned values.
+ CopyValue: func(c *Cached) *Cached {
+ c2 := new(Cached)
+ *c2 = *c
+ return c2
+ },
+
+ // User defined invalidation hook.
+ Invalidate: func(c *Cached) {
+ log.Println("invalidated:", c)
+ },
+})
+
+var url string
+
+// Load value from cache, with callback function to hydrate
+// cache if value cannot be found under index name with key.
+// Negative (error) results are also cached, with user definable
+// errors to ignore from caching (e.g. context deadline errs).
+value, err := c.LoadOne("URL", func() (*Cached, error) {
+ return dbType.SelectByURL(url)
+}, url)
+if err != nil {
+ return nil, err
+}
+
+// Store value in cache, only if provided callback
+// function returns without error. Passes value through
+// invalidation hook regardless of error return value.
+//
+// On success value will be automatically added to and
+// accessible under all initially configured indices.
+if err := c.Store(value, func() error {
+ return dbType.Insert(value)
+}); err != nil {
+ return nil, err
+}
+
+// Invalidate all cached results stored under
+// provided index name with give field value(s).
+c.Invalidate("CountryCode", 42)
+```
+
This is a core underpinning of [GoToSocial](https://github.com/superseriousbusiness/gotosocial)'s performance. \ No newline at end of file
diff --git a/vendor/codeberg.org/gruf/go-structr/cache.go b/vendor/codeberg.org/gruf/go-structr/cache.go
index b958fdfdb..fb52f0d8d 100644
--- a/vendor/codeberg.org/gruf/go-structr/cache.go
+++ b/vendor/codeberg.org/gruf/go-structr/cache.go
@@ -111,8 +111,8 @@ func (c *Cache[T]) Init(config Config[T]) {
// provided config.
c.mutex.Lock()
c.indices = make([]Index[T], len(config.Indices))
- for i, config := range config.Indices {
- c.indices[i].init(config)
+ for i, cfg := range config.Indices {
+ c.indices[i].init(cfg, config.MaxSize)
}
c.ignore = config.IgnoreErr
c.copy = config.CopyValue
@@ -138,7 +138,7 @@ func (c *Cache[T]) GetOne(index string, keyParts ...any) (T, bool) {
idx := c.Index(index)
// Generate index key from provided parts.
- key, ok := idx.keygen.FromParts(keyParts...)
+ key, ok := idx.hasher.FromParts(keyParts...)
if !ok {
var zero T
return zero, false
@@ -149,7 +149,7 @@ func (c *Cache[T]) GetOne(index string, keyParts ...any) (T, bool) {
}
// GetOneBy fetches value from cache stored under index, using precalculated index key.
-func (c *Cache[T]) GetOneBy(index *Index[T], key string) (T, bool) {
+func (c *Cache[T]) GetOneBy(index *Index[T], key uint64) (T, bool) {
if index == nil {
panic("no index given")
} else if !index.unique {
@@ -170,37 +170,33 @@ func (c *Cache[T]) Get(index string, keysParts ...[]any) []T {
idx := c.Index(index)
// Preallocate expected keys slice length.
- keys := make([]string, 0, len(keysParts))
+ keys := make([]uint64, 0, len(keysParts))
- // Acquire buf.
- buf := getBuf()
+ // Acquire hasher.
+ h := getHasher()
for _, parts := range keysParts {
- // Reset buf.
- buf.Reset()
+ h.Reset()
// Generate key from provided parts into buffer.
- if !idx.keygen.AppendFromParts(buf, parts...) {
+ key, ok := idx.hasher.fromParts(h, parts...)
+ if !ok {
continue
}
- // Get string copy of
- // genarated idx key.
- key := string(buf.B)
-
- // Append key to keys.
+ // Append hash sum to keys.
keys = append(keys, key)
}
- // Done with buf.
- putBuf(buf)
+ // Done with h.
+ putHasher(h)
// Continue fetching values.
return c.GetBy(idx, keys...)
}
// GetBy fetches values from the cache stored under index, using precalculated index keys.
-func (c *Cache[T]) GetBy(index *Index[T], keys ...string) []T {
+func (c *Cache[T]) GetBy(index *Index[T], keys ...uint64) []T {
if index == nil {
panic("no index given")
}
@@ -265,7 +261,7 @@ func (c *Cache[T]) Put(values ...T) {
// Store all the passed values.
for _, value := range values {
- c.store(nil, "", value, nil)
+ c.store(nil, 0, value, nil)
}
// Done with lock.
@@ -288,7 +284,7 @@ func (c *Cache[T]) LoadOne(index string, load func() (T, error), keyParts ...any
idx := c.Index(index)
// Generate cache from from provided parts.
- key, _ := idx.keygen.FromParts(keyParts...)
+ key, _ := idx.hasher.FromParts(keyParts...)
// Continue loading this result.
return c.LoadOneBy(idx, load, key)
@@ -296,7 +292,7 @@ func (c *Cache[T]) LoadOne(index string, load func() (T, error), keyParts ...any
// LoadOneBy fetches one result from the cache stored under index, using precalculated index key.
// In the case that no result is found, provided load callback will be used to hydrate the cache.
-func (c *Cache[T]) LoadOneBy(index *Index[T], load func() (T, error), key string) (T, error) {
+func (c *Cache[T]) LoadOneBy(index *Index[T], load func() (T, error), key uint64) (T, error) {
if index == nil {
panic("no index given")
} else if !index.unique {
@@ -421,26 +417,21 @@ func (c *Cache[T]) LoadBy(index *Index[T], get func(load func(keyParts ...any) b
}
}()
- // Acquire buf.
- buf := getBuf()
+ // Acquire hasher.
+ h := getHasher()
// Pass cache check to user func.
get(func(keyParts ...any) bool {
-
- // Reset buf.
- buf.Reset()
+ h.Reset()
// Generate index key from provided key parts.
- if !index.keygen.AppendFromParts(buf, keyParts...) {
+ key, ok := index.hasher.fromParts(h, keyParts...)
+ if !ok {
return false
}
- // Get temp generated key str,
- // (not needed after return).
- keyStr := buf.String()
-
// Get all indexed results.
- list := index.data[keyStr]
+ list := index.data[key]
if list != nil && list.len > 0 {
// Value length before
@@ -471,8 +462,8 @@ func (c *Cache[T]) LoadBy(index *Index[T], get func(load func(keyParts ...any) b
return false
})
- // Done with buf.
- putBuf(buf)
+ // Done with h.
+ putHasher(h)
// Done with lock.
c.mutex.Unlock()
@@ -528,7 +519,7 @@ func (c *Cache[T]) Invalidate(index string, keyParts ...any) {
idx := c.Index(index)
// Generate cache from from provided parts.
- key, ok := idx.keygen.FromParts(keyParts...)
+ key, ok := idx.hasher.FromParts(keyParts...)
if !ok {
return
}
@@ -538,7 +529,7 @@ func (c *Cache[T]) Invalidate(index string, keyParts ...any) {
}
// InvalidateBy invalidates all results stored under index key.
-func (c *Cache[T]) InvalidateBy(index *Index[T], key string) {
+func (c *Cache[T]) InvalidateBy(index *Index[T], key uint64) {
if index == nil {
panic("no index given")
}
@@ -639,7 +630,7 @@ func (c *Cache[T]) Cap() int {
// store will store the given value / error result in the cache, storing it under the
// already provided index + key if provided, else generating keys from provided value.
-func (c *Cache[T]) store(index *Index[T], key string, value T, err error) {
+func (c *Cache[T]) store(index *Index[T], key uint64, value T, err error) {
// Acquire new result.
res := result_acquire(c)
@@ -671,8 +662,8 @@ func (c *Cache[T]) store(index *Index[T], key string, value T, err error) {
// value, used during cache key gen.
rvalue := reflect.ValueOf(value)
- // Acquire buf.
- buf := getBuf()
+ // Acquire hasher.
+ h := getHasher()
for i := range c.indices {
// Get current index ptr.
@@ -684,22 +675,20 @@ func (c *Cache[T]) store(index *Index[T], key string, value T, err error) {
continue
}
- // Generate key from reflect value,
+ // Generate hash from reflect value,
// (this ignores zero value keys).
- buf.Reset() // reset buf first
- if !idx.keygen.appendFromRValue(buf, rvalue) {
+ h.Reset() // reset buf first
+ key, ok := idx.hasher.fromRValue(h, rvalue)
+ if !ok {
continue
}
- // Alloc key copy.
- key := string(buf.B)
-
// Append result to index at key.
index_append(c, idx, key, res)
}
- // Done with buf.
- putBuf(buf)
+ // Done with h.
+ putHasher(h)
}
if c.lruList.len > c.maxSize {
diff --git a/vendor/codeberg.org/gruf/go-structr/hash.go b/vendor/codeberg.org/gruf/go-structr/hash.go
new file mode 100644
index 000000000..84f0e62fc
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-structr/hash.go
@@ -0,0 +1,370 @@
+package structr
+
+import (
+ "reflect"
+ "unsafe"
+
+ "github.com/zeebo/xxh3"
+)
+
+func hasher(t reflect.Type) func(*xxh3.Hasher, any) bool {
+ switch t.Kind() {
+ case reflect.Int,
+ reflect.Uint,
+ reflect.Uintptr:
+ switch unsafe.Sizeof(int(0)) {
+ case 4:
+ return hash32bit
+ case 8:
+ return hash64bit
+ default:
+ panic("unexpected platform int size")
+ }
+
+ case reflect.Int8,
+ reflect.Uint8:
+ return hash8bit
+
+ case reflect.Int16,
+ reflect.Uint16:
+ return hash16bit
+
+ case reflect.Int32,
+ reflect.Uint32,
+ reflect.Float32:
+ return hash32bit
+
+ case reflect.Int64,
+ reflect.Uint64,
+ reflect.Float64,
+ reflect.Complex64:
+ return hash64bit
+
+ case reflect.String:
+ return hashstring
+
+ case reflect.Pointer:
+ switch t.Elem().Kind() {
+ case reflect.Int,
+ reflect.Uint,
+ reflect.Uintptr:
+ switch unsafe.Sizeof(int(0)) {
+ case 4:
+ return hash32bitptr
+ case 8:
+ return hash64bitptr
+ default:
+ panic("unexpected platform int size")
+ }
+
+ case reflect.Int8,
+ reflect.Uint8:
+ return hash8bitptr
+
+ case reflect.Int16,
+ reflect.Uint16:
+ return hash16bitptr
+
+ case reflect.Int32,
+ reflect.Uint32,
+ reflect.Float32:
+ return hash32bitptr
+
+ case reflect.Int64,
+ reflect.Uint64,
+ reflect.Float64,
+ reflect.Complex64:
+ return hash64bitptr
+
+ case reflect.String:
+ return hashstringptr
+ }
+
+ case reflect.Slice:
+ switch t.Elem().Kind() {
+ case reflect.Int,
+ reflect.Uint,
+ reflect.Uintptr:
+ switch unsafe.Sizeof(int(0)) {
+ case 4:
+ return hash32bitslice
+ case 8:
+ return hash64bitslice
+ default:
+ panic("unexpected platform int size")
+ }
+
+ case reflect.Int8,
+ reflect.Uint8:
+ return hash8bitslice
+
+ case reflect.Int16,
+ reflect.Uint16:
+ return hash16bitslice
+
+ case reflect.Int32,
+ reflect.Uint32,
+ reflect.Float32:
+ return hash32bitslice
+
+ case reflect.Int64,
+ reflect.Uint64,
+ reflect.Float64,
+ reflect.Complex64:
+ return hash64bitslice
+
+ case reflect.String:
+ return hashstringslice
+ }
+ }
+ switch {
+ case t.Implements(reflect.TypeOf((*interface{ MarshalBinary() ([]byte, error) })(nil)).Elem()):
+ return hashbinarymarshaler
+
+ case t.Implements(reflect.TypeOf((*interface{ Bytes() []byte })(nil)).Elem()):
+ return hashbytesmethod
+
+ case t.Implements(reflect.TypeOf((*interface{ String() string })(nil)).Elem()):
+ return hashstringmethod
+
+ case t.Implements(reflect.TypeOf((*interface{ MarshalText() ([]byte, error) })(nil)).Elem()):
+ return hashtextmarshaler
+
+ case t.Implements(reflect.TypeOf((*interface{ MarshalJSON() ([]byte, error) })(nil)).Elem()):
+ return hashjsonmarshaler
+ }
+ panic("unhashable type")
+}
+
+func hash8bit(h *xxh3.Hasher, a any) bool {
+ u := *(*uint8)(iface_value(a))
+ _, _ = h.Write([]byte{u})
+ return u == 0
+}
+
+func hash8bitptr(h *xxh3.Hasher, a any) bool {
+ u := (*uint8)(iface_value(a))
+ if u == nil {
+ _, _ = h.Write([]byte{
+ 0,
+ })
+ return true
+ } else {
+ _, _ = h.Write([]byte{
+ 1,
+ byte(*u),
+ })
+ return false
+ }
+}
+
+func hash8bitslice(h *xxh3.Hasher, a any) bool {
+ b := *(*[]byte)(iface_value(a))
+ _, _ = h.Write(b)
+ return b == nil
+}
+
+func hash16bit(h *xxh3.Hasher, a any) bool {
+ u := *(*uint16)(iface_value(a))
+ _, _ = h.Write([]byte{
+ byte(u),
+ byte(u >> 8),
+ })
+ return u == 0
+}
+
+func hash16bitptr(h *xxh3.Hasher, a any) bool {
+ u := (*uint16)(iface_value(a))
+ if u == nil {
+ _, _ = h.Write([]byte{
+ 0,
+ })
+ return true
+ } else {
+ _, _ = h.Write([]byte{
+ 1,
+ byte(*u),
+ byte(*u >> 8),
+ })
+ return false
+ }
+}
+
+func hash16bitslice(h *xxh3.Hasher, a any) bool {
+ u := *(*[]uint16)(iface_value(a))
+ for i := range u {
+ _, _ = h.Write([]byte{
+ byte(u[i]),
+ byte(u[i] >> 8),
+ })
+ }
+ return u == nil
+}
+
+func hash32bit(h *xxh3.Hasher, a any) bool {
+ u := *(*uint32)(iface_value(a))
+ _, _ = h.Write([]byte{
+ byte(u),
+ byte(u >> 8),
+ byte(u >> 16),
+ byte(u >> 24),
+ })
+ return u == 0
+}
+
+func hash32bitptr(h *xxh3.Hasher, a any) bool {
+ u := (*uint32)(iface_value(a))
+ if u == nil {
+ _, _ = h.Write([]byte{
+ 0,
+ })
+ return true
+ } else {
+ _, _ = h.Write([]byte{
+ 1,
+ byte(*u),
+ byte(*u >> 8),
+ byte(*u >> 16),
+ byte(*u >> 24),
+ })
+ return false
+ }
+}
+
+func hash32bitslice(h *xxh3.Hasher, a any) bool {
+ u := *(*[]uint32)(iface_value(a))
+ for i := range u {
+ _, _ = h.Write([]byte{
+ byte(u[i]),
+ byte(u[i] >> 8),
+ byte(u[i] >> 16),
+ byte(u[i] >> 24),
+ })
+ }
+ return u == nil
+}
+
+func hash64bit(h *xxh3.Hasher, a any) bool {
+ u := *(*uint64)(iface_value(a))
+ _, _ = h.Write([]byte{
+ byte(u),
+ byte(u >> 8),
+ byte(u >> 16),
+ byte(u >> 24),
+ byte(u >> 32),
+ byte(u >> 40),
+ byte(u >> 48),
+ byte(u >> 56),
+ })
+ return u == 0
+}
+
+func hash64bitptr(h *xxh3.Hasher, a any) bool {
+ u := (*uint64)(iface_value(a))
+ if u == nil {
+ _, _ = h.Write([]byte{
+ 0,
+ })
+ return true
+ } else {
+ _, _ = h.Write([]byte{
+ 1,
+ byte(*u),
+ byte(*u >> 8),
+ byte(*u >> 16),
+ byte(*u >> 24),
+ byte(*u >> 32),
+ byte(*u >> 40),
+ byte(*u >> 48),
+ byte(*u >> 56),
+ })
+ return false
+ }
+}
+
+func hash64bitslice(h *xxh3.Hasher, a any) bool {
+ u := *(*[]uint64)(iface_value(a))
+ for i := range u {
+ _, _ = h.Write([]byte{
+ byte(u[i]),
+ byte(u[i] >> 8),
+ byte(u[i] >> 16),
+ byte(u[i] >> 24),
+ byte(u[i] >> 32),
+ byte(u[i] >> 40),
+ byte(u[i] >> 48),
+ byte(u[i] >> 56),
+ })
+ }
+ return u == nil
+}
+
+func hashstring(h *xxh3.Hasher, a any) bool {
+ s := *(*string)(iface_value(a))
+ _, _ = h.WriteString(s)
+ return s == ""
+}
+
+func hashstringptr(h *xxh3.Hasher, a any) bool {
+ s := (*string)(iface_value(a))
+ if s == nil {
+ _, _ = h.Write([]byte{
+ 0,
+ })
+ return true
+ } else {
+ _, _ = h.Write([]byte{
+ 1,
+ })
+ _, _ = h.WriteString(*s)
+ return false
+ }
+}
+
+func hashstringslice(h *xxh3.Hasher, a any) bool {
+ s := *(*[]string)(iface_value(a))
+ for i := range s {
+ _, _ = h.WriteString(s[i])
+ }
+ return s == nil
+}
+
+func hashbinarymarshaler(h *xxh3.Hasher, a any) bool {
+ i := a.(interface{ MarshalBinary() ([]byte, error) })
+ b, _ := i.MarshalBinary()
+ _, _ = h.Write(b)
+ return b == nil
+}
+
+func hashbytesmethod(h *xxh3.Hasher, a any) bool {
+ i := a.(interface{ Bytes() []byte })
+ b := i.Bytes()
+ _, _ = h.Write(b)
+ return b == nil
+}
+
+func hashstringmethod(h *xxh3.Hasher, a any) bool {
+ i := a.(interface{ String() string })
+ s := i.String()
+ _, _ = h.WriteString(s)
+ return s == ""
+}
+
+func hashtextmarshaler(h *xxh3.Hasher, a any) bool {
+ i := a.(interface{ MarshalText() ([]byte, error) })
+ b, _ := i.MarshalText()
+ _, _ = h.Write(b)
+ return b == nil
+}
+
+func hashjsonmarshaler(h *xxh3.Hasher, a any) bool {
+ i := a.(interface{ MarshalJSON() ([]byte, error) })
+ b, _ := i.MarshalJSON()
+ _, _ = h.Write(b)
+ return b == nil
+}
+
+func iface_value(a any) unsafe.Pointer {
+ type eface struct{ _, v unsafe.Pointer }
+ return (*eface)(unsafe.Pointer(&a)).v
+}
diff --git a/vendor/codeberg.org/gruf/go-structr/hasher.go b/vendor/codeberg.org/gruf/go-structr/hasher.go
new file mode 100644
index 000000000..77b8a0991
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-structr/hasher.go
@@ -0,0 +1,176 @@
+package structr
+
+import (
+ "reflect"
+ "strings"
+
+ "github.com/zeebo/xxh3"
+)
+
+// Hasher provides hash checksumming for a configured
+// index, based on an arbitrary combination of generic
+// paramter struct type's fields. This provides hashing
+// both by input of the fields separately, or passing
+// an instance of the generic paramter struct type.
+//
+// Supported field types by the hasher include:
+// - ~int
+// - ~int8
+// - ~int16
+// - ~int32
+// - ~int64
+// - ~float32
+// - ~float64
+// - ~string
+// - slices / ptrs of the above
+type Hasher[StructType any] struct {
+
+ // fields contains our representation
+ // of struct fields contained in the
+ // creation of sums by this hasher.
+ fields []structfield
+
+ // zero specifies whether zero
+ // value fields are permitted.
+ zero bool
+}
+
+// NewHasher returns a new initialized Hasher for the receiving generic
+// parameter type, comprising of the given field strings, and whether to
+// allow zero values to be incldued within generated hash checksum values.
+func NewHasher[T any](fields []string, allowZero bool) Hasher[T] {
+ var h Hasher[T]
+
+ // Preallocate expected struct field slice.
+ h.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.
+ h.fields[i] = sfield
+ }
+
+ // Set config flags.
+ h.zero = allowZero
+
+ return h
+}
+
+// FromParts generates hash checksum (used as index key) from individual key parts.
+func (h *Hasher[T]) FromParts(parts ...any) (sum uint64, ok bool) {
+ hh := getHasher()
+ sum, ok = h.fromParts(hh, parts...)
+ putHasher(hh)
+ return
+
+}
+
+func (h *Hasher[T]) fromParts(hh *xxh3.Hasher, parts ...any) (sum uint64, ok bool) {
+ if len(parts) != len(h.fields) {
+ // User must provide correct number of parts for key.
+ panicf("incorrect number key parts: want=%d received=%d",
+ len(parts),
+ len(h.fields),
+ )
+ }
+
+ if h.zero {
+ // Zero values are permitted,
+ // mangle all values and ignore
+ // zero value return booleans.
+ for i, part := range parts {
+
+ // Write mangled part to hasher.
+ _ = h.fields[i].hasher(hh, part)
+ }
+ } else {
+ // Zero values are NOT permitted.
+ for i, part := range parts {
+
+ // Write mangled field to hasher.
+ z := h.fields[i].hasher(hh, part)
+
+ if z {
+ // The value was zero for
+ // this type, return early.
+ return 0, false
+ }
+ }
+ }
+
+ return hh.Sum64(), true
+}
+
+// FromValue generates hash checksum (used as index key) from a value, via reflection.
+func (h *Hasher[T]) FromValue(value T) (sum uint64, ok bool) {
+ rvalue := reflect.ValueOf(value)
+ hh := getHasher()
+ sum, ok = h.fromRValue(hh, rvalue)
+ putHasher(hh)
+ return
+}
+
+func (h *Hasher[T]) fromRValue(hh *xxh3.Hasher, rvalue reflect.Value) (uint64, bool) {
+ // Follow any ptrs leading to value.
+ for rvalue.Kind() == reflect.Pointer {
+ rvalue = rvalue.Elem()
+ }
+
+ if h.zero {
+ // Zero values are permitted,
+ // mangle all values and ignore
+ // zero value return booleans.
+ for i := range h.fields {
+
+ // Get the reflect value's field at idx.
+ fv := rvalue.FieldByIndex(h.fields[i].index)
+ fi := fv.Interface()
+
+ // Write mangled field to hasher.
+ _ = h.fields[i].hasher(hh, fi)
+ }
+ } else {
+ // Zero values are NOT permitted.
+ for i := range h.fields {
+
+ // Get the reflect value's field at idx.
+ fv := rvalue.FieldByIndex(h.fields[i].index)
+ fi := fv.Interface()
+
+ // Write mangled field to hasher.
+ z := h.fields[i].hasher(hh, fi)
+
+ if z {
+ // The value was zero for
+ // this type, return early.
+ return 0, false
+ }
+ }
+ }
+
+ return hh.Sum64(), true
+}
+
+type structfield struct {
+ // index is the reflected index
+ // of this field (this takes into
+ // account struct nesting).
+ index []int
+
+ // hasher is the relevant function
+ // for hashing value of structfield
+ // into the supplied hashbuf, where
+ // return value indicates if zero.
+ hasher func(*xxh3.Hasher, any) bool
+}
diff --git a/vendor/codeberg.org/gruf/go-structr/index.go b/vendor/codeberg.org/gruf/go-structr/index.go
index bacf6142e..4999249f5 100644
--- a/vendor/codeberg.org/gruf/go-structr/index.go
+++ b/vendor/codeberg.org/gruf/go-structr/index.go
@@ -45,12 +45,12 @@ type Index[StructType any] struct {
// string value of contained fields.
name string
- // struct field key serializer.
- keygen KeyGen[StructType]
+ // struct field key hasher.
+ hasher Hasher[StructType]
// backing in-memory data store of
// generated index keys to result lists.
- data map[string]*list[*result[StructType]]
+ data map[uint64]*list[*result[StructType]]
// whether to allow
// multiple results
@@ -59,20 +59,20 @@ type Index[StructType any] struct {
}
// init initializes this index with the given configuration.
-func (i *Index[T]) init(config IndexConfig) {
+func (i *Index[T]) init(config IndexConfig, max int) {
fields := strings.Split(config.Fields, ",")
i.name = config.Fields
- i.keygen = NewKeyGen[T](fields, config.AllowZero)
+ i.hasher = NewHasher[T](fields, config.AllowZero)
i.unique = !config.Multiple
- i.data = make(map[string]*list[*result[T]])
+ i.data = make(map[uint64]*list[*result[T]], max+1)
}
-// KeyGen returns the key generator associated with this index.
-func (i *Index[T]) KeyGen() *KeyGen[T] {
- return &i.keygen
+// Hasher returns the hash checksummer associated with this index.
+func (i *Index[T]) Hasher() *Hasher[T] {
+ return &i.hasher
}
-func index_append[T any](c *Cache[T], i *Index[T], key string, res *result[T]) {
+func index_append[T any](c *Cache[T], i *Index[T], key uint64, res *result[T]) {
// Acquire + setup indexkey.
ikey := indexkey_acquire(c)
ikey.entry.Value = res
@@ -138,7 +138,7 @@ func index_deleteOne[T any](c *Cache[T], i *Index[T], ikey *indexkey[T]) {
}
}
-func index_delete[T any](c *Cache[T], i *Index[T], key string, fn func(*result[T])) {
+func index_delete[T any](c *Cache[T], i *Index[T], key uint64, fn func(*result[T])) {
if fn == nil {
panic("nil fn")
}
@@ -180,7 +180,7 @@ type indexkey[T any] struct {
// key is the generated index key
// the related result is indexed
// under, in the below index.
- key string
+ key uint64
// index is the index that the
// related result is indexed in.
@@ -205,7 +205,7 @@ func indexkey_acquire[T any](c *Cache[T]) *indexkey[T] {
func indexkey_release[T any](c *Cache[T], ikey *indexkey[T]) {
// Reset indexkey.
ikey.entry.Value = nil
- ikey.key = ""
+ ikey.key = 0
ikey.index = nil
// Release indexkey to memory pool.
diff --git a/vendor/codeberg.org/gruf/go-structr/key.go b/vendor/codeberg.org/gruf/go-structr/key.go
deleted file mode 100644
index 557a5f033..000000000
--- a/vendor/codeberg.org/gruf/go-structr/key.go
+++ /dev/null
@@ -1,204 +0,0 @@
-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
-}
diff --git a/vendor/codeberg.org/gruf/go-structr/util.go b/vendor/codeberg.org/gruf/go-structr/util.go
index d8f227baf..01ac06cf1 100644
--- a/vendor/codeberg.org/gruf/go-structr/util.go
+++ b/vendor/codeberg.org/gruf/go-structr/util.go
@@ -7,8 +7,7 @@ import (
"unicode"
"unicode/utf8"
- "codeberg.org/gruf/go-byteutil"
- "codeberg.org/gruf/go-mangler"
+ "github.com/zeebo/xxh3"
)
// findField will search for a struct field with given set of names, where names is a len > 0 slice of names account for nesting.
@@ -68,22 +67,8 @@ func findField(t reflect.Type, names []string, allowZero bool) (sfield structfie
t = field.Type
}
- // Get final type mangler func.
- sfield.mangler = mangler.Get(t)
-
- if allowZero {
- var buf []byte
-
- // Allocate field instance.
- v := reflect.New(field.Type)
- v = v.Elem()
-
- // Serialize this zero value into buf.
- buf = sfield.mangler(buf, v.Interface())
-
- // Set zero value str.
- sfield.zero = string(buf)
- }
+ // Get final type hash func.
+ sfield.hasher = hasher(t)
return
}
@@ -93,26 +78,21 @@ func panicf(format string, args ...any) {
panic(fmt.Sprintf(format, args...))
}
-// bufpool provides a memory pool of byte
-// buffers used when encoding key types.
-var bufPool sync.Pool
+// hashPool provides a memory pool of xxh3
+// hasher objects used indexing field vals.
+var hashPool sync.Pool
-// getBuf fetches buffer from memory pool.
-func getBuf() *byteutil.Buffer {
- v := bufPool.Get()
+// gethashbuf fetches hasher from memory pool.
+func getHasher() *xxh3.Hasher {
+ v := hashPool.Get()
if v == nil {
- buf := new(byteutil.Buffer)
- buf.B = make([]byte, 0, 512)
- v = buf
+ v = new(xxh3.Hasher)
}
- return v.(*byteutil.Buffer)
+ return v.(*xxh3.Hasher)
}
-// putBuf replaces buffer in memory pool.
-func putBuf(buf *byteutil.Buffer) {
- if buf.Cap() > int(^uint16(0)) {
- return // drop large bufs
- }
- buf.Reset()
- bufPool.Put(buf)
+// putHasher replaces hasher in memory pool.
+func putHasher(h *xxh3.Hasher) {
+ h.Reset()
+ hashPool.Put(h)
}