github.com/mitranim/sqlb@v0.7.2/sqlb_dict.go (about) 1 package sqlb 2 3 import r "reflect" 4 5 /* 6 Variant of `[]any` conforming to the `ArgDict` interface. Supports only 7 ordinal parameters, not named parameters. Used for `StrQ`. See the `ListQ` 8 shortcut. 9 */ 10 type List []any 11 12 // Implement part of the `ArgDict` interface. 13 func (self List) IsEmpty() bool { return self.Len() == 0 } 14 15 // Implement part of the `ArgDict` interface. 16 func (self List) Len() int { return len(self) } 17 18 // Implement part of the `ArgDict` interface. 19 func (self List) GotOrdinal(key int) (any, bool) { 20 if key >= 0 && key < len(self) { 21 return self[key], true 22 } 23 return nil, false 24 } 25 26 // Implement part of the `ArgDict` interface. Always returns `nil, false`. 27 func (self List) GotNamed(string) (any, bool) { return nil, false } 28 29 // Implement `OrdinalRanger` to automatically validate used/unused arguments. 30 func (self List) RangeOrdinal(fun func(int)) { 31 if fun != nil { 32 for ind := range counter(len(self)) { 33 fun(ind) 34 } 35 } 36 } 37 38 /* 39 Variant of `map[string]any` conforming to the `ArgDict` interface. 40 Supports only named parameters, not ordinal parameters. Used for `StrQ`. See 41 the `DictQ` shortcut. 42 */ 43 type Dict map[string]any 44 45 // Implement part of the `ArgDict` interface. 46 func (self Dict) IsEmpty() bool { return self.Len() == 0 } 47 48 // Implement part of the `ArgDict` interface. 49 func (self Dict) Len() int { return len(self) } 50 51 // Implement part of the `ArgDict` interface. Always returns `nil, false`. 52 func (self Dict) GotOrdinal(int) (any, bool) { return nil, false } 53 54 // Implement part of the `ArgDict` interface. 55 func (self Dict) GotNamed(key string) (any, bool) { 56 val, ok := self[key] 57 return val, ok 58 } 59 60 // Implement `NamedRanger` to automatically validate used/unused arguments. 61 func (self Dict) RangeNamed(fun func(string)) { 62 if fun != nil { 63 for key := range self { 64 fun(key) 65 } 66 } 67 } 68 69 /* 70 Variant of `Dict` without support for validating unused arguments. Note that 71 missing arguments are still detected and cause errors. Useful when generating 72 the dictionary dynamically, rather than hardcoding the set of keys. Must be 73 used with `StrQ` or `Prep`, rather than with `DictQ`, because the latter always 74 converts the given dictionary to `Dict`. 75 */ 76 type LaxDict Dict 77 78 // Implement part of the `ArgDict` interface. 79 func (self LaxDict) IsEmpty() bool { return Dict(self).IsEmpty() } 80 81 // Implement part of the `ArgDict` interface. 82 func (self LaxDict) Len() int { return Dict(self).Len() } 83 84 // Implement part of the `ArgDict` interface. Always returns `nil, false`. 85 func (self LaxDict) GotOrdinal(int) (any, bool) { return nil, false } 86 87 // Implement part of the `ArgDict` interface. 88 func (self LaxDict) GotNamed(key string) (any, bool) { return Dict(self).GotNamed(key) } 89 90 /* 91 Implements `ArgDict` by reading struct fields and methods by name. Supports only 92 named parameters, not ordinal parameters. The inner value must be either 93 invalid or a struct. Compared to `Dict`, a struct is way faster to construct, 94 but reading fields by name is way slower. Used for `StrQ`. See the `StructQ` 95 shortcut. 96 */ 97 type StructDict [1]r.Value 98 99 // Implement part of the `ArgDict` interface. 100 func (self StructDict) IsEmpty() bool { 101 return !self[0].IsValid() || isStructTypeEmpty(self[0].Type()) 102 } 103 104 // Implement part of the `ArgDict` interface. Always returns 0. 105 func (self StructDict) Len() int { return 0 } 106 107 // Implement part of the `ArgDict` interface. Always returns `nil, false`. 108 func (self StructDict) GotOrdinal(int) (any, bool) { return nil, false } 109 110 // Implement part of the `ArgDict` interface. 111 func (self StructDict) GotNamed(key string) (any, bool) { 112 /** 113 (Tested in Go 1.17.) 114 115 In our benchmarks, making a struct dict is about 15 times faster than a normal 116 dict (100ns vs 1500ns for 12 fields and 12 methods), but accessing various 117 fields and methods is about 25 times slower on average (5000ns vs 200ns for 118 12 fields and 12 methods). When using only fields without methods, the 119 access time numbers are much closer (700ns vs 100ns for 12 fields). The 120 total numbers are close enough, and small enough, to justify both, depending 121 on the use case. 122 123 Compared to using `reflect.Value.FieldByName` and `reflect.Value.MethodByName` 124 every time, using a cached dict with field and method indexes improves 125 average access performance by about 3 times in our benchmarks. 126 */ 127 128 val := valueDeref(self[0]) 129 if !val.IsValid() { 130 return nil, false 131 } 132 133 path, ok := loadStructPathMap(val.Type())[key] 134 if !ok { 135 return nil, false 136 } 137 138 if path.FieldIndex != nil { 139 return val.FieldByIndex(path.FieldIndex).Interface(), true 140 } 141 142 meth := val.Method(path.MethodIndex) 143 if meth.IsValid() { 144 reqGetter(val.Type(), meth.Type(), key) 145 return meth.Call(nil)[0].Interface(), true 146 } 147 148 return nil, false 149 }