Skip to content

Commit b5d11ff

Browse files
Implement $not
1 parent a7572af commit b5d11ff

File tree

4 files changed

+40
-0
lines changed

4 files changed

+40
-0
lines changed

filter/converter.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string
121121
} else {
122122
conditions = append(conditions, strings.Join(inner, " "+op+" "))
123123
}
124+
case "$not":
125+
vv, ok := value.(map[string]any)
126+
if !ok {
127+
return "", nil, fmt.Errorf("invalid value for $not operator (must be object): %v", value)
128+
}
129+
innerConditions, innerValues, err := c.convertFilter(vv, paramIndex)
130+
if err != nil {
131+
return "", nil, err
132+
}
133+
paramIndex += len(innerValues)
134+
// Just putting a NOT around the condition is not enough, a non existing jsonb field will for example
135+
// make the whole inner condition NULL. And NOT NULL is still a falsy value, so we need to check for NULL explicitly.
136+
conditions = append(conditions, fmt.Sprintf("(NOT %s OR %s IS NULL)", innerConditions, innerConditions))
137+
values = append(values, innerValues...)
124138
default:
125139
if !isValidPostgresIdentifier(key) {
126140
return "", nil, fmt.Errorf("invalid column name: %s", key)
@@ -144,6 +158,8 @@ func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string
144158
return "", nil, fmt.Errorf("$or as scalar operator not supported")
145159
case "$and":
146160
return "", nil, fmt.Errorf("$and as scalar operator not supported")
161+
case "$not":
162+
return "", nil, fmt.Errorf("$not as scalar operator not supported")
147163
case "$in":
148164
if !isScalarSlice(v[operator]) {
149165
return "", nil, fmt.Errorf("invalid value for $in operator (must array of primatives): %v", v[operator])

filter/converter_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,22 @@ func TestConverter_Convert(t *testing.T) {
216216
nil,
217217
fmt.Errorf("invalid column name: \"bla = 1 --"),
218218
},
219+
{
220+
"$not operator",
221+
nil,
222+
`{"$not": {"name": "John"}}`,
223+
`(NOT ("name" = $1) OR ("name" = $1) IS NULL)`,
224+
[]any{"John"},
225+
nil,
226+
},
227+
{
228+
"$not in the wrong place",
229+
nil,
230+
`{"name": {"$not": {"$eq": "John"}}}`,
231+
``,
232+
nil,
233+
fmt.Errorf("$not as scalar operator not supported"),
234+
},
219235
}
220236

221237
for _, tt := range tests {

fuzz/fuzz_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ func FuzzConverter(f *testing.F) {
3636
`{"status": {"$in": []}}`,
3737
`{"$or": [{}, {}]}`,
3838
`{"\"bla = 1 --": 1}`,
39+
`{"$not": {"name": "John"}}`,
40+
`{"name": {"$not": {"$eq": "John"}}}`,
3941
}
4042
for _, tc := range tcs {
4143
f.Add(tc, true)

integration/postgres_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ func TestIntegration_BasicOperators(t *testing.T) {
303303
[]int{7, 8, 9, 10},
304304
nil,
305305
},
306+
{
307+
`jsonb equal to null`,
308+
`{"$not": {"pet": "cat"}}`,
309+
[]int{1, 3, 5, 7, 9, 10},
310+
nil,
311+
},
306312
}
307313

308314
for _, tt := range tests {

0 commit comments

Comments
 (0)