Fix Mongo queries with {$regex: /foo/}

The bug manifested as incorrect cursor de-duping, making
queries on different regular expressions "de-dup" into one,
which, combined with hot code reload caused crazy seeming
things such as needing to save twice to see a code change
executed.
This commit is contained in:
Avital Oliver
2013-07-29 11:33:19 -07:00
parent 1297034201
commit f7d991cc87
2 changed files with 45 additions and 19 deletions

View File

@@ -261,17 +261,11 @@ Meteor.Collection._rewriteSelector = function (selector) {
var ret = {};
_.each(selector, function (value, key) {
// Mongo supports both {field: /foo/} and {field: {$regex: /foo/}}
if (value instanceof RegExp) {
ret[key] = {$regex: value.source};
var regexOptions = '';
// JS RegExp objects support 'i', 'm', and 'g'. Mongo regex $options
// support 'i', 'm', 'x', and 's'. So we support 'i' and 'm' here.
if (value.ignoreCase)
regexOptions += 'i';
if (value.multiline)
regexOptions += 'm';
if (regexOptions)
ret[key].$options = regexOptions;
ret[key] = convertRegexpToMongoSelector(value);
} else if (value && value.$regex instanceof RegExp) {
ret[key] = convertRegexpToMongoSelector(value.$regex);
}
else if (_.contains(['$or','$and','$nor'], key)) {
// Translate lower levels of $and/$or/$nor
@@ -279,12 +273,32 @@ Meteor.Collection._rewriteSelector = function (selector) {
return Meteor.Collection._rewriteSelector(v);
});
}
else
else {
ret[key] = value;
}
});
return ret;
};
// convert a JS RegExp object to a Mongo {$regex: ..., $options: ...}
// selector
var convertRegexpToMongoSelector = function (regexp) {
check(regexp, RegExp); // safety belt
var selector = {$regex: regexp.source};
var regexOptions = '';
// JS RegExp objects support 'i', 'm', and 'g'. Mongo regex $options
// support 'i', 'm', 'x', and 's'. So we support 'i' and 'm' here.
if (regexp.ignoreCase)
regexOptions += 'i';
if (regexp.multiline)
regexOptions += 'm';
if (regexOptions)
selector.$options = regexOptions;
return selector;
};
var throwIfSelectorIsNotId = function (selector, methodName) {
if (!LocalCollection._selectorIsIdPerhapsAsObject(selector)) {
throw new Meteor.Error(

View File

@@ -876,8 +876,12 @@ if (Meteor.isServer) {
Tinytest.add('mongo-livedata - rewrite selector', function (test) {
test.equal(Meteor.Collection._rewriteSelector({x: /^o+B/im}),
{x: {$regex: '^o+B', $options: 'im'}});
test.equal(Meteor.Collection._rewriteSelector({x: {$regex: /^o+B/im}}),
{x: {$regex: '^o+B', $options: 'im'}});
test.equal(Meteor.Collection._rewriteSelector({x: /^o+B/}),
{x: {$regex: '^o+B'}});
test.equal(Meteor.Collection._rewriteSelector({x: {$regex: /^o+B/}}),
{x: {$regex: '^o+B'}});
test.equal(Meteor.Collection._rewriteSelector('foo'),
{_id: 'foo'});
@@ -886,13 +890,15 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) {
{'$or': [
{x: /^o/},
{y: /^p/},
{z: 'q'}
{z: 'q'},
{w: {$regex: /^r/}}
]}
),
{'$or': [
{x: {$regex: '^o'}},
{y: {$regex: '^p'}},
{z: 'q'}
{z: 'q'},
{w: {$regex: '^r'}}
]}
);
@@ -901,22 +907,28 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) {
{'$or': [
{'$and': [
{x: /^a/i},
{y: /^b/}
{y: /^b/},
{z: {$regex: /^c/i}},
{w: {$regex: '^[abc]', $options: 'i'}} // make sure we don't break vanilla selectors
]},
{'$nor': [
{s: /^c/},
{t: /^d/i}
{s: /^d/},
{t: /^e/i},
{u: {$regex: /^f/i}}
]}
]}
),
{'$or': [
{'$and': [
{x: {$regex: '^a', $options: 'i'}},
{y: {$regex: '^b'}}
{y: {$regex: '^b'}},
{z: {$regex: '^c', $options: 'i'}},
{w: {$regex: '^[abc]', $options: 'i'}}
]},
{'$nor': [
{s: {$regex: '^c'}},
{t: {$regex: '^d', $options: 'i'}}
{s: {$regex: '^d'}},
{t: {$regex: '^e', $options: 'i'}},
{u: {$regex: '^f', $options: 'i'}}
]}
]}
);