mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Got rid of self (sic!).
This commit is contained in:
@@ -4,39 +4,36 @@ import {LocalCollection} from './local_collection.js';
|
||||
// a defined order, limit, and offset. creating a Cursor with LocalCollection.find(),
|
||||
export class Cursor {
|
||||
// don't call this ctor directly. use LocalCollection.find().
|
||||
constructor (collection, selector, options) {
|
||||
const self = this;
|
||||
if (!options) options = {};
|
||||
|
||||
self.collection = collection;
|
||||
self.sorter = null;
|
||||
self.matcher = new Minimongo.Matcher(selector);
|
||||
constructor (collection, selector, options = {}) {
|
||||
this.collection = collection;
|
||||
this.sorter = null;
|
||||
this.matcher = new Minimongo.Matcher(selector);
|
||||
|
||||
if (LocalCollection._selectorIsId(selector)) {
|
||||
// stash for fast path
|
||||
self._selectorId = selector;
|
||||
this._selectorId = selector;
|
||||
} else if (LocalCollection._selectorIsIdPerhapsAsObject(selector)) {
|
||||
// also do the fast path for { _id: idString }
|
||||
self._selectorId = selector._id;
|
||||
this._selectorId = selector._id;
|
||||
} else {
|
||||
self._selectorId = undefined;
|
||||
if (self.matcher.hasGeoQuery() || options.sort) {
|
||||
self.sorter = new Minimongo.Sorter(options.sort || [],
|
||||
{ matcher: self.matcher });
|
||||
this._selectorId = undefined;
|
||||
if (this.matcher.hasGeoQuery() || options.sort) {
|
||||
this.sorter = new Minimongo.Sorter(options.sort || [],
|
||||
{ matcher: this.matcher });
|
||||
}
|
||||
}
|
||||
|
||||
self.skip = options.skip;
|
||||
self.limit = options.limit;
|
||||
self.fields = options.fields;
|
||||
this.skip = options.skip;
|
||||
this.limit = options.limit;
|
||||
this.fields = options.fields;
|
||||
|
||||
self._projectionFn = LocalCollection._compileProjection(self.fields || {});
|
||||
this._projectionFn = LocalCollection._compileProjection(this.fields || {});
|
||||
|
||||
self._transform = LocalCollection.wrapTransform(options.transform);
|
||||
this._transform = LocalCollection.wrapTransform(options.transform);
|
||||
|
||||
// by default, queries register w/ Tracker when it is available.
|
||||
if (typeof Tracker !== "undefined")
|
||||
self.reactive = (options.reactive === undefined) ? true : options.reactive;
|
||||
this.reactive = (options.reactive === undefined) ? true : options.reactive;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,13 +45,11 @@ export class Cursor {
|
||||
* @returns {Number}
|
||||
*/
|
||||
count () {
|
||||
const self = this;
|
||||
|
||||
if (self.reactive)
|
||||
self._depend({added: true, removed: true},
|
||||
if (this.reactive)
|
||||
this._depend({added: true, removed: true},
|
||||
true /* allow the observe to be unordered */);
|
||||
|
||||
return self._getRawObjects({ordered: true}).length;
|
||||
return this._getRawObjects({ordered: true}).length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,9 +61,8 @@ export class Cursor {
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
fetch () {
|
||||
const self = this;
|
||||
const res = [];
|
||||
self.forEach(doc => {
|
||||
this.forEach(doc => {
|
||||
res.push(doc);
|
||||
});
|
||||
return res;
|
||||
@@ -89,12 +83,10 @@ export class Cursor {
|
||||
* @param {Any} [thisArg] An object which will be the value of `this` inside `callback`.
|
||||
*/
|
||||
forEach (callback, thisArg) {
|
||||
const self = this;
|
||||
const objects = this._getRawObjects({ordered: true});
|
||||
|
||||
const objects = self._getRawObjects({ordered: true});
|
||||
|
||||
if (self.reactive) {
|
||||
self._depend({
|
||||
if (this.reactive) {
|
||||
this._depend({
|
||||
addedBefore: true,
|
||||
removed: true,
|
||||
changed: true,
|
||||
@@ -103,11 +95,11 @@ export class Cursor {
|
||||
|
||||
objects.forEach((elt, i) => {
|
||||
// This doubles as a clone operation.
|
||||
elt = self._projectionFn(elt);
|
||||
elt = this._projectionFn(elt);
|
||||
|
||||
if (self._transform)
|
||||
elt = self._transform(elt);
|
||||
callback.call(thisArg, elt, i, self);
|
||||
if (this._transform)
|
||||
elt = this._transform(elt);
|
||||
callback.call(thisArg, elt, i, this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,10 +117,9 @@ export class Cursor {
|
||||
* @param {Any} [thisArg] An object which will be the value of `this` inside `callback`.
|
||||
*/
|
||||
map (callback, thisArg) {
|
||||
const self = this;
|
||||
const res = [];
|
||||
self.forEach((doc, index) => {
|
||||
res.push(callback.call(thisArg, doc, index, self));
|
||||
this.forEach((doc, index) => {
|
||||
res.push(callback.call(thisArg, doc, index, this));
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@@ -162,8 +153,7 @@ export class Cursor {
|
||||
* @param {Object} callbacks Functions to call to deliver the result set as it changes
|
||||
*/
|
||||
observe (options) {
|
||||
const self = this;
|
||||
return LocalCollection._observeFromObserveChanges(self, options);
|
||||
return LocalCollection._observeFromObserveChanges(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,42 +164,40 @@ export class Cursor {
|
||||
* @param {Object} callbacks Functions to call to deliver the result set as it changes
|
||||
*/
|
||||
observeChanges (options) {
|
||||
const self = this;
|
||||
|
||||
const ordered = LocalCollection._observeChangesCallbacksAreOrdered(options);
|
||||
|
||||
// there are several places that assume you aren't combining skip/limit with
|
||||
// unordered observe. eg, update's EJSON.clone, and the "there are several"
|
||||
// comment in _modifyAndNotify
|
||||
// XXX allow skip/limit with unordered observe
|
||||
if (!options._allow_unordered && !ordered && (self.skip || self.limit))
|
||||
if (!options._allow_unordered && !ordered && (this.skip || this.limit))
|
||||
throw new Error("must use ordered observe (ie, 'addedBefore' instead of 'added') with skip or limit");
|
||||
|
||||
if (self.fields && (self.fields._id === 0 || self.fields._id === false))
|
||||
if (this.fields && (this.fields._id === 0 || this.fields._id === false))
|
||||
throw Error("You may not observe a cursor with {fields: {_id: 0}}");
|
||||
|
||||
const query = {
|
||||
dirty: false,
|
||||
matcher: self.matcher, // not fast pathed
|
||||
sorter: ordered && self.sorter,
|
||||
matcher: this.matcher, // not fast pathed
|
||||
sorter: ordered && this.sorter,
|
||||
distances: (
|
||||
self.matcher.hasGeoQuery() && ordered && new LocalCollection._IdMap),
|
||||
this.matcher.hasGeoQuery() && ordered && new LocalCollection._IdMap),
|
||||
resultsSnapshot: null,
|
||||
ordered,
|
||||
cursor: self,
|
||||
projectionFn: self._projectionFn
|
||||
cursor: this,
|
||||
projectionFn: this._projectionFn
|
||||
};
|
||||
let qid;
|
||||
|
||||
// Non-reactive queries call added[Before] and then never call anything
|
||||
// else.
|
||||
if (self.reactive) {
|
||||
qid = self.collection.next_qid++;
|
||||
self.collection.queries[qid] = query;
|
||||
if (this.reactive) {
|
||||
qid = this.collection.next_qid++;
|
||||
this.collection.queries[qid] = query;
|
||||
}
|
||||
query.results = self._getRawObjects({
|
||||
query.results = this._getRawObjects({
|
||||
ordered, distances: query.distances});
|
||||
if (self.collection.paused)
|
||||
if (this.collection.paused)
|
||||
query.resultsSnapshot = (ordered ? [] : new LocalCollection._IdMap);
|
||||
|
||||
// wrap callbacks we were passed. callbacks only fire when not paused and
|
||||
@@ -222,15 +210,15 @@ export class Cursor {
|
||||
const wrapCallback = f => {
|
||||
if (!f)
|
||||
return () => {};
|
||||
const self = this;
|
||||
return function (/*args*/) {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
|
||||
if (self.collection.paused)
|
||||
return;
|
||||
|
||||
self.collection._observeQueue.queueTask(() => {
|
||||
f.apply(context, args);
|
||||
f.apply(this, args);
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -242,7 +230,7 @@ export class Cursor {
|
||||
query.movedBefore = wrapCallback(options.movedBefore);
|
||||
}
|
||||
|
||||
if (!options._suppress_initial && !self.collection.paused) {
|
||||
if (!options._suppress_initial && !this.collection.paused) {
|
||||
const results = query.results._map || query.results;
|
||||
Object.keys(results).forEach(key => {
|
||||
const doc = results[key];
|
||||
@@ -250,21 +238,21 @@ export class Cursor {
|
||||
|
||||
delete fields._id;
|
||||
if (ordered)
|
||||
query.addedBefore(doc._id, self._projectionFn(fields), null);
|
||||
query.added(doc._id, self._projectionFn(fields));
|
||||
query.addedBefore(doc._id, this._projectionFn(fields), null);
|
||||
query.added(doc._id, this._projectionFn(fields));
|
||||
});
|
||||
}
|
||||
|
||||
const handle = new LocalCollection.ObserveHandle;
|
||||
Object.assign(handle, {
|
||||
collection: self.collection,
|
||||
stop() {
|
||||
if (self.reactive)
|
||||
delete self.collection.queries[qid];
|
||||
collection: this.collection,
|
||||
stop: () => {
|
||||
if (this.reactive)
|
||||
delete this.collection.queries[qid];
|
||||
}
|
||||
});
|
||||
|
||||
if (self.reactive && Tracker.active) {
|
||||
if (this.reactive && Tracker.active) {
|
||||
// XXX in many cases, the same observe will be recreated when
|
||||
// the current autorun is rerun. we could save work by
|
||||
// letting it linger across rerun and potentially get
|
||||
@@ -276,7 +264,7 @@ export class Cursor {
|
||||
}
|
||||
// run the observe callbacks resulting from the initial contents
|
||||
// before we leave the observe.
|
||||
self.collection._observeQueue.drain();
|
||||
this.collection._observeQueue.drain();
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -290,8 +278,6 @@ export class Cursor {
|
||||
// XXX Maybe we need a version of observe that just calls a callback if
|
||||
// anything changed.
|
||||
_depend (changers, _allow_unordered) {
|
||||
const self = this;
|
||||
|
||||
if (Tracker.active) {
|
||||
const v = new Tracker.Dependency;
|
||||
v.depend();
|
||||
@@ -307,13 +293,12 @@ export class Cursor {
|
||||
});
|
||||
|
||||
// observeChanges will stop() when this computation is invalidated
|
||||
self.observeChanges(options);
|
||||
this.observeChanges(options);
|
||||
}
|
||||
}
|
||||
|
||||
_getCollectionName () {
|
||||
const self = this;
|
||||
return self.collection.name;
|
||||
return this.collection.name;
|
||||
}
|
||||
|
||||
// Returns a collection of matching objects, but doesn't deep copy them.
|
||||
@@ -331,28 +316,25 @@ export class Cursor {
|
||||
// argument, this function will clear it and use it for this purpose (otherwise
|
||||
// it will just create its own _IdMap). The observeChanges implementation uses
|
||||
// this to remember the distances after this function returns.
|
||||
_getRawObjects (options) {
|
||||
const self = this;
|
||||
options = options || {};
|
||||
|
||||
_getRawObjects (options = {}) {
|
||||
// XXX use OrderedDict instead of array, and make IdMap and OrderedDict
|
||||
// compatible
|
||||
const results = options.ordered ? [] : new LocalCollection._IdMap;
|
||||
|
||||
// fast path for single ID value
|
||||
if (self._selectorId !== undefined) {
|
||||
if (this._selectorId !== undefined) {
|
||||
// If you have non-zero skip and ask for a single id, you get
|
||||
// nothing. This is so it matches the behavior of the '{_id: foo}'
|
||||
// path.
|
||||
if (self.skip)
|
||||
if (this.skip)
|
||||
return results;
|
||||
|
||||
const selectedDoc = self.collection._docs.get(self._selectorId);
|
||||
const selectedDoc = this.collection._docs.get(this._selectorId);
|
||||
if (selectedDoc) {
|
||||
if (options.ordered)
|
||||
results.push(selectedDoc);
|
||||
else
|
||||
results.set(self._selectorId, selectedDoc);
|
||||
results.set(this._selectorId, selectedDoc);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -363,7 +345,7 @@ export class Cursor {
|
||||
// live results set) object. in other cases, distances is only used inside
|
||||
// this function.
|
||||
let distances;
|
||||
if (self.matcher.hasGeoQuery() && options.ordered) {
|
||||
if (this.matcher.hasGeoQuery() && options.ordered) {
|
||||
if (options.distances) {
|
||||
distances = options.distances;
|
||||
distances.clear();
|
||||
@@ -372,8 +354,8 @@ export class Cursor {
|
||||
}
|
||||
}
|
||||
|
||||
self.collection._docs.forEach((doc, id) => {
|
||||
const matchResult = self.matcher.documentMatches(doc);
|
||||
this.collection._docs.forEach((doc, id) => {
|
||||
const matchResult = this.matcher.documentMatches(doc);
|
||||
if (matchResult.result) {
|
||||
if (options.ordered) {
|
||||
results.push(doc);
|
||||
@@ -385,8 +367,8 @@ export class Cursor {
|
||||
}
|
||||
// Fast path for limited unsorted queries.
|
||||
// XXX 'length' check here seems wrong for ordered
|
||||
if (self.limit && !self.skip && !self.sorter &&
|
||||
results.length === self.limit)
|
||||
if (this.limit && !this.skip && !this.sorter &&
|
||||
results.length === this.limit)
|
||||
return false; // break
|
||||
return true; // continue
|
||||
});
|
||||
@@ -394,27 +376,26 @@ export class Cursor {
|
||||
if (!options.ordered)
|
||||
return results;
|
||||
|
||||
if (self.sorter) {
|
||||
const comparator = self.sorter.getComparator({distances});
|
||||
if (this.sorter) {
|
||||
const comparator = this.sorter.getComparator({distances});
|
||||
results.sort(comparator);
|
||||
}
|
||||
|
||||
const idx_start = self.skip || 0;
|
||||
const idx_end = self.limit ? (self.limit + idx_start) : results.length;
|
||||
const idx_start = this.skip || 0;
|
||||
const idx_end = this.limit ? (this.limit + idx_start) : results.length;
|
||||
return results.slice(idx_start, idx_end);
|
||||
}
|
||||
|
||||
_publishCursor (sub) {
|
||||
const self = this;
|
||||
if (! self.collection.name)
|
||||
if (! this.collection.name)
|
||||
throw new Error("Can't publish a cursor from a collection without a name.");
|
||||
const collection = self.collection.name;
|
||||
const collection = this.collection.name;
|
||||
|
||||
// XXX minimongo should not depend on mongo-livedata!
|
||||
if (! Package.mongo) {
|
||||
throw new Error("Can't publish from Minimongo without the `mongo` package.");
|
||||
}
|
||||
|
||||
return Package.mongo.Mongo.Collection._publishCursor(self, sub, collection);
|
||||
return Package.mongo.Mongo.Collection._publishCursor(this, sub, collection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@ export class LocalCollection {
|
||||
// XXX possibly enforce that 'undefined' does not appear (we assume
|
||||
// this in our handling of null and $exists)
|
||||
insert (doc, callback) {
|
||||
const self = this;
|
||||
doc = EJSON.clone(doc);
|
||||
|
||||
assertHasValidFieldNames(doc);
|
||||
@@ -99,16 +98,16 @@ export class LocalCollection {
|
||||
}
|
||||
const id = doc._id;
|
||||
|
||||
if (self._docs.has(id))
|
||||
if (this._docs.has(id))
|
||||
throw MinimongoError(`Duplicate _id '${id}'`);
|
||||
|
||||
self._saveOriginal(id, undefined);
|
||||
self._docs.set(id, doc);
|
||||
this._saveOriginal(id, undefined);
|
||||
this._docs.set(id, doc);
|
||||
|
||||
const queriesToRecompute = [];
|
||||
// trigger live queries that match
|
||||
for (let qid in self.queries) {
|
||||
const query = self.queries[qid];
|
||||
for (let qid in this.queries) {
|
||||
const query = this.queries[qid];
|
||||
if (query.dirty) continue;
|
||||
const matchResult = query.matcher.documentMatches(doc);
|
||||
if (matchResult.result) {
|
||||
@@ -122,10 +121,10 @@ export class LocalCollection {
|
||||
}
|
||||
|
||||
queriesToRecompute.forEach(qid => {
|
||||
if (self.queries[qid])
|
||||
self._recomputeResults(self.queries[qid]);
|
||||
if (this.queries[qid])
|
||||
this._recomputeResults(this.queries[qid]);
|
||||
});
|
||||
self._observeQueue.drain();
|
||||
this._observeQueue.drain();
|
||||
|
||||
// Defer because the caller likely doesn't expect the callback to be run
|
||||
// immediately.
|
||||
@@ -155,16 +154,14 @@ export class LocalCollection {
|
||||
}
|
||||
|
||||
remove (selector, callback) {
|
||||
const self = this;
|
||||
|
||||
// Easy special case: if we're not calling observeChanges callbacks and we're
|
||||
// not saving originals and we got asked to remove everything, then just empty
|
||||
// everything directly.
|
||||
if (self.paused && !self._savedOriginals && EJSON.equals(selector, {})) {
|
||||
const result = self._docs.size();
|
||||
self._docs.clear();
|
||||
Object.keys(self.queries).forEach(qid => {
|
||||
const query = self.queries[qid];
|
||||
if (this.paused && !this._savedOriginals && EJSON.equals(selector, {})) {
|
||||
const result = this._docs.size();
|
||||
this._docs.clear();
|
||||
Object.keys(this.queries).forEach(qid => {
|
||||
const query = this.queries[qid];
|
||||
if (query.ordered) {
|
||||
query.results = [];
|
||||
} else {
|
||||
@@ -181,7 +178,7 @@ export class LocalCollection {
|
||||
|
||||
const matcher = new Minimongo.Matcher(selector);
|
||||
const remove = [];
|
||||
self._eachPossiblyMatchingDoc(selector, (doc, id) => {
|
||||
this._eachPossiblyMatchingDoc(selector, (doc, id) => {
|
||||
if (matcher.documentMatches(doc).result)
|
||||
remove.push(id);
|
||||
});
|
||||
@@ -190,9 +187,9 @@ export class LocalCollection {
|
||||
const queryRemove = [];
|
||||
for (let i = 0; i < remove.length; i++) {
|
||||
const removeId = remove[i];
|
||||
const removeDoc = self._docs.get(removeId);
|
||||
Object.keys(self.queries).forEach(qid => {
|
||||
const query = self.queries[qid];
|
||||
const removeDoc = this._docs.get(removeId);
|
||||
Object.keys(this.queries).forEach(qid => {
|
||||
const query = this.queries[qid];
|
||||
if (query.dirty) return;
|
||||
|
||||
if (query.matcher.documentMatches(removeDoc).result) {
|
||||
@@ -202,24 +199,24 @@ export class LocalCollection {
|
||||
queryRemove.push({qid, doc: removeDoc});
|
||||
}
|
||||
});
|
||||
self._saveOriginal(removeId, removeDoc);
|
||||
self._docs.remove(removeId);
|
||||
this._saveOriginal(removeId, removeDoc);
|
||||
this._docs.remove(removeId);
|
||||
}
|
||||
|
||||
// run live query callbacks _after_ we've removed the documents.
|
||||
queryRemove.forEach(remove => {
|
||||
const query = self.queries[remove.qid];
|
||||
const query = this.queries[remove.qid];
|
||||
if (query) {
|
||||
query.distances && query.distances.remove(remove.doc._id);
|
||||
LocalCollection._removeFromResults(query, remove.doc);
|
||||
}
|
||||
});
|
||||
queriesToRecompute.forEach(qid => {
|
||||
const query = self.queries[qid];
|
||||
const query = this.queries[qid];
|
||||
if (query)
|
||||
self._recomputeResults(query);
|
||||
this._recomputeResults(query);
|
||||
});
|
||||
self._observeQueue.drain();
|
||||
this._observeQueue.drain();
|
||||
const result = remove.length;
|
||||
if (callback)
|
||||
Meteor.defer(() => {
|
||||
@@ -233,7 +230,6 @@ export class LocalCollection {
|
||||
// database. Note that this is not just replaying all the changes that
|
||||
// happened during the pause, it is a smarter 'coalesced' diff.
|
||||
resumeObservers () {
|
||||
const self = this;
|
||||
// No-op if not paused.
|
||||
if (!this.paused)
|
||||
return;
|
||||
@@ -243,11 +239,11 @@ export class LocalCollection {
|
||||
this.paused = false;
|
||||
|
||||
for (let qid in this.queries) {
|
||||
const query = self.queries[qid];
|
||||
const query = this.queries[qid];
|
||||
if (query.dirty) {
|
||||
query.dirty = false;
|
||||
// re-compute results will perform `LocalCollection._diffQueryChanges` automatically.
|
||||
self._recomputeResults(query, query.resultsSnapshot);
|
||||
this._recomputeResults(query, query.resultsSnapshot);
|
||||
} else {
|
||||
// Diff the current results against the snapshot and send to observers.
|
||||
// pass the query object for its observer callbacks.
|
||||
@@ -257,16 +253,15 @@ export class LocalCollection {
|
||||
}
|
||||
query.resultsSnapshot = null;
|
||||
}
|
||||
self._observeQueue.drain();
|
||||
this._observeQueue.drain();
|
||||
}
|
||||
|
||||
retrieveOriginals () {
|
||||
const self = this;
|
||||
if (!self._savedOriginals)
|
||||
if (!this._savedOriginals)
|
||||
throw new Error("Called retrieveOriginals without saveOriginals");
|
||||
|
||||
const originals = self._savedOriginals;
|
||||
self._savedOriginals = null;
|
||||
const originals = this._savedOriginals;
|
||||
this._savedOriginals = null;
|
||||
return originals;
|
||||
}
|
||||
|
||||
@@ -278,16 +273,14 @@ export class LocalCollection {
|
||||
// is the value.) You must alternate between calls to saveOriginals() and
|
||||
// retrieveOriginals().
|
||||
saveOriginals () {
|
||||
const self = this;
|
||||
if (self._savedOriginals)
|
||||
if (this._savedOriginals)
|
||||
throw new Error("Called saveOriginals twice without retrieveOriginals");
|
||||
self._savedOriginals = new LocalCollection._IdMap;
|
||||
this._savedOriginals = new LocalCollection._IdMap;
|
||||
}
|
||||
|
||||
// XXX atomicity: if multi is true, and one modification fails, do
|
||||
// we rollback the whole operation, or what?
|
||||
update (selector, mod, options, callback) {
|
||||
const self = this;
|
||||
if (! callback && options instanceof Function) {
|
||||
callback = options;
|
||||
options = null;
|
||||
@@ -306,9 +299,9 @@ export class LocalCollection {
|
||||
const docMap = new LocalCollection._IdMap;
|
||||
const idsMatchedBySelector = LocalCollection._idsMatchedBySelector(selector);
|
||||
|
||||
Object.keys(self.queries).forEach(qid => {
|
||||
const query = self.queries[qid];
|
||||
if ((query.cursor.skip || query.cursor.limit) && ! self.paused) {
|
||||
Object.keys(this.queries).forEach(qid => {
|
||||
const query = this.queries[qid];
|
||||
if ((query.cursor.skip || query.cursor.limit) && ! this.paused) {
|
||||
// Catch the case of a reactive `count()` on a cursor with skip
|
||||
// or limit, which registers an unordered observe. This is a
|
||||
// pretty rare case, so we just clone the entire result set with
|
||||
@@ -351,12 +344,12 @@ export class LocalCollection {
|
||||
|
||||
let updateCount = 0;
|
||||
|
||||
self._eachPossiblyMatchingDoc(selector, (doc, id) => {
|
||||
this._eachPossiblyMatchingDoc(selector, (doc, id) => {
|
||||
const queryResult = matcher.documentMatches(doc);
|
||||
if (queryResult.result) {
|
||||
// XXX Should we save the original even if mod ends up being a no-op?
|
||||
self._saveOriginal(id, doc);
|
||||
self._modifyAndNotify(doc, mod, recomputeQids, queryResult.arrayIndices);
|
||||
this._saveOriginal(id, doc);
|
||||
this._modifyAndNotify(doc, mod, recomputeQids, queryResult.arrayIndices);
|
||||
++updateCount;
|
||||
if (!options.multi)
|
||||
return false; // break
|
||||
@@ -365,11 +358,11 @@ export class LocalCollection {
|
||||
});
|
||||
|
||||
Object.keys(recomputeQids).forEach(qid => {
|
||||
const query = self.queries[qid];
|
||||
const query = this.queries[qid];
|
||||
if (query)
|
||||
self._recomputeResults(query, qidToOriginalResults[qid]);
|
||||
this._recomputeResults(query, qidToOriginalResults[qid]);
|
||||
});
|
||||
self._observeQueue.drain();
|
||||
this._observeQueue.drain();
|
||||
|
||||
// If we are doing an upsert, and we didn't modify any documents yet, then
|
||||
// it's time to do an insert. Figure out what document we are inserting, and
|
||||
@@ -396,7 +389,7 @@ export class LocalCollection {
|
||||
|
||||
if (! newDoc._id && options.insertedId)
|
||||
newDoc._id = options.insertedId;
|
||||
insertedId = self.insert(newDoc);
|
||||
insertedId = this.insert(newDoc);
|
||||
updateCount = 1;
|
||||
}
|
||||
|
||||
@@ -425,12 +418,11 @@ export class LocalCollection {
|
||||
// equivalent to LocalCollection.update(sel, mod, { upsert: true, _returnObject:
|
||||
// true }).
|
||||
upsert (selector, mod, options, callback) {
|
||||
const self = this;
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
return self.update(selector, mod, Object.assign({}, options, {
|
||||
return this.update(selector, mod, Object.assign({}, options, {
|
||||
upsert: true,
|
||||
_returnObject: true
|
||||
}), callback);
|
||||
@@ -441,12 +433,11 @@ export class LocalCollection {
|
||||
// specific _id's, it only looks at those. doc is *not* cloned: it is the
|
||||
// same object that is in _docs.
|
||||
_eachPossiblyMatchingDoc (selector, f) {
|
||||
const self = this;
|
||||
const specificIds = LocalCollection._idsMatchedBySelector(selector);
|
||||
if (specificIds) {
|
||||
for (let i = 0; i < specificIds.length; ++i) {
|
||||
const id = specificIds[i];
|
||||
const doc = self._docs.get(id);
|
||||
const doc = this._docs.get(id);
|
||||
if (doc) {
|
||||
const breakIfFalse = f(doc, id);
|
||||
if (breakIfFalse === false)
|
||||
@@ -454,16 +445,14 @@ export class LocalCollection {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self._docs.forEach(f);
|
||||
this._docs.forEach(f);
|
||||
}
|
||||
}
|
||||
|
||||
_modifyAndNotify (doc, mod, recomputeQids, arrayIndices) {
|
||||
const self = this;
|
||||
|
||||
const matched_before = {};
|
||||
for (let qid in self.queries) {
|
||||
const query = self.queries[qid];
|
||||
for (let qid in this.queries) {
|
||||
const query = this.queries[qid];
|
||||
if (query.dirty) continue;
|
||||
|
||||
if (query.ordered) {
|
||||
@@ -479,8 +468,8 @@ export class LocalCollection {
|
||||
|
||||
LocalCollection._modify(doc, mod, {arrayIndices});
|
||||
|
||||
for (let qid in self.queries) {
|
||||
const query = self.queries[qid];
|
||||
for (let qid in this.queries) {
|
||||
const query = this.queries[qid];
|
||||
if (query.dirty) continue;
|
||||
|
||||
const before = matched_before[qid];
|
||||
@@ -520,8 +509,7 @@ export class LocalCollection {
|
||||
//
|
||||
// oldResults is guaranteed to be ignored if the query is not paused.
|
||||
_recomputeResults (query, oldResults) {
|
||||
const self = this;
|
||||
if (self.paused) {
|
||||
if (this.paused) {
|
||||
// There's no reason to recompute the results now as we're still paused.
|
||||
// By flagging the query as "dirty", the recompute will be performed
|
||||
// when resumeObservers is called.
|
||||
@@ -529,14 +517,14 @@ export class LocalCollection {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! self.paused && ! oldResults)
|
||||
if (! this.paused && ! oldResults)
|
||||
oldResults = query.results;
|
||||
if (query.distances)
|
||||
query.distances.clear();
|
||||
query.results = query.cursor._getRawObjects({
|
||||
ordered: query.ordered, distances: query.distances});
|
||||
|
||||
if (! self.paused) {
|
||||
if (! this.paused) {
|
||||
LocalCollection._diffQueryChanges(
|
||||
query.ordered, oldResults, query.results, query,
|
||||
{ projectionFn: query.projectionFn });
|
||||
@@ -544,16 +532,15 @@ export class LocalCollection {
|
||||
}
|
||||
|
||||
_saveOriginal (id, doc) {
|
||||
const self = this;
|
||||
// Are we even trying to save originals?
|
||||
if (!self._savedOriginals)
|
||||
if (!this._savedOriginals)
|
||||
return;
|
||||
// Have we previously mutated the original (and so 'doc' is not actually
|
||||
// original)? (Note the 'has' check rather than truth: we store undefined
|
||||
// here for inserted docs!)
|
||||
if (self._savedOriginals.has(id))
|
||||
if (this._savedOriginals.has(id))
|
||||
return;
|
||||
self._savedOriginals.set(id, EJSON.clone(doc));
|
||||
this._savedOriginals.set(id, EJSON.clone(doc));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,76 +551,73 @@ LocalCollection.ObserveHandle = ObserveHandle;
|
||||
// XXX maybe move these into another ObserveHelpers package or something
|
||||
|
||||
// _CachingChangeObserver is an object which receives observeChanges callbacks
|
||||
// and keeps a cache of the current cursor state up to date in self.docs. Users
|
||||
// and keeps a cache of the current cursor state up to date in this.docs. Users
|
||||
// of this class should read the docs field but not modify it. You should pass
|
||||
// the "applyChange" field as the callbacks to the underlying observeChanges
|
||||
// call. Optionally, you can specify your own observeChanges callbacks which are
|
||||
// invoked immediately before the docs field is updated; this object is made
|
||||
// available as `this` to those callbacks.
|
||||
LocalCollection._CachingChangeObserver = class _CachingChangeObserver {
|
||||
constructor (options) {
|
||||
const self = this;
|
||||
options = options || {};
|
||||
|
||||
constructor (options = {}) {
|
||||
const orderedFromCallbacks = options.callbacks &&
|
||||
LocalCollection._observeChangesCallbacksAreOrdered(options.callbacks);
|
||||
if (options.hasOwnProperty('ordered')) {
|
||||
self.ordered = options.ordered;
|
||||
this.ordered = options.ordered;
|
||||
if (options.callbacks && options.ordered !== orderedFromCallbacks)
|
||||
throw Error("ordered option doesn't match callbacks");
|
||||
} else if (options.callbacks) {
|
||||
self.ordered = orderedFromCallbacks;
|
||||
this.ordered = orderedFromCallbacks;
|
||||
} else {
|
||||
throw Error("must provide ordered or callbacks");
|
||||
}
|
||||
const callbacks = options.callbacks || {};
|
||||
|
||||
if (self.ordered) {
|
||||
self.docs = new OrderedDict(MongoID.idStringify);
|
||||
self.applyChange = {
|
||||
addedBefore(id, fields, before) {
|
||||
if (this.ordered) {
|
||||
this.docs = new OrderedDict(MongoID.idStringify);
|
||||
this.applyChange = {
|
||||
addedBefore: (id, fields, before) => {
|
||||
const doc = EJSON.clone(fields);
|
||||
doc._id = id;
|
||||
callbacks.addedBefore && callbacks.addedBefore.call(
|
||||
self, id, fields, before);
|
||||
this, id, fields, before);
|
||||
// This line triggers if we provide added with movedBefore.
|
||||
callbacks.added && callbacks.added.call(self, id, fields);
|
||||
callbacks.added && callbacks.added.call(this, id, fields);
|
||||
// XXX could `before` be a falsy ID? Technically
|
||||
// idStringify seems to allow for them -- though
|
||||
// OrderedDict won't call stringify on a falsy arg.
|
||||
self.docs.putBefore(id, doc, before || null);
|
||||
this.docs.putBefore(id, doc, before || null);
|
||||
},
|
||||
movedBefore(id, before) {
|
||||
const doc = self.docs.get(id);
|
||||
callbacks.movedBefore && callbacks.movedBefore.call(self, id, before);
|
||||
self.docs.moveBefore(id, before || null);
|
||||
movedBefore: (id, before) => {
|
||||
const doc = this.docs.get(id);
|
||||
callbacks.movedBefore && callbacks.movedBefore.call(this, id, before);
|
||||
this.docs.moveBefore(id, before || null);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
self.docs = new LocalCollection._IdMap;
|
||||
self.applyChange = {
|
||||
added(id, fields) {
|
||||
this.docs = new LocalCollection._IdMap;
|
||||
this.applyChange = {
|
||||
added: (id, fields) => {
|
||||
const doc = EJSON.clone(fields);
|
||||
callbacks.added && callbacks.added.call(self, id, fields);
|
||||
callbacks.added && callbacks.added.call(this, id, fields);
|
||||
doc._id = id;
|
||||
self.docs.set(id, doc);
|
||||
this.docs.set(id, doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The methods in _IdMap and OrderedDict used by these callbacks are
|
||||
// identical.
|
||||
self.applyChange.changed = (id, fields) => {
|
||||
const doc = self.docs.get(id);
|
||||
this.applyChange.changed = (id, fields) => {
|
||||
const doc = this.docs.get(id);
|
||||
if (!doc)
|
||||
throw new Error(`Unknown id for changed: ${id}`);
|
||||
callbacks.changed && callbacks.changed.call(
|
||||
self, id, EJSON.clone(fields));
|
||||
this, id, EJSON.clone(fields));
|
||||
DiffSequence.applyChanges(doc, fields);
|
||||
};
|
||||
self.applyChange.removed = id => {
|
||||
callbacks.removed && callbacks.removed.call(self, id);
|
||||
self.docs.remove(id);
|
||||
this.applyChange.removed = id => {
|
||||
callbacks.removed && callbacks.removed.call(this, id);
|
||||
this.docs.remove(id);
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -987,60 +971,56 @@ LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => {
|
||||
// relative order, transforms, and applyChanges -- without the speed hit.
|
||||
const indices = !observeCallbacks._no_indices;
|
||||
observeChangesCallbacks = {
|
||||
addedBefore(id, fields, before) {
|
||||
const self = this;
|
||||
addedBefore (id, fields, before) {
|
||||
if (suppressed || !(observeCallbacks.addedAt || observeCallbacks.added))
|
||||
return;
|
||||
const doc = transform(Object.assign(fields, {_id: id}));
|
||||
if (observeCallbacks.addedAt) {
|
||||
const index = indices
|
||||
? (before ? self.docs.indexOf(before) : self.docs.size()) : -1;
|
||||
? (before ? this.docs.indexOf(before) : this.docs.size()) : -1;
|
||||
observeCallbacks.addedAt(doc, index, before);
|
||||
} else {
|
||||
observeCallbacks.added(doc);
|
||||
}
|
||||
},
|
||||
changed(id, fields) {
|
||||
const self = this;
|
||||
changed (id, fields) {
|
||||
if (!(observeCallbacks.changedAt || observeCallbacks.changed))
|
||||
return;
|
||||
let doc = EJSON.clone(self.docs.get(id));
|
||||
let doc = EJSON.clone(this.docs.get(id));
|
||||
if (!doc)
|
||||
throw new Error(`Unknown id for changed: ${id}`);
|
||||
const oldDoc = transform(EJSON.clone(doc));
|
||||
DiffSequence.applyChanges(doc, fields);
|
||||
doc = transform(doc);
|
||||
if (observeCallbacks.changedAt) {
|
||||
const index = indices ? self.docs.indexOf(id) : -1;
|
||||
const index = indices ? this.docs.indexOf(id) : -1;
|
||||
observeCallbacks.changedAt(doc, oldDoc, index);
|
||||
} else {
|
||||
observeCallbacks.changed(doc, oldDoc);
|
||||
}
|
||||
},
|
||||
movedBefore(id, before) {
|
||||
const self = this;
|
||||
movedBefore (id, before) {
|
||||
if (!observeCallbacks.movedTo)
|
||||
return;
|
||||
const from = indices ? self.docs.indexOf(id) : -1;
|
||||
const from = indices ? this.docs.indexOf(id) : -1;
|
||||
|
||||
let to = indices
|
||||
? (before ? self.docs.indexOf(before) : self.docs.size()) : -1;
|
||||
? (before ? this.docs.indexOf(before) : this.docs.size()) : -1;
|
||||
// When not moving backwards, adjust for the fact that removing the
|
||||
// document slides everything back one slot.
|
||||
if (to > from)
|
||||
--to;
|
||||
observeCallbacks.movedTo(transform(EJSON.clone(self.docs.get(id))),
|
||||
observeCallbacks.movedTo(transform(EJSON.clone(this.docs.get(id))),
|
||||
from, to, before || null);
|
||||
},
|
||||
removed(id) {
|
||||
const self = this;
|
||||
removed (id) {
|
||||
if (!(observeCallbacks.removedAt || observeCallbacks.removed))
|
||||
return;
|
||||
// technically maybe there should be an EJSON.clone here, but it's about
|
||||
// to be removed from self.docs!
|
||||
const doc = transform(self.docs.get(id));
|
||||
// to be removed from this.docs!
|
||||
const doc = transform(this.docs.get(id));
|
||||
if (observeCallbacks.removedAt) {
|
||||
const index = indices ? self.docs.indexOf(id) : -1;
|
||||
const index = indices ? this.docs.indexOf(id) : -1;
|
||||
observeCallbacks.removedAt(doc, index);
|
||||
} else {
|
||||
observeCallbacks.removed(doc);
|
||||
@@ -1049,26 +1029,24 @@ LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => {
|
||||
};
|
||||
} else {
|
||||
observeChangesCallbacks = {
|
||||
added(id, fields) {
|
||||
added (id, fields) {
|
||||
if (!suppressed && observeCallbacks.added) {
|
||||
const doc = Object.assign(fields, {_id: id});
|
||||
observeCallbacks.added(transform(doc));
|
||||
}
|
||||
},
|
||||
changed(id, fields) {
|
||||
const self = this;
|
||||
changed (id, fields) {
|
||||
if (observeCallbacks.changed) {
|
||||
const oldDoc = self.docs.get(id);
|
||||
const oldDoc = this.docs.get(id);
|
||||
const doc = EJSON.clone(oldDoc);
|
||||
DiffSequence.applyChanges(doc, fields);
|
||||
observeCallbacks.changed(transform(doc),
|
||||
transform(EJSON.clone(oldDoc)));
|
||||
}
|
||||
},
|
||||
removed(id) {
|
||||
const self = this;
|
||||
removed (id) {
|
||||
if (observeCallbacks.removed) {
|
||||
observeCallbacks.removed(transform(self.docs.get(id)));
|
||||
observeCallbacks.removed(transform(this.docs.get(id)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from './common.js';
|
||||
|
||||
Minimongo._pathsElidingNumericKeys = function (paths) {
|
||||
const self = this;
|
||||
return paths.map(path => path.split('.').filter(part => !isNumericKey(part)).join('.'));
|
||||
};
|
||||
|
||||
@@ -20,11 +19,10 @@ Minimongo._pathsElidingNumericKeys = function (paths) {
|
||||
// - $unset
|
||||
// - 'abc.d': 1
|
||||
Minimongo.Matcher.prototype.affectedByModifier = function (modifier) {
|
||||
const self = this;
|
||||
// safe check for $set/$unset being objects
|
||||
modifier = Object.assign({ $set: {}, $unset: {} }, modifier);
|
||||
const modifiedPaths = Object.keys(modifier.$set).concat(Object.keys(modifier.$unset));
|
||||
const meaningfulPaths = self._getPaths();
|
||||
const meaningfulPaths = this._getPaths();
|
||||
|
||||
return modifiedPaths.some(path => {
|
||||
const mod = path.split('.');
|
||||
@@ -66,17 +64,16 @@ Minimongo.Matcher.prototype.affectedByModifier = function (modifier) {
|
||||
// stay 'false'.
|
||||
// Currently doesn't support $-operators and numeric indices precisely.
|
||||
Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) {
|
||||
const self = this;
|
||||
if (!this.affectedByModifier(modifier))
|
||||
return false;
|
||||
|
||||
modifier = Object.assign({$set:{}, $unset:{}}, modifier);
|
||||
const modifierPaths = Object.keys(modifier.$set).concat(Object.keys(modifier.$unset));
|
||||
|
||||
if (!self.isSimple())
|
||||
if (!this.isSimple())
|
||||
return true;
|
||||
|
||||
if (self._getPaths().some(pathHasNumericKeys) ||
|
||||
if (this._getPaths().some(pathHasNumericKeys) ||
|
||||
modifierPaths.some(pathHasNumericKeys))
|
||||
return true;
|
||||
|
||||
@@ -85,8 +82,8 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) {
|
||||
// NOTE: it is correct since we allow only scalars in $-operators
|
||||
// Example: for selector {'a.b': {$gt: 5}} the modifier {'a.b.c':7} would
|
||||
// definitely set the result to false as 'a.b' appears to be an object.
|
||||
const expectedScalarIsObject = Object.keys(self._selector).some(path => {
|
||||
const sel = self._selector[path];
|
||||
const expectedScalarIsObject = Object.keys(this._selector).some(path => {
|
||||
const sel = this._selector[path];
|
||||
if (! isOperatorObject(sel))
|
||||
return false;
|
||||
return modifierPaths.some(modifierPath => startsWith(modifierPath, `${path}.`));
|
||||
@@ -98,7 +95,7 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) {
|
||||
// See if we can apply the modifier on the ideally matching object. If it
|
||||
// still matches the selector, then the modifier could have turned the real
|
||||
// object in the database into something matching.
|
||||
const matchingDocument = EJSON.clone(self.matchingDocument());
|
||||
const matchingDocument = EJSON.clone(this.matchingDocument());
|
||||
|
||||
// The selector is too complex, anything can happen.
|
||||
if (matchingDocument === null)
|
||||
@@ -122,15 +119,14 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return self.documentMatches(matchingDocument).result;
|
||||
return this.documentMatches(matchingDocument).result;
|
||||
};
|
||||
|
||||
// Knows how to combine a mongo selector and a fields projection to a new fields
|
||||
// projection taking into account active fields from the passed selector.
|
||||
// @returns Object - projection object (same as fields option of mongo cursor)
|
||||
Minimongo.Matcher.prototype.combineIntoProjection = function (projection) {
|
||||
const self = this;
|
||||
const selectorPaths = Minimongo._pathsElidingNumericKeys(self._getPaths());
|
||||
const selectorPaths = Minimongo._pathsElidingNumericKeys(this._getPaths());
|
||||
|
||||
// Special case for $where operator in the selector - projection should depend
|
||||
// on all fields of the document. getSelectorPaths returns a list of paths
|
||||
@@ -147,18 +143,16 @@ Minimongo.Matcher.prototype.combineIntoProjection = function (projection) {
|
||||
// { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" }
|
||||
// => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } }
|
||||
Minimongo.Matcher.prototype.matchingDocument = function () {
|
||||
const self = this;
|
||||
|
||||
// check if it was computed before
|
||||
if (self._matchingDocument !== undefined)
|
||||
return self._matchingDocument;
|
||||
if (this._matchingDocument !== undefined)
|
||||
return this._matchingDocument;
|
||||
|
||||
// If the analysis of this selector is too hard for our implementation
|
||||
// fallback to "YES"
|
||||
let fallback = false;
|
||||
self._matchingDocument = pathsToTree(self._getPaths(),
|
||||
this._matchingDocument = pathsToTree(this._getPaths(),
|
||||
path => {
|
||||
const valueSelector = self._selector[path];
|
||||
const valueSelector = this._selector[path];
|
||||
if (isOperatorObject(valueSelector)) {
|
||||
// if there is a strict equality, there is a good
|
||||
// chance we can use one of those as "matching"
|
||||
@@ -191,7 +185,7 @@ Minimongo.Matcher.prototype.matchingDocument = function () {
|
||||
|
||||
return middle;
|
||||
} else if (onlyContainsKeys(valueSelector, ['$nin', '$ne'])) {
|
||||
// Since self._isSimple makes sure $nin and $ne are not combined with
|
||||
// Since this._isSimple makes sure $nin and $ne are not combined with
|
||||
// objects or arrays, we can confidently return an empty object as it
|
||||
// never matches any scalar.
|
||||
return {};
|
||||
@@ -199,26 +193,24 @@ Minimongo.Matcher.prototype.matchingDocument = function () {
|
||||
fallback = true;
|
||||
}
|
||||
}
|
||||
return self._selector[path];
|
||||
return this._selector[path];
|
||||
},
|
||||
x => x);
|
||||
|
||||
if (fallback)
|
||||
self._matchingDocument = null;
|
||||
this._matchingDocument = null;
|
||||
|
||||
return self._matchingDocument;
|
||||
return this._matchingDocument;
|
||||
};
|
||||
|
||||
// Minimongo.Sorter gets a similar method, which delegates to a Matcher it made
|
||||
// for this exact purpose.
|
||||
Minimongo.Sorter.prototype.affectedByModifier = function (modifier) {
|
||||
const self = this;
|
||||
return self._selectorForAffectedByModifier.affectedByModifier(modifier);
|
||||
return this._selectorForAffectedByModifier.affectedByModifier(modifier);
|
||||
};
|
||||
|
||||
Minimongo.Sorter.prototype.combineIntoProjection = function (projection) {
|
||||
const self = this;
|
||||
const specPaths = Minimongo._pathsElidingNumericKeys(self._getPaths());
|
||||
const specPaths = Minimongo._pathsElidingNumericKeys(this._getPaths());
|
||||
return combineImportantPathsIntoProjection(specPaths, projection);
|
||||
};
|
||||
|
||||
|
||||
@@ -21,19 +21,16 @@ import {
|
||||
// first, or 0 if neither object comes before the other.
|
||||
|
||||
export class Sorter {
|
||||
constructor (spec, options) {
|
||||
const self = this;
|
||||
options = options || {};
|
||||
|
||||
self._sortSpecParts = [];
|
||||
self._sortFunction = null;
|
||||
constructor (spec, options = {}) {
|
||||
this._sortSpecParts = [];
|
||||
this._sortFunction = null;
|
||||
|
||||
const addSpecPart = (path, ascending) => {
|
||||
if (!path)
|
||||
throw Error("sort keys must be non-empty");
|
||||
if (path.charAt(0) === '$')
|
||||
throw Error(`unsupported sort key: ${path}`);
|
||||
self._sortSpecParts.push({
|
||||
this._sortSpecParts.push({
|
||||
path,
|
||||
lookup: makeLookupFunction(path, {forSort: true}),
|
||||
ascending
|
||||
@@ -54,46 +51,44 @@ export class Sorter {
|
||||
addSpecPart(key, value >= 0);
|
||||
});
|
||||
} else if (typeof spec === "function") {
|
||||
self._sortFunction = spec;
|
||||
this._sortFunction = spec;
|
||||
} else {
|
||||
throw Error(`Bad sort specification: ${JSON.stringify(spec)}`);
|
||||
}
|
||||
|
||||
// If a function is specified for sorting, we skip the rest.
|
||||
if (self._sortFunction)
|
||||
if (this._sortFunction)
|
||||
return;
|
||||
|
||||
// To implement affectedByModifier, we piggy-back on top of Matcher's
|
||||
// affectedByModifier code; we create a selector that is affected by the same
|
||||
// modifiers as this sort order. This is only implemented on the server.
|
||||
if (self.affectedByModifier) {
|
||||
if (this.affectedByModifier) {
|
||||
const selector = {};
|
||||
self._sortSpecParts.forEach(spec => {
|
||||
this._sortSpecParts.forEach(spec => {
|
||||
selector[spec.path] = 1;
|
||||
});
|
||||
self._selectorForAffectedByModifier = new Minimongo.Matcher(selector);
|
||||
this._selectorForAffectedByModifier = new Minimongo.Matcher(selector);
|
||||
}
|
||||
|
||||
self._keyComparator = composeComparators(
|
||||
self._sortSpecParts.map((spec, i) => self._keyFieldComparator(i)));
|
||||
this._keyComparator = composeComparators(
|
||||
this._sortSpecParts.map((spec, i) => this._keyFieldComparator(i)));
|
||||
|
||||
// If you specify a matcher for this Sorter, _keyFilter may be set to a
|
||||
// function which selects whether or not a given "sort key" (tuple of values
|
||||
// for the different sort spec fields) is compatible with the selector.
|
||||
self._keyFilter = null;
|
||||
options.matcher && self._useWithMatcher(options.matcher);
|
||||
this._keyFilter = null;
|
||||
options.matcher && this._useWithMatcher(options.matcher);
|
||||
}
|
||||
|
||||
getComparator (options) {
|
||||
const self = this;
|
||||
|
||||
// If sort is specified or have no distances, just use the comparator from
|
||||
// the source specification (which defaults to "everything is equal".
|
||||
// issue #3599
|
||||
// https://docs.mongodb.com/manual/reference/operator/query/near/#sort-operation
|
||||
// sort effectively overrides $near
|
||||
if (self._sortSpecParts.length || !options || !options.distances) {
|
||||
return self._getBaseComparator();
|
||||
if (this._sortSpecParts.length || !options || !options.distances) {
|
||||
return this._getBaseComparator();
|
||||
}
|
||||
|
||||
const distances = options.distances;
|
||||
@@ -112,21 +107,18 @@ export class Sorter {
|
||||
// parts. Returns negative, 0, or positive based on using the sort spec to
|
||||
// compare fields.
|
||||
_compareKeys (key1, key2) {
|
||||
const self = this;
|
||||
if (key1.length !== self._sortSpecParts.length ||
|
||||
key2.length !== self._sortSpecParts.length) {
|
||||
if (key1.length !== this._sortSpecParts.length ||
|
||||
key2.length !== this._sortSpecParts.length) {
|
||||
throw Error("Key has wrong length");
|
||||
}
|
||||
|
||||
return self._keyComparator(key1, key2);
|
||||
return this._keyComparator(key1, key2);
|
||||
}
|
||||
|
||||
// Iterates over each possible "key" from doc (ie, over each branch), calling
|
||||
// 'cb' with the key.
|
||||
_generateKeysFromDoc (doc, cb) {
|
||||
const self = this;
|
||||
|
||||
if (self._sortSpecParts.length === 0)
|
||||
if (this._sortSpecParts.length === 0)
|
||||
throw new Error("can't generate keys without a spec");
|
||||
|
||||
// maps index -> ({'' -> value} or {path -> value})
|
||||
@@ -136,7 +128,7 @@ export class Sorter {
|
||||
|
||||
let knownPaths = null;
|
||||
|
||||
self._sortSpecParts.forEach((spec, whichField) => {
|
||||
this._sortSpecParts.forEach((spec, whichField) => {
|
||||
// Expand any leaf arrays that we find, and ignore those arrays
|
||||
// themselves. (We never sort based on an array itself.)
|
||||
let branches = expandArraysInBranches(spec.lookup(doc), true);
|
||||
@@ -221,21 +213,19 @@ export class Sorter {
|
||||
// Returns a comparator that represents the sort specification (but not
|
||||
// including a possible geoquery distance tie-breaker).
|
||||
_getBaseComparator () {
|
||||
const self = this;
|
||||
|
||||
if (self._sortFunction)
|
||||
return self._sortFunction;
|
||||
if (this._sortFunction)
|
||||
return this._sortFunction;
|
||||
|
||||
// If we're only sorting on geoquery distance and no specs, just say
|
||||
// everything is equal.
|
||||
if (!self._sortSpecParts.length) {
|
||||
if (!this._sortSpecParts.length) {
|
||||
return (doc1, doc2) => 0;
|
||||
}
|
||||
|
||||
return (doc1, doc2) => {
|
||||
const key1 = self._getMinKeyFromDoc(doc1);
|
||||
const key2 = self._getMinKeyFromDoc(doc2);
|
||||
return self._compareKeys(key1, key2);
|
||||
const key1 = this._getMinKeyFromDoc(doc1);
|
||||
const key2 = this._getMinKeyFromDoc(doc2);
|
||||
return this._compareKeys(key1, key2);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -250,18 +240,17 @@ export class Sorter {
|
||||
// 1, y: 3}]} with sort spec {'a.x': 1, 'a.y': 1}, the only keys are [0,5] and
|
||||
// [1,3], and the minimum key is [0,5]; notably, [0,3] is NOT a key.
|
||||
_getMinKeyFromDoc (doc) {
|
||||
const self = this;
|
||||
let minKey = null;
|
||||
|
||||
self._generateKeysFromDoc(doc, key => {
|
||||
if (!self._keyCompatibleWithSelector(key))
|
||||
this._generateKeysFromDoc(doc, key => {
|
||||
if (!this._keyCompatibleWithSelector(key))
|
||||
return;
|
||||
|
||||
if (minKey === null) {
|
||||
minKey = key;
|
||||
return;
|
||||
}
|
||||
if (self._compareKeys(key, minKey) < 0) {
|
||||
if (this._compareKeys(key, minKey) < 0) {
|
||||
minKey = key;
|
||||
}
|
||||
});
|
||||
@@ -274,20 +263,17 @@ export class Sorter {
|
||||
}
|
||||
|
||||
_getPaths () {
|
||||
const self = this;
|
||||
return self._sortSpecParts.map(part => part.path);
|
||||
return this._sortSpecParts.map(part => part.path);
|
||||
}
|
||||
|
||||
_keyCompatibleWithSelector (key) {
|
||||
const self = this;
|
||||
return !self._keyFilter || self._keyFilter(key);
|
||||
return !this._keyFilter || this._keyFilter(key);
|
||||
}
|
||||
|
||||
// Given an index 'i', returns a comparator that compares two key arrays based
|
||||
// on field 'i'.
|
||||
_keyFieldComparator (i) {
|
||||
const self = this;
|
||||
const invert = !self._sortSpecParts[i].ascending;
|
||||
const invert = !this._sortSpecParts[i].ascending;
|
||||
return (key1, key2) => {
|
||||
let compare = LocalCollection._f._cmp(key1[i], key2[i]);
|
||||
if (invert)
|
||||
@@ -316,15 +302,13 @@ export class Sorter {
|
||||
// subtle and undocumented; we've gotten as close as we can figure out based
|
||||
// on our understanding of Mongo's behavior.
|
||||
_useWithMatcher (matcher) {
|
||||
const self = this;
|
||||
|
||||
if (self._keyFilter)
|
||||
if (this._keyFilter)
|
||||
throw Error("called _useWithMatcher twice?");
|
||||
|
||||
// If we are only sorting by distance, then we're not going to bother to
|
||||
// build a key filter.
|
||||
// XXX figure out how geoqueries interact with this stuff
|
||||
if (!self._sortSpecParts.length)
|
||||
if (!this._sortSpecParts.length)
|
||||
return;
|
||||
|
||||
const selector = matcher._selector;
|
||||
@@ -335,7 +319,7 @@ export class Sorter {
|
||||
return;
|
||||
|
||||
const constraintsByPath = {};
|
||||
self._sortSpecParts.forEach((spec, i) => {
|
||||
this._sortSpecParts.forEach((spec, i) => {
|
||||
constraintsByPath[spec.path] = [];
|
||||
});
|
||||
|
||||
@@ -394,10 +378,10 @@ export class Sorter {
|
||||
// others; we shouldn't create a key filter unless the first sort field is
|
||||
// restricted, though after that point we can restrict the other sort fields
|
||||
// or not as we wish.
|
||||
if (!constraintsByPath[self._sortSpecParts[0].path].length)
|
||||
if (!constraintsByPath[this._sortSpecParts[0].path].length)
|
||||
return;
|
||||
|
||||
self._keyFilter = key => self._sortSpecParts.every((specPart, index) => constraintsByPath[specPart.path].every(f => f(key[index])));
|
||||
this._keyFilter = key => this._sortSpecParts.every((specPart, index) => constraintsByPath[specPart.path].every(f => f(key[index])));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user