github.com/mitranim/sqlb@v0.7.2/sqlb_ord_parse.go (about) 1 package sqlb 2 3 import ( 4 "encoding/json" 5 r "reflect" 6 "unsafe" 7 ) 8 9 /* 10 Options related to parsing text into `Ords`. Used by `ParserOrds` and 11 `OrdsParser`. 12 */ 13 type ParseOpt struct { 14 /** 15 Must be a struct type. Ords parsing uses this to detect which fields are 16 allowed, and to convert JSON field names to DB column names. 17 */ 18 Type r.Type 19 20 /** 21 Optional filter. When non-nil, this is invoked for each struct field during 22 ords parsing. If this returns false, the field is "unknown" and may generate 23 a parse error depending on `.Lax`. 24 */ 25 Filter Filter 26 27 /** 28 When true, unknown JSON fields are skipped/ignored durung parsing. When false, 29 unknown JSON fields cause ords parsing to fail with a descriptive error. 30 */ 31 Lax bool 32 } 33 34 /* 35 If `.Type` is empty, sets the type of the provided value. Otherwise this is a 36 nop. The input is used only as a type carrier; its actual value is ignored. The 37 type is consulted when decoding orderings from an input such as JSON. 38 */ 39 func (self *ParseOpt) OrType(typ any) { 40 if self.Type == nil { 41 self.Type = typeElemOf(typ) 42 } 43 } 44 45 /* 46 Contains `Ords` and parsing options, and implements decoder interfaces such as 47 `json.Unmarshaler`. Intended to be included into other structs. Unmarshals text 48 into inner `Ords` in accordance with the parsing options. 49 */ 50 type ParserOrds struct { 51 Ords 52 ParseOpt 53 } 54 55 /* 56 Implement `json.Unmarshaler`. Consults `.Type` to determine known field paths, 57 and converts them to DB column paths, rejecting unknown identifiers. The JSON 58 input must represent an array of strings. See the method `.ParseSlice` for more 59 docs. 60 */ 61 func (self *ParserOrds) UnmarshalJSON(src []byte) error { 62 return OrdsParser{&self.Ords, self.ParseOpt}.UnmarshalJSON(src) 63 } 64 65 /* 66 Parses a string slice which must consist of individual ordering strings such 67 as "one.two.three desc". Ignores empty strings. Used internally for parsing 68 JSON. String slices may also come from URL queries, form-encoded data, and so 69 on. Supported input format: 70 71 <path> <asc|desc>? <nulls first | nulls last>? 72 73 Each path can be a single identifier or dot-separated: 74 75 one 76 one.two 77 one.two.three 78 79 The path MUST correspond to JSON-tagged fields in the reference struct type, 80 which MUST have corresponding DB column names. The parsed ordering uses DB 81 column names, rather than the original JSON names. 82 */ 83 func (self *ParserOrds) ParseSlice(src []string) error { 84 return OrdsParser{&self.Ords, self.ParseOpt}.ParseSlice(src) 85 } 86 87 /* 88 Similar to `ParserOrds`, but intended to be transient and stackframe-local, 89 rather than included into other types. Usually obtained by calling 90 `(*Ords).OrdsParser`. 91 */ 92 type OrdsParser struct { 93 *Ords 94 ParseOpt 95 } 96 97 // Implement `json.Unmarshaler`. See `(*ParserOrds).UnmarshalJSON` for docs. 98 func (self OrdsParser) UnmarshalJSON(src []byte) (err error) { 99 defer rec(&err) 100 var vals []string 101 try(json.Unmarshal(src, &vals)) 102 self.noescape().parseSlice(vals) 103 return 104 } 105 106 // See `(*ParserOrds).ParseSlice` for docs. 107 func (self OrdsParser) ParseSlice(src []string) (err error) { 108 defer rec(&err) 109 self.noescape().parseSlice(src) 110 return 111 } 112 113 func (self *OrdsParser) parseSlice(src []string) { 114 self.Zero() 115 self.Grow(countNonEmptyStrings(src)) 116 for _, val := range src { 117 self.parseAppend(val) 118 } 119 } 120 121 func (self *OrdsParser) parseAppend(src string) { 122 if src == `` { 123 return 124 } 125 126 /** 127 This regexp-based parsing is simple to implement, but extremely inefficient, 128 easily trouncing all our optimizations. TODO rewrite in an efficient manner. 129 */ 130 match := ordReg.FindStringSubmatch(src) 131 if match == nil { 132 panic(errInvalidOrd(src)) 133 } 134 135 typ := self.Type 136 pathStr := match[1] 137 entry, ok := loadStructJsonPathToDbPathFieldValueMap(typ)[pathStr] 138 139 if !ok || !self.filter(entry.Field) { 140 if self.Lax { 141 return 142 } 143 panic(errUnknownField(`converting JSON identifier path to DB path`, pathStr, typeName(typ))) 144 } 145 146 dir := strDir(match[2]) 147 if dir == DirNone { 148 def := entry.Field.Tag.Get(`ord.dir`) 149 if def != `` { 150 dir = strDir(def) 151 } 152 } 153 154 nulls := strNulls(match[3]) 155 if nulls == NullsNone { 156 def := entry.Field.Tag.Get(`ord.nulls`) 157 if def != `` { 158 nulls = strNulls(def) 159 } 160 } 161 162 path := entry.Value 163 164 /** 165 This weird trickery saves some allocations. If we had unwrapped the concrete 166 type `[]string` or `Path`, converted it to another concrete type such as 167 `OrdAsc`, and then converted back to an indirect `Expr`, the final 168 conversion would allocate an exact copy of the original slice header, even 169 though due to being stored behind an interface it's immutable and still 170 points to the same original backing array. As far as I'm concerned, that's a 171 language defect. This is a workaround. 172 */ 173 switch { 174 case dir == DirAsc && nulls == NullsFirst: 175 path = path.Convert(typOrdAscNullsFirst) 176 case dir == DirAsc && nulls == NullsLast: 177 path = path.Convert(typOrdAscNullsLast) 178 case dir == DirDesc && nulls == NullsFirst: 179 path = path.Convert(typOrdDescNullsFirst) 180 case dir == DirDesc && nulls == NullsLast: 181 path = path.Convert(typOrdDescNullsLast) 182 case dir == DirAsc: 183 path = path.Convert(typOrdAsc) 184 case dir == DirDesc: 185 path = path.Convert(typOrdDesc) 186 case nulls == NullsFirst: 187 path = path.Convert(typOrdNullsFirst) 188 case nulls == NullsLast: 189 path = path.Convert(typOrdNullsLast) 190 default: 191 path = path.Convert(typPath) 192 } 193 194 self.Add(path.Interface().(Expr)) 195 } 196 197 func (self *OrdsParser) filter(field r.StructField) bool { 198 return self.Filter == nil || self.Filter.AllowField(field) 199 } 200 201 // Prevents a weird spurious escape that shows up in benchmarks. 202 func (self *OrdsParser) noescape() *OrdsParser { 203 return (*OrdsParser)(noescape(unsafe.Pointer(self))) 204 } 205 206 var ( 207 typOrdAsc = r.TypeOf((*OrdAsc)(nil)).Elem() 208 typOrdDesc = r.TypeOf((*OrdDesc)(nil)).Elem() 209 typOrdNullsFirst = r.TypeOf((*OrdNullsFirst)(nil)).Elem() 210 typOrdNullsLast = r.TypeOf((*OrdNullsLast)(nil)).Elem() 211 typOrdAscNullsFirst = r.TypeOf((*OrdAscNullsFirst)(nil)).Elem() 212 typOrdAscNullsLast = r.TypeOf((*OrdAscNullsLast)(nil)).Elem() 213 typOrdDescNullsFirst = r.TypeOf((*OrdDescNullsFirst)(nil)).Elem() 214 typOrdDescNullsLast = r.TypeOf((*OrdDescNullsLast)(nil)).Elem() 215 typPath = r.TypeOf((*Path)(nil)).Elem() 216 )