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 }