diff --git a/packages/node_modules/pouchdb-selector-core/src/utils.js b/packages/node_modules/pouchdb-selector-core/src/utils.js index 4bd322ca55..6e2e632889 100644 --- a/packages/node_modules/pouchdb-selector-core/src/utils.js +++ b/packages/node_modules/pouchdb-selector-core/src/utils.js @@ -91,12 +91,10 @@ function mergeAndedSelectors(selectors) { res[field].forEach(function (existing) { Object.keys(matcher).forEach(function (key) { var m = matcher[key]; - var longest = Math.max(Object.keys(existing).length, Object.keys(m).length); var merged = mergeAndedSelectors([existing, m]); - if (Object.keys(merged).length <= longest) { + if (checkFallacy(merged)) { // we have a situation like: (a :{$eq :1} || ...) && (a {$eq: 2} || ...) // merging would produce a $eq 2 when actually we shouldn't ever match against these merged conditions - // merged should always contain more values to be valid return; } entries.push(merged); @@ -132,7 +130,22 @@ function mergeAndedSelectors(selectors) { return res; } +// check for fallacies in the selector +// e.g. {foo: {$gt: 1, $lt: 2}} is a fallacy +function checkFallacy(selector) { + let fallacy = false; + Object.keys(selector).forEach(function (field) { + var matcher = selector[field]; + if (typeof matcher !== 'object' || matcher === null) { + return; + } + if ( '$eq' in matcher && '$ne' in matcher && matcher.$ne.indexOf(matcher.$eq) !== -1) { + fallacy = true; + } + }); + return fallacy; +} // collapse logically equivalent gt/gte values function mergeGtGte(operator, value, fieldMatchers) { @@ -217,6 +230,9 @@ function mergeEq(value, fieldMatchers) { delete fieldMatchers.$lt; delete fieldMatchers.$lte; delete fieldMatchers.$ne; + if ( '$eq' in fieldMatchers && fieldMatchers.$eq !== value ) { + mergeNe(value, fieldMatchers); + } fieldMatchers.$eq = value; } diff --git a/tests/find/test-suite-1/test.or.js b/tests/find/test-suite-1/test.or.js index 8ae4449afa..195db39cc9 100644 --- a/tests/find/test-suite-1/test.or.js +++ b/tests/find/test-suite-1/test.or.js @@ -281,6 +281,62 @@ describe('test.or.js', function () { } }); }); + it('should handle correct merge of $gte', function () { + var db = context.db; + var index = { + "index": { + "fields": ["field.a"] + } + }; + + var selector = { + $and: [ + { + $or: [ + {a: 1}, + {b: {$gte: 3}} + ] + }, + { + $or: [ + {a: 3}, + {b: {$gte: 4}} + ] + } + ] + }; + return db.createIndex(index).then(function () { + return db.bulkDocs([ + {_id: '1', a: 1, b: 2}, + {_id: '2', a: 1, b: 4}, + {_id: '3', a: 3, b: 2}, + {_id: '4', a: 3, b: 4}, + {_id: '5', a: 5, b: 5} + ]); + }).then(function () { + return db.find({ + selector, + fields: ["_id"] + }).then(function (resp) { + resp.docs.should.deep.equal([{_id: '2'}, {_id: '4'}, {_id: '5'}]); + }); + }).then(function () { + if (db.adapter === "local") { + return db.explain({ + selector, + fields: ["_id"] + }).then(function (resp) { + resp.selector.should.deep.equal({ + "$or": [ + {"a": {"$eq": 1}, "b": {"$gte": 4}}, + {"a": {"$eq": 3}, "b": {"$gte": 3}}, + {"b": {"$gte": 4}} + ] + }); + }); + } + }); + }); it('should do complex queries', function () { var db = context.db; var index = {