diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 5c6be09cb..3858043a6 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -496,13 +496,53 @@ Memory.prototype._findAllSkippingIncludes = function(model, filter) { // field selection if (filter.fields) { + if (filter.count) filter.fields.push('count'); + if (filter.max) filter.fields.push('max'); + if (filter.min) filter.fields.push('min'); + if (filter.sum) filter.fields.push('sum'); + if (filter.avg) filter.fields.push('avg'); nodes = nodes.map(utils.selectFields(filter.fields)); } // limit/skip const skip = filter.skip || filter.offset || 0; const limit = filter.limit || nodes.length; + // groupBy nodes = nodes.slice(skip, skip + limit); + if (filter.groupBy) { + nodes = utils.groupBy(nodes, filter.groupBy); + const tempNodes = []; + Object.keys(nodes).forEach(nodeKey => { + let count = undefined; + const tempNode = {...nodes[nodeKey][0]}; + if (filter.count) { + count = nodes[nodeKey].filter((obj) => { + const id = obj[filter.count]; + return obj[filter.count] === id; + }).length; + tempNode.count = count; + } + if (filter.max) { + tempNode.max = Math.max(...nodes[nodeKey].map(o => o[filter.max])); + } + if (filter.min) { + tempNode.min = Math.min(...nodes[nodeKey].map(o => o[filter.min])); + } + if (filter.sum) { + tempNode.sum = nodes[nodeKey].reduce((accumulator, object) => { + return accumulator + object[filter.sum]; + }, 0); + } + if (filter.avg) { + tempNode.avg = nodes[nodeKey].reduce((accumulator, object) => { + return accumulator + object[filter.avg]; + }, 0); + tempNode.avg = tempNode.avg / nodes[nodeKey].length; + } + tempNodes.push(tempNode); + }); + nodes = tempNodes; + } } return nodes; diff --git a/lib/dao.js b/lib/dao.js index f34ed8b32..555fd779f 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -1930,6 +1930,18 @@ DataAccessObject.find = function find(query, options, cb) { } } + const keys = Object.keys(data); + keys.forEach(key => { + if ( + key.includes('sumOf') || + key.includes('countOf') || + key.includes('avgOf') || + key.includes('minOf') || + key.includes('maxOf') + ) { + obj.__data[key] = data[key]; + } + }); callback(null, obj); } diff --git a/lib/utils.js b/lib/utils.js index c73e9d07a..ffa518038 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -31,6 +31,7 @@ exports.idsHaveDuplicates = idsHaveDuplicates; exports.isClass = isClass; exports.escapeRegExp = escapeRegExp; exports.applyParentProperty = applyParentProperty; +exports.groupBy = groupBy; const g = require('strong-globalize')(); const traverse = require('neotraverse/legacy'); @@ -893,3 +894,16 @@ function applyParentProperty(element, parent) { }); } } + +function groupBy(items, key) { + return items.reduce( + (result, item) => ({ + ...result, + [item[key]]: [ + ...(result[item[key]] || []), + item, + ], + }), + {}, + ); +} diff --git a/test/crud-with-options.test.js b/test/crud-with-options.test.js index c1a9bd5cf..83c28e54d 100644 --- a/test/crud-with-options.test.js +++ b/test/crud-with-options.test.js @@ -272,6 +272,56 @@ describe('crud-with-options', function() { User.find({limit: 3}); }); + it('should allow filter with groupBy, count, max, min, sum & avg', function(done) { + User.find({ + groupBy: ['vip'], + count: 'vip', + max: 'id', + min: 'id', + sum: 'id', + avg: 'id', + }, options, function(err, users) { + should.not.exist(err); + should.exist(users); + users.length.should.be.above(0); + users.forEach(user => { + user.should.have.property('count', user.count); + user.should.have.property('max'); + user.should.have.property('min'); + user.should.have.property('sum'); + user.should.have.property('avg'); + }); + done(); + }); + }); + + it('should allow filter with groupBy, aggregate methods and other filters', function(done) { + User.find({ + groupBy: ['vip'], + count: 'vip', + max: 'id', + min: 'id', + sum: 'id', + avg: 'id', + limit: 1, + fields: ['name', 'id'], + }, options, function(err, users) { + should.not.exist(err); + should.exist(users); + users.length.should.be.equal(1); + users.forEach(user => { + user.should.have.property('count', user.count); + user.should.have.property('max'); + user.should.have.property('min'); + user.should.have.property('sum'); + user.should.have.property('avg'); + user.should.have.property('name'); + user.should.have.property('id'); + }); + done(); + }); + }); + it('should skip trailing undefined args', function(done) { User.find({limit: 3}, function(err, users) { should.exists(users);