|
|
@@ -34,6 +34,8 @@ var SortDocumentSource = module.exports = function SortDocumentSource(ctx){
|
|
|
|
|
|
// DEPENDENCIES
|
|
|
var FieldPathExpression = require("../expressions/FieldPathExpression"),
|
|
|
+ VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
|
|
|
+ VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
|
|
|
Value = require("../Value");
|
|
|
|
|
|
klass.sortName = "$sort";
|
|
|
@@ -82,6 +84,9 @@ proto.coalesce = function coalesce(nextSource) {
|
|
|
proto.getNext = function getNext(callback) {
|
|
|
if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
|
|
|
|
|
|
+ if (this.expCtx instanceof Object && this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false)
|
|
|
+ return callback(new Error("Interrupted"));
|
|
|
+
|
|
|
var self = this,
|
|
|
out;
|
|
|
async.series(
|
|
|
@@ -118,15 +123,21 @@ proto.getNext = function getNext(callback) {
|
|
|
return out;
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+* Serialize to Array.
|
|
|
+*
|
|
|
+* @param {Array} array
|
|
|
+* @param {bool} explain
|
|
|
+**/
|
|
|
proto.serializeToArray = function serializeToArray(array, explain) {
|
|
|
var doc = {};
|
|
|
- if (explain) {
|
|
|
- doc.sortKey = this.serializeSortKey();
|
|
|
+ if (explain) { // always one obj for combined $sort + $limit
|
|
|
+ doc.sortKey = this.serializeSortKey(explain);
|
|
|
doc.mergePresorted = this._mergePresorted;
|
|
|
doc.limit = this.limitSrc ? this.limitSrc.getLimit() : undefined;
|
|
|
array.push(doc);
|
|
|
- } else {
|
|
|
- var inner = this.serializeSortKey();
|
|
|
+ } else { // one Value for $sort and maybe a Value for $limit
|
|
|
+ var inner = this.serializeSortKey(explain);
|
|
|
if (this._mergePresorted)
|
|
|
inner.$mergePresorted = true;
|
|
|
doc[this.getSourceName()] = inner;
|
|
|
@@ -151,7 +162,10 @@ proto.serialize = function serialize(explain) {
|
|
|
* @param {bool} ascending if true, use the key for an ascending sort, otherwise, use it for descending
|
|
|
**/
|
|
|
proto.addKey = function addKey(fieldPath, ascending) {
|
|
|
- var pathExpr = new FieldPathExpression(fieldPath);
|
|
|
+ var idGenerator = new VariablesIdGenerator(),
|
|
|
+ vps = new VariablesParseState(idGenerator);
|
|
|
+
|
|
|
+ var pathExpr = new FieldPathExpression("$$ROOT." + fieldPath, vps);
|
|
|
this.vSortKey.push(pathExpr);
|
|
|
if (ascending === true || ascending === false) {
|
|
|
this.vAscending.push(ascending);
|
|
|
@@ -161,40 +175,66 @@ proto.addKey = function addKey(fieldPath, ascending) {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-proto.populate = function populate(callback) {
|
|
|
+proto.makeSortOptions = function makeSortOptions(){
|
|
|
/* make sure we've got a sort key */
|
|
|
if (!this.vSortKey.length) throw new Error("no sort key for " + this.getSourceName());
|
|
|
|
|
|
- // Skipping stuff about mergeCursors and commandShards
|
|
|
+ // Skipping memory checks
|
|
|
+
|
|
|
+ var opts;
|
|
|
+ if ( this.limitSrc)
|
|
|
+ opts.limit = limitSrc.getLimt();
|
|
|
+
|
|
|
+ return opts;
|
|
|
+}
|
|
|
+// TODO: Add makeSortOptions
|
|
|
+
|
|
|
+proto.populate = function populate(callback) {
|
|
|
+ if ( this._mergePresorted ){
|
|
|
+ // Skipping stuff about mergeCursors and commandShards
|
|
|
+
|
|
|
+
|
|
|
+ if ( this.source instanceof MergeCursorDocumentSouce ){
|
|
|
+ populateFromCursors( this.source);
|
|
|
+ } else if ( this.source instanceof CommandShardsDocumentSource){
|
|
|
+ populateFromJsonArrays(this.source);
|
|
|
+ } else {
|
|
|
+ throw new Error("code 17196; the " + klass.sortName + "can only mergePresorted from MergeCursors and CommandShards");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ /* pull everything from the underlying source */
|
|
|
+ var self = this,
|
|
|
+ next;
|
|
|
+ async.doWhilst(
|
|
|
+ function (cb) {
|
|
|
+ self.source.getNext(function(err, doc) {
|
|
|
+ next = doc;
|
|
|
+
|
|
|
+ // Don't add EOF; it doesn't sort well.
|
|
|
+ if (doc !== DocumentSource.EOF)
|
|
|
+ self.documents.push(doc);
|
|
|
+ return cb();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ function() {
|
|
|
+ return next !== DocumentSource.EOF;
|
|
|
+ },
|
|
|
+ function(err) {
|
|
|
+ /* sort the list */
|
|
|
+ self.documents.sort(SortDocumentSource.prototype.compare.bind(self));
|
|
|
+
|
|
|
+ /* start the sort iterator */
|
|
|
+ self.docIterator = 0;
|
|
|
+
|
|
|
+ self.populated = true;
|
|
|
+ return callback();
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
|
|
|
- /* pull everything from the underlying source */
|
|
|
- var self = this,
|
|
|
- next;
|
|
|
- async.doWhilst(
|
|
|
- function (cb) {
|
|
|
- self.source.getNext(function(err, doc) {
|
|
|
- next = doc;
|
|
|
-
|
|
|
- // Don't add EOF; it doesn't sort well.
|
|
|
- if (doc !== DocumentSource.EOF)
|
|
|
- self.documents.push(doc);
|
|
|
- return cb();
|
|
|
- });
|
|
|
- },
|
|
|
- function() {
|
|
|
- return next !== DocumentSource.EOF;
|
|
|
- },
|
|
|
- function(err) {
|
|
|
- /* sort the list */
|
|
|
- self.documents.sort(SortDocumentSource.prototype.compare.bind(self));
|
|
|
-
|
|
|
- /* start the sort iterator */
|
|
|
- self.docIterator = 0;
|
|
|
-
|
|
|
- self.populated = true;
|
|
|
- return callback();
|
|
|
}
|
|
|
- );
|
|
|
+ this.populated = true;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -240,13 +280,21 @@ proto.compare = function compare(pL,pR) {
|
|
|
/**
|
|
|
* Write out an object whose contents are the sort key.
|
|
|
**/
|
|
|
-proto.serializeSortKey = function sortKeyToJson() {
|
|
|
+proto.serializeSortKey = function serializeSortKey(explain) {
|
|
|
var keyObj = {};
|
|
|
|
|
|
var n = this.vSortKey.length;
|
|
|
for (var i = 0; i < n; i++) {
|
|
|
- var fieldPath = this.vSortKey[i].getFieldPath();
|
|
|
- keyObj[fieldPath] = this.vAscending[i] ? 1 : -1;
|
|
|
+ if ( this.vSortKey[i] instanceof FieldPathExpression ) {
|
|
|
+ var fieldPath = this.vSortKey[i].getFieldPath();
|
|
|
+
|
|
|
+ // append a named integer based on the sort order
|
|
|
+ keyObj[fieldPath] = this.vAscending[i] ? 1 : -1;
|
|
|
+ } else {
|
|
|
+ // other expressions use a made-up field name
|
|
|
+ keyObj[{"$computed":i}] = this.vSortKey[i].serializeToArray(explain);
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
return keyObj;
|
|
|
};
|
|
|
@@ -254,6 +302,8 @@ proto.serializeSortKey = function sortKeyToJson() {
|
|
|
/**
|
|
|
* Creates a new SortDocumentSource
|
|
|
* @param {Object} jsonElement
|
|
|
+ * @ctx {object} context
|
|
|
+ *
|
|
|
**/
|
|
|
klass.createFromJson = function createFromJson(jsonElement, ctx) {
|
|
|
if (typeof jsonElement !== "object") throw new Error("code 15973; the " + klass.sortName + " key specification must be an object");
|
|
|
@@ -263,18 +313,70 @@ klass.createFromJson = function createFromJson(jsonElement, ctx) {
|
|
|
|
|
|
/* check for then iterate over the sort object */
|
|
|
var sortKeys = 0;
|
|
|
- for(var key in jsonElement) {
|
|
|
- var sortOrder = 0;
|
|
|
+ for(var keyField in jsonElement) {
|
|
|
+ var fieldName = keyField.fieldName;
|
|
|
|
|
|
- if (typeof jsonElement[key] !== "number") throw new Error("code 15974; " + klass.sortName + " key ordering must be specified using a number");
|
|
|
+ if ( fieldName === "$mergePresorted" ){
|
|
|
+ Sort._mergePresorted = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( keyField instanceof object) {
|
|
|
+ // this restriction is due to needing to figure out sort direction
|
|
|
+ throw new Error("code 17312; " + klass.sortName + "the only expression supported by $sort right now is {$meta: 'textScore'}");
|
|
|
+
|
|
|
+ nextSort.vSortKey.push(new ExpressionMeta());
|
|
|
+ nextSort.vAscending.push(false); // best scoring documents first
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- sortOrder = jsonElement[key];
|
|
|
- if ((sortOrder != 1) && (sortOrder !== -1)) throw new Error("code 15975; " + klass.sortName + " key ordering must be 1 (for ascending) or 0 (for descending)");
|
|
|
+ if (typeof jsonElement[keyField] !== "number") throw new Error("code 15974; " + klass.sortName + "$sort key ordering must be specified using a number or {$meta: 'text'}");
|
|
|
|
|
|
- nextSort.addKey(key, (sortOrder > 0));
|
|
|
+ var sortOrder = 0;
|
|
|
+ sortOrder = jsonElement[keyField];
|
|
|
+ if ((sortOrder != 1) && (sortOrder !== -1)) throw new Error("code 15975; " + klass.sortName + "$sort key ordering must be 1 (for ascending) or -1 (for descending)");
|
|
|
+
|
|
|
+ nextSort.addKey(keyField, (sortOrder > 0));
|
|
|
++sortKeys;
|
|
|
}
|
|
|
|
|
|
if (sortKeys <= 0) throw new Error("code 15976; " + klass.sortName + " must have at least one sort key");
|
|
|
+
|
|
|
+ if ( limit > 0) {
|
|
|
+ var coalesced = nextSort.coalesce( create(ctx, limit));
|
|
|
+ // should always coalesce
|
|
|
+ }
|
|
|
+
|
|
|
return nextSort;
|
|
|
};
|
|
|
+
|
|
|
+// SplittableDocumentSource implementation.
|
|
|
+klass.isSplittableDocumentSource = true;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get dependencies.
|
|
|
+ *
|
|
|
+ * @param deps
|
|
|
+ * @returns {number}
|
|
|
+ */
|
|
|
+proto.getDependencies = function getDependencies(deps) {
|
|
|
+ return DocumentSource.GetDepsReturn.SEE_NEXT;
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get shard source.
|
|
|
+ *
|
|
|
+ * @returns {null}
|
|
|
+ */
|
|
|
+proto.getShardSource = function getShardSource() {
|
|
|
+ return null;
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get router source.
|
|
|
+ *
|
|
|
+ * @returns {SkipDocumentSource}
|
|
|
+ */
|
|
|
+proto.getRouterSource = function getRouterSource() {
|
|
|
+ return this;
|
|
|
+};
|