UNPKG

marsdb

Version:

MarsDB is a lightweight client-side MongoDB-like database, Promise based, written in ES6

899 lines (795 loc) 39.3 kB
import EJSON from '../../lib/EJSON'; import DocumentMatcher, {makeLookupFunction} from '../../lib/DocumentMatcher'; import chai, {except, assert} from 'chai'; import _ from 'lodash'; chai.use(require('chai-as-promised')); chai.should(); describe('DocumentMatcher', () => { it("#makeLookupFunction", function () { var lookupA = makeLookupFunction('a'); assert.deepEqual(lookupA({}), [{value: undefined}]); assert.deepEqual(lookupA({a: 1}), [{value: 1}]); assert.deepEqual(lookupA({a: [1]}), [{value: [1]}]); var lookupAX = makeLookupFunction('a.x'); assert.deepEqual(lookupAX({a: {x: 1}}), [{value: 1}]); assert.deepEqual(lookupAX({a: {x: [1]}}), [{value: [1]}]); assert.deepEqual(lookupAX({a: 5}), [{value: undefined}]); assert.deepEqual(lookupAX({a: [{x: 1}, {x: [2]}, {y: 3}]}), [{value: 1, arrayIndices: [0]}, {value: [2], arrayIndices: [1]}, {value: undefined, arrayIndices: [2]}]); var lookupA0X = makeLookupFunction('a.0.x'); assert.deepEqual(lookupA0X({a: [{x: 1}]}), [ // From interpreting '0' as "0th array element". {value: 1, arrayIndices: [0, 'x']}, // From interpreting '0' as "after branching in the array, look in the // object {x:1} for a field named 0". {value: undefined, arrayIndices: [0]}]); assert.deepEqual(lookupA0X({a: [{x: [1]}]}), [ {value: [1], arrayIndices: [0, 'x']}, {value: undefined, arrayIndices: [0]}]); assert.deepEqual(lookupA0X({a: 5}), [{value: undefined}]); assert.deepEqual(lookupA0X({a: [{x: 1}, {x: [2]}, {y: 3}]}), [ // From interpreting '0' as "0th array element". {value: 1, arrayIndices: [0, 'x']}, // From interpreting '0' as "after branching in the array, look in the // object {x:1} for a field named 0". {value: undefined, arrayIndices: [0]}, {value: undefined, arrayIndices: [1]}, {value: undefined, arrayIndices: [2]} ]); assert.deepEqual( makeLookupFunction('w.x.0.z')({ w: [{x: [{z: 5}]}]}), [ // From interpreting '0' as "0th array element". {value: 5, arrayIndices: [0, 0, 'x']}, // From interpreting '0' as "after branching in the array, look in the // object {z:5} for a field named "0". {value: undefined, arrayIndices: [0, 0]} ]); }); describe('Matching', function () { var matches = function (shouldMatch, selector, doc) { var doesMatch = new DocumentMatcher(selector).documentMatches(doc).result; it(`should ${shouldMatch ? 'match' : 'NOT match'} document ${JSON.stringify(doc)} with query ${JSON.stringify(selector)}`, function() { assert.equal(doesMatch, shouldMatch); }); }; var match = _.bind(matches, null, true); var nomatch = _.bind(matches, null, false); // XXX blog post about what I learned while writing these tests (weird // mongo edge cases) // empty selectors match({}, {}); match({}, {a: 12}); // scalars match(1, {_id: 1, a: 'foo'}); nomatch(1, {_id: 2, a: 'foo'}); match('a', {_id: 'a', a: 'foo'}); nomatch('a', {_id: 'b', a: 'foo'}); // safety nomatch(undefined, {}); nomatch(undefined, {_id: 'foo'}); nomatch(false, {_id: 'foo'}); nomatch(null, {_id: 'foo'}); nomatch({_id: undefined}, {_id: 'foo'}); nomatch({_id: false}, {_id: 'foo'}); nomatch({_id: null}, {_id: 'foo'}); // matching one or more keys nomatch({a: 12}, {}); match({a: 12}, {a: 12}); match({a: 12}, {a: 12, b: 13}); match({a: 12, b: 13}, {a: 12, b: 13}); match({a: 12, b: 13}, {a: 12, b: 13, c: 14}); nomatch({a: 12, b: 13, c: 14}, {a: 12, b: 13}); nomatch({a: 12, b: 13}, {b: 13, c: 14}); match({a: 12}, {a: [12]}); match({a: 12}, {a: [11, 12, 13]}); nomatch({a: 12}, {a: [11, 13]}); match({a: 12, b: 13}, {a: [11, 12, 13], b: [13, 14, 15]}); nomatch({a: 12, b: 13}, {a: [11, 12, 13], b: [14, 15]}); // dates var date1 = new Date; var date2 = new Date(date1.getTime() + 1000); match({a: date1}, {a: date1}); nomatch({a: date1}, {a: date2}); // arrays match({a: [1,2]}, {a: [1, 2]}); match({a: [1,2]}, {a: [[1, 2]]}); match({a: [1,2]}, {a: [[3, 4], [1, 2]]}); nomatch({a: [1,2]}, {a: [3, 4]}); nomatch({a: [1,2]}, {a: [[[1, 2]]]}); // literal documents match({a: {b: 12}}, {a: {b: 12}}); nomatch({a: {b: 12, c: 13}}, {a: {b: 12}}); nomatch({a: {b: 12}}, {a: {b: 12, c: 13}}); match({a: {b: 12, c: 13}}, {a: {b: 12, c: 13}}); nomatch({a: {b: 12, c: 13}}, {a: {c: 13, b: 12}}); // tested on mongodb nomatch({a: {}}, {a: {b: 12}}); nomatch({a: {b:12}}, {a: {}}); match( {a: {b: 12, c: [13, true, false, 2.2, "a", null, {d: 14}]}}, {a: {b: 12, c: [13, true, false, 2.2, "a", null, {d: 14}]}}); match({a: {b: 12}}, {a: {b: 12}, k: 99}); match({a: {b: 12}}, {a: [{b: 12}]}); nomatch({a: {b: 12}}, {a: [[{b: 12}]]}); match({a: {b: 12}}, {a: [{b: 11}, {b: 12}, {b: 13}]}); nomatch({a: {b: 12}}, {a: [{b: 11}, {b: 12, c: 20}, {b: 13}]}); nomatch({a: {b: 12, c: 20}}, {a: [{b: 11}, {b: 12}, {c: 20}]}); match({a: {b: 12, c: 20}}, {a: [{b: 11}, {b: 12, c: 20}, {b: 13}]}); // null match({a: null}, {a: null}); match({a: null}, {b: 12}); nomatch({a: null}, {a: 12}); match({a: null}, {a: [1, 2, null, 3]}); // tested on mongodb nomatch({a: null}, {a: [1, 2, {}, 3]}); // tested on mongodb // order comparisons: $lt, $gt, $lte, $gte match({a: {$lt: 10}}, {a: 9}); nomatch({a: {$lt: 10}}, {a: 10}); nomatch({a: {$lt: 10}}, {a: 11}); match({a: {$gt: 10}}, {a: 11}); nomatch({a: {$gt: 10}}, {a: 10}); nomatch({a: {$gt: 10}}, {a: 9}); match({a: {$lte: 10}}, {a: 9}); match({a: {$lte: 10}}, {a: 10}); nomatch({a: {$lte: 10}}, {a: 11}); match({a: {$gte: 10}}, {a: 11}); match({a: {$gte: 10}}, {a: 10}); nomatch({a: {$gte: 10}}, {a: 9}); match({a: {$lt: 10}}, {a: [11, 9, 12]}); nomatch({a: {$lt: 10}}, {a: [11, 12]}); // (there's a full suite of ordering test elsewhere) nomatch({a: {$lt: "null"}}, {a: null}); match({a: {$lt: {x: [2, 3, 4]}}}, {a: {x: [1, 3, 4]}}); match({a: {$gt: {x: [2, 3, 4]}}}, {a: {x: [3, 3, 4]}}); nomatch({a: {$gt: {x: [2, 3, 4]}}}, {a: {x: [1, 3, 4]}}); nomatch({a: {$gt: {x: [2, 3, 4]}}}, {a: {x: [2, 3, 4]}}); nomatch({a: {$lt: {x: [2, 3, 4]}}}, {a: {x: [2, 3, 4]}}); match({a: {$gte: {x: [2, 3, 4]}}}, {a: {x: [2, 3, 4]}}); match({a: {$lte: {x: [2, 3, 4]}}}, {a: {x: [2, 3, 4]}}); nomatch({a: {$gt: [2, 3]}}, {a: [1, 2]}); // tested against mongodb // composition of two qualifiers nomatch({a: {$lt: 11, $gt: 9}}, {a: 8}); nomatch({a: {$lt: 11, $gt: 9}}, {a: 9}); match({a: {$lt: 11, $gt: 9}}, {a: 10}); nomatch({a: {$lt: 11, $gt: 9}}, {a: 11}); nomatch({a: {$lt: 11, $gt: 9}}, {a: 12}); match({a: {$lt: 11, $gt: 9}}, {a: [8, 9, 10, 11, 12]}); match({a: {$lt: 11, $gt: 9}}, {a: [8, 9, 11, 12]}); // tested against mongodb // $all match({a: {$all: [1, 2]}}, {a: [1, 2]}); nomatch({a: {$all: [1, 2, 3]}}, {a: [1, 2]}); match({a: {$all: [1, 2]}}, {a: [3, 2, 1]}); match({a: {$all: [1, "x"]}}, {a: [3, "x", 1]}); nomatch({a: {$all: ['2']}}, {a: 2}); nomatch({a: {$all: [2]}}, {a: '2'}); match({a: {$all: [[1, 2], [1, 3]]}}, {a: [[1, 3], [1, 2], [1, 4]]}); nomatch({a: {$all: [[1, 2], [1, 3]]}}, {a: [[1, 4], [1, 2], [1, 4]]}); match({a: {$all: [2, 2]}}, {a: [2]}); // tested against mongodb nomatch({a: {$all: [2, 3]}}, {a: [2, 2]}); nomatch({a: {$all: [1, 2]}}, {a: [[1, 2]]}); // tested against mongodb nomatch({a: {$all: [1, 2]}}, {}); // tested against mongodb, field doesn't exist nomatch({a: {$all: [1, 2]}}, {a: {foo: 'bar'}}); // tested against mongodb, field is not an object nomatch({a: {$all: []}}, {a: []}); nomatch({a: {$all: []}}, {a: [5]}); match({a: {$all: [/i/, /e/i]}}, {a: ["foo", "bEr", "biz"]}); nomatch({a: {$all: [/i/, /e/i]}}, {a: ["foo", "bar", "biz"]}); match({a: {$all: [{b: 3}]}}, {a: [{b: 3}]}); // Members of $all other than regexps are *equality matches*, not document // matches. nomatch({a: {$all: [{b: 3}]}}, {a: [{b: 3, k: 4}]}); assert.throws(function () { match({a: {$all: [{$gt: 4}]}}, {}); }); // $exists match({a: {$exists: true}}, {a: 12}); nomatch({a: {$exists: true}}, {b: 12}); nomatch({a: {$exists: false}}, {a: 12}); match({a: {$exists: false}}, {b: 12}); match({a: {$exists: true}}, {a: []}); nomatch({a: {$exists: true}}, {b: []}); nomatch({a: {$exists: false}}, {a: []}); match({a: {$exists: false}}, {b: []}); match({a: {$exists: true}}, {a: [1]}); nomatch({a: {$exists: true}}, {b: [1]}); nomatch({a: {$exists: false}}, {a: [1]}); match({a: {$exists: false}}, {b: [1]}); match({a: {$exists: 1}}, {a: 5}); match({a: {$exists: 0}}, {b: 5}); nomatch({'a.x':{$exists: false}}, {a: [{}, {x: 5}]}); match({'a.x':{$exists: true}}, {a: [{}, {x: 5}]}); match({'a.x':{$exists: true}}, {a: [{}, {x: 5}]}); match({'a.x':{$exists: true}}, {a: {x: []}}); match({'a.x':{$exists: true}}, {a: {x: null}}); // $mod match({a: {$mod: [10, 1]}}, {a: 11}); nomatch({a: {$mod: [10, 1]}}, {a: 12}); match({a: {$mod: [10, 1]}}, {a: [10, 11, 12]}); nomatch({a: {$mod: [10, 1]}}, {a: [10, 12]}); _.each([ 5, [10], [10, 1, 2], "foo", {bar: 1}, [] ], function (badMod) { assert.throws(function () { match({a: {$mod: badMod}}, {a: 11}); }); }); // $ne match({a: {$ne: 1}}, {a: 2}); nomatch({a: {$ne: 2}}, {a: 2}); match({a: {$ne: [1]}}, {a: [2]}); nomatch({a: {$ne: [1, 2]}}, {a: [1, 2]}); // all tested against mongodb 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}}); match({a: {$ne: {x: 1}}}, {a: {x: 1, y: 2}}); // This query means: All 'a.b' must be non-5, and some 'a.b' must be >6. match({'a.b': {$ne: 5, $gt: 6}}, {a: [{b: 2}, {b: 10}]}); nomatch({'a.b': {$ne: 5, $gt: 6}}, {a: [{b: 2}, {b: 4}]}); nomatch({'a.b': {$ne: 5, $gt: 6}}, {a: [{b: 2}, {b: 5}]}); nomatch({'a.b': {$ne: 5, $gt: 6}}, {a: [{b: 10}, {b: 5}]}); // Should work the same if the branch is at the bottom. match({a: {$ne: 5, $gt: 6}}, {a: [2, 10]}); nomatch({a: {$ne: 5, $gt: 6}}, {a: [2, 4]}); nomatch({a: {$ne: 5, $gt: 6}}, {a: [2, 5]}); nomatch({a: {$ne: 5, $gt: 6}}, {a: [10, 5]}); // $in match({a: {$in: [1, 2, 3]}}, {a: 2}); nomatch({a: {$in: [1, 2, 3]}}, {a: 4}); match({a: {$in: [[1], [2], [3]]}}, {a: [2]}); nomatch({a: {$in: [[1], [2], [3]]}}, {a: [4]}); match({a: {$in: [{b: 1}, {b: 2}, {b: 3}]}}, {a: {b: 2}}); nomatch({a: {$in: [{b: 1}, {b: 2}, {b: 3}]}}, {a: {b: 4}}); match({a: {$in: [1, 2, 3]}}, {a: [2]}); // tested against mongodb match({a: {$in: [{x: 1}, {x: 2}, {x: 3}]}}, {a: [{x: 2}]}); match({a: {$in: [1, 2, 3]}}, {a: [4, 2]}); nomatch({a: {$in: [1, 2, 3]}}, {a: [4]}); match({a: {$in: ['x', /foo/i]}}, {a: 'x'}); match({a: {$in: ['x', /foo/i]}}, {a: 'fOo'}); match({a: {$in: ['x', /foo/i]}}, {a: ['f', 'fOo']}); nomatch({a: {$in: ['x', /foo/i]}}, {a: ['f', 'fOx']}); match({a: {$in: [1, null]}}, {}); match({'a.b': {$in: [1, null]}}, {}); match({'a.b': {$in: [1, null]}}, {a: {}}); match({'a.b': {$in: [1, null]}}, {a: {b: null}}); nomatch({'a.b': {$in: [1, null]}}, {a: {b: 5}}); nomatch({'a.b': {$in: [1]}}, {a: {b: null}}); nomatch({'a.b': {$in: [1]}}, {a: {}}); nomatch({'a.b': {$in: [1, null]}}, {a: [{b: 5}]}); match({'a.b': {$in: [1, null]}}, {a: [{b: 5}, {}]}); nomatch({'a.b': {$in: [1, null]}}, {a: [{b: 5}, []]}); nomatch({'a.b': {$in: [1, null]}}, {a: [{b: 5}, 5]}); // $nin nomatch({a: {$nin: [1, 2, 3]}}, {a: 2}); match({a: {$nin: [1, 2, 3]}}, {a: 4}); nomatch({a: {$nin: [[1], [2], [3]]}}, {a: [2]}); match({a: {$nin: [[1], [2], [3]]}}, {a: [4]}); nomatch({a: {$nin: [{b: 1}, {b: 2}, {b: 3}]}}, {a: {b: 2}}); match({a: {$nin: [{b: 1}, {b: 2}, {b: 3}]}}, {a: {b: 4}}); 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}]}); nomatch({a: {$nin: ['x', /foo/i]}}, {a: 'x'}); nomatch({a: {$nin: ['x', /foo/i]}}, {a: 'fOo'}); nomatch({a: {$nin: ['x', /foo/i]}}, {a: ['f', 'fOo']}); match({a: {$nin: ['x', /foo/i]}}, {a: ['f', 'fOx']}); nomatch({a: {$nin: [1, null]}}, {}); nomatch({'a.b': {$nin: [1, null]}}, {}); nomatch({'a.b': {$nin: [1, null]}}, {a: {}}); nomatch({'a.b': {$nin: [1, null]}}, {a: {b: null}}); match({'a.b': {$nin: [1, null]}}, {a: {b: 5}}); match({'a.b': {$nin: [1]}}, {a: {b: null}}); match({'a.b': {$nin: [1]}}, {a: {}}); match({'a.b': {$nin: [1, null]}}, {a: [{b: 5}]}); nomatch({'a.b': {$nin: [1, null]}}, {a: [{b: 5}, {}]}); match({'a.b': {$nin: [1, null]}}, {a: [{b: 5}, []]}); match({'a.b': {$nin: [1, null]}}, {a: [{b: 5}, 5]}); // $size match({a: {$size: 0}}, {a: []}); match({a: {$size: 1}}, {a: [2]}); match({a: {$size: 2}}, {a: [2, 2]}); nomatch({a: {$size: 0}}, {a: [2]}); nomatch({a: {$size: 1}}, {a: []}); nomatch({a: {$size: 1}}, {a: [2, 2]}); nomatch({a: {$size: 0}}, {a: "2"}); nomatch({a: {$size: 1}}, {a: "2"}); nomatch({a: {$size: 2}}, {a: "2"}); nomatch({a: {$size: 2}}, {a: [[2,2]]}); // tested against mongodb // $type match({a: {$type: 1}}, {a: 1.1}); match({a: {$type: 1}}, {a: 1}); nomatch({a: {$type: 1}}, {a: "1"}); match({a: {$type: 2}}, {a: "1"}); nomatch({a: {$type: 2}}, {a: 1}); match({a: {$type: 3}}, {a: {}}); match({a: {$type: 3}}, {a: {b: 2}}); nomatch({a: {$type: 3}}, {a: []}); nomatch({a: {$type: 3}}, {a: [1]}); nomatch({a: {$type: 3}}, {a: null}); match({a: {$type: 5}}, {a: EJSON.newBinary(0)}); match({a: {$type: 5}}, {a: EJSON.newBinary(4)}); nomatch({a: {$type: 5}}, {a: []}); nomatch({a: {$type: 5}}, {a: [42]}); nomatch({a: {$type: 7}}, {a: "1234567890abcd1234567890"}); match({a: {$type: 8}}, {a: true}); match({a: {$type: 8}}, {a: false}); nomatch({a: {$type: 8}}, {a: "true"}); nomatch({a: {$type: 8}}, {a: 0}); nomatch({a: {$type: 8}}, {a: null}); nomatch({a: {$type: 8}}, {a: ''}); nomatch({a: {$type: 8}}, {}); match({a: {$type: 9}}, {a: (new Date)}); nomatch({a: {$type: 9}}, {a: +(new Date)}); match({a: {$type: 10}}, {a: null}); nomatch({a: {$type: 10}}, {a: false}); nomatch({a: {$type: 10}}, {a: ''}); nomatch({a: {$type: 10}}, {a: 0}); nomatch({a: {$type: 10}}, {}); match({a: {$type: 11}}, {a: /x/}); nomatch({a: {$type: 11}}, {a: 'x'}); nomatch({a: {$type: 11}}, {}); // The normal rule for {$type:4} (4 means array) is that it NOT good enough to // just have an array that's the leaf that matches the path. (An array inside // that array is good, though.) nomatch({a: {$type: 4}}, {a: []}); nomatch({a: {$type: 4}}, {a: [1]}); // tested against mongodb match({a: {$type: 1}}, {a: [1]}); nomatch({a: {$type: 2}}, {a: [1]}); match({a: {$type: 1}}, {a: ["1", 1]}); match({a: {$type: 2}}, {a: ["1", 1]}); nomatch({a: {$type: 3}}, {a: ["1", 1]}); nomatch({a: {$type: 4}}, {a: ["1", 1]}); nomatch({a: {$type: 1}}, {a: ["1", []]}); match({a: {$type: 2}}, {a: ["1", []]}); match({a: {$type: 4}}, {a: ["1", []]}); // tested against mongodb // An exception to the normal rule is that an array found via numeric index is // examined itself, and its elements are not. match({'a.0': {$type: 4}}, {a: [[0]]}); nomatch({'a.0': {$type: 1}}, {a: [[0]]}); // regular expressions match({a: /a/}, {a: 'cat'}); nomatch({a: /a/}, {a: 'cut'}); nomatch({a: /a/}, {a: 'CAT'}); match({a: /a/i}, {a: 'CAT'}); match({a: /a/}, {a: ['foo', 'bar']}); // search within array... nomatch({a: /,/}, {a: ['foo', 'bar']}); // but not by stringifying match({a: {$regex: 'a'}}, {a: ['foo', 'bar']}); nomatch({a: {$regex: ','}}, {a: ['foo', 'bar']}); match({a: {$regex: /a/}}, {a: 'cat'}); nomatch({a: {$regex: /a/}}, {a: 'cut'}); nomatch({a: {$regex: /a/}}, {a: 'CAT'}); match({a: {$regex: /a/i}}, {a: 'CAT'}); match({a: {$regex: /a/, $options: 'i'}}, {a: 'CAT'}); // tested match({a: {$regex: /a/i, $options: 'i'}}, {a: 'CAT'}); // tested nomatch({a: {$regex: /a/i, $options: ''}}, {a: 'CAT'}); // tested match({a: {$regex: 'a'}}, {a: 'cat'}); nomatch({a: {$regex: 'a'}}, {a: 'cut'}); nomatch({a: {$regex: 'a'}}, {a: 'CAT'}); match({a: {$regex: 'a', $options: 'i'}}, {a: 'CAT'}); match({a: {$regex: '', $options: 'i'}}, {a: 'foo'}); nomatch({a: {$regex: '', $options: 'i'}}, {}); nomatch({a: {$regex: '', $options: 'i'}}, {a: 5}); nomatch({a: /undefined/}, {}); nomatch({a: {$regex: 'undefined'}}, {}); nomatch({a: /xxx/}, {}); nomatch({a: {$regex: 'xxx'}}, {}); // GitHub issue #2817: // Regexps with a global flag ('g') keep a state when tested against the same // string. Selector shouldn't return different result for similar documents // because of this state. var reusedRegexp = /sh/ig; match({a: reusedRegexp}, {a: 'Shorts'}); match({a: reusedRegexp}, {a: 'Shorts'}); match({a: reusedRegexp}, {a: 'Shorts'}); match({a: {$regex: reusedRegexp}}, {a: 'Shorts'}); match({a: {$regex: reusedRegexp}}, {a: 'Shorts'}); match({a: {$regex: reusedRegexp}}, {a: 'Shorts'}); assert.throws(function () { match({a: {$options: 'i'}}, {a: 12}); }); match({a: /a/}, {a: ['dog', 'cat']}); nomatch({a: /a/}, {a: ['dog', 'puppy']}); // we don't support regexps in minimongo very well (eg, there's no EJSON // encoding so it won't go over the wire), but run these tests anyway match({a: /a/}, {a: /a/}); match({a: /a/}, {a: ['x', /a/]}); nomatch({a: /a/}, {a: /a/i}); nomatch({a: /a/m}, {a: /a/}); nomatch({a: /a/}, {a: /b/}); nomatch({a: /5/}, {a: 5}); nomatch({a: /t/}, {a: true}); match({a: /m/i}, {a: ['x', 'xM']}); assert.throws(function () { match({a: {$regex: /a/, $options: 'x'}}, {a: 'cat'}); }); assert.throws(function () { match({a: {$regex: /a/, $options: 's'}}, {a: 'cat'}); }); // $not match({x: {$not: {$gt: 7}}}, {x: 6}); nomatch({x: {$not: {$gt: 7}}}, {x: 8}); match({x: {$not: {$lt: 10, $gt: 7}}}, {x: 11}); nomatch({x: {$not: {$lt: 10, $gt: 7}}}, {x: 9}); 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"}); match({x: {$not: /a/}}, {x: ["dog", "puppy"]}); nomatch({x: {$not: /a/}}, {x: ["kitten", "cat"]}); // dotted keypaths: bare values match({"a.b": 1}, {a: {b: 1}}); nomatch({"a.b": 1}, {a: {b: 2}}); match({"a.b": [1,2,3]}, {a: {b: [1,2,3]}}); nomatch({"a.b": [1,2,3]}, {a: {b: [4]}}); match({"a.b": /a/}, {a: {b: "cat"}}); nomatch({"a.b": /a/}, {a: {b: "dog"}}); match({"a.b.c": null}, {}); match({"a.b.c": null}, {a: 1}); match({"a.b": null}, {a: 1}); match({"a.b.c": null}, {a: {b: 4}}); // dotted keypaths, nulls, numeric indices, arrays nomatch({"a.b": null}, {a: [1]}); match({"a.b": []}, {a: {b: []}}); var big = {a: [{b: 1}, 2, {}, {b: [3, 4]}]}; match({"a.b": 1}, big); match({"a.b": [3, 4]}, big); match({"a.b": 3}, big); match({"a.b": 4}, big); match({"a.b": null}, big); // matches on slot 2 match({'a.1': 8}, {a: [7, 8, 9]}); nomatch({'a.1': 7}, {a: [7, 8, 9]}); nomatch({'a.1': null}, {a: [7, 8, 9]}); match({'a.1': [8, 9]}, {a: [7, [8, 9]]}); nomatch({'a.1': 6}, {a: [[6, 7], [8, 9]]}); nomatch({'a.1': 7}, {a: [[6, 7], [8, 9]]}); nomatch({'a.1': 8}, {a: [[6, 7], [8, 9]]}); nomatch({'a.1': 9}, {a: [[6, 7], [8, 9]]}); match({"a.1": 2}, {a: [0, {1: 2}, 3]}); match({"a.1": {1: 2}}, {a: [0, {1: 2}, 3]}); match({"x.1.y": 8}, {x: [7, {y: 8}, 9]}); // comes from trying '1' as key in the plain object match({"x.1.y": null}, {x: [7, {y: 8}, 9]}); match({"a.1.b": 9}, {a: [7, {b: 9}, {1: {b: 'foo'}}]}); match({"a.1.b": 'foo'}, {a: [7, {b: 9}, {1: {b: 'foo'}}]}); match({"a.1.b": null}, {a: [7, {b: 9}, {1: {b: 'foo'}}]}); match({"a.1.b": 2}, {a: [1, [{b: 2}], 3]}); nomatch({"a.1.b": null}, {a: [1, [{b: 2}], 3]}); // this is new behavior in mongo 2.5 nomatch({"a.0.b": null}, {a: [5]}); match({"a.1": 4}, {a: [{1: 4}, 5]}); match({"a.1": 5}, {a: [{1: 4}, 5]}); nomatch({"a.1": null}, {a: [{1: 4}, 5]}); match({"a.1.foo": 4}, {a: [{1: {foo: 4}}, {foo: 5}]}); match({"a.1.foo": 5}, {a: [{1: {foo: 4}}, {foo: 5}]}); match({"a.1.foo": null}, {a: [{1: {foo: 4}}, {foo: 5}]}); // trying to access a dotted field that is undefined at some point // down the chain nomatch({"a.b": 1}, {x: 2}); nomatch({"a.b.c": 1}, {a: {x: 2}}); nomatch({"a.b.c": 1}, {a: {b: {x: 2}}}); nomatch({"a.b.c": 1}, {a: {b: 1}}); nomatch({"a.b.c": 1}, {a: {b: 0}}); // dotted keypaths: literal objects match({"a.b": {c: 1}}, {a: {b: {c: 1}}}); nomatch({"a.b": {c: 1}}, {a: {b: {c: 2}}}); nomatch({"a.b": {c: 1}}, {a: {b: 2}}); match({"a.b": {c: 1, d: 2}}, {a: {b: {c: 1, d: 2}}}); nomatch({"a.b": {c: 1, d: 2}}, {a: {b: {c: 1, d: 1}}}); nomatch({"a.b": {c: 1, d: 2}}, {a: {b: {d: 2}}}); // dotted keypaths: $ operators match({"a.b": {$in: [1, 2, 3]}}, {a: {b: [2]}}); // tested against mongodb match({"a.b": {$in: [{x: 1}, {x: 2}, {x: 3}]}}, {a: {b: [{x: 2}]}}); match({"a.b": {$in: [1, 2, 3]}}, {a: {b: [4, 2]}}); nomatch({"a.b": {$in: [1, 2, 3]}}, {a: {b: [4]}}); // $or assert.throws(function () { match({$or: []}, {}); }); assert.throws(function () { match({$or: [5]}, {}); }); assert.throws(function () { match({$or: []}, {a: 1}); }); match({$or: [{a: 1}]}, {a: 1}); nomatch({$or: [{b: 2}]}, {a: 1}); match({$or: [{a: 1}, {b: 2}]}, {a: 1}); nomatch({$or: [{c: 3}, {d: 4}]}, {a: 1}); match({$or: [{a: 1}, {b: 2}]}, {a: [1, 2, 3]}); nomatch({$or: [{a: 1}, {b: 2}]}, {c: [1, 2, 3]}); nomatch({$or: [{a: 1}, {b: 2}]}, {a: [2, 3, 4]}); match({$or: [{a: 1}, {a: 2}]}, {a: 1}); match({$or: [{a: 1}, {a: 2}], b: 2}, {a: 1, b: 2}); nomatch({$or: [{a: 2}, {a: 3}], b: 2}, {a: 1, b: 2}); nomatch({$or: [{a: 1}, {a: 2}], b: 3}, {a: 1, b: 2}); // Combining $or with equality match({x: 1, $or: [{a: 1}, {b: 1}]}, {x: 1, b: 1}); match({$or: [{a: 1}, {b: 1}], x: 1}, {x: 1, b: 1}); nomatch({x: 1, $or: [{a: 1}, {b: 1}]}, {b: 1}); nomatch({x: 1, $or: [{a: 1}, {b: 1}]}, {x: 1}); // $or and $lt, $lte, $gt, $gte match({$or: [{a: {$lte: 1}}, {a: 2}]}, {a: 1}); nomatch({$or: [{a: {$lt: 1}}, {a: 2}]}, {a: 1}); match({$or: [{a: {$gte: 1}}, {a: 2}]}, {a: 1}); nomatch({$or: [{a: {$gt: 1}}, {a: 2}]}, {a: 1}); match({$or: [{b: {$gt: 1}}, {b: {$lt: 3}}]}, {b: 2}); nomatch({$or: [{b: {$lt: 1}}, {b: {$gt: 3}}]}, {b: 2}); // $or and $in match({$or: [{a: {$in: [1, 2, 3]}}]}, {a: 1}); nomatch({$or: [{a: {$in: [4, 5, 6]}}]}, {a: 1}); match({$or: [{a: {$in: [1, 2, 3]}}, {b: 2}]}, {a: 1}); match({$or: [{a: {$in: [1, 2, 3]}}, {b: 2}]}, {b: 2}); nomatch({$or: [{a: {$in: [1, 2, 3]}}, {b: 2}]}, {c: 3}); match({$or: [{a: {$in: [1, 2, 3]}}, {b: {$in: [1, 2, 3]}}]}, {b: 2}); nomatch({$or: [{a: {$in: [1, 2, 3]}}, {b: {$in: [4, 5, 6]}}]}, {b: 2}); // $or and $nin nomatch({$or: [{a: {$nin: [1, 2, 3]}}]}, {a: 1}); match({$or: [{a: {$nin: [4, 5, 6]}}]}, {a: 1}); nomatch({$or: [{a: {$nin: [1, 2, 3]}}, {b: 2}]}, {a: 1}); match({$or: [{a: {$nin: [1, 2, 3]}}, {b: 2}]}, {b: 2}); match({$or: [{a: {$nin: [1, 2, 3]}}, {b: 2}]}, {c: 3}); match({$or: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [1, 2, 3]}}]}, {b: 2}); nomatch({$or: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [1, 2, 3]}}]}, {a: 1, b: 2}); match({$or: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [4, 5, 6]}}]}, {b: 2}); // $or and dot-notation match({$or: [{"a.b": 1}, {"a.b": 2}]}, {a: {b: 1}}); match({$or: [{"a.b": 1}, {"a.c": 1}]}, {a: {b: 1}}); nomatch({$or: [{"a.b": 2}, {"a.c": 1}]}, {a: {b: 1}}); // $or and nested objects match({$or: [{a: {b: 1, c: 2}}, {a: {b: 2, c: 1}}]}, {a: {b: 1, c: 2}}); nomatch({$or: [{a: {b: 1, c: 3}}, {a: {b: 2, c: 1}}]}, {a: {b: 1, c: 2}}); // $or and regexes match({$or: [{a: /a/}]}, {a: "cat"}); nomatch({$or: [{a: /o/}]}, {a: "cat"}); match({$or: [{a: /a/}, {a: /o/}]}, {a: "cat"}); nomatch({$or: [{a: /i/}, {a: /o/}]}, {a: "cat"}); match({$or: [{a: /i/}, {b: /o/}]}, {a: "cat", b: "dog"}); // $or and $ne match({$or: [{a: {$ne: 1}}]}, {}); nomatch({$or: [{a: {$ne: 1}}]}, {a: 1}); match({$or: [{a: {$ne: 1}}]}, {a: 2}); match({$or: [{a: {$ne: 1}}]}, {b: 1}); match({$or: [{a: {$ne: 1}}, {a: {$ne: 2}}]}, {a: 1}); match({$or: [{a: {$ne: 1}}, {b: {$ne: 1}}]}, {a: 1}); nomatch({$or: [{a: {$ne: 1}}, {b: {$ne: 2}}]}, {a: 1, b: 2}); // $or and $not match({$or: [{a: {$not: {$mod: [10, 1]}}}]}, {}); nomatch({$or: [{a: {$not: {$mod: [10, 1]}}}]}, {a: 1}); match({$or: [{a: {$not: {$mod: [10, 1]}}}]}, {a: 2}); match({$or: [{a: {$not: {$mod: [10, 1]}}}, {a: {$not: {$mod: [10, 2]}}}]}, {a: 1}); nomatch({$or: [{a: {$not: {$mod: [10, 1]}}}, {a: {$mod: [10, 2]}}]}, {a: 1}); match({$or: [{a: {$not: {$mod: [10, 1]}}}, {a: {$mod: [10, 2]}}]}, {a: 2}); match({$or: [{a: {$not: {$mod: [10, 1]}}}, {a: {$mod: [10, 2]}}]}, {a: 3}); // this is possibly an open-ended task, so we stop here ... // $nor assert.throws(function () { match({$nor: []}, {}); }); assert.throws(function () { match({$nor: [5]}, {}); }); assert.throws(function () { match({$nor: []}, {a: 1}); }); nomatch({$nor: [{a: 1}]}, {a: 1}); match({$nor: [{b: 2}]}, {a: 1}); nomatch({$nor: [{a: 1}, {b: 2}]}, {a: 1}); match({$nor: [{c: 3}, {d: 4}]}, {a: 1}); nomatch({$nor: [{a: 1}, {b: 2}]}, {a: [1, 2, 3]}); match({$nor: [{a: 1}, {b: 2}]}, {c: [1, 2, 3]}); match({$nor: [{a: 1}, {b: 2}]}, {a: [2, 3, 4]}); nomatch({$nor: [{a: 1}, {a: 2}]}, {a: 1}); // $nor and $lt, $lte, $gt, $gte nomatch({$nor: [{a: {$lte: 1}}, {a: 2}]}, {a: 1}); match({$nor: [{a: {$lt: 1}}, {a: 2}]}, {a: 1}); nomatch({$nor: [{a: {$gte: 1}}, {a: 2}]}, {a: 1}); match({$nor: [{a: {$gt: 1}}, {a: 2}]}, {a: 1}); nomatch({$nor: [{b: {$gt: 1}}, {b: {$lt: 3}}]}, {b: 2}); match({$nor: [{b: {$lt: 1}}, {b: {$gt: 3}}]}, {b: 2}); // $nor and $in nomatch({$nor: [{a: {$in: [1, 2, 3]}}]}, {a: 1}); match({$nor: [{a: {$in: [4, 5, 6]}}]}, {a: 1}); nomatch({$nor: [{a: {$in: [1, 2, 3]}}, {b: 2}]}, {a: 1}); nomatch({$nor: [{a: {$in: [1, 2, 3]}}, {b: 2}]}, {b: 2}); match({$nor: [{a: {$in: [1, 2, 3]}}, {b: 2}]}, {c: 3}); nomatch({$nor: [{a: {$in: [1, 2, 3]}}, {b: {$in: [1, 2, 3]}}]}, {b: 2}); match({$nor: [{a: {$in: [1, 2, 3]}}, {b: {$in: [4, 5, 6]}}]}, {b: 2}); // $nor and $nin match({$nor: [{a: {$nin: [1, 2, 3]}}]}, {a: 1}); nomatch({$nor: [{a: {$nin: [4, 5, 6]}}]}, {a: 1}); match({$nor: [{a: {$nin: [1, 2, 3]}}, {b: 2}]}, {a: 1}); nomatch({$nor: [{a: {$nin: [1, 2, 3]}}, {b: 2}]}, {b: 2}); nomatch({$nor: [{a: {$nin: [1, 2, 3]}}, {b: 2}]}, {c: 3}); nomatch({$nor: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [1, 2, 3]}}]}, {b: 2}); match({$nor: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [1, 2, 3]}}]}, {a: 1, b: 2}); nomatch({$nor: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [4, 5, 6]}}]}, {b: 2}); // $nor and dot-notation nomatch({$nor: [{"a.b": 1}, {"a.b": 2}]}, {a: {b: 1}}); nomatch({$nor: [{"a.b": 1}, {"a.c": 1}]}, {a: {b: 1}}); match({$nor: [{"a.b": 2}, {"a.c": 1}]}, {a: {b: 1}}); // $nor and nested objects nomatch({$nor: [{a: {b: 1, c: 2}}, {a: {b: 2, c: 1}}]}, {a: {b: 1, c: 2}}); match({$nor: [{a: {b: 1, c: 3}}, {a: {b: 2, c: 1}}]}, {a: {b: 1, c: 2}}); // $nor and regexes nomatch({$nor: [{a: /a/}]}, {a: "cat"}); match({$nor: [{a: /o/}]}, {a: "cat"}); nomatch({$nor: [{a: /a/}, {a: /o/}]}, {a: "cat"}); match({$nor: [{a: /i/}, {a: /o/}]}, {a: "cat"}); nomatch({$nor: [{a: /i/}, {b: /o/}]}, {a: "cat", b: "dog"}); // $nor and $ne nomatch({$nor: [{a: {$ne: 1}}]}, {}); match({$nor: [{a: {$ne: 1}}]}, {a: 1}); nomatch({$nor: [{a: {$ne: 1}}]}, {a: 2}); nomatch({$nor: [{a: {$ne: 1}}]}, {b: 1}); nomatch({$nor: [{a: {$ne: 1}}, {a: {$ne: 2}}]}, {a: 1}); nomatch({$nor: [{a: {$ne: 1}}, {b: {$ne: 1}}]}, {a: 1}); match({$nor: [{a: {$ne: 1}}, {b: {$ne: 2}}]}, {a: 1, b: 2}); // $nor and $not nomatch({$nor: [{a: {$not: {$mod: [10, 1]}}}]}, {}); match({$nor: [{a: {$not: {$mod: [10, 1]}}}]}, {a: 1}); nomatch({$nor: [{a: {$not: {$mod: [10, 1]}}}]}, {a: 2}); nomatch({$nor: [{a: {$not: {$mod: [10, 1]}}}, {a: {$not: {$mod: [10, 2]}}}]}, {a: 1}); match({$nor: [{a: {$not: {$mod: [10, 1]}}}, {a: {$mod: [10, 2]}}]}, {a: 1}); nomatch({$nor: [{a: {$not: {$mod: [10, 1]}}}, {a: {$mod: [10, 2]}}]}, {a: 2}); nomatch({$nor: [{a: {$not: {$mod: [10, 1]}}}, {a: {$mod: [10, 2]}}]}, {a: 3}); // $and assert.throws(function () { match({$and: []}, {}); }); assert.throws(function () { match({$and: [5]}, {}); }); assert.throws(function () { match({$and: []}, {a: 1}); }); match({$and: [{a: 1}]}, {a: 1}); nomatch({$and: [{a: 1}, {a: 2}]}, {a: 1}); nomatch({$and: [{a: 1}, {b: 1}]}, {a: 1}); match({$and: [{a: 1}, {b: 2}]}, {a: 1, b: 2}); nomatch({$and: [{a: 1}, {b: 1}]}, {a: 1, b: 2}); match({$and: [{a: 1}, {b: 2}], c: 3}, {a: 1, b: 2, c: 3}); nomatch({$and: [{a: 1}, {b: 2}], c: 4}, {a: 1, b: 2, c: 3}); // $and and regexes match({$and: [{a: /a/}]}, {a: "cat"}); match({$and: [{a: /a/i}]}, {a: "CAT"}); nomatch({$and: [{a: /o/}]}, {a: "cat"}); nomatch({$and: [{a: /a/}, {a: /o/}]}, {a: "cat"}); match({$and: [{a: /a/}, {b: /o/}]}, {a: "cat", b: "dog"}); nomatch({$and: [{a: /a/}, {b: /a/}]}, {a: "cat", b: "dog"}); // $and, dot-notation, and nested objects match({$and: [{"a.b": 1}]}, {a: {b: 1}}); match({$and: [{a: {b: 1}}]}, {a: {b: 1}}); nomatch({$and: [{"a.b": 2}]}, {a: {b: 1}}); nomatch({$and: [{"a.c": 1}]}, {a: {b: 1}}); nomatch({$and: [{"a.b": 1}, {"a.b": 2}]}, {a: {b: 1}}); nomatch({$and: [{"a.b": 1}, {a: {b: 2}}]}, {a: {b: 1}}); match({$and: [{"a.b": 1}, {"c.d": 2}]}, {a: {b: 1}, c: {d: 2}}); nomatch({$and: [{"a.b": 1}, {"c.d": 1}]}, {a: {b: 1}, c: {d: 2}}); match({$and: [{"a.b": 1}, {c: {d: 2}}]}, {a: {b: 1}, c: {d: 2}}); nomatch({$and: [{"a.b": 1}, {c: {d: 1}}]}, {a: {b: 1}, c: {d: 2}}); nomatch({$and: [{"a.b": 2}, {c: {d: 2}}]}, {a: {b: 1}, c: {d: 2}}); match({$and: [{a: {b: 1}}, {c: {d: 2}}]}, {a: {b: 1}, c: {d: 2}}); nomatch({$and: [{a: {b: 2}}, {c: {d: 2}}]}, {a: {b: 1}, c: {d: 2}}); // $and and $in nomatch({$and: [{a: {$in: []}}]}, {}); match({$and: [{a: {$in: [1, 2, 3]}}]}, {a: 1}); nomatch({$and: [{a: {$in: [4, 5, 6]}}]}, {a: 1}); nomatch({$and: [{a: {$in: [1, 2, 3]}}, {a: {$in: [4, 5, 6]}}]}, {a: 1}); nomatch({$and: [{a: {$in: [1, 2, 3]}}, {b: {$in: [1, 2, 3]}}]}, {a: 1, b: 4}); match({$and: [{a: {$in: [1, 2, 3]}}, {b: {$in: [4, 5, 6]}}]}, {a: 1, b: 4}); // $and and $nin match({$and: [{a: {$nin: []}}]}, {}); nomatch({$and: [{a: {$nin: [1, 2, 3]}}]}, {a: 1}); match({$and: [{a: {$nin: [4, 5, 6]}}]}, {a: 1}); nomatch({$and: [{a: {$nin: [1, 2, 3]}}, {a: {$nin: [4, 5, 6]}}]}, {a: 1}); nomatch({$and: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [1, 2, 3]}}]}, {a: 1, b: 4}); nomatch({$and: [{a: {$nin: [1, 2, 3]}}, {b: {$nin: [4, 5, 6]}}]}, {a: 1, b: 4}); // $and and $lt, $lte, $gt, $gte match({$and: [{a: {$lt: 2}}]}, {a: 1}); nomatch({$and: [{a: {$lt: 1}}]}, {a: 1}); match({$and: [{a: {$lte: 1}}]}, {a: 1}); match({$and: [{a: {$gt: 0}}]}, {a: 1}); nomatch({$and: [{a: {$gt: 1}}]}, {a: 1}); match({$and: [{a: {$gte: 1}}]}, {a: 1}); match({$and: [{a: {$gt: 0}}, {a: {$lt: 2}}]}, {a: 1}); nomatch({$and: [{a: {$gt: 1}}, {a: {$lt: 2}}]}, {a: 1}); nomatch({$and: [{a: {$gt: 0}}, {a: {$lt: 1}}]}, {a: 1}); match({$and: [{a: {$gte: 1}}, {a: {$lte: 1}}]}, {a: 1}); nomatch({$and: [{a: {$gte: 2}}, {a: {$lte: 0}}]}, {a: 1}); // $and and $ne match({$and: [{a: {$ne: 1}}]}, {}); nomatch({$and: [{a: {$ne: 1}}]}, {a: 1}); match({$and: [{a: {$ne: 1}}]}, {a: 2}); nomatch({$and: [{a: {$ne: 1}}, {a: {$ne: 2}}]}, {a: 2}); match({$and: [{a: {$ne: 1}}, {a: {$ne: 3}}]}, {a: 2}); // $and and $not match({$and: [{a: {$not: {$gt: 2}}}]}, {a: 1}); nomatch({$and: [{a: {$not: {$lt: 2}}}]}, {a: 1}); match({$and: [{a: {$not: {$lt: 0}}}, {a: {$not: {$gt: 2}}}]}, {a: 1}); nomatch({$and: [{a: {$not: {$lt: 2}}}, {a: {$not: {$gt: 0}}}]}, {a: 1}); // $where match({$where: "this.a === 1"}, {a: 1}); match({$where: "obj.a === 1"}, {a: 1}); nomatch({$where: "this.a !== 1"}, {a: 1}); nomatch({$where: "obj.a !== 1"}, {a: 1}); nomatch({$where: "this.a === 1", a: 2}, {a: 1}); match({$where: "this.a === 1", b: 2}, {a: 1, b: 2}); match({$where: "this.a === 1 && this.b === 2"}, {a: 1, b: 2}); match({$where: "this.a instanceof Array"}, {a: []}); nomatch({$where: "this.a instanceof Array"}, {a: 1}); // reaching into array match({"dogs.0.name": "Fido"}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); match({"dogs.1.name": "Rex"}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); nomatch({"dogs.1.name": "Fido"}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); match({"room.1b": "bla"}, {room: {"1b": "bla"}}); match({"dogs.name": "Fido"}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); match({"dogs.name": "Rex"}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); match({"animals.dogs.name": "Fido"}, {animals: [{dogs: [{name: "Rover"}]}, {}, {dogs: [{name: "Fido"}, {name: "Rex"}]}]}); match({"animals.dogs.name": "Fido"}, {animals: [{dogs: {name: "Rex"}}, {dogs: {name: "Fido"}}]}); match({"animals.dogs.name": "Fido"}, {animals: [{dogs: [{name: "Rover"}]}, {}, {dogs: [{name: ["Fido"]}, {name: "Rex"}]}]}); nomatch({"dogs.name": "Fido"}, {dogs: []}); // $elemMatch match({dogs: {$elemMatch: {name: /e/}}}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); nomatch({dogs: {$elemMatch: {name: /a/}}}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); match({dogs: {$elemMatch: {age: {$gt: 4}}}}, {dogs: [{name: "Fido", age: 5}, {name: "Rex", age: 3}]}); match({dogs: {$elemMatch: {name: "Fido", age: {$gt: 4}}}}, {dogs: [{name: "Fido", age: 5}, {name: "Rex", age: 3}]}); nomatch({dogs: {$elemMatch: {name: "Fido", age: {$gt: 5}}}}, {dogs: [{name: "Fido", age: 5}, {name: "Rex", age: 3}]}); match({dogs: {$elemMatch: {name: /i/, age: {$gt: 4}}}}, {dogs: [{name: "Fido", age: 5}, {name: "Rex", age: 3}]}); nomatch({dogs: {$elemMatch: {name: /e/, age: 5}}}, {dogs: [{name: "Fido", age: 5}, {name: "Rex", age: 3}]}); match({x: {$elemMatch: {y: 9}}}, {x: [{y: 9}]}); nomatch({x: {$elemMatch: {y: 9}}}, {x: [[{y: 9}]]}); match({x: {$elemMatch: {$gt: 5, $lt: 9}}}, {x: [8]}); nomatch({x: {$elemMatch: {$gt: 5, $lt: 9}}}, {x: [[8]]}); match({'a.x': {$elemMatch: {y: 9}}}, {a: [{x: []}, {x: [{y: 9}]}]}); nomatch({a: {$elemMatch: {x: 5}}}, {a: {x: 5}}); match({a: {$elemMatch: {0: {$gt: 5, $lt: 9}}}}, {a: [[6]]}); match({a: {$elemMatch: {'0.b': {$gt: 5, $lt: 9}}}}, {a: [[{b:6}]]}); match({a: {$elemMatch: {x: 1, $or: [{a: 1}, {b: 1}]}}}, {a: [{x: 1, b: 1}]}); match({a: {$elemMatch: {$or: [{a: 1}, {b: 1}], x: 1}}}, {a: [{x: 1, b: 1}]}); nomatch({a: {$elemMatch: {x: 1, $or: [{a: 1}, {b: 1}]}}}, {a: [{b: 1}]}); nomatch({a: {$elemMatch: {x: 1, $or: [{a: 1}, {b: 1}]}}}, {a: [{x: 1}]}); nomatch({a: {$elemMatch: {x: 1, $or: [{a: 1}, {b: 1}]}}}, {a: [{x: 1}, {b: 1}]}); // $comment match({a: 5, $comment: "asdf"}, {a: 5}); nomatch({a: 6, $comment: "asdf"}, {a: 5}); // XXX still needs tests: // - non-scalar arguments to $gt, $lt, etc }); });