mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into shark
This commit is contained in:
@@ -42,7 +42,7 @@ LocalCollection = function (options) {
|
||||
// ordered: bool. ordered queries have addedBefore/movedBefore callbacks.
|
||||
// results: array (ordered) or object (unordered) of current results
|
||||
// (aliased with self._docs!)
|
||||
// results_snapshot: snapshot of results. null if not paused.
|
||||
// resultsSnapshot: snapshot of results. null if not paused.
|
||||
// cursor: Cursor object for the query.
|
||||
// selector, sorter, (callbacks): functions
|
||||
self.queries = {};
|
||||
@@ -128,7 +128,7 @@ LocalCollection.Cursor = function (collection, selector, options) {
|
||||
self.fields = options.fields;
|
||||
|
||||
if (self.fields)
|
||||
self.projection_f = LocalCollection._compileProjection(self.fields);
|
||||
self.projectionFn = LocalCollection._compileProjection(self.fields);
|
||||
|
||||
self._transform = LocalCollection.wrapTransform(options.transform);
|
||||
|
||||
@@ -170,7 +170,7 @@ LocalCollection.Cursor.prototype.forEach = function (callback, thisArg) {
|
||||
var self = this;
|
||||
|
||||
if (self.db_objects === null)
|
||||
self.db_objects = self._getRawObjects(true);
|
||||
self.db_objects = self._getRawObjects({ordered: true});
|
||||
|
||||
if (self.reactive)
|
||||
self._depend({
|
||||
@@ -181,8 +181,8 @@ LocalCollection.Cursor.prototype.forEach = function (callback, thisArg) {
|
||||
|
||||
while (self.cursor_pos < self.db_objects.length) {
|
||||
var elt = EJSON.clone(self.db_objects[self.cursor_pos]);
|
||||
if (self.projection_f)
|
||||
elt = self.projection_f(elt);
|
||||
if (self.projectionFn)
|
||||
elt = self.projectionFn(elt);
|
||||
if (self._transform)
|
||||
elt = self._transform(elt);
|
||||
callback.call(thisArg, elt, self.cursor_pos, self);
|
||||
@@ -220,7 +220,7 @@ LocalCollection.Cursor.prototype.count = function () {
|
||||
true /* allow the observe to be unordered */);
|
||||
|
||||
if (self.db_objects === null)
|
||||
self.db_objects = self._getRawObjects(true);
|
||||
self.db_objects = self._getRawObjects({ordered: true});
|
||||
|
||||
return self.db_objects.length;
|
||||
};
|
||||
@@ -302,12 +302,10 @@ _.extend(LocalCollection.Cursor.prototype, {
|
||||
sorter: ordered && self.sorter,
|
||||
distances: (
|
||||
self.matcher.hasGeoQuery() && ordered && new LocalCollection._IdMap),
|
||||
results_snapshot: null,
|
||||
resultsSnapshot: null,
|
||||
ordered: ordered,
|
||||
cursor: self,
|
||||
observeChanges: options.observeChanges,
|
||||
fields: self.fields,
|
||||
projection_f: self.projection_f
|
||||
projectionFn: self.projectionFn
|
||||
};
|
||||
var qid;
|
||||
|
||||
@@ -317,9 +315,10 @@ _.extend(LocalCollection.Cursor.prototype, {
|
||||
qid = self.collection.next_qid++;
|
||||
self.collection.queries[qid] = query;
|
||||
}
|
||||
query.results = self._getRawObjects(ordered, query.distances);
|
||||
query.results = self._getRawObjects({
|
||||
ordered: ordered, distances: query.distances});
|
||||
if (self.collection.paused)
|
||||
query.results_snapshot = (ordered ? [] : new LocalCollection._IdMap);
|
||||
query.resultsSnapshot = (ordered ? [] : new LocalCollection._IdMap);
|
||||
|
||||
// wrap callbacks we were passed. callbacks only fire when not paused and
|
||||
// are never undefined
|
||||
@@ -335,17 +334,18 @@ _.extend(LocalCollection.Cursor.prototype, {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
|
||||
if (fieldsIndex !== undefined && self.projection_f) {
|
||||
args[fieldsIndex] = self.projection_f(args[fieldsIndex]);
|
||||
if (self.collection.paused)
|
||||
return;
|
||||
|
||||
if (fieldsIndex !== undefined && self.projectionFn) {
|
||||
args[fieldsIndex] = self.projectionFn(args[fieldsIndex]);
|
||||
if (ignoreEmptyFields && _.isEmpty(args[fieldsIndex]))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.collection.paused) {
|
||||
self.collection._observeQueue.queueTask(function () {
|
||||
f.apply(context, args);
|
||||
});
|
||||
}
|
||||
self.collection._observeQueue.queueTask(function () {
|
||||
f.apply(context, args);
|
||||
});
|
||||
};
|
||||
};
|
||||
query.added = wrapCallback(options.added, 1);
|
||||
@@ -413,13 +413,13 @@ _.extend(LocalCollection.Cursor.prototype, {
|
||||
// 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.
|
||||
LocalCollection.Cursor.prototype._getRawObjects = function (ordered,
|
||||
distances) {
|
||||
LocalCollection.Cursor.prototype._getRawObjects = function (options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
// XXX use OrderedDict instead of array, and make IdMap and OrderedDict
|
||||
// compatible
|
||||
var results = ordered ? [] : new LocalCollection._IdMap;
|
||||
var results = options.ordered ? [] : new LocalCollection._IdMap;
|
||||
|
||||
// fast path for single ID value
|
||||
if (self._selectorId !== undefined) {
|
||||
@@ -431,7 +431,7 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered,
|
||||
|
||||
var selectedDoc = self.collection._docs.get(self._selectorId);
|
||||
if (selectedDoc) {
|
||||
if (ordered)
|
||||
if (options.ordered)
|
||||
results.push(selectedDoc);
|
||||
else
|
||||
results.set(self._selectorId, selectedDoc);
|
||||
@@ -444,17 +444,20 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered,
|
||||
// in the observeChanges case, distances is actually part of the "query" (ie,
|
||||
// live results set) object. in other cases, distances is only used inside
|
||||
// this function.
|
||||
if (self.matcher.hasGeoQuery() && ordered) {
|
||||
if (distances)
|
||||
var distances;
|
||||
if (self.matcher.hasGeoQuery() && options.ordered) {
|
||||
if (options.distances) {
|
||||
distances = options.distances;
|
||||
distances.clear();
|
||||
else
|
||||
} else {
|
||||
distances = new LocalCollection._IdMap();
|
||||
}
|
||||
}
|
||||
|
||||
self.collection._docs.forEach(function (doc, id) {
|
||||
var matchResult = self.matcher.documentMatches(doc);
|
||||
if (matchResult.result) {
|
||||
if (ordered) {
|
||||
if (options.ordered) {
|
||||
results.push(doc);
|
||||
if (distances && matchResult.distance !== undefined)
|
||||
distances.set(id, matchResult.distance);
|
||||
@@ -470,7 +473,7 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered,
|
||||
return true; // continue
|
||||
});
|
||||
|
||||
if (!ordered)
|
||||
if (!options.ordered)
|
||||
return results;
|
||||
|
||||
if (self.sorter) {
|
||||
@@ -560,31 +563,60 @@ LocalCollection.prototype.insert = function (doc, callback) {
|
||||
return id;
|
||||
};
|
||||
|
||||
LocalCollection.prototype.remove = function (selector, callback) {
|
||||
// Iterates over a subset of documents that could match selector; calls
|
||||
// f(doc, id) on each of them. Specifically, if selector specifies
|
||||
// specific _id's, it only looks at those. doc is *not* cloned: it is the
|
||||
// same object that is in _docs.
|
||||
LocalCollection.prototype._eachPossiblyMatchingDoc = function (selector, f) {
|
||||
var self = this;
|
||||
var remove = [];
|
||||
|
||||
var queriesToRecompute = [];
|
||||
var matcher = new Minimongo.Matcher(selector, self);
|
||||
|
||||
// Avoid O(n) for "remove a single doc by ID".
|
||||
var specificIds = LocalCollection._idsMatchedBySelector(selector);
|
||||
if (specificIds) {
|
||||
_.each(specificIds, function (id) {
|
||||
// We still have to run matcher, in case it's something like
|
||||
// {_id: "X", a: 42}
|
||||
for (var i = 0; i < specificIds.length; ++i) {
|
||||
var id = specificIds[i];
|
||||
var doc = self._docs.get(id);
|
||||
if (doc && matcher.documentMatches(doc).result)
|
||||
remove.push(id);
|
||||
});
|
||||
if (doc) {
|
||||
var breakIfFalse = f(doc, id);
|
||||
if (breakIfFalse === false)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self._docs.forEach(function (doc, id) {
|
||||
if (matcher.documentMatches(doc).result) {
|
||||
remove.push(id);
|
||||
self._docs.forEach(f);
|
||||
}
|
||||
};
|
||||
|
||||
LocalCollection.prototype.remove = function (selector, callback) {
|
||||
var 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, {})) {
|
||||
var result = self._docs.size();
|
||||
self._docs.clear();
|
||||
_.each(self.queries, function (query) {
|
||||
if (query.ordered) {
|
||||
query.results = [];
|
||||
} else {
|
||||
query.results.clear();
|
||||
}
|
||||
});
|
||||
if (callback) {
|
||||
Meteor.defer(function () {
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var matcher = new Minimongo.Matcher(selector, self);
|
||||
var remove = [];
|
||||
self._eachPossiblyMatchingDoc(selector, function (doc, id) {
|
||||
if (matcher.documentMatches(doc).result)
|
||||
remove.push(id);
|
||||
});
|
||||
|
||||
var queriesToRecompute = [];
|
||||
var queryRemove = [];
|
||||
for (var i = 0; i < remove.length; i++) {
|
||||
var removeId = remove[i];
|
||||
@@ -615,7 +647,7 @@ LocalCollection.prototype.remove = function (selector, callback) {
|
||||
LocalCollection._recomputeResults(query);
|
||||
});
|
||||
self._observeQueue.drain();
|
||||
var result = remove.length;
|
||||
result = remove.length;
|
||||
if (callback)
|
||||
Meteor.defer(function () {
|
||||
callback(null, result);
|
||||
@@ -638,7 +670,7 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) {
|
||||
// Save the original results of any query that we might need to
|
||||
// _recomputeResults on, because _modifyAndNotify will mutate the objects in
|
||||
// it. (We don't need to save the original results of paused queries because
|
||||
// they already have a results_snapshot and we won't be diffing in
|
||||
// they already have a resultsSnapshot and we won't be diffing in
|
||||
// _recomputeResults.)
|
||||
var qidToOriginalResults = {};
|
||||
_.each(self.queries, function (query, qid) {
|
||||
@@ -651,7 +683,7 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) {
|
||||
|
||||
var updateCount = 0;
|
||||
|
||||
self._docs.forEach(function (doc, id) {
|
||||
self._eachPossiblyMatchingDoc(selector, function (doc, id) {
|
||||
var queryResult = matcher.documentMatches(doc);
|
||||
if (queryResult.result) {
|
||||
// XXX Should we save the original even if mod ends up being a no-op?
|
||||
@@ -860,7 +892,8 @@ LocalCollection._recomputeResults = function (query, oldResults) {
|
||||
oldResults = query.results;
|
||||
if (query.distances)
|
||||
query.distances.clear();
|
||||
query.results = query.cursor._getRawObjects(query.ordered, query.distances);
|
||||
query.results = query.cursor._getRawObjects({
|
||||
ordered: query.ordered, distances: query.distances});
|
||||
|
||||
if (!query.paused) {
|
||||
LocalCollection._diffQueryChanges(
|
||||
@@ -956,7 +989,7 @@ LocalCollection.prototype.pauseObservers = function () {
|
||||
for (var qid in this.queries) {
|
||||
var query = this.queries[qid];
|
||||
|
||||
query.results_snapshot = EJSON.clone(query.results);
|
||||
query.resultsSnapshot = EJSON.clone(query.results);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -979,8 +1012,8 @@ LocalCollection.prototype.resumeObservers = function () {
|
||||
// Diff the current results against the snapshot and send to observers.
|
||||
// pass the query object for its observer callbacks.
|
||||
LocalCollection._diffQueryChanges(
|
||||
query.ordered, query.results_snapshot, query.results, query);
|
||||
query.results_snapshot = null;
|
||||
query.ordered, query.resultsSnapshot, query.results, query);
|
||||
query.resultsSnapshot = null;
|
||||
}
|
||||
self._observeQueue.drain();
|
||||
};
|
||||
|
||||
@@ -2170,7 +2170,8 @@ Tinytest.add("minimongo - observe ordered", function (test) {
|
||||
handle.stop();
|
||||
|
||||
// test _suppress_initial
|
||||
handle = c.find({}, {sort: {a: -1}}).observe(_.extend(cbs, {_suppress_initial: true}));
|
||||
handle = c.find({}, {sort: {a: -1}}).observe(_.extend({
|
||||
_suppress_initial: true}, cbs));
|
||||
test.equal(operations.shift(), undefined);
|
||||
c.insert({a:100});
|
||||
test.equal(operations.shift(), ['added', {a:100}, 0, idA2]);
|
||||
@@ -2197,6 +2198,21 @@ Tinytest.add("minimongo - observe ordered", function (test) {
|
||||
test.equal(operations.shift(), ['changed', {a:3.5}, 0, {a:3}]);
|
||||
handle.stop();
|
||||
|
||||
// test observe limit with pre-existing docs
|
||||
c.remove({});
|
||||
c.insert({a: 1});
|
||||
c.insert({_id: 'two', a: 2});
|
||||
c.insert({a: 3});
|
||||
handle = c.find({}, {sort: {a: 1}, limit: 2}).observe(cbs);
|
||||
test.equal(operations.shift(), ['added', {a:1}, 0, null]);
|
||||
test.equal(operations.shift(), ['added', {a:2}, 1, null]);
|
||||
test.equal(operations.shift(), undefined);
|
||||
c.remove({a: 2});
|
||||
test.equal(operations.shift(), ['removed', 'two', 1, {a:2}]);
|
||||
test.equal(operations.shift(), ['added', {a:3}, 1, null]);
|
||||
test.equal(operations.shift(), undefined);
|
||||
handle.stop();
|
||||
|
||||
// test _no_indices
|
||||
|
||||
c.remove({});
|
||||
@@ -2565,6 +2581,14 @@ Tinytest.add("minimongo - pause", function (test) {
|
||||
test.equal(operations.shift(), ['changed', {a:3}, 0, {a:1}]);
|
||||
test.length(operations, 0);
|
||||
|
||||
// test special case for remove({})
|
||||
c.pauseObservers();
|
||||
test.equal(c.remove({}), 1);
|
||||
test.length(operations, 0);
|
||||
c.resumeObservers();
|
||||
test.equal(operations.shift(), ['removed', 1, 0, {a:3}]);
|
||||
test.length(operations, 0);
|
||||
|
||||
h.stop();
|
||||
});
|
||||
|
||||
|
||||
@@ -70,6 +70,21 @@
|
||||
// Collection Functions
|
||||
// --------------------
|
||||
|
||||
// METEOR CHANGE: Define _isArguments instead of depending on
|
||||
// _.isArguments which is defined using each. In looksLikeArray
|
||||
// (which each depends on), we then use _isArguments instead of
|
||||
// _.isArguments.
|
||||
var _isArguments = function (obj) {
|
||||
return toString.call(obj) === '[object Arguments]';
|
||||
};
|
||||
// Define a fallback version of the method in browsers (ahem, IE), where
|
||||
// there isn't any inspectable "Arguments" type.
|
||||
if (!_isArguments(arguments)) {
|
||||
_isArguments = function (obj) {
|
||||
return !!(obj && hasOwnProperty.call(obj, 'callee'));
|
||||
};
|
||||
}
|
||||
|
||||
// METEOR CHANGE: _.each({length: 5}) should be treated like an object, not an
|
||||
// array. This looksLikeArray function is introduced by Meteor, and replaces
|
||||
// all instances of `obj.length === +obj.length`.
|
||||
@@ -77,7 +92,8 @@
|
||||
// https://github.com/jashkenas/underscore/issues/770
|
||||
var looksLikeArray = function (obj) {
|
||||
return (obj.length === +obj.length
|
||||
&& (_.isArguments(obj) || obj.constructor !== Object));
|
||||
// _.isArguments not yet necessarily defined here
|
||||
&& (_isArguments(obj) || obj.constructor !== Object));
|
||||
};
|
||||
|
||||
// The cornerstone, an `each` implementation, aka `forEach`.
|
||||
|
||||
Reference in New Issue
Block a user