Skip to content

Commit 4c26937

Browse files
authored
Merge pull request #1044 from skaggmannen/master
x/mango: Fix evaluation of `$exists: false` always failing
2 parents 0b67452 + a797e87 commit 4c26937

File tree

3 files changed

+89
-23
lines changed

3 files changed

+89
-23
lines changed

x/collate/collate.go

+5
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func CompareObject(a, b interface{}) int {
7373
return -1
7474
}
7575
return 1
76+
case jsonTypeNull:
77+
if b == nil {
78+
return 0
79+
}
80+
return -1
7681
case jsonTypeNumber:
7782
return int(a.(float64) - b.(float64))
7883
case jsonTypeString:

x/mango/match_test.go

+71-16
Original file line numberDiff line numberDiff line change
@@ -129,35 +129,43 @@ func TestMatch(t *testing.T) {
129129
want: false,
130130
})
131131
tests.Add("exists", test{
132-
sel: &conditionNode{
133-
op: OpExists,
134-
cond: true,
132+
sel: &fieldNode{
133+
field: "foo",
134+
cond: &conditionNode{op: OpExists, cond: true},
135+
},
136+
doc: map[string]interface{}{
137+
"foo": "bar",
135138
},
136-
doc: "foo",
137139
want: true,
138140
})
139141
tests.Add("!exists", test{
140-
sel: &conditionNode{
141-
op: OpExists,
142-
cond: false,
142+
sel: &fieldNode{
143+
field: "baz",
144+
cond: &conditionNode{op: OpExists, cond: true},
145+
},
146+
doc: map[string]interface{}{
147+
"foo": "bar",
143148
},
144-
doc: "foo",
145149
want: false,
146150
})
147151
tests.Add("not exists", test{
148-
sel: &conditionNode{
149-
op: OpExists,
150-
cond: false,
152+
sel: &fieldNode{
153+
field: "baz",
154+
cond: &conditionNode{op: OpExists, cond: false},
155+
},
156+
doc: map[string]interface{}{
157+
"foo": "bar",
151158
},
152-
doc: nil,
153159
want: true,
154160
})
155161
tests.Add("!not exists", test{
156-
sel: &conditionNode{
157-
op: OpExists,
158-
cond: true,
162+
sel: &fieldNode{
163+
field: "baz",
164+
cond: &conditionNode{op: OpExists, cond: true},
165+
},
166+
doc: map[string]interface{}{
167+
"foo": "bar",
159168
},
160-
doc: nil,
161169
want: false,
162170
})
163171
tests.Add("type, null", test{
@@ -436,6 +444,53 @@ func TestMatch(t *testing.T) {
436444
doc: "bar",
437445
want: false,
438446
})
447+
tests.Add("field selector, nested", test{
448+
sel: &fieldNode{
449+
field: "foo.bar.baz",
450+
cond: &conditionNode{
451+
op: OpEqual,
452+
cond: "hello",
453+
},
454+
},
455+
doc: map[string]interface{}{
456+
"foo": map[string]interface{}{
457+
"bar": map[string]interface{}{
458+
"baz": "hello",
459+
},
460+
},
461+
},
462+
want: true,
463+
})
464+
tests.Add("field selector, nested, non-object", test{
465+
sel: &fieldNode{
466+
field: "foo.bar.baz",
467+
cond: &conditionNode{
468+
op: OpEqual,
469+
cond: "hello",
470+
},
471+
},
472+
doc: map[string]interface{}{
473+
"foo": "hello",
474+
},
475+
want: false,
476+
})
477+
tests.Add("!field selector, nested", test{
478+
sel: &fieldNode{
479+
field: "foo.bar.baz",
480+
cond: &conditionNode{
481+
op: OpEqual,
482+
cond: "hello",
483+
},
484+
},
485+
doc: map[string]interface{}{
486+
"foo": map[string]interface{}{
487+
"bar": map[string]interface{}{
488+
"buzz": "hello",
489+
},
490+
},
491+
},
492+
want: false,
493+
})
439494
tests.Add("elemMatch", test{
440495
sel: &fieldNode{
441496
field: "foo",

x/mango/selector.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,20 @@ func (f *fieldNode) String() string {
127127
}
128128

129129
func (f *fieldNode) Match(doc interface{}) bool {
130-
m, ok := doc.(map[string]interface{})
131-
if !ok {
132-
return false
133-
}
134-
val, ok := m[f.field]
135-
if !ok {
136-
return false
130+
val := doc
131+
132+
// Traverse nested fields (e.g. "foo.bar.baz")
133+
segments := strings.Split(f.field, ".")
134+
for _, segment := range segments {
135+
m, ok := val.(map[string]interface{})
136+
if !ok {
137+
return false
138+
}
139+
140+
val = m[segment]
137141
}
142+
143+
// Even if the field does not exist we need to pass it to the condition expression because of `$exists`
138144
return f.cond.Match(val)
139145
}
140146

0 commit comments

Comments
 (0)