Skip to content

Commit

Permalink
fix aggregate sort operator
Browse files Browse the repository at this point in the history
  • Loading branch information
kofrasa committed Jul 5, 2016
1 parent ba85b26 commit b432916
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 35 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
Changelog
=========

Changes between releases are kept here beginning from v0.5.0
Changes starting from v0.5.0 are tracked here

## v0.6.5 / 2016-07-04
- Fix incorrect de-duping of Date types in $sort aggregate operator. See [issue#23](https://github.com/kofrasa/mingo/pull/23)

## v0.6.4 / 2016-05-19
- Support matching against user-defined types. See [issue#22](https://github.com/kofrasa/mingo/issues/22)
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mingo",
"main": "mingo.js",
"version": "0.6.4",
"version": "0.6.5",
"homepage": "https://github.com/kofrasa/mingo",
"authors": [
"Francis Asante <[email protected]>"
Expand Down
92 changes: 65 additions & 27 deletions mingo.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mingo.js 0.6.4
// Mingo.js 0.6.5
// Copyright (c) 2016 Francis Asante <[email protected]>
// MIT

Expand All @@ -11,7 +11,7 @@
var Mingo = {}, previousMingo;
var _;

Mingo.VERSION = '0.6.3';
Mingo.VERSION = '0.6.5';

// backup previous Mingo
if (root != null) {
Expand Down Expand Up @@ -860,21 +860,23 @@
if (!_.isEmpty(sortKeys) && _.isObject(sortKeys)) {
var modifiers = _.keys(sortKeys);
modifiers.reverse().forEach(function (key) {
var indexes = [];
var grouped = _.groupBy(collection, function (obj) {
var value = resolve(obj, key);
indexes.push(value);
return value;
var grouped = groupBy(collection, function (obj) {
return resolve(obj, key);
});
indexes = _.sortBy(_.uniq(indexes), function (item) {
var sortedIndex = {};
var findIndex = function (k) { return sortedIndex[hashcode(k)]; }

var indexKeys = _.sortBy(grouped.keys, function (item, i) {
sortedIndex[hashcode(item)] = i;
return item;
});

if (sortKeys[key] === -1) {
indexes.reverse();
indexKeys.reverse();
}
collection = [];
_.each(indexes, function (item) {
Array.prototype.push.apply(collection, grouped[item]);
_.each(indexKeys, function (item) {
Array.prototype.push.apply(collection, grouped.groups[findIndex(item)]);
});
});
}
Expand Down Expand Up @@ -2099,7 +2101,7 @@
* Groups the collection into sets by the returned key
*
* @param collection
* @param fn
* @param fn {function} to compute the group key of an item in the collection
*/
function groupBy(collection, fn) {

Expand All @@ -2108,28 +2110,22 @@
'groups': []
};

var lookup = {};

_.each(collection, function (obj) {

var key = fn(obj);
var h = hashcode(key);
var index = -1;

if (_.isObject(key)) {
for (var i = 0; i < result.keys.length; i++) {
if (_.isEqual(key, result.keys[i])) {
index = i;
break;
}
}
} else {
index = _.indexOf(result.keys, key);
}

if (index > -1) {
result.groups[index].push(obj);
} else {
if (_.isUndefined(lookup[h])) {
index = result.keys.length;
lookup[h] = index;
result.keys.push(key);
result.groups.push([obj]);
result.groups.push([]);
}
index = lookup[h];
result.groups[index].push(obj);
});

// assert this
Expand All @@ -2140,6 +2136,48 @@
return result;
}

// encode value to a unique string form that is easily reversable
function encode(value) {
if (_.isNull(value)) {
return "null";
} else if (_.isUndefined(value)) {
return "undef";
} else {
var type = value.constructor.name;
switch (type) {
case "Boolean":
return "b|" + value.toString();
case "String":
return "s|" + value.toString();
case "RegExp":
return "r|" + value.toString();
case "Number":
return "n|" + value.toString();
case "Date":
return "d|" + value.toISOString();
case "Array":
return "a|" + JSON.stringify(_.map(value, function (v) { return encode(v); }));
case "Object":
return "o|" + JSON.stringify(_.mapObject(value, function (v) { return encode(v); }));
default:
return type + "|" + JSON.stringify(_.mapObject(value, function (v) { return encode(v); }));
}
}
}

// http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
// http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
function hashcode(value) {
var hash = 0, i, chr, len, s = encode(value);
if (s.length === 0) return hash;
for (i = 0, len = s.length; i < len; i++) {
chr = s.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash.toString();
}

/**
* Returns the result of evaluating a $group operation over a collection
*
Expand Down
2 changes: 1 addition & 1 deletion mingo.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion mingo.min.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mingo",
"version": "0.6.4",
"version": "0.6.5",
"description": "JavaScript implementation of MongoDB query language",
"main": "mingo.js",
"directories": {
Expand Down
19 changes: 17 additions & 2 deletions test/aggregation.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ test("Aggregation Pipeline Operators", function (t) {
t.ok(grouped.length === 3, "can group collection with $group");
grouped = Mingo.aggregate(SalesData, [
{$group: {max: {$max: "$price"}, sum: {$sum: "$price"}}}
]);
]);
console.log(grouped);
t.ok(grouped.length === 1 && grouped[0]['max'] === 20, "can compute $max");
t.ok(grouped.length === 1 && grouped[0]['sum'] === 45, "can compute $sum");
Expand Down Expand Up @@ -206,11 +206,26 @@ test("Aggregation Pipeline Operators", function (t) {
});

t.test("$sort operator", function (t) {
t.plan(1);
t.plan(2);
var result = Mingo.aggregate(samples.students, [
{'$sort': {'_id': -1}}
]);
t.ok(result[0]['_id'] === 199, "can sort collection with $sort");

var data = [
{ _id: 'c', date: new Date(2018, 01, 01) },
{ _id: 'a', date: new Date(2017, 01, 01) },
{ _id: 'b', date: new Date(2017, 01, 01) }
];
var expected = [
{ _id: 'a', date: new Date(2017, 01, 01) },
{ _id: 'b', date: new Date(2017, 01, 01) },
{ _id: 'c', date: new Date(2018, 01, 01) },
];

result = Mingo.aggregate(data, [{"$sort": {"date": 1}}]);
t.deepEqual(result, expected, "can sort on complex fields");

});
});

Expand Down
2 changes: 1 addition & 1 deletion test/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ test("Custom Operators", function (t) {
t.ok(result.length == 1, "must return one result after grouping");
t.equal(28.57362029450366 , result[0].stddev, "must return correct stddev");
})
});
});

0 comments on commit b432916

Please sign in to comment.