diff --git a/filter/converter.go b/filter/converter.go index 08ebf4f..035978a 100644 --- a/filter/converter.go +++ b/filter/converter.go @@ -89,7 +89,7 @@ func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string value := filter[key] switch key { - case "$or", "$and": + case "$or", "$and", "$nor": opConditions, ok := anyToSliceMapAny(value) if !ok { return "", nil, fmt.Errorf("invalid value for $or operator (must be array of objects): %v", value) @@ -108,14 +108,18 @@ func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string inner = append(inner, innerConditions) values = append(values, innerValues...) } - op := "AND" - if key == "$or" { - op = "OR" - } - if len(inner) > 1 { - conditions = append(conditions, "("+strings.Join(inner, " "+op+" ")+")") + if key == "$nor" { + conditions = append(conditions, "NOT ("+strings.Join(inner, " OR ")+")") } else { - conditions = append(conditions, strings.Join(inner, " "+op+" ")) + op := "AND" + if key == "$or" { + op = "OR" + } + if len(inner) > 1 { + conditions = append(conditions, "("+strings.Join(inner, " "+op+" ")+")") + } else { + conditions = append(conditions, strings.Join(inner, " "+op+" ")) + } } default: if !isValidPostgresIdentifier(key) { diff --git a/filter/converter_test.go b/filter/converter_test.go index 4104c58..05e59e2 100644 --- a/filter/converter_test.go +++ b/filter/converter_test.go @@ -137,6 +137,14 @@ func TestConverter_Convert(t *testing.T) { nil, fmt.Errorf("$or as scalar operator not supported"), }, + { + "$nor operator basic", + nil, + `{"$nor": [{"name": "John"}, {"name": "Doe"}]}`, + `NOT (("name" = $1) OR ("name" = $2))`, + []any{"John", "Doe"}, + nil, + }, { "and operator basic", nil, diff --git a/fuzz/fuzz_test.go b/fuzz/fuzz_test.go index 50ae23c..f73d96b 100644 --- a/fuzz/fuzz_test.go +++ b/fuzz/fuzz_test.go @@ -26,6 +26,7 @@ func FuzzConverter(f *testing.F) { `{"$or": [{"org": "poki", "admin": true}, {"age": {"$gte": 18}}]}`, `{"$or": [{"$or": [{"name": "John"}, {"name": "Doe"}]}, {"name": "Jane"}]}`, `{"foo": { "$or": [ "bar", "baz" ] }}`, + `{"$nor": [{"name": "John"}, {"name": "Doe"}]}`, `{"$and": [{"name": "John"}, {"version": 3}]}`, `{"$and": [{"name": "John", "version": 3}]}`, `{"name": {"$regex": "John"}}`, diff --git a/integration/postgres_test.go b/integration/postgres_test.go index d638d1e..6d1e024 100644 --- a/integration/postgres_test.go +++ b/integration/postgres_test.go @@ -303,6 +303,12 @@ func TestIntegration_BasicOperators(t *testing.T) { []int{7, 8, 9, 10}, nil, }, + { + "$nor", + `{"$nor": [{"name": "Alice"}, {"name": "Bob"}]}`, + []int{3, 4, 5, 6, 7, 8, 9, 10}, + nil, + }, } for _, tt := range tests {