Skip to content

Commit

Permalink
Added .group() function louischatriot#153 by @adityamukho
Browse files Browse the repository at this point in the history
This commit is a cleaned version of original PR.
  • Loading branch information
pi0 committed Nov 30, 2016
1 parent 3b7b555 commit 631bad4
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 7 deletions.
3 changes: 2 additions & 1 deletion browser-version/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"async": "~0.2.9",
"fs-extra": "~0.6.3",
"uglify-js": "~2.3.6",
"browserify": "~2.25.0"
"browserify": "~2.25.0",
"es-hash": "^1.0.4"
}
}
31 changes: 25 additions & 6 deletions lib/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ function Cursor (db, query, execFn) {
if (execFn) { this.execFn = execFn; }
}

/**
* Aggregate on results
*/
Cursor.prototype.group = function (group) {
this._group = group;
return this;
};


/**
* Set a limit to the number of results
Expand Down Expand Up @@ -136,7 +144,7 @@ Cursor.prototype._exec = function(_callback) {
for (i = 0; i < candidates.length; i += 1) {
if (model.match(candidates[i], self.query)) {
// If a sort is defined, wait for the results to be sorted before applying limit and skip
if (!self._sort) {
if (!self._sort && !this._group) {
if (self._skip && self._skip > skipped) {
skipped += 1;
} else {
Expand All @@ -153,6 +161,15 @@ Cursor.prototype._exec = function(_callback) {
return callback(err);
}

//Apply grouping
if (this._group) {
try {
res = model.aggregate(res, this._group);
} catch (err) {
return callback(err);
}
}

// Apply all sorts
if (self._sort) {
keys = Object.keys(self._sort);
Expand All @@ -161,9 +178,9 @@ Cursor.prototype._exec = function(_callback) {
var criteria = [];
for (i = 0; i < keys.length; i++) {
key = keys[i];
criteria.push({ key: key, direction: self._sort[key] });
criteria.push({key: key, direction: self._sort[key]});
}
res.sort(function(a, b) {
res.sort(function (a, b) {
var criterion, compare, i;
for (i = 0; i < criteria.length; i++) {
criterion = criteria[i];
Expand All @@ -175,10 +192,12 @@ Cursor.prototype._exec = function(_callback) {
return 0;
});

// Applying limit and skip
var limit = self._limit || res.length
, skip = self._skip || 0;
}

// Applying limit and skip
if (this._sort || this._group) {
var limit = this._limit || res.length,
skip = this._skip || 0;
res = res.slice(skip, skip + limit);
}

Expand Down
127 changes: 127 additions & 0 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,132 @@ function matchQueryPart (obj, queryKey, queryValue, treatObjAsValue) {
return true;
}

function aggregate(res, group) {
var nested = {},
flattened = [],
outKeys = [],
keyHash = {},
keys = null,
i, j, k, l, handle, sub, entry, nodeKeys, hash, djb2, str;

if (!_.isFunction(group.reduce)) {
throw new Error("A reduce function must be provided.");
}

if (!group.initial) {
throw new Error("An initial vector must be provided.");
}

if (group.finalize && !_.isFunction(group.finalize)) {
throw new Error("'finalize', if provided, must be a function.");
}

if (group.key) {
keys = _.filter(_.keys(group.key), function (key) {
return group.key[key];
});
if (_.isEmpty(keys)) {
throw new Error("At least one key should be enabled.");
}
for (i = 0; i < keys.length; ++i) {
outKeys.push(keys[i].replace(/\./g, '_'));
keys[i] = keys[i].split('.');
}
}

if (keys) {
for (i = 0; i < res.length; ++i) {
handle = nested;
for (j = 0; j < keys.length; ++j) {
sub = _getValue(res[i], keys[j]);
if (!handle[sub]) {
if (j === keys.length - 1) {
handle[sub] = deepCopy(group.initial);
} else {
handle[sub] = {};
}
}
if (j === keys.length - 1) {
group.reduce(res[i], handle[sub]);
}
handle = handle[sub];
}
}
// console.dir(nested);
_walk(nested, 0, []);
}
else {
flattened = [_.reduce(res, function(memo, item){
group.reduce(item, memo);
return memo;
}, deepCopy(group.initial))];
if (group.finalize) group.finalize(flattened[0]);
}
return flattened;

function _getValue(item, key) {
try {
for (k = 0; k < key.length; ++k) {
item = item[key[k]];
if (k === key.length - 1) {
hash = (group.noHashKeys || _.isEmpty(item) || isPrimitiveType(item)) ? JSON.stringify(item) : _djb2(item);
}
}
keyHash[hash] = item;
return hash;
} catch (err) {
return null;
}
}

function _walk(node, depth, path) {
if (depth === keys.length) {
if (group.finalize) group.finalize(node);

entry = {};
for (i = 0; i < path.length; ++i) {
entry[outKeys[i]] = keyHash[path[i]];
}

nodeKeys = Object.keys(node);
for (i = 0; i < nodeKeys.length; ++i) {
entry[nodeKeys[i]] = node[nodeKeys[i]];
}

flattened.push(entry);
} else {
for (var key in node) {
_walk(node[key], depth + 1, path.concat([key]));
}
}
}

function _djb2(obj) {
str = JSON.stringify(_sortObject(obj));
djb2 = 5381;
for (l = 0; l < str.length; l++) {
djb2 = ((djb2 << 5) + djb2) + str.charCodeAt(l);
}
return djb2;
}

function _sortObject(object){
var sortedObj = {},
keys = _.keys(object);

keys = _.sortBy(keys, _.identity);

_.each(keys, function(key) {
if(!isPrimitiveType(object[key])){
sortedObj[key] = _sortObject(object[key]);
} else {
sortedObj[key] = object[key];
}
});

return sortedObj;
}
}

// Interface
module.exports.serialize = serialize;
Expand All @@ -877,3 +1003,4 @@ module.exports.getDotValue = getDotValue;
module.exports.match = match;
module.exports.areThingsEqual = areThingsEqual;
module.exports.compareThings = compareThings;
module.exports.aggregate = aggregate;

0 comments on commit 631bad4

Please sign in to comment.