2024-01-19 15:57:29 +03:00
|
|
|
package structr
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// IndexConfig defines config variables
|
|
|
|
// for initializing a struct index.
|
|
|
|
type IndexConfig struct {
|
|
|
|
|
|
|
|
// Fields should contain a comma-separated
|
|
|
|
// list of struct fields used when generating
|
|
|
|
// keys for this index. Nested fields should
|
|
|
|
// be specified using periods. An example:
|
|
|
|
// "Username,Favorites.Color"
|
|
|
|
Fields string
|
|
|
|
|
|
|
|
// Multiple indicates whether to accept multiple
|
|
|
|
// possible values for any single index key. The
|
|
|
|
// default behaviour is to only accept one value
|
|
|
|
// and overwrite existing on any write operation.
|
|
|
|
Multiple bool
|
|
|
|
|
|
|
|
// AllowZero indicates whether to accept zero
|
|
|
|
// value fields in index keys. i.e. whether to
|
|
|
|
// index structs for this set of field values
|
|
|
|
// IF any one of those field values is the zero
|
|
|
|
// value for that type. The default behaviour
|
|
|
|
// is to skip indexing structs for this lookup
|
|
|
|
// when any of the indexing fields are zero.
|
|
|
|
AllowZero bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Index is an exposed Cache internal model, used to
|
|
|
|
// generate keys and store struct results by the init
|
|
|
|
// defined key generation configuration. This model is
|
|
|
|
// exposed to provide faster lookups in the case that
|
|
|
|
// you would like to manually provide the used index
|
|
|
|
// via the Cache.___By() series of functions, or access
|
|
|
|
// the underlying index key generator.
|
|
|
|
type Index[StructType any] struct {
|
|
|
|
|
|
|
|
// name is the actual name of this
|
|
|
|
// index, which is the unparsed
|
|
|
|
// string value of contained fields.
|
|
|
|
name string
|
|
|
|
|
2024-01-26 15:14:10 +03:00
|
|
|
// struct field key hasher.
|
|
|
|
hasher Hasher[StructType]
|
2024-01-19 15:57:29 +03:00
|
|
|
|
|
|
|
// backing in-memory data store of
|
|
|
|
// generated index keys to result lists.
|
2024-01-26 15:14:10 +03:00
|
|
|
data map[uint64]*list[*result[StructType]]
|
2024-01-19 15:57:29 +03:00
|
|
|
|
|
|
|
// whether to allow
|
|
|
|
// multiple results
|
|
|
|
// per index key.
|
|
|
|
unique bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// init initializes this index with the given configuration.
|
2024-01-26 15:14:10 +03:00
|
|
|
func (i *Index[T]) init(config IndexConfig, max int) {
|
2024-01-19 15:57:29 +03:00
|
|
|
fields := strings.Split(config.Fields, ",")
|
|
|
|
i.name = config.Fields
|
2024-01-26 15:14:10 +03:00
|
|
|
i.hasher = NewHasher[T](fields, config.AllowZero)
|
2024-01-19 15:57:29 +03:00
|
|
|
i.unique = !config.Multiple
|
2024-01-26 15:14:10 +03:00
|
|
|
i.data = make(map[uint64]*list[*result[T]], max+1)
|
2024-01-19 15:57:29 +03:00
|
|
|
}
|
|
|
|
|
2024-01-26 15:14:10 +03:00
|
|
|
// Hasher returns the hash checksummer associated with this index.
|
|
|
|
func (i *Index[T]) Hasher() *Hasher[T] {
|
|
|
|
return &i.hasher
|
2024-01-19 15:57:29 +03:00
|
|
|
}
|
|
|
|
|
2024-01-26 15:14:10 +03:00
|
|
|
func index_append[T any](c *Cache[T], i *Index[T], key uint64, res *result[T]) {
|
2024-01-19 15:57:29 +03:00
|
|
|
// Acquire + setup indexkey.
|
|
|
|
ikey := indexkey_acquire(c)
|
|
|
|
ikey.entry.Value = res
|
|
|
|
ikey.key = key
|
|
|
|
ikey.index = i
|
|
|
|
|
|
|
|
// Append to result's indexkeys.
|
|
|
|
res.keys = append(res.keys, ikey)
|
|
|
|
|
|
|
|
// Get list at key.
|
|
|
|
l := i.data[key]
|
|
|
|
|
|
|
|
if l == nil {
|
|
|
|
|
|
|
|
// Allocate new list.
|
|
|
|
l = list_acquire(c)
|
|
|
|
i.data[key] = l
|
|
|
|
|
|
|
|
} else if i.unique {
|
|
|
|
|
|
|
|
// Remove currently
|
|
|
|
// indexed result.
|
|
|
|
old := l.head
|
|
|
|
l.remove(old)
|
|
|
|
|
|
|
|
// Get ptr to old
|
|
|
|
// result before we
|
|
|
|
// release to pool.
|
|
|
|
res := old.Value
|
|
|
|
|
|
|
|
// Drop this index's key from
|
|
|
|
// old res now not indexed here.
|
|
|
|
result_dropIndex(c, res, i)
|
|
|
|
if len(res.keys) == 0 {
|
|
|
|
|
|
|
|
// Old res now unused,
|
|
|
|
// release to mem pool.
|
|
|
|
result_release(c, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add result indexkey to
|
|
|
|
// front of results list.
|
|
|
|
l.pushFront(&ikey.entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
func index_deleteOne[T any](c *Cache[T], i *Index[T], ikey *indexkey[T]) {
|
|
|
|
// Get list at key.
|
|
|
|
l := i.data[ikey.key]
|
|
|
|
if l == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove from list.
|
|
|
|
l.remove(&ikey.entry)
|
|
|
|
if l.len == 0 {
|
|
|
|
|
|
|
|
// Remove list from map.
|
|
|
|
delete(i.data, ikey.key)
|
|
|
|
|
|
|
|
// Release list to pool.
|
|
|
|
list_release(c, l)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 15:14:10 +03:00
|
|
|
func index_delete[T any](c *Cache[T], i *Index[T], key uint64, fn func(*result[T])) {
|
2024-01-19 15:57:29 +03:00
|
|
|
if fn == nil {
|
|
|
|
panic("nil fn")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get list at key.
|
|
|
|
l := i.data[key]
|
|
|
|
if l == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete data at key.
|
|
|
|
delete(i.data, key)
|
|
|
|
|
|
|
|
// Iterate results in list.
|
|
|
|
for x := 0; x < l.len; x++ {
|
|
|
|
|
|
|
|
// Pop current head.
|
|
|
|
res := l.head.Value
|
|
|
|
l.remove(l.head)
|
|
|
|
|
|
|
|
// Delete index's key
|
|
|
|
// from result tracking.
|
|
|
|
result_dropIndex(c, res, i)
|
|
|
|
|
|
|
|
// Call hook.
|
|
|
|
fn(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Release list to pool.
|
|
|
|
list_release(c, l)
|
|
|
|
}
|
|
|
|
|
|
|
|
type indexkey[T any] struct {
|
|
|
|
// linked list entry the related
|
|
|
|
// result is stored under in the
|
|
|
|
// Index.data[key] linked list.
|
|
|
|
entry elem[*result[T]]
|
|
|
|
|
|
|
|
// key is the generated index key
|
|
|
|
// the related result is indexed
|
|
|
|
// under, in the below index.
|
2024-01-26 15:14:10 +03:00
|
|
|
key uint64
|
2024-01-19 15:57:29 +03:00
|
|
|
|
|
|
|
// index is the index that the
|
|
|
|
// related result is indexed in.
|
|
|
|
index *Index[T]
|
|
|
|
}
|
|
|
|
|
|
|
|
func indexkey_acquire[T any](c *Cache[T]) *indexkey[T] {
|
|
|
|
var ikey *indexkey[T]
|
|
|
|
|
|
|
|
if len(c.keyPool) == 0 {
|
|
|
|
// Allocate new key.
|
|
|
|
ikey = new(indexkey[T])
|
|
|
|
} else {
|
|
|
|
// Pop result from pool slice.
|
|
|
|
ikey = c.keyPool[len(c.keyPool)-1]
|
|
|
|
c.keyPool = c.keyPool[:len(c.keyPool)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return ikey
|
|
|
|
}
|
|
|
|
|
|
|
|
func indexkey_release[T any](c *Cache[T], ikey *indexkey[T]) {
|
|
|
|
// Reset indexkey.
|
|
|
|
ikey.entry.Value = nil
|
2024-01-26 15:14:10 +03:00
|
|
|
ikey.key = 0
|
2024-01-19 15:57:29 +03:00
|
|
|
ikey.index = nil
|
|
|
|
|
|
|
|
// Release indexkey to memory pool.
|
|
|
|
c.keyPool = append(c.keyPool, ikey)
|
|
|
|
}
|