gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

key.go (6090B)


      1 package result
      2 
      3 import (
      4 	"fmt"
      5 	"reflect"
      6 	"strings"
      7 	"sync"
      8 	"unicode"
      9 	"unicode/utf8"
     10 
     11 	"codeberg.org/gruf/go-byteutil"
     12 	"codeberg.org/gruf/go-mangler"
     13 )
     14 
     15 // structKeys provides convience methods for a list
     16 // of structKey field combinations used for cache keys.
     17 type structKeys []structKey
     18 
     19 // get fetches the structKey info for given lookup name (else, panics).
     20 func (sk structKeys) get(name string) *structKey {
     21 	for i := range sk {
     22 		if sk[i].name == name {
     23 			return &sk[i]
     24 		}
     25 	}
     26 	panic("unknown lookup: \"" + name + "\"")
     27 }
     28 
     29 // generate will calculate and produce a slice of cache keys the given value
     30 // can be stored under in the, as determined by receiving struct keys.
     31 func (sk structKeys) generate(a any) []cacheKey {
     32 	var keys []cacheKey
     33 
     34 	// Get reflected value in order
     35 	// to access the struct fields
     36 	v := reflect.ValueOf(a)
     37 
     38 	// Iteratively deref pointer value
     39 	for v.Kind() == reflect.Pointer {
     40 		if v.IsNil() {
     41 			panic("nil ptr")
     42 		}
     43 		v = v.Elem()
     44 	}
     45 
     46 	// Acquire byte buffer
     47 	buf := getBuf()
     48 	defer putBuf(buf)
     49 
     50 	for i := range sk {
     51 		// Reset buffer
     52 		buf.B = buf.B[:0]
     53 
     54 		// Append each field value to buffer.
     55 		for _, field := range sk[i].fields {
     56 			fv := v.Field(field.index)
     57 			fi := fv.Interface()
     58 			buf.B = field.mangle(buf.B, fi)
     59 			buf.B = append(buf.B, '.')
     60 		}
     61 
     62 		// Drop last '.'
     63 		buf.Truncate(1)
     64 
     65 		// Don't generate keys for zero values
     66 		if allowZero := sk[i].zero == ""; // nocollapse
     67 		!allowZero && buf.String() == sk[i].zero {
     68 			continue
     69 		}
     70 
     71 		// Append new cached key to slice
     72 		keys = append(keys, cacheKey{
     73 			info: &sk[i],
     74 			key:  string(buf.B), // copy
     75 		})
     76 	}
     77 
     78 	return keys
     79 }
     80 
     81 type cacheKeys []cacheKey
     82 
     83 // drop will drop the cachedKey with lookup name from receiving cacheKeys slice.
     84 func (ck *cacheKeys) drop(name string) {
     85 	_ = *ck // move out of loop
     86 	for i := range *ck {
     87 		if (*ck)[i].info.name == name {
     88 			(*ck) = append((*ck)[:i], (*ck)[i+1:]...)
     89 			break
     90 		}
     91 	}
     92 }
     93 
     94 // cacheKey represents an actual cached key.
     95 type cacheKey struct {
     96 	// info is a reference to the structKey this
     97 	// cacheKey is representing. This is a shared
     98 	// reference and as such only the structKey.pkeys
     99 	// lookup map is expecting to be modified.
    100 	info *structKey
    101 
    102 	// value is the actual string representing
    103 	// this cache key for hashmap lookups.
    104 	key string
    105 }
    106 
    107 // structKey represents a list of struct fields
    108 // encompassing a single cache key, the string name
    109 // of the lookup, the lookup map to primary cache
    110 // keys, and the key's possible zero value string.
    111 type structKey struct {
    112 	// name is the provided cache lookup name for
    113 	// this particular struct key, consisting of
    114 	// period ('.') separated struct field names.
    115 	name string
    116 
    117 	// zero is the possible zero value for this key.
    118 	// if set, this will _always_ be non-empty, as
    119 	// the mangled cache key will never be empty.
    120 	//
    121 	// i.e. zero = ""  --> allow zero value keys
    122 	//      zero != "" --> don't allow zero value keys
    123 	zero string
    124 
    125 	// unique determines whether this structKey supports
    126 	// multiple or just the singular unique result.
    127 	unique bool
    128 
    129 	// fields is a slice of runtime struct field
    130 	// indices, of the fields encompassed by this key.
    131 	fields []structField
    132 
    133 	// pkeys is a lookup of stored struct key values
    134 	// to the primary cache lookup key (int64).
    135 	pkeys map[string][]int64
    136 }
    137 
    138 type structField struct {
    139 	// index is the reflect index of this struct field.
    140 	index int
    141 
    142 	// mangle is the mangler function for
    143 	// serializing values of this struct field.
    144 	mangle mangler.Mangler
    145 }
    146 
    147 // genKey generates a cache key string for given key parts (i.e. serializes them using "go-mangler").
    148 func (sk structKey) genKey(parts []any) string {
    149 	// Check this expected no. key parts.
    150 	if len(parts) != len(sk.fields) {
    151 		panic(fmt.Sprintf("incorrect no. key parts provided: want=%d received=%d", len(parts), len(sk.fields)))
    152 	}
    153 
    154 	// Acquire byte buffer
    155 	buf := getBuf()
    156 	defer putBuf(buf)
    157 	buf.Reset()
    158 
    159 	// Encode each key part
    160 	for i, part := range parts {
    161 		buf.B = sk.fields[i].mangle(buf.B, part)
    162 		buf.B = append(buf.B, '.')
    163 	}
    164 
    165 	// Drop last '.'
    166 	buf.Truncate(1)
    167 
    168 	// Return string copy
    169 	return string(buf.B)
    170 }
    171 
    172 // newStructKey will generate a structKey{} information object for user-given lookup
    173 // key information, and the receiving generic paramter's type information. Panics on error.
    174 func newStructKey(lk Lookup, t reflect.Type) structKey {
    175 	var (
    176 		sk    structKey
    177 		zeros []any
    178 	)
    179 
    180 	// Set the lookup name
    181 	sk.name = lk.Name
    182 
    183 	// Split dot-separated lookup to get
    184 	// the individual struct field names
    185 	names := strings.Split(lk.Name, ".")
    186 	if len(names) == 0 {
    187 		panic("no key fields specified")
    188 	}
    189 
    190 	// Allocate the mangler and field indices slice.
    191 	sk.fields = make([]structField, len(names))
    192 
    193 	for i, name := range names {
    194 		// Get field info for given name
    195 		ft, ok := t.FieldByName(name)
    196 		if !ok {
    197 			panic("no field found for name: \"" + name + "\"")
    198 		}
    199 
    200 		// Check field is usable
    201 		if !isExported(name) {
    202 			panic("field must be exported")
    203 		}
    204 
    205 		// Set the runtime field index
    206 		sk.fields[i].index = ft.Index[0]
    207 
    208 		// Allocate new instance of field
    209 		v := reflect.New(ft.Type)
    210 		v = v.Elem()
    211 
    212 		// Fetch mangler for field type.
    213 		sk.fields[i].mangle = mangler.Get(ft.Type)
    214 
    215 		if !lk.AllowZero {
    216 			// Append the zero value interface
    217 			zeros = append(zeros, v.Interface())
    218 		}
    219 	}
    220 
    221 	if len(zeros) > 0 {
    222 		// Generate zero value string
    223 		sk.zero = sk.genKey(zeros)
    224 	}
    225 
    226 	// Set unique lookup flag.
    227 	sk.unique = !lk.Multi
    228 
    229 	// Allocate primary lookup map
    230 	sk.pkeys = make(map[string][]int64)
    231 
    232 	return sk
    233 }
    234 
    235 // isExported checks whether function name is exported.
    236 func isExported(fnName string) bool {
    237 	r, _ := utf8.DecodeRuneInString(fnName)
    238 	return unicode.IsUpper(r)
    239 }
    240 
    241 // bufpool provides a memory pool of byte
    242 // buffers use when encoding key types.
    243 var bufPool = sync.Pool{
    244 	New: func() any {
    245 		return &byteutil.Buffer{B: make([]byte, 0, 512)}
    246 	},
    247 }
    248 
    249 func getBuf() *byteutil.Buffer {
    250 	return bufPool.Get().(*byteutil.Buffer)
    251 }
    252 
    253 func putBuf(buf *byteutil.Buffer) {
    254 	if buf.Cap() > int(^uint16(0)) {
    255 		return // drop large bufs
    256 	}
    257 	bufPool.Put(buf)
    258 }