@@ -2,7 +2,9 @@ package integration
22
33import (
44 "context"
5+ "errors"
56 "reflect"
7+ "strings"
68 "testing"
79
810 "github.com/lib/pq"
@@ -217,3 +219,265 @@ func TestIntegration_InAny_PGX(t *testing.T) {
217219 t .Fatalf ("expected [3, 4, 5, 6, 7, 8, 9, 10], got %v" , ids )
218220 }
219221}
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