diff --git a/lib/dao.js b/lib/dao.js index 887711df..e5bb5c9b 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -1540,6 +1540,12 @@ DataAccessObject.find = function find(query, options, cb) { let geoQueryObject; if (near) { + if (options.splitLongInq) { + const msg = g.f('The option "splitLongInq" is not supported for near/geo queries.'); + const error = new Error(msg); + cb(error); + return cb.promise; + } if (supportsGeo) { // convert it connector.buildNearFilter(query, near); @@ -1568,7 +1574,6 @@ DataAccessObject.find = function find(query, options, cb) { // already handled return cb.promise; } - } function geoCallback(err, data) { const memory = new Memory(); @@ -1599,6 +1604,25 @@ DataAccessObject.find = function find(query, options, cb) { geoQueryObject = query; near = query && geo.nearFilter(query.where); invokeConnectorMethod(connector, 'all', self, [{}], options, geoCallback); + } + + function queryWithInqSplit() { + const inqLimit = self.dataSource && self.dataSource.settings && + self.dataSource.settings.inqLimit || 256; + + // clone the original query + const query = Object.assign({}, query); + query.where = Object.assign({}, query.where); + + if (query.where.and || query.where.or) { + const msg = g.f('The option "splitLongInq" does not support "and" and "or" operators yet.'); + const error = new Error(msg); + cb(error); + return cb.promise; + + + + } function allCb(err, data) { @@ -1699,6 +1723,11 @@ DataAccessObject.find = function find(query, options, cb) { } if (options.notify === false) { + if (options.splitLongInq && isInqQuery(query.where)) { + queryWithInqSplit(); + return cb.promise; + } + invokeConnectorMethod(connector, 'all', self, [query], options, allCb); } else { const context = { @@ -1709,6 +1738,12 @@ DataAccessObject.find = function find(query, options, cb) { }; this.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); + + if (options.splitLongInq && isInqQuery(query.where)) { + queryWithInqSplit(); + return; + } + invokeConnectorMethod(connector, 'all', self, [ctx.query], options, allCb); }); } diff --git a/test/basic-querying.test.js b/test/basic-querying.test.js index 0beed102..e7011215 100644 --- a/test/basic-querying.test.js +++ b/test/basic-querying.test.js @@ -12,7 +12,7 @@ const bdd = require('./helpers/bdd-if'); const should = require('./init.js'); const uid = require('./helpers/uid-generator'); -let db, User; +let db, User, Product; describe('basic-querying', function() { before(function(done) { @@ -52,6 +52,11 @@ describe('basic-querying', function() { (db.adapter.name != 'informix') && (db.adapter.name != 'cassandra'); if (connectorCapabilities.geoPoint) userModelDef.addressLoc = {type: 'GeoPoint'}; User = db.define('User', userModelDef); + + Product = db.define('Product', { + name: {type: String, required: true}, + }); + db.automigrate(done); }); @@ -1127,6 +1132,71 @@ describe('basic-querying', function() { }, done); }); }); + + bdd.describeIf(connectorCapabilities.supportInq !== false, 'inq query', () => { + let originalInqLimit; + let originalAll; + let observedCalls; + + before(async function setupTestModels() { + Product = db.define('Product', { + name: {type: String, required: true}, + }); + + await db.automigrate(Product.modelName); + + originalInqLimit = db.settings.inqLimit; + originalAll = db.connector.all; + + if ((db.settings.inqLimit || Infinity) > 5) { + // artificially reduce the inqLimit to a small number to trigger + // inq splitting even for connectors that support arbitrarily-long + // inq lists and also to keep the test fast + db.settings.inqLimit = 3; + } + }); + + afterEach(function restoreOriginalState() { + db.settings.inqLimit = originalInqLimit; + db.connector.all = originalAll; + }); + + it('rejects geo queries with inq splitting', function() { + const where = { + id: {inq: [1, 2, 3]}, + location: {near: {lat: 29.9, lng: -90.07}}, + }; + return Product.find({where}, {splitLongInq: true}) + .should.be.rejectedWith(/splitLongInq.*not supported/); + }); + + it('splits large inq list', async function() { + const observedCalls = []; + db.connector.all = function(modelName, query, options, cb) { + observedCalls.push({modelName, query, options}); + originalAll.apply(this, arguments); + }; + + const created = []; + // Notice that 10 is not divisible by inqLimit + for (let i = 0; i < 10; i++) { + // Create a product that we will look for + created.push(await Product.create({name: `a-product-${i}`})); + // Create also a product that won't be matched by the query + await Product.create({name: `another-product-${i}`}); + } + + const found = await Product.find( + {where: {id: {inq: created.map(u => u.id)}}}, + {splitLongInq: true} + ); + + // Records were found correctly + found.map(u => u.name).should.eql(created.map(u => u.name)); + // Multiple database queries were sent + observedCalls.length.should.be.greaterThan(1); + }); + }); }); // FIXME: This should either be re-enabled or removed.