github.com/mitranim/sqlb@v0.7.2/sqlb_misc.go (about) 1 package sqlb 2 3 import ( 4 r "reflect" 5 ) 6 7 const ( 8 TagNameDb = `db` 9 TagNameJson = `json` 10 ) 11 12 /* 13 Encodes the provided expressions and returns the resulting text and args. 14 Shortcut for using `(*Bui).Exprs` and `Bui.Reify`. Provided mostly for 15 examples. Actual code may want to use `Bui`: 16 17 bui := MakeBui(4096, 64) 18 panic(bui.CatchExprs(someExprs...)) 19 text, args := bui.Reify() 20 */ 21 func Reify(vals ...Expr) (string, []any) { 22 var bui Bui 23 bui.Exprs(vals...) 24 return bui.Reify() 25 } 26 27 /* 28 Returns the output of `Cols` for the given type, but takes `reflect.Type` as 29 input, rather than a type-carrying `any`. Used internally by `Cols`. 30 The result is cached and reused. Subsequent calls for the same type are nearly 31 free. 32 */ 33 func TypeCols(typ r.Type) string { 34 return colsCache.Get(typeElem(typ)) 35 } 36 37 /* 38 Returns the output of `ColsDeep` for the given type, but takes `reflect.Type` as 39 input, rather than a type-carrying `any`. Used internally by 40 `ColsDeep`. The result is cached and reused. Subsequent calls for the same type 41 are nearly free. 42 */ 43 func TypeColsDeep(typ r.Type) string { 44 return colsDeepCache.Get(typeElem(typ)) 45 } 46 47 /* 48 Returns a parsed `Prep` for the given source string. Panics if parsing fails. 49 Caches the result for each source string, reusing it for future calls. Used 50 internally by `StrQ`. User code shouldn't have to call this, but it's exported 51 just in case. 52 */ 53 func Preparse(val string) Prep { return prepCache.Get(val) } 54 55 // Shortcut for `StrQ{text, List(args)}`. 56 func ListQ(text string, args ...any) StrQ { 57 if len(args) == 0 { 58 return StrQ{text, nil} 59 } 60 return StrQ{text, List(args)} 61 } 62 63 // Shortcut for `StrQ{text, Dict(args)}`. 64 func DictQ(text string, args map[string]any) StrQ { 65 if len(args) == 0 { 66 return StrQ{text, nil} 67 } 68 return StrQ{text, Dict(args)} 69 } 70 71 // Shortcut for `StrQ{text, StructDict{reflect.ValueOf(args)}}`. 72 func StructQ(text string, args any) StrQ { 73 val := valueOf(args) 74 if !val.IsValid() { 75 return StrQ{text, nil} 76 } 77 return StrQ{text, StructDict{val}} 78 } 79 80 // Returns the field's DB column name from the "db" tag, following the JSON 81 // convention of eliding anything after a comma and treating "-" as a 82 // non-name. 83 func FieldDbName(field r.StructField) string { 84 return tagIdent(field.Tag.Get(TagNameDb)) 85 } 86 87 // Returns the field's JSON column name from the "json" tag, following the same 88 // conventions as the `encoding/json` package. 89 func FieldJsonName(field r.StructField) string { 90 return tagIdent(field.Tag.Get(TagNameJson)) 91 } 92 93 const ( 94 DirNone Dir = 0 95 DirAsc Dir = 1 96 DirDesc Dir = 2 97 ) 98 99 // Short for "direction". Enum for ordering direction: none, "asc", "desc". 100 type Dir byte 101 102 // Implement the `AppenderTo` interface, sometimes allowing more efficient text 103 // encoding. 104 func (self Dir) AppendTo(text []byte) []byte { 105 return appendMaybeSpaced(text, self.String()) 106 } 107 108 // Implement `fmt.Stringer` for debug purposes. 109 func (self Dir) String() string { 110 switch self { 111 default: 112 return `` 113 case DirAsc: 114 return `asc` 115 case DirDesc: 116 return `desc` 117 } 118 } 119 120 // Parses from a string, which must be either empty, "asc" or "desc". 121 func (self *Dir) Parse(src string) error { 122 switch src { 123 case ``: 124 *self = DirNone 125 return nil 126 case `asc`: 127 *self = DirAsc 128 return nil 129 case `desc`: 130 *self = DirDesc 131 return nil 132 default: 133 return ErrInvalidInput{Err{ 134 `parsing order direction`, 135 errf(`unrecognized direction %q`, src), 136 }} 137 } 138 } 139 140 // Implement `encoding.TextUnmarshaler`. 141 func (self Dir) MarshalText() ([]byte, error) { 142 return stringToBytesUnsafe(self.String()), nil 143 } 144 145 // Implement `encoding.TextMarshaler`. 146 func (self *Dir) UnmarshalText(src []byte) error { 147 return self.Parse(bytesToMutableString(src)) 148 } 149 150 // Implement `json.Marshaler`. 151 func (self Dir) MarshalJSON() ([]byte, error) { 152 switch self { 153 default: 154 return stringToBytesUnsafe(`null`), nil 155 case DirAsc: 156 return stringToBytesUnsafe(`"asc"`), nil 157 case DirDesc: 158 return stringToBytesUnsafe(`"desc"`), nil 159 } 160 } 161 162 // Implement `fmt.GoStringer` for debug purposes. Returns valid Go code 163 // representing this value. 164 func (self Dir) GoString() string { 165 switch self { 166 default: 167 return `sqlb.DirNone` 168 case DirAsc: 169 return `sqlb.DirAsc` 170 case DirDesc: 171 return `sqlb.DirDesc` 172 } 173 } 174 175 const ( 176 NullsNone Nulls = 0 177 NullsFirst Nulls = 1 178 NullsLast Nulls = 2 179 ) 180 181 // Enum for nulls handling in ordering: none, "nulls first", "nulls last". 182 type Nulls byte 183 184 // Implement the `AppenderTo` interface, sometimes allowing more efficient text 185 // encoding. 186 func (self Nulls) AppendTo(text []byte) []byte { 187 return appendMaybeSpaced(text, self.String()) 188 } 189 190 // Implement `fmt.Stringer` for debug purposes. 191 func (self Nulls) String() string { 192 switch self { 193 case NullsFirst: 194 return `nulls first` 195 case NullsLast: 196 return `nulls last` 197 default: 198 return `` 199 } 200 } 201 202 // Implement `fmt.GoStringer` for debug purposes. Returns valid Go code 203 // representing this value. 204 func (self Nulls) GoString() string { 205 switch self { 206 case NullsFirst: 207 return `sqlb.NullsFirst` 208 case NullsLast: 209 return `sqlb.NullsLast` 210 default: 211 return `sqlb.NullsNone` 212 } 213 } 214 215 /* 216 Implements `Sparse` by filtering fields on their JSON names, using only 217 explicit "json" tags. Fields without explicit "json" names are automatically 218 considered missing. Fields with "json" tags must be present in the provided 219 string set represented by `.Fil`. 220 221 Designed for compatibility with HTTP request decoders provided 222 by "github.com/mitranim/rd", which either implement `Haser` or can easily 223 generate one. Example PATCH endpoint using "rd": 224 225 import "github.com/mitranim/rd" 226 import "github.com/mitranim/try" 227 import s "github.com/mitranim/sqlb" 228 229 dec := rd.TryDownload(req) 230 231 var input SomeStructType 232 try.To(dec.Decode(&input)) 233 234 expr := s.Exprs{ 235 s.Update{s.Ident(`some_table`)}, 236 s.Set{s.StructAssign{s.Partial{input, dec.Haser()}}}, 237 } 238 */ 239 type Partial struct { 240 Val any 241 Fil Haser 242 } 243 244 var _ = Sparse(Partial{}) 245 246 // Implement `Sparse`, returning the underlying value. 247 func (self Partial) Get() any { return self.Val } 248 249 // Implement `Sparse`, using the underlying filter. 250 func (self Partial) AllowField(field r.StructField) bool { 251 name := FieldJsonName(field) 252 return name != `` && self.Fil != nil && self.Fil.Has(name) 253 } 254 255 /* 256 Implements `Filter` by requiring that the struct field has this specific tag. 257 The tag's value for any given field is ignored, only its existence is checked. 258 */ 259 type TagFilter string 260 261 func (self TagFilter) AllowField(field r.StructField) bool { 262 _, ok := field.Tag.Lookup(string(self)) 263 return ok 264 }