Skip to content

Commit fd06327

Browse files
toriers
authored andcommitted
Predicate operators (#188)
* Add testing for GetField * Add tests for nested fields * Add support for $in and $nin queries against arrays
1 parent f5dab0a commit fd06327

File tree

3 files changed

+76
-6
lines changed

3 files changed

+76
-6
lines changed

schema/query/predicate.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,27 @@ type In struct {
140140
// Match implements Expression interface.
141141
func (e In) Match(payload map[string]interface{}) bool {
142142
value := getField(payload, e.Field)
143-
for _, v := range e.Values {
144-
if reflect.DeepEqual(v, value) {
145-
return true
143+
144+
// The $in operator in MongoDB allows matching both single values and an
145+
// array of values.
146+
// https://docs.mongodb.com/manual/reference/operator/query/in/
147+
switch vt := value.(type) {
148+
case []interface{}:
149+
for _, v := range e.Values {
150+
for _, vv := range vt {
151+
if reflect.DeepEqual(v, vv) {
152+
return true
153+
}
154+
}
155+
}
156+
default:
157+
for _, v := range e.Values {
158+
if reflect.DeepEqual(v, value) {
159+
return true
160+
}
146161
}
147162
}
163+
148164
return false
149165
}
150166

@@ -171,9 +187,24 @@ type NotIn struct {
171187
// Match implements Expression interface.
172188
func (e NotIn) Match(payload map[string]interface{}) bool {
173189
value := getField(payload, e.Field)
174-
for _, v := range e.Values {
175-
if reflect.DeepEqual(v, value) {
176-
return false
190+
191+
// The $nin operator in MongoDB allows matching both single values and an
192+
// array of values.
193+
// https://docs.mongodb.com/manual/reference/operator/query/nin/
194+
switch vt := value.(type) {
195+
case []interface{}:
196+
for _, v := range e.Values {
197+
for _, vv := range vt {
198+
if reflect.DeepEqual(v, vv) {
199+
return false
200+
}
201+
}
202+
}
203+
default:
204+
for _, v := range e.Values {
205+
if reflect.DeepEqual(v, value) {
206+
return false
207+
}
177208
}
178209
}
179210
return true

schema/query/predicate_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,24 @@ func TestMatch(t *testing.T) {
6868
{map[string]interface{}{"foo": "foo"}, false},
6969
},
7070
},
71+
{
72+
`{"foo": {"$in": ["baz"]}}`, []test{
73+
{map[string]interface{}{"foo": []interface{}{"baz"}}, true},
74+
{map[string]interface{}{"foo": []interface{}{"bar"}}, false},
75+
},
76+
},
7177
{
7278
`{"foo": {"$nin": ["bar", "baz"]}}`, []test{
7379
{map[string]interface{}{"foo": "bar"}, false},
7480
{map[string]interface{}{"foo": "foo"}, true},
7581
},
7682
},
83+
{
84+
`{"foo": {"$nin": ["baz"]}}`, []test{
85+
{map[string]interface{}{"foo": []interface{}{"baz"}}, false},
86+
{map[string]interface{}{"foo": []interface{}{"bar"}}, true},
87+
},
88+
},
7789
{
7890
`{"$or": [{"foo": "bar"}, {"bar": 1}]}`, []test{
7991
{map[string]interface{}{"foo": "bar"}, true},
@@ -106,6 +118,12 @@ func TestMatch(t *testing.T) {
106118
{map[string]interface{}{"bar": float64(1)}, false},
107119
},
108120
},
121+
{
122+
`{"foo.bar": "baz"}`, []test{
123+
{map[string]interface{}{"foo": map[string]interface{}{"bar": "baz"}}, true},
124+
{map[string]interface{}{"foo": map[string]interface{}{"bar": "bar"}}, false},
125+
},
126+
},
109127
}
110128
for i := range tests {
111129
tt := tests[i]
@@ -143,6 +161,7 @@ func TestString(t *testing.T) {
143161
`{"$and": [{"foo": "bar"}, {"foo": "baz"}]}`: `{$and: [{foo: "bar"}, {foo: "baz"}]}`,
144162
`{"foo": "bar", "$or": [{"bar": "baz"}, {"bar": "foo"}]}`: `{foo: "bar", $or: [{bar: "baz"}, {bar: "foo"}]}`,
145163
`{"foo": ["bar", "baz"]}`: `{foo: ["bar","baz"]}`,
164+
`{"foo.bar": "baz"}`: `{foo.bar: "baz"}`,
146165
}
147166
for query, want := range tests {
148167
q, err := ParsePredicate(query)

schema/query/utils_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,23 @@ func TestIsNumber(t *testing.T) {
3434
})
3535
}
3636
}
37+
38+
func TestGetField(t *testing.T) {
39+
cases := []struct {
40+
name string
41+
payload map[string]interface{}
42+
fieldName string
43+
want string
44+
}{
45+
{"foo", map[string]interface{}{"foo": "bar"}, "foo", "bar"},
46+
{"foo.bar", map[string]interface{}{"foo": map[string]interface{}{"bar": "baz"}}, "foo.bar", "baz"},
47+
}
48+
for i := range cases {
49+
tc := cases[i]
50+
t.Run(tc.name, func(t *testing.T) {
51+
if res := getField(tc.payload, tc.fieldName); res != tc.want {
52+
t.Errorf("field = %v, wanted %v", res, tc.want)
53+
}
54+
})
55+
}
56+
}

0 commit comments

Comments
 (0)