Skip to content

Commit b4f78e2

Browse files
Implement $not (#14)
See: https://www.mongodb.com/docs/manual/reference/operator/query/not/ --------- Co-authored-by: Koen Bollen <[email protected]>
1 parent 832fc99 commit b4f78e2

File tree

4 files changed

+54
-0
lines changed

4 files changed

+54
-0
lines changed

filter/converter.go

+16
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string
125125
conditions = append(conditions, strings.Join(inner, " "+op+" "))
126126
}
127127
}
128+
case "$not":
129+
vv, ok := value.(map[string]any)
130+
if !ok {
131+
return "", nil, fmt.Errorf("invalid value for $not operator (must be object): %v", value)
132+
}
133+
innerConditions, innerValues, err := c.convertFilter(vv, paramIndex)
134+
if err != nil {
135+
return "", nil, err
136+
}
137+
paramIndex += len(innerValues)
138+
// Just putting a NOT around the condition is not enough, a non existing jsonb field will for example
139+
// make the whole inner condition NULL. And NOT NULL is still a falsy value, so we need to check for NULL explicitly.
140+
conditions = append(conditions, fmt.Sprintf("(NOT COALESCE(%s, FALSE))", innerConditions))
141+
values = append(values, innerValues...)
128142
default:
129143
if !isValidPostgresIdentifier(key) {
130144
return "", nil, fmt.Errorf("invalid column name: %s", key)
@@ -148,6 +162,8 @@ func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string
148162
return "", nil, fmt.Errorf("$or as scalar operator not supported")
149163
case "$and":
150164
return "", nil, fmt.Errorf("$and as scalar operator not supported")
165+
case "$not":
166+
return "", nil, fmt.Errorf("$not as scalar operator not supported")
151167
case "$in":
152168
if !isScalarSlice(v[operator]) {
153169
return "", nil, fmt.Errorf("invalid value for $in operator (must array of primatives): %v", v[operator])

filter/converter_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,30 @@ func TestConverter_Convert(t *testing.T) {
224224
nil,
225225
fmt.Errorf("invalid column name: \"bla = 1 --"),
226226
},
227+
{
228+
"$not operator",
229+
nil,
230+
`{"$not": {"name": "John"}}`,
231+
`(NOT COALESCE(("name" = $1), FALSE))`,
232+
[]any{"John"},
233+
nil,
234+
},
235+
{
236+
"$not in the wrong place",
237+
nil,
238+
`{"name": {"$not": {"$eq": "John"}}}`,
239+
``,
240+
nil,
241+
fmt.Errorf("$not as scalar operator not supported"),
242+
},
243+
{
244+
"$not with a scalar",
245+
nil,
246+
`{"$not": "John"}`,
247+
``,
248+
nil,
249+
fmt.Errorf("invalid value for $not operator (must be object): John"),
250+
},
227251
}
228252

229253
for _, tt := range tests {

fuzz/fuzz_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ func FuzzConverter(f *testing.F) {
3737
`{"status": {"$in": []}}`,
3838
`{"$or": [{}, {}]}`,
3939
`{"\"bla = 1 --": 1}`,
40+
`{"$not": {"name": "John"}}`,
41+
`{"name": {"$not": {"$eq": "John"}}}`,
4042
}
4143
for _, tc := range tcs {
4244
f.Add(tc, true)

integration/postgres_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,18 @@ func TestIntegration_BasicOperators(t *testing.T) {
309309
[]int{3, 4, 5, 6, 7, 8, 9, 10},
310310
nil,
311311
},
312+
{
313+
`$not on jsonb column`,
314+
`{"$not": {"pet": "cat"}}`,
315+
[]int{1, 3, 5, 7, 9, 10},
316+
nil,
317+
},
318+
{
319+
`$not on normal column`,
320+
`{"$not": {"mount": "horse"}}`,
321+
[]int{3, 4, 5, 6, 7, 8, 9, 10},
322+
nil,
323+
},
312324
}
313325

314326
for _, tt := range tests {

0 commit comments

Comments
 (0)