| 
									
										
										
										
											2024-01-19 12:57:29 +00: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 12:14:10 +00:00
										 |  |  | 	// struct field key hasher. | 
					
						
							|  |  |  | 	hasher Hasher[StructType] | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// backing in-memory data store of | 
					
						
							|  |  |  | 	// generated index keys to result lists. | 
					
						
							| 
									
										
										
										
											2024-01-26 12:14:10 +00:00
										 |  |  | 	data map[uint64]*list[*result[StructType]] | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// whether to allow | 
					
						
							|  |  |  | 	// multiple results | 
					
						
							|  |  |  | 	// per index key. | 
					
						
							|  |  |  | 	unique bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // init initializes this index with the given configuration. | 
					
						
							| 
									
										
										
										
											2024-01-26 12:14:10 +00:00
										 |  |  | func (i *Index[T]) init(config IndexConfig, max int) { | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	fields := strings.Split(config.Fields, ",") | 
					
						
							|  |  |  | 	i.name = config.Fields | 
					
						
							| 
									
										
										
										
											2024-01-26 12:14:10 +00:00
										 |  |  | 	i.hasher = NewHasher[T](fields, config.AllowZero) | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	i.unique = !config.Multiple | 
					
						
							| 
									
										
										
										
											2024-01-26 12:14:10 +00:00
										 |  |  | 	i.data = make(map[uint64]*list[*result[T]], max+1) | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 12:14:10 +00:00
										 |  |  | // Hasher returns the hash checksummer associated with this index. | 
					
						
							|  |  |  | func (i *Index[T]) Hasher() *Hasher[T] { | 
					
						
							|  |  |  | 	return &i.hasher | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 12:14:10 +00:00
										 |  |  | func index_append[T any](c *Cache[T], i *Index[T], key uint64, res *result[T]) { | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00: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 12:14:10 +00:00
										 |  |  | func index_delete[T any](c *Cache[T], i *Index[T], key uint64, fn func(*result[T])) { | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00: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 12:14:10 +00:00
										 |  |  | 	key uint64 | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00: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 12:14:10 +00:00
										 |  |  | 	ikey.key = 0 | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	ikey.index = nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Release indexkey to memory pool. | 
					
						
							|  |  |  | 	c.keyPool = append(c.keyPool, ikey) | 
					
						
							|  |  |  | } |