diff --git a/README.md b/README.md index 87a857263..89b277639 100644 --- a/README.md +++ b/README.md @@ -206,9 +206,10 @@ The results will be output in `./benchmarks/results.md`. ## strictObjectIDCoercion flag -In version 1.17.0, the id of string type is being converted to ObjectID, when the string length is 12 or 24 and has the format of an ObjectID i.e /^[0-9a-fA-F]{24}$/. To avoid this issue, the strictObjectIDCoercion flag should be set to true in the model-definition file. +In version 1.17.0, the id of string type is being converted to ObjectID, when the string length is 12 or 24 and has the format of an ObjectID i.e /^[0-9a-fA-F]{24}$/. +To avoid this issue, the strictObjectIDCoercion flag should be set to true in the model-definition file. It is also possible to enable this flag on a per method bases by passing it in as part of the options object. -model-definition.js +### model-definition.js ```js { @@ -264,6 +265,18 @@ module.exports = function(app) { }; ``` +### Per method basis + +```js +myModelName.find( + {where: {id: {inq: ['59460487e9532ae90c324b59', '59460487e9532ae90c324b5a']}}}, + {strictObjectIDCoercion: true}, + function(err, result) { + // ... + } +) +``` + ## Release notes * 1.1.7 - Do not return MongoDB-specific _id to client API, except if specifically specified in the model definition diff --git a/lib/mongodb.js b/lib/mongodb.js index 1eef886f1..d64fa3652 100644 --- a/lib/mongodb.js +++ b/lib/mongodb.js @@ -116,14 +116,14 @@ exports.initialize = function initializeDataSource(dataSource, callback) { // compatibility of model connector hooks, this maps the new // commands to previous names for the observors of this command. const COMMAND_MAPPINGS = { - 'insertOne': 'insert', - 'updateOne': 'save', - 'findOneAndUpdate': 'findAndModify', - 'deleteOne': 'delete', - 'deleteMany': 'delete', - 'replaceOne': 'update', - 'updateMany': 'update', - 'countDocuments': 'count', + insertOne: 'insert', + updateOne: 'save', + findOneAndUpdate: 'findAndModify', + deleteOne: 'delete', + deleteMany: 'delete', + replaceOne: 'update', + updateMany: 'update', + countDocuments: 'count', }; exports.MongoDB = MongoDB; @@ -475,7 +475,7 @@ MongoDB.prototype.execute = function(model, command) { } }; -MongoDB.prototype.coerceId = function(model, id) { +MongoDB.prototype.coerceId = function(model, id, options) { // See https://github.com/strongloop/loopback-connector-mongodb/issues/206 if (id == null) return id; var self = this; @@ -493,7 +493,7 @@ MongoDB.prototype.coerceId = function(model, id) { } } - if (self.isObjectIDProperty(model, idProp, idValue)) { + if (self.isObjectIDProperty(model, idProp, idValue, options)) { idValue = ObjectID(idValue); } } @@ -517,7 +517,7 @@ MongoDB.prototype.create = function(model, data, options, callback) { if (idValue === null) { delete data[idName]; // Allow MongoDB to generate the id } else { - var oid = self.coerceId(model, idValue); // Is it an Object ID?c + var oid = self.coerceId(model, idValue, options); // Is it an Object ID?c data._id = oid; // Set it to _id if (idName !== '_id') { delete data[idName]; @@ -534,7 +534,7 @@ MongoDB.prototype.create = function(model, data, options, callback) { return callback(err); } idValue = result.ops[0]._id; - idValue = self.coerceId(model, idValue); + idValue = self.coerceId(model, idValue, options); // Wrap it to process.nextTick as delete data._id seems to be interferring // with mongo insert process.nextTick(function() { @@ -560,7 +560,7 @@ MongoDB.prototype.save = function(model, data, options, callback) { var idValue = self.getIdValue(model, data); var idName = self.idName(model); - var oid = self.coerceId(model, idValue); + var oid = self.coerceId(model, idValue, options); data._id = oid; if (idName !== '_id') { delete data[idName]; @@ -613,7 +613,7 @@ MongoDB.prototype.exists = function(model, id, options, callback) { if (self.debug) { debug('exists', model, id); } - id = self.coerceId(model, id); + id = self.coerceId(model, id, options); this.execute(model, 'findOne', {_id: id}, function(err, data) { if (self.debug) { debug('exists.callback', model, id, err, data); @@ -634,7 +634,7 @@ MongoDB.prototype.find = function find(model, id, options, callback) { debug('find', model, id); } var idName = self.idName(model); - var oid = self.coerceId(model, id); + var oid = self.coerceId(model, id, options); this.execute(model, 'findOne', {_id: oid}, function(err, data) { if (self.debug) { debug('find.callback', model, id, err, data); @@ -744,7 +744,7 @@ MongoDB.prototype.updateOrCreate = function updateOrCreate( var id = self.getIdValue(model, data); var idName = self.idName(model); - var oid = self.coerceId(model, id); + var oid = self.coerceId(model, id, options); delete data[idName]; data = self.toDatabase(model, data); @@ -806,7 +806,7 @@ MongoDB.prototype.replaceOrCreate = function(model, data, options, cb) { if (this.debug) debug('replaceOrCreate', model, data); var id = this.getIdValue(model, data); - var oid = this.coerceId(model, id); + var oid = this.coerceId(model, id, options); var idName = this.idName(model); data._id = data[idName]; delete data[idName]; @@ -824,7 +824,7 @@ MongoDB.prototype.destroy = function destroy(model, id, options, callback) { if (self.debug) { debug('delete', model, id); } - id = self.coerceId(model, id); + id = self.coerceId(model, id, options); this.execute(model, 'deleteOne', {_id: id}, function(err, result) { if (self.debug) { debug('delete.callback', model, id, err, result); @@ -902,9 +902,9 @@ MongoDB.prototype.buildWhere = function(model, where, options) { k = self.getDatabaseColumnName(model, k); var spec = false; - var options = null; + var regexOptions = null; if (cond && cond.constructor.name === 'Object') { - options = cond.options; + regexOptions = cond.options; spec = Object.keys(cond)[0]; cond = cond[spec]; } @@ -915,7 +915,8 @@ MongoDB.prototype.buildWhere = function(model, where, options) { cond = [].concat(cond || []); query[k] = { $in: cond.map(function(x) { - if (self.isObjectIDProperty(model, prop, x)) return ObjectID(x); + if (self.isObjectIDProperty(model, prop, x, options)) + return ObjectID(x); return x; }), }; @@ -923,7 +924,8 @@ MongoDB.prototype.buildWhere = function(model, where, options) { cond = [].concat(cond || []); query[k] = { $nin: cond.map(function(x) { - if (self.isObjectIDProperty(model, prop, x)) return ObjectID(x); + if (self.isObjectIDProperty(model, prop, x, options)) + return ObjectID(x); return x; }), }; @@ -931,13 +933,13 @@ MongoDB.prototype.buildWhere = function(model, where, options) { if (cond instanceof RegExp) { query[k] = {$regex: cond}; } else { - query[k] = {$regex: new RegExp(cond, options)}; + query[k] = {$regex: new RegExp(cond, regexOptions)}; } } else if (spec === 'nlike') { if (cond instanceof RegExp) { query[k] = {$not: cond}; } else { - query[k] = {$not: new RegExp(cond, options)}; + query[k] = {$not: new RegExp(cond, regexOptions)}; } } else if (spec === 'neq') { query[k] = {$ne: cond}; @@ -956,7 +958,7 @@ MongoDB.prototype.buildWhere = function(model, where, options) { // Null: 10 query[k] = {$type: 10}; } else { - if (self.isObjectIDProperty(model, prop, cond)) { + if (self.isObjectIDProperty(model, prop, cond, options)) { cond = ObjectID(cond); } query[k] = cond; @@ -1387,7 +1389,7 @@ MongoDB.prototype.count = function count(model, where, options, callback) { */ MongoDB.prototype.replaceById = function replace(model, id, data, options, cb) { if (this.debug) debug('replace', model, id, data); - var oid = this.coerceId(model, id); + var oid = this.coerceId(model, id, options); this.replaceWithOptions(model, oid, data, {upsert: false}, function( err, data @@ -1480,7 +1482,7 @@ MongoDB.prototype.updateAttributes = function updateAttrs( return; } - var oid = self.coerceId(model, id); + var oid = self.coerceId(model, id, options); var idName = this.idName(model); this.execute( @@ -1813,7 +1815,7 @@ MongoDB.prototype.ping = function(cb) { * Check whether the property is an ObjectID (or Array thereof) * */ -MongoDB.prototype.isObjectIDProperty = function(model, prop, value) { +MongoDB.prototype.isObjectIDProperty = function(model, prop, value, options) { if ( prop && (prop.type === ObjectID || @@ -1822,9 +1824,11 @@ MongoDB.prototype.isObjectIDProperty = function(model, prop, value) { return true; } else if ('string' === typeof value) { var settings = this._models[model] && this._models[model].settings; + options = options || {}; var strict = (settings && settings.strictObjectIDCoercion) || - this.settings.strictObjectIDCoercion; + this.settings.strictObjectIDCoercion || + options.strictObjectIDCoercion; if (strict) return false; // unless explicitly typed, don't coerce return /^[0-9a-fA-F]{24}$/.test(value); } else { @@ -1873,7 +1877,7 @@ function optimizedFindOrCreate(model, filter, data, options, callback) { if (idValue == null) { delete data[idName]; // Allow MongoDB to generate the id } else { - var oid = self.coerceId(model, idValue); // Is it an Object ID? + var oid = self.coerceId(model, idValue, options); // Is it an Object ID? data._id = oid; // Set it to _id if (idName !== '_id') { delete data[idName]; @@ -1886,7 +1890,7 @@ function optimizedFindOrCreate(model, filter, data, options, callback) { if (filter.where[idName]) { var id = filter.where[idName]; delete filter.where[idName]; - id = self.coerceId(model, id); + id = self.coerceId(model, id, options); filter.where._id = id; } query = self.buildWhere(model, filter.where, options); diff --git a/test/objectid.test.js b/test/objectid.test.js index 0bb0e39c9..dcd20face 100644 --- a/test/objectid.test.js +++ b/test/objectid.test.js @@ -53,4 +53,19 @@ describe('ObjectID', function() { var id = 123; ObjectID(id).should.be.equal(123); }); + + it('coerces ObjectID', function() { + const coercedId = db.connector.isObjectIDProperty('Book', {}, '52fcef5c0325ace8dcb7a0bd'); + coercedId.should.be.True(); + }); + + it('given strictObjectIDCoercion: true, does not coerce ObjectID', function() { + const coercedId = db.connector.isObjectIDProperty( + 'Book', + {}, + '52fcef5c0325ace8dcb7a0bd', + {strictObjectIDCoercion: true} + ); + coercedId.should.be.False(); + }); });