mongo-portable
Version:
Portable Pure JS MongoDB - Based on Monglodb (https://github.com/euforic/monglodb.git) by Christian Sullivan (http://RogueSynaptics.com)
244 lines (189 loc) • 5.3 kB
text/typescript
/**
* @file Cursor.js - based on Monglo#Cursor ({@link https://github.com/Monglo}) by Christian Sullivan <cs@euforic.co> | Copyright (c) 2012
* @version 1.0.0
*
* @author Eduardo Astolfi <eduardo.astolfi91@gmail.com>
* @copyright 2016 Eduardo Astolfi <eduardo.astolfi91@gmail.com>
* @license MIT Licensed
*/
import { JSWLogger } from "jsw-logger";
import * as _ from "lodash";
import { Cursor } from "../collection";
import { Selector } from "../selector";
const stages = {
$project: true,
$match: true,
$redact: false,
$limit: false,
$skip: false,
$unwind: false,
$group: true,
$sample: false,
$sort: true,
$geoNear: false,
$lookup: false,
$out: false,
$indexStats: false
};
const groupOperators = {
$sum(documents, newId, newField, value, isCount) {
const newDocs = {};
for (const doc of documents) {
let val = value;
if (!isCount) {
val = doc[value.substr(1, value.length)] || 0;
}
if (_.hasIn(doc, newId)) {
const _id = doc[newId];
if (!_.hasIn(newDocs, _id)) {
newDocs[_id] = {
_id,
[newField]: _.toNumber(val)
};
} else {
newDocs[_id][newField] += _.toNumber(val);
}
}
}
return newDocs;
},
$avg(documents, newId, newField, value, isCount) {
const newDocs = {};
for (const doc of documents) {
let val = value;
if (!isCount) {
val = doc[value.substr(1, value.length)] || 0;
}
if (_.hasIn(doc, newId) || _.isNull(newId)) {
const _id = doc[newId] || null;
if (!_.hasIn(newDocs, _id)) {
newDocs[_id] = {
_id,
[newField]: _.toNumber(val),
__COUNT__: 1
};
} else {
newDocs[_id][newField] += _.toNumber(val);
newDocs[_id].__COUNT__++;
}
}
}
for (const key of Object.keys(newDocs)) {
newDocs[key][newField] = newDocs[key][newField] / newDocs[key].__COUNT__;
delete newDocs[key].__COUNT__;
}
return newDocs;
}
};
const doSingleGroup = (groupId, groupStage, documents) => {
// var operators = {};
const docs = {};
for (const field in groupStage) {
if (field !== "_id") {
// handle group field
// let group_key = key;
const groupField = groupStage[field];
for (const key of Object.keys(groupField)) {
if (!_.hasIn(groupOperators, key)) { this.logger.throw(`Unknown accumulator operator "${key}" for group stage`); }
// loop through all documents
// var newDocs = {};
// for (let i = 0; i < documents.length; i++) {
// let doc = documents[i];
// if (_.hasIn(doc, groupId)) {
// let _id = doc[groupId];
// if (!_.hasIn(newDocs, _id)) {
// newDocs[_id] = {
// _id: _id,
// [newField]: value
// };
// } else {
// newDocs[_id][newField] += value;
// }
// }
// }
// if (!_.hasIn(operators, key)) operators[key] = [];
// operators[key].push({
// newField: field,
// value: groupField[key]
// });
let count = true;
if (_.isString(groupField[key])) {
if (groupField[key].substr(0, 1) !== "$") { this.logger.throw("Field names references in a right side assignement must be preceded by '$'"); }
if (!_.isFinite(_.toNumber(groupField[key]))) {
count = false;
}
}
const operator = groupOperators[key];
_.merge(docs, operator(documents, groupId, field, groupField[key], count));
break;
}
}
}
return _.values(docs);
};
const doComplexGroup = () => {
// TODO
};
const doSort = (documents, sortStage) => {
return documents.sort(new Selector(sortStage, Selector.SORT_SELECTOR));
};
const doMatch = (documents, matchStage) => {
const cursor = new Cursor(documents, matchStage);
return cursor.fetch();
};
const doGroup = (documents, groupStage) => {
if (!_.hasIn(groupStage, "_id")) { this.logger.throw('The field "_id" is required in the "$group" stage'); }
let newId = groupStage._id;
if (!_.isNull(newId)) {
if (newId.substr(0, 1) !== "$") {
this.logger.throw("Field names references in a right side assignement must be preceded by '$'");
} else {
newId = newId.substr(1, newId.length);
}
}
if (_.isPlainObject(newId)) {
// complex_id
// doComplexGroup();
} else {
// single_id
return doSingleGroup(newId, groupStage, documents);
}
};
const doProject = (documents, projectStage) => {
return Cursor.project(documents, projectStage, true);
};
export class Aggregation {
public pipeline;
protected logger: JSWLogger;
constructor(pipeline) {
this.logger = JSWLogger.instance;
this.pipeline = pipeline;
}
public aggregate(collection) {
let docs = collection.docs;
for (const stage of this.pipeline) {
for (const key of Object.keys(stage)) {
switch (key) {
case "$project":
docs = doProject(docs, stage[key]);
break;
case "$match":
docs = doMatch(docs, stage[key]);
break;
case "$group":
docs = doGroup(docs, stage[key]);
break;
case "$sort":
docs = doSort(docs, stage[key]);
break;
}
}
}
return docs; // move to cursor
}
public validStage(stage) {
if (!_.hasIn(stages, stage)) { return this.logger.throw(`Unknown stage "${stage}"`); }
if (stages[stage] === false) { return this.logger.throw(`Unsupported stage "${stage}"`); }
return true;
}
}