From c8394066e4066e400df62dbe8688865956408606 Mon Sep 17 00:00:00 2001 From: Agnes Lin Date: Tue, 7 Jul 2020 16:35:40 -0400 Subject: [PATCH] fix: sanitize extra dollar signs for operators --- README.md | 28 +++++++++++++++++++++++++++- lib/mongodb.js | 13 ++++++++++++- test/mongodb.test.js | 26 +++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c041eaf3c..f3a34a98f 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ If you run a MongoDB with authentification ([Docker's example here](https://gith allowExtendedOperators Boolean false - Set to true to enable using MongoDB operators such as $currentDate, $inc, $max, $min, $mul, $rename, $setOnInsert, $set, $unset, $addToSet, $pop, $pullAll, $pull, $pushAll, $push, and $bit. + Set to true to enable using MongoDB operators such as $currentDate, $inc, $max, $min, $mul, $rename, $setOnInsert, $set, $unset, $addToSet, $pop, $pullAll, $pull, $push, and $bit. See Update Operators section below enableGeoIndexing @@ -249,6 +249,32 @@ The .loopbackrc file is in JSON format, for example: +## Update Operators + +Except the comparison and logical operators LoopBack supports in the [operator list](https://loopback.io/doc/en/lb4/Where-filter.html#operators) of `Where` filter, you can also enable [MongoDB update operators](https://docs.mongodb.com/manual/reference/operator/update/) for `update*` methods by setting the flag `allowExtendedOperators` to `true` in the datasource configuration. + +Here is an example of updating the price for all the products under category `furniture` if their current price is lower than 100: + +``` +await productRepo.updateAll({ $max: { price: 100 }}, { category: {eq: 'furniture'} // where clause goes in here }); +``` + +
For LoopBack 3 users + +``` +Product.updateAll( + { category: {eq: 'furniture'} // where clause goes in here }, + {$max: {price: 100}}, + options, + function(err, updateproducts) { + ... +``` + +
+ +{% include tip.html content="you **will not** need the dollar sign `'$'` for operators in the Where +clause." %} + ## Handling ObjectId MongoDB uses `ObjectId` for its primary key, which is an object instead of a diff --git a/lib/mongodb.js b/lib/mongodb.js index c1f9285bc..ab46ec062 100644 --- a/lib/mongodb.js +++ b/lib/mongodb.js @@ -765,7 +765,6 @@ MongoDB.prototype.parseUpdateData = function(modelName, data, options) { '$pop', '$pullAll', '$pull', - '$pushAll', '$push', // Bitwise operator '$bit', @@ -993,6 +992,7 @@ MongoDB.prototype.buildWhere = function(modelName, where, options) { const modelCtor = self._models[modelName]; if (spec) { + spec = trimLeadingDollarSigns(spec); if (spec === 'between') { query[k] = {$gte: cond[0], $lte: cond[1]}; } else if (spec === 'inq') { @@ -1994,6 +1994,17 @@ function isObjectIDProperty(modelCtor, propDef, value, options) { } } +/** + * Removes extra dollar sign '$' for operators + * + * @param {*} spec the operator for Where filter + */ +function trimLeadingDollarSigns(spec) { + return spec.replace(/^(\$)+/, ''); +} + +exports.trimLeadingDollarSigns = trimLeadingDollarSigns; + function sanitizeFilter(filter, options) { options = Object.assign({}, options); if (options && options.disableSanitization) return filter; diff --git a/test/mongodb.test.js b/test/mongodb.test.js index 9626279a3..2ffb09f73 100644 --- a/test/mongodb.test.js +++ b/test/mongodb.test.js @@ -12,6 +12,7 @@ const testUtils = require('../lib/test-utils'); const async = require('async'); const sinon = require('sinon'); const sanitizeFilter = require('../lib/mongodb').sanitizeFilter; +const trimLeadingDollarSigns = require('../lib/mongodb').trimLeadingDollarSigns; const GeoPoint = require('loopback-datasource-juggler').GeoPoint; @@ -1485,7 +1486,7 @@ describe('mongodb connector', function() { should.not.exist(err); updatedusers.should.have.property('count', 1); - User.find({where: {name: 'Simon'}}, function( + User.find({where: {name: {$eq: 'Simon'}}}, function( err, foundusers, ) { @@ -3726,6 +3727,29 @@ describe('mongodb connector', function() { }); }); + context('trimLeadingDollarSigns', () =>{ + it('removes an extra leading dollar sign in ths operators', () => { + const spec = '$eq'; + const updatedSpec = trimLeadingDollarSigns(spec); + updatedSpec.should.equal('eq'); + }); + it('removes extra leading dollar signs in ths operators', () => { + const spec = '$$eq'; + const updatedSpec = trimLeadingDollarSigns(spec); + updatedSpec.should.equal('eq'); + }); + + it('remains the same if the input does not contain any dollar signs', () => { + const spec = 'eq'; + const updatedSpec = trimLeadingDollarSigns(spec); + updatedSpec.should.equal(spec); + }); + it('remains the same if the input does not start with dollar signs', () => { + const spec = 'eq$'; + const updatedSpec = trimLeadingDollarSigns(spec); + updatedSpec.should.equal(spec); + }); + }); context('sanitizeFilter()', () => { it('returns filter if not an object', () => { const input = false;