Improve Mongo compatibility with $ne/$nin/$not and arrays.

Fixes #1451.

We currently handle matches where the key has multiple values due to arrays in
the document in two subtley different ways, depending on whether or not the
array is in the last element of the keypath or not. (This is annoyingly
necessary, at least in the current structure of how we compile selectors, due to
various selectors differing in how they treat arrays.) The negative-style
operators have "must match all values" semantics, but this was only being
enforced when the branching was in the last element of the keypath. This commit
semi-hackily applies those semantics for other branching too.

We are still not 100% Mongo compatible (see XXX comment) but it's closer.
This commit is contained in:
David Glasser
2013-09-26 13:33:42 -05:00
parent fcf6fdaa71
commit 0559491e87
2 changed files with 26 additions and 3 deletions

View File

@@ -427,6 +427,9 @@ Tinytest.add("minimongo - selector_compiler", function (test) {
nomatch({a: {$ne: 1}}, {a: [1, 2]});
nomatch({a: {$ne: 2}}, {a: [1, 2]});
match({a: {$ne: 3}}, {a: [1, 2]});
nomatch({'a.b': {$ne: 1}}, {a: [{b: 1}, {b: 2}]});
nomatch({'a.b': {$ne: 2}}, {a: [{b: 1}, {b: 2}]});
match({'a.b': {$ne: 3}}, {a: [{b: 1}, {b: 2}]});
nomatch({a: {$ne: {x: 1}}}, {a: {x: 1}});
match({a: {$ne: {x: 1}}}, {a: {x: 2}});
@@ -456,7 +459,9 @@ Tinytest.add("minimongo - selector_compiler", function (test) {
nomatch({a: {$nin: [1, 2, 3]}}, {a: [2]}); // tested against mongodb
nomatch({a: {$nin: [{x: 1}, {x: 2}, {x: 3}]}}, {a: [{x: 2}]});
nomatch({a: {$nin: [1, 2, 3]}}, {a: [4, 2]});
nomatch({'a.b': {$nin: [1, 2, 3]}}, {a: [{b:4}, {b:2}]});
match({a: {$nin: [1, 2, 3]}}, {a: [4]});
match({'a.b': {$nin: [1, 2, 3]}}, {a: [{b:4}]});
// $size
match({a: {$size: 0}}, {a: []});
@@ -564,7 +569,9 @@ Tinytest.add("minimongo - selector_compiler", function (test) {
match({x: {$not: {$lt: 10, $gt: 7}}}, {x: 6});
match({x: {$not: {$gt: 7}}}, {x: [2, 3, 4]});
match({'x.y': {$not: {$gt: 7}}}, {x: [{y:2}, {y:3}, {y:4}]});
nomatch({x: {$not: {$gt: 7}}}, {x: [2, 3, 4, 10]});
nomatch({'x.y': {$not: {$gt: 7}}}, {x: [{y:2}, {y:3}, {y:4}, {y:10}]});
match({x: {$not: /a/}}, {x: "dog"});
nomatch({x: {$not: /a/}}, {x: "cat"});

View File

@@ -551,9 +551,25 @@ var compileDocumentSelector = function (docSelector) {
perKeySelectors.push(function (doc) {
var branchValues = lookUpByIndex(doc);
// We apply the selector to each "branched" value and return true if any
// match. This isn't 100% consistent with MongoDB; eg, see:
// https://jira.mongodb.org/browse/SERVER-8585
return _.any(branchValues, valueSelectorFunc);
// match. However, for "negative" selectors like $ne or $not we actually
// require *all* elements to match.
//
// This is because {'x.tag': {$ne: "foo"}} applied to {x: [{tag: 'foo'},
// {tag: 'bar'}]} should NOT match even though there is a branch that
// matches. (This matches the fact that $ne uses a negated
// _anyIfArrayPlus, for when the last level of the key is the array,
// which deMorgans into an 'all'.)
//
// XXX This isn't 100% consistent with MongoDB in 'null' cases:
// https://jira.mongodb.org/browse/SERVER-8585
// XXX this still isn't right. consider {a: {$ne: 5, $gt: 6}}. the
// $ne needs to use the "all" logic and the $gt needs the "any"
// logic
var combiner = (subSelector &&
(subSelector.$not || subSelector.$ne ||
subSelector.$nin))
? _.all : _.any;
return combiner(branchValues, valueSelectorFunc);
});
}
});