diff --git a/go.mod b/go.mod index 6efbe550..c0a67954 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( codeberg.org/gruf/go-bytesize v1.0.0 codeberg.org/gruf/go-byteutil v1.0.2 codeberg.org/gruf/go-cache/v2 v2.1.4 - codeberg.org/gruf/go-cache/v3 v3.1.6 + codeberg.org/gruf/go-cache/v3 v3.1.7 codeberg.org/gruf/go-debug v1.2.0 codeberg.org/gruf/go-errors/v2 v2.0.2 codeberg.org/gruf/go-kv v1.5.2 diff --git a/go.sum b/go.sum index 2e108223..b055e31c 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ codeberg.org/gruf/go-byteutil v1.0.2 h1:OesVyK5VKWeWdeDR00zRJ+Oy8hjXx1pBhn7WVvcZ codeberg.org/gruf/go-byteutil v1.0.2/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU= codeberg.org/gruf/go-cache/v2 v2.1.4 h1:r+6wJiTHZn0qqf+p1VtAjGOgXXJl7s8txhPIwoSMZtI= codeberg.org/gruf/go-cache/v2 v2.1.4/go.mod h1:j7teiz814lG0PfSfnUs+6HA+2/jcjTAR71Ou3Wbt2Xk= -codeberg.org/gruf/go-cache/v3 v3.1.6 h1:LMpQoLRoGTH64WyLCew6wMVqC3Vzve09MCYbt5c0WR4= -codeberg.org/gruf/go-cache/v3 v3.1.6/go.mod h1:h6im2UVGdrGtNt4IVKARVeoW4kAdok5ts7CbH15UWXs= +codeberg.org/gruf/go-cache/v3 v3.1.7 h1:mWeLxh4CnIfBIbzxdCPlPsRjrernqIlFsMQdLQhUBMo= +codeberg.org/gruf/go-cache/v3 v3.1.7/go.mod h1:h6im2UVGdrGtNt4IVKARVeoW4kAdok5ts7CbH15UWXs= codeberg.org/gruf/go-debug v1.2.0 h1:WBbTMnK1ArFKUmgv04aO2JiC/daTOB8zQGi521qb7OU= codeberg.org/gruf/go-debug v1.2.0/go.mod h1:N+vSy9uJBQgpQcJUqjctvqFz7tBHJf+S/PIjLILzpLg= codeberg.org/gruf/go-errors/v2 v2.0.0/go.mod h1:ZRhbdhvgoUA3Yw6e56kd9Ox984RrvbEFC2pOXyHDJP4= diff --git a/internal/db/bundb/tombstone.go b/internal/db/bundb/tombstone.go index 35032f43..7ce3327a 100644 --- a/internal/db/bundb/tombstone.go +++ b/internal/db/bundb/tombstone.go @@ -36,9 +36,9 @@ type tombstoneDB struct { func (t *tombstoneDB) init() { // Initialize tombstone result cache - t.cache = result.NewSized([]string{ - "ID", - "URI", + t.cache = result.NewSized([]result.Lookup{ + {Name: "ID"}, + {Name: "URI"}, }, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone { t2 := new(gtsmodel.Tombstone) *t2 = *t1 diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go b/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go index 69f5593e..45b7c16d 100644 --- a/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go +++ b/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go @@ -7,7 +7,20 @@ import ( "codeberg.org/gruf/go-cache/v3/ttl" ) -// Cache ... +// Lookup represents a struct object lookup method in the cache. +type Lookup struct { + // Name is a period ('.') separated string + // of struct fields this Key encompasses. + Name string + + // AllowZero indicates whether to accept and cache + // under zero value keys, otherwise ignore them. + AllowZero bool +} + +// Cache provides a means of caching value structures, along with +// the results of attempting to load them. An example usecase of this +// cache would be in wrapping a database, allowing caching of sql.ErrNoRows. type Cache[Value any] struct { cache ttl.Cache[int64, result[Value]] // underlying result cache lookups structKeys // pre-determined struct lookups @@ -16,12 +29,12 @@ type Cache[Value any] struct { } // New returns a new initialized Cache, with given lookups and underlying value copy function. -func New[Value any](lookups []string, copy func(Value) Value) *Cache[Value] { +func New[Value any](lookups []Lookup, copy func(Value) Value) *Cache[Value] { return NewSized(lookups, copy, 64) } // NewSized returns a new initialized Cache, with given lookups, underlying value copy function and provided capacity. -func NewSized[Value any](lookups []string, copy func(Value) Value, cap int) *Cache[Value] { +func NewSized[Value any](lookups []Lookup, copy func(Value) Value, cap int) *Cache[Value] { var z Value // Determine generic type @@ -39,13 +52,11 @@ func NewSized[Value any](lookups []string, copy func(Value) Value, cap int) *Cac // Allocate new cache object c := &Cache[Value]{copy: copy} - c.lookups = make([]keyFields, len(lookups)) + c.lookups = make([]structKey, len(lookups)) for i, lookup := range lookups { // Generate keyed field info for lookup - c.lookups[i].pkeys = make(map[string]int64, cap) - c.lookups[i].lookup = lookup - c.lookups[i].populate(t) + c.lookups[i] = genStructKey(lookup, t) } // Create and initialize underlying cache @@ -82,7 +93,7 @@ func (c *Cache[Value]) SetEvictionCallback(hook func(Value)) { c.cache.SetEvictionCallback(func(item *ttl.Entry[int64, result[Value]]) { for _, key := range item.Value.Keys { // Delete key->pkey lookup - pkeys := key.fields.pkeys + pkeys := key.key.pkeys delete(pkeys, key.value) } @@ -104,11 +115,9 @@ func (c *Cache[Value]) SetInvalidateCallback(hook func(Value)) { } c.cache.SetInvalidateCallback(func(item *ttl.Entry[int64, result[Value]]) { for _, key := range item.Value.Keys { - if key.fields != nil { - // Delete key->pkey lookup - pkeys := key.fields.pkeys - delete(pkeys, key.value) - } + // Delete key->pkey lookup + pkeys := key.key.pkeys + delete(pkeys, key.value) } if item.Value.Error != nil { @@ -121,25 +130,24 @@ func (c *Cache[Value]) SetInvalidateCallback(hook func(Value)) { }) } -// Load ... +// Load will attempt to load an existing result from the cacche for the given lookup and key parts, else calling the load function and caching that result. func (c *Cache[Value]) Load(lookup string, load func() (Value, error), keyParts ...any) (Value, error) { var ( zero Value res result[Value] ) - // Get lookup map by name. - kfields := c.getFields(lookup) - lmap := kfields.pkeys + // Get lookup key info by name. + keyInfo := c.lookups.get(lookup) // Generate cache key string. - ckey := genkey(keyParts...) + ckey := genKey(keyParts...) // Acquire cache lock c.cache.Lock() - // Look for primary key - pkey, ok := lmap[ckey] + // Look for primary key for cache key + pkey, ok := keyInfo.pkeys[ckey] if ok { // Fetch the result for primary key @@ -157,9 +165,9 @@ func (c *Cache[Value]) Load(lookup string, load func() (Value, error), keyParts if res.Error != nil { // This load returned an error, only // store this item under provided key. - res.Keys = []cacheKey{{ - value: ckey, - fields: kfields, + res.Keys = []cachedKey{{ + key: keyInfo, + value: ckey, }} } else { // This was a successful load, generate keys. @@ -185,7 +193,7 @@ func (c *Cache[Value]) Load(lookup string, load func() (Value, error), keyParts return c.copy(res.Value), nil } -// Store ... +// Store will call the given store function, and on success store the value in the cache as a positive result. func (c *Cache[Value]) Store(value Value, store func() error) error { // Attempt to store this value. if err := store(); err != nil { @@ -212,22 +220,21 @@ func (c *Cache[Value]) Store(value Value, store func() error) error { return nil } -// Has ... +// Has checks the cache for a positive result under the given lookup and key parts. func (c *Cache[Value]) Has(lookup string, keyParts ...any) bool { var res result[Value] - // Get lookup map by name. - kfields := c.getFields(lookup) - lmap := kfields.pkeys + // Get lookup key type by name. + keyType := c.lookups.get(lookup) // Generate cache key string. - ckey := genkey(keyParts...) + ckey := genKey(keyParts...) // Acquire cache lock c.cache.Lock() - // Look for primary key - pkey, ok := lmap[ckey] + // Look for primary key for cache key + pkey, ok := keyType.pkeys[ckey] if ok { // Fetch the result for primary key @@ -242,18 +249,17 @@ func (c *Cache[Value]) Has(lookup string, keyParts ...any) bool { return ok && (res.Error == nil) } -// Invalidate ... +// Invalidate will invalidate any result from the cache found under given lookup and key parts. func (c *Cache[Value]) Invalidate(lookup string, keyParts ...any) { - // Get lookup map by name. - kfields := c.getFields(lookup) - lmap := kfields.pkeys + // Get lookup key type by name. + keyType := c.lookups.get(lookup) // Generate cache key string. - ckey := genkey(keyParts...) + ckey := genKey(keyParts...) - // Look for primary key + // Look for primary key for cache key c.cache.Lock() - pkey, ok := lmap[ckey] + pkey, ok := keyType.pkeys[ckey] c.cache.Unlock() if !ok { @@ -269,29 +275,19 @@ func (c *Cache[Value]) Clear() { c.cache.Clear() } -// Len ... +// Len returns the current length of the cache. func (c *Cache[Value]) Len() int { return c.cache.Cache.Len() } -// Cap ... +// Cap returns the maximum capacity of this result cache. func (c *Cache[Value]) Cap() int { return c.cache.Cache.Cap() } -func (c *Cache[Value]) getFields(name string) *keyFields { - for _, k := range c.lookups { - // Find key fields with name - if k.lookup == name { - return &k - } - } - panic("invalid lookup: " + name) -} - func (c *Cache[Value]) storeResult(res result[Value]) (string, bool) { for _, key := range res.Keys { - pkeys := key.fields.pkeys + pkeys := key.key.pkeys // Look for cache primary key pkey, ok := pkeys[key.value] @@ -304,6 +300,9 @@ func (c *Cache[Value]) storeResult(res result[Value]) (string, bool) { if entry.Value.Error == nil { return key.value, false } + + // Delete existing error result + c.cache.Cache.Delete(pkey) } } @@ -313,7 +312,7 @@ func (c *Cache[Value]) storeResult(res result[Value]) (string, bool) { // Store all primary key lookups for _, key := range res.Keys { - pkeys := key.fields.pkeys + pkeys := key.key.pkeys pkeys[key.value] = pkey } @@ -331,7 +330,7 @@ func (c *Cache[Value]) storeResult(res result[Value]) (string, bool) { type result[Value any] struct { // keys accessible under - Keys []cacheKey + Keys []cachedKey // cached value Value Value diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/key.go b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go index ec58e0ef..0dc1276a 100644 --- a/vendor/codeberg.org/gruf/go-cache/v3/result/key.go +++ b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go @@ -12,22 +12,24 @@ import ( ) // structKeys provides convience methods for a list -// of struct field combinations used for cache keys. -type structKeys []keyFields +// of structKey field combinations used for cache keys. +type structKeys []structKey -// get fetches the key-fields for given lookup (else, panics). -func (sk structKeys) get(lookup string) *keyFields { +// 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].lookup == lookup { + if sk[i].name == name { return &sk[i] } } - panic("unknown lookup: \"" + lookup + "\"") + panic("unknown lookup: \"" + name + "\"") } -// 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 { +// 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) []cachedKey { + var keys []cachedKey + // Get reflected value in order // to access the struct fields v := reflect.ValueOf(a) @@ -40,9 +42,6 @@ func (sk structKeys) generate(a any) []cacheKey { 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) @@ -51,54 +50,63 @@ func (sk structKeys) generate(a any) []cacheKey { // Reset buffer buf.B = buf.B[:0] - // Set the key-fields reference - keys[i].fields = &sk[i] + // Append each field value to buffer. + for _, idx := range sk[i].fields { + fv := v.Field(idx) + fi := fv.Interface() + buf.B = mangler.Append(buf.B, fi) + buf.B = append(buf.B, '.') + } - // Calculate cache-key value - keys[i].populate(buf, v) + // Drop last '.' + buf.Truncate(1) + + // Don't generate keys for zero values + if allowZero := sk[i].zero == ""; // nocollapse + !allowZero && buf.String() == sk[i].zero { + continue + } + + // Append new cached key to slice + keys = append(keys, cachedKey{ + key: &sk[i], + value: string(buf.B), // copy + }) } return keys } -// cacheKey represents an actual cache key. -type cacheKey struct { +// cachedKey represents an actual cached key. +type cachedKey struct { + // key 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. + key *structKey + // 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, '.') - } +// 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 - // 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 + // zero is the possible zero value for this key. + // if set, this will _always_ be non-empty, as + // the mangled cache key will never be empty. + // + // i.e. zero = "" --> allow zero value keys + // zero != "" --> don't allow zero value keys + zero string // fields is a slice of runtime struct field // indices, of the fields encompassed by this key. @@ -109,19 +117,20 @@ type keyFields struct { 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) { +// genStructKey will generate a structKey{} information object for user-given lookup +// key information, and the receiving generic paramter's type information. Panics on error. +func genStructKey(lk Lookup, t reflect.Type) structKey { + var zeros []any + // Split dot-separated lookup to get // the individual struct field names - names := strings.Split(kf.lookup, ".") + names := strings.Split(lk.Name, ".") if len(names) == 0 { panic("no key fields specified") } // Pre-allocate slice of expected length - kf.fields = make([]int, len(names)) + fields := make([]int, len(names)) for i, name := range names { // Get field info for given name @@ -136,13 +145,36 @@ func (kf *keyFields) populate(t reflect.Type) { } // Set the runtime field index - kf.fields[i] = ft.Index[0] + fields[i] = ft.Index[0] + + // Allocate new instance of field + v := reflect.New(ft.Type) + v = v.Elem() + + if !lk.AllowZero { + // Append the zero value interface + zeros = append(zeros, v.Interface()) + } + } + + var zvalue string + + if len(zeros) > 0 { + // Generate zero value string + zvalue = genKey(zeros...) + } + + return structKey{ + name: lk.Name, + zero: zvalue, + fields: fields, + pkeys: make(map[string]int64), } } -// genkey generates a cache key for given key values. -func genkey(parts ...any) string { - if len(parts) < 1 { +// genKey generates a cache key for given key values. +func genKey(parts ...any) string { + if len(parts) == 0 { // Panic to prevent annoying usecase // where user forgets to pass lookup // and instead only passes a key part, diff --git a/vendor/modules.txt b/vendor/modules.txt index 1e9e8590..fcf7d062 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -16,7 +16,7 @@ codeberg.org/gruf/go-byteutil # codeberg.org/gruf/go-cache/v2 v2.1.4 ## explicit; go 1.19 codeberg.org/gruf/go-cache/v2 -# codeberg.org/gruf/go-cache/v3 v3.1.6 +# codeberg.org/gruf/go-cache/v3 v3.1.7 ## explicit; go 1.19 codeberg.org/gruf/go-cache/v3/result codeberg.org/gruf/go-cache/v3/ttl