@@ -2,7 +2,9 @@ package integration
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"reflect"
7
+ "strings"
6
8
"testing"
7
9
8
10
"github.com/lib/pq"
@@ -217,3 +219,265 @@ func TestIntegration_InAny_PGX(t *testing.T) {
217
219
t .Fatalf ("expected [3, 4, 5, 6, 7, 8, 9, 10], got %v" , ids )
218
220
}
219
221
}
222
+
223
+ func TestIntegration_BasicOperators (t * testing.T ) {
224
+ db := setupPQ (t )
225
+
226
+ createPlayersTable (t , db )
227
+
228
+ tests := []struct {
229
+ name string
230
+ input string
231
+ expectedPlayers []int
232
+ expectedError error
233
+ }{
234
+ {
235
+ `$gt` ,
236
+ `{"level": {"$gt": 50}}` ,
237
+ []int {6 , 7 , 8 , 9 , 10 },
238
+ nil ,
239
+ },
240
+ {
241
+ `$gte` ,
242
+ `{"level": {"$gte": 50}}` ,
243
+ []int {5 , 6 , 7 , 8 , 9 , 10 },
244
+ nil ,
245
+ },
246
+ {
247
+ `$lt` ,
248
+ `{"level": {"$lt": 50}}` ,
249
+ []int {1 , 2 , 3 , 4 },
250
+ nil ,
251
+ },
252
+ {
253
+ `$lte` ,
254
+ `{"level": {"$lte": 50}}` ,
255
+ []int {1 , 2 , 3 , 4 , 5 },
256
+ nil ,
257
+ },
258
+ {
259
+ `$eq` ,
260
+ `{"name": "Alice"}` ,
261
+ []int {1 },
262
+ nil ,
263
+ },
264
+ {
265
+ `$ne` ,
266
+ `{"name": {"$eq": "Alice"}}` ,
267
+ []int {1 },
268
+ nil ,
269
+ },
270
+ {
271
+ `$ne` ,
272
+ `{"name": {"$ne": "Alice"}}` ,
273
+ []int {2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 },
274
+ nil ,
275
+ },
276
+ {
277
+ `$regex` ,
278
+ `{"name": {"$regex": "a.k$"}}` ,
279
+ []int {6 , 8 , 10 },
280
+ nil ,
281
+ },
282
+ {
283
+ `unknown column` ,
284
+ `{"foobar": "admin"}` ,
285
+ nil ,
286
+ errors .New ("pq: column \" foobar\" does not exist" ),
287
+ },
288
+ {
289
+ `invalid value` ,
290
+ `{"level": "town1"}` , // Level is an integer column, but the value is a string.
291
+ nil ,
292
+ errors .New ("pq: invalid input syntax for type integer: \" town1\" " ),
293
+ },
294
+ }
295
+
296
+ for _ , tt := range tests {
297
+ t .Run (tt .name , func (t * testing.T ) {
298
+ c := filter .NewConverter (filter .WithArrayDriver (pq .Array ))
299
+ where , values , err := c .Convert ([]byte (tt .input ))
300
+ if err != nil {
301
+ t .Fatal (err )
302
+ }
303
+
304
+ rows , err := db .Query (`
305
+ SELECT id
306
+ FROM players
307
+ WHERE ` + where + `;
308
+ ` , values ... )
309
+ if err != nil {
310
+ if tt .expectedError == nil {
311
+ t .Fatalf ("unexpected error: %v" , err )
312
+ } else if ! strings .Contains (err .Error (), tt .expectedError .Error ()) {
313
+ t .Fatalf ("expected error %q, got %q" , tt .expectedError , err )
314
+ }
315
+ return
316
+ }
317
+ defer rows .Close ()
318
+ players := []int {}
319
+ for rows .Next () {
320
+ var id int
321
+ if err := rows .Scan (& id ); err != nil {
322
+ t .Fatal (err )
323
+ }
324
+ players = append (players , id )
325
+ }
326
+
327
+ if ! reflect .DeepEqual (players , tt .expectedPlayers ) {
328
+ t .Fatalf ("%q expected %v, got %v (where clause used: %q)" , tt .input , tt .expectedPlayers , players , where )
329
+ }
330
+ })
331
+ }
332
+
333
+ for op := range filter .BasicOperatorMap {
334
+ found := false
335
+ for _ , tt := range tests {
336
+ if strings .Contains (tt .input , op ) {
337
+ found = true
338
+ break
339
+ }
340
+ }
341
+ if ! found {
342
+ t .Fatalf ("operator %q is not tested" , op )
343
+ }
344
+ }
345
+ }
346
+
347
+ func TestIntegration_NestedJSONB (t * testing.T ) {
348
+ db := setupPQ (t )
349
+
350
+ createPlayersTable (t , db )
351
+
352
+ tests := []struct {
353
+ name string
354
+ input string
355
+ expectedPlayers []int
356
+ }{
357
+ {
358
+ "jsonb equals" ,
359
+ `{"guild_id": 20}` ,
360
+ []int {1 , 2 },
361
+ },
362
+ {
363
+ "jsonb regex" ,
364
+ `{"pet": {"$regex": "^.{3}$"}}` ,
365
+ []int {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 },
366
+ },
367
+ {
368
+ "excemption column" ,
369
+ `{"name": "Alice"}` ,
370
+ []int {1 },
371
+ },
372
+ {
373
+ "unknown column" ,
374
+ `{"foobar": "admin"}` ,
375
+ []int {}, // Will always default to the jsonb column and return no results since it doesn't exist.
376
+ },
377
+ {
378
+ "invalid value" ,
379
+ `{"guild_id": "dragon_slayers"}` , // Guild ID only contains integer values in the test data.
380
+ []int {},
381
+ },
382
+ }
383
+
384
+ for _ , tt := range tests {
385
+ t .Run (tt .name , func (t * testing.T ) {
386
+ c := filter .NewConverter (filter .WithArrayDriver (pq .Array ), filter .WithNestedJSONB ("metadata" , "name" , "level" , "class" ))
387
+ where , values , err := c .Convert ([]byte (tt .input ))
388
+ if err != nil {
389
+ t .Fatal (err )
390
+ }
391
+
392
+ rows , err := db .Query (`
393
+ SELECT id
394
+ FROM players
395
+ WHERE ` + where + `;
396
+ ` , values ... )
397
+ if err != nil {
398
+ t .Fatal (err )
399
+ }
400
+ defer rows .Close ()
401
+ players := []int {}
402
+ for rows .Next () {
403
+ var id int
404
+ if err := rows .Scan (& id ); err != nil {
405
+ t .Fatal (err )
406
+ }
407
+ players = append (players , id )
408
+ }
409
+
410
+ if ! reflect .DeepEqual (players , tt .expectedPlayers ) {
411
+ t .Fatalf ("%q expected %v, got %v (where clause used: %q)" , tt .input , tt .expectedPlayers , players , where )
412
+ }
413
+ })
414
+ }
415
+ }
416
+
417
+ func TestIntegration_Logic (t * testing.T ) {
418
+ db := setupPQ (t )
419
+
420
+ createPlayersTable (t , db )
421
+
422
+ tests := []struct {
423
+ name string
424
+ input string
425
+ expectedPlayers []int
426
+ }{
427
+ {
428
+ "basic or" ,
429
+ `{"$or": [{"level": {"$gt": 50}}, {"pet": "dog"}]}` ,
430
+ []int {1 , 3 , 5 , 6 , 7 , 8 , 9 , 10 },
431
+ },
432
+ {
433
+ // (mages and (ends with E or ends with K)) or (dog owners and (guild in (50, 20)))
434
+ "complex triple nested" ,
435
+ `{"$or": [
436
+ {"$and": [
437
+ {"class": "mage"},
438
+ {"$or": [
439
+ {"name": {"$regex": "e$"}},
440
+ {"name": {"$regex": "k$"}}
441
+ ]}
442
+ ]},
443
+ {"$and": [
444
+ {"pet": "dog"},
445
+ {"guild_id": {"$in": [50, 20]}}
446
+ ]}
447
+ ]}` ,
448
+ []int {1 , 5 , 7 , 8 },
449
+ },
450
+ }
451
+
452
+ for _ , tt := range tests {
453
+ t .Run (tt .name , func (t * testing.T ) {
454
+ c := filter .NewConverter (filter .WithArrayDriver (pq .Array ), filter .WithNestedJSONB ("metadata" , "name" , "level" , "class" ))
455
+ where , values , err := c .Convert ([]byte (tt .input ))
456
+ if err != nil {
457
+ t .Fatal (err )
458
+ }
459
+
460
+ rows , err := db .Query (`
461
+ SELECT id
462
+ FROM players
463
+ WHERE ` + where + `;
464
+ ` , values ... )
465
+ if err != nil {
466
+ t .Fatal (err )
467
+ }
468
+ defer rows .Close ()
469
+ players := []int {}
470
+ for rows .Next () {
471
+ var id int
472
+ if err := rows .Scan (& id ); err != nil {
473
+ t .Fatal (err )
474
+ }
475
+ players = append (players , id )
476
+ }
477
+
478
+ if ! reflect .DeepEqual (players , tt .expectedPlayers ) {
479
+ t .Fatalf ("%q expected %v, got %v (where clause used: %q)" , tt .input , tt .expectedPlayers , players , where )
480
+ }
481
+ })
482
+ }
483
+ }
0 commit comments