github.com/mitranim/sqlb@v0.7.2/t_main_test.go (about) 1 package sqlb 2 3 import ( 4 "fmt" 5 r "reflect" 6 "runtime" 7 "strings" 8 "testing" 9 "time" 10 u "unsafe" 11 ) 12 13 type Internal struct { 14 Id string `json:"internalId" db:"id"` 15 Name string `json:"internalName" db:"name"` 16 } 17 18 type External struct { 19 Id string `json:"externalId" db:"id"` 20 Name string `json:"externalName" db:"name"` 21 Internal Internal `json:"externalInternal" db:"internal"` 22 } 23 24 // nolint:govet 25 type Embed struct { 26 Id string `json:"embedId" db:"embed_id"` 27 Name string `json:"embedName" db:"embed_name"` 28 private string `json:"embedPrivate" db:"embed_private"` 29 Untagged0 string `` 30 Untagged1 string `db:"-"` 31 _ string `db:"blank"` 32 } 33 34 type Outer struct { 35 Embed 36 Id string `json:"outerId" db:"outer_id"` 37 Name string `json:"outerName" db:"outer_name"` 38 OnlyJson string `json:"onlyJson"` 39 } 40 41 var testOuter = Outer{ 42 Id: `outer id`, 43 Name: `outer name`, 44 Embed: Embed{ 45 Id: `embed id`, 46 Name: `embed name`, 47 private: `private`, 48 Untagged0: `untagged 0`, 49 Untagged1: `untagged 1`, 50 }, 51 } 52 53 type Void struct{} 54 55 func (Void) GetVal() any { return `val` } 56 57 type UnitStruct struct { 58 One any `db:"one" json:"one"` 59 } 60 61 func (self UnitStruct) GetOne() any { return self.One } 62 func (self UnitStruct) UnaryVoid(any) {} 63 func (self UnitStruct) NullaryPair() (_, _ any) { return } 64 65 type UnitStruct1 struct { 66 Two any `db:"two" json:"two"` 67 } 68 69 type PairStruct struct { 70 One any `db:"one" json:"one"` 71 Two any `db:"two" json:"two"` 72 } 73 74 func (self PairStruct) GetOne() any { return self.One } 75 func (self PairStruct) GetTwo() any { return self.Two } 76 77 type PairStruct1 struct { 78 Three any `db:"three" json:"three"` 79 Four any `db:"four" json:"four"` 80 } 81 82 type TrioStruct struct { 83 One any `db:"one" json:"one"` 84 Two any `db:"two" json:"two"` 85 Three any `db:"three" json:"three"` 86 } 87 88 func (self TrioStruct) GetOne() any { return self.One } 89 func (self TrioStruct) GetTwo() any { return self.Two } 90 func (self TrioStruct) GetThree() any { return self.Three } 91 92 type list = []any 93 94 type Encoder interface { 95 fmt.Stringer 96 AppenderTo 97 } 98 99 type EncoderExpr interface { 100 Encoder 101 Expr 102 } 103 104 func testEncoder(t testing.TB, exp string, val Encoder) { 105 t.Helper() 106 eq(t, exp, val.String()) 107 eq(t, exp, string(val.AppendTo(nil))) 108 } 109 110 func testEncoderExpr(t testing.TB, exp string, val EncoderExpr) { 111 t.Helper() 112 testEncoder(t, exp, val) 113 eq(t, exp, string(reify(val).Text)) 114 } 115 116 func testExpr(t testing.TB, exp R, val EncoderExpr) { 117 t.Helper() 118 testEncoderExpr(t, string(exp.Text), val) 119 testExprs(t, exp, val) 120 } 121 122 func testExprs(t testing.TB, exp R, vals ...Expr) { 123 t.Helper() 124 eq(t, exp, reify(vals...)) 125 } 126 127 func exprTest(t testing.TB) func(R, EncoderExpr) { 128 return func(exp R, val EncoderExpr) { 129 t.Helper() 130 testExpr(t, exp, val) 131 } 132 } 133 134 func reify(vals ...Expr) R { 135 text, args := Reify(vals...) 136 return R{text, args}.Norm() 137 } 138 139 // Short for "reified". 140 func rei(text string, args ...any) R { return R{text, args}.Norm() } 141 142 func reiFrom(text []byte, args []any) R { 143 return R{bytesToMutableString(text), args}.Norm() 144 } 145 146 func reifyParamExpr(expr ParamExpr, dict ArgDict) R { 147 return reiFrom(expr.AppendParamExpr(nil, nil, dict)) 148 } 149 150 func reifyUnparamPreps(vals ...string) (text []byte, args []any) { 151 for _, val := range vals { 152 text, args = Preparse(val).AppendParamExpr(text, args, nil) 153 } 154 return 155 } 156 157 /* 158 Short for "reified". Test-only for now. Similar to `StrQ` but uses ordinal params. 159 Implements `Expr` incorrectly, see below. 160 */ 161 type R struct { 162 Text string 163 Args list 164 } 165 166 /* 167 Note: this is NOT a valid implementation of an expr with ordinal params. When 168 the input args are non-empty, a real implementation would have to parse its own 169 text to renumerate the params, appending that modified text to the output. 170 */ 171 func (self R) AppendExpr(text []byte, args list) ([]byte, list) { 172 text = append(text, self.Text...) 173 args = append(args, self.Args...) 174 return text, args 175 } 176 177 /* 178 Without this equivalence, tests break due to slice prealloc/growth in 179 `StrQ.AppendExpr`, violating some equality tests. We don't really care about the 180 difference between nil and zero-length arg lists. 181 */ 182 func (self R) Norm() R { 183 if self.Args == nil { 184 self.Args = list{} 185 } 186 return self 187 } 188 189 func eq(t testing.TB, exp, act any) { 190 t.Helper() 191 if !r.DeepEqual(exp, act) { 192 t.Fatalf(` 193 expected (detailed): 194 %#[1]v 195 actual (detailed): 196 %#[2]v 197 expected (simple): 198 %[1]v 199 actual (simple): 200 %[2]v 201 `, exp, act) 202 } 203 } 204 205 func sliceIs[A any](t testing.TB, exp, act []A) { 206 t.Helper() 207 208 expSlice := *(*sliceHeader)(u.Pointer(&exp)) 209 actSlice := *(*sliceHeader)(u.Pointer(&act)) 210 211 if !r.DeepEqual(expSlice, actSlice) { 212 t.Fatalf(` 213 expected (slice): 214 %#[1]v 215 actual (slice): 216 %#[2]v 217 expected (detailed): 218 %#[3]v 219 actual (detailed): 220 %#[4]v 221 expected (simple): 222 %[3]v 223 actual (simple): 224 %[4]v 225 `, expSlice, actSlice, exp, act) 226 } 227 } 228 229 // nolint:structcheck 230 type sliceHeader struct { 231 dat u.Pointer 232 len int 233 cap int 234 } 235 236 func notEq(t testing.TB, exp, act any) { 237 t.Helper() 238 if r.DeepEqual(exp, act) { 239 t.Fatalf(` 240 unexpected equality (detailed): 241 %#[1]v 242 unexpected equality (simple): 243 %[1]v 244 `, exp, act) 245 } 246 } 247 248 func panics(t testing.TB, msg string, fun func()) { 249 t.Helper() 250 val := catchAny(fun) 251 252 if val == nil { 253 t.Fatalf(`expected %v to panic, found no panic`, funcName(fun)) 254 } 255 256 str := fmt.Sprint(val) 257 if !strings.Contains(str, msg) { 258 t.Fatalf(` 259 expected %v to panic with a message containing: 260 %v 261 found the following message: 262 %v 263 `, funcName(fun), msg, str) 264 } 265 } 266 267 func funcName(val any) string { 268 return runtime.FuncForPC(r.ValueOf(val).Pointer()).Name() 269 } 270 271 func catchAny(fun func()) (val any) { 272 defer recAny(&val) 273 fun() 274 return 275 } 276 277 func recAny(ptr *any) { *ptr = recover() } 278 279 var hugeBui = MakeBui(len(hugeQuery)*2, len(hugeQueryArgs)*2) 280 281 func tHugeBuiEmpty(t testing.TB) { 282 eq(t, Bui{[]byte{}, list{}}, hugeBui) 283 } 284 285 func parseTime(str string) *time.Time { 286 inst, err := time.Parse(time.RFC3339, str) 287 try(err) 288 return &inst 289 } 290 291 const hugeQuery = /*pgsql*/ ` 292 select col_name 293 from 294 table_name 295 296 left join table_name using (col_name) 297 298 inner join ( 299 select agg(col_name) as col_name 300 from table_name 301 where ( 302 false 303 or col_name = 'enum_value' 304 or (:arg_one and (:arg_two or col_name = :arg_three)) 305 ) 306 group by col_name 307 ) as table_name using (col_name) 308 309 left join ( 310 select 311 table_name.col_name 312 from 313 table_name 314 left join table_name on table_name.col_name = table_name.col_name 315 where 316 false 317 or :arg_four::type_name is null 318 or table_name.col_name between :arg_four and (:arg_four + 'literal input'::some_type) 319 ) as table_name using (col_name) 320 321 left join ( 322 select distinct col_name as col_name 323 from table_name 324 where (:arg_five::type_name[] is null or col_name = any(:arg_five)) 325 ) as table_name using (col_name) 326 327 left join ( 328 select distinct col_name as col_name 329 from table_name 330 where (:arg_six::type_name[] is null or col_name = any(:arg_six)) 331 ) as table_name using (col_name) 332 where 333 true 334 and (:arg_seven or col_name in (table table_name)) 335 and (:arg_four :: type_name is null or table_name.col_name is not null) 336 and (:arg_five :: type_name[] is null or table_name.col_name is not null) 337 and (:arg_six :: type_name[] is null or table_name.col_name is not null) 338 and ( 339 false 340 or not col_name 341 or (:arg_eight and (:arg_two or col_name = :arg_three)) 342 ) 343 and ( 344 false 345 or not col_name 346 or (:arg_nine and (:arg_two or col_name = :arg_three)) 347 ) 348 and (:arg_ten or not col_name) 349 and (:arg_eleven :: type_name is null or col_name @@ func_name(:arg_eleven)) 350 and (:arg_fifteen :: type_name is null or col_name <> :arg_fifteen) 351 and (:arg_sixteen :: type_name is null or col_name = :arg_sixteen) 352 and (:arg_twelve :: type_name is null or col_name = :arg_twelve) 353 and (:arg_thirteen :: type_name is null or func_name(col_name) <= :arg_thirteen) 354 :arg_fourteen 355 ` 356 357 var hugeQueryArgs = Dict{ 358 `arg_one`: nil, 359 `arg_two`: nil, 360 `arg_three`: nil, 361 `arg_four`: nil, 362 `arg_five`: nil, 363 `arg_six`: nil, 364 `arg_seven`: nil, 365 `arg_eight`: nil, 366 `arg_nine`: nil, 367 `arg_ten`: nil, 368 `arg_eleven`: nil, 369 `arg_twelve`: nil, 370 `arg_thirteen`: nil, 371 `arg_fourteen`: nil, 372 `arg_fifteen`: nil, 373 `arg_sixteen`: nil, 374 } 375 376 type HaserTrue struct{} 377 378 func (HaserTrue) Has(string) bool { return true } 379 380 type HaserFalse struct{} 381 382 func (HaserFalse) Has(string) bool { return false } 383 384 type HaserSlice []string 385 386 func (self HaserSlice) Has(tar string) bool { 387 for _, val := range self { 388 if val == tar { 389 return true 390 } 391 } 392 return false 393 } 394 395 type Stringer [1]any 396 397 func (self Stringer) String() string { 398 if self[0] == nil { 399 return `` 400 } 401 return fmt.Sprint(self[0]) 402 } 403 404 func (self Stringer) AppendTo(buf []byte) []byte { 405 return append(buf, self.String()...) 406 }