Merge pull request #1295 from drew-gross/test-1259

Fixes #1271
This commit is contained in:
Florent Vilmart
2016-03-30 23:14:17 -04:00
5 changed files with 100 additions and 72 deletions

View File

@@ -26,6 +26,7 @@
"commander": "^2.9.0", "commander": "^2.9.0",
"deepcopy": "^0.6.1", "deepcopy": "^0.6.1",
"express": "^4.13.4", "express": "^4.13.4",
"intersect": "^1.0.1",
"lru-cache": "^4.0.0", "lru-cache": "^4.0.0",
"mailgun-js": "^0.7.7", "mailgun-js": "^0.7.7",
"mime": "^1.3.4", "mime": "^1.3.4",

View File

@@ -2228,4 +2228,22 @@ describe('Parse.Query testing', () => {
}); });
}); });
}); });
it('objectId containedIn with multiple large array', done => {
let obj = new Parse.Object('MyClass');
obj.save().then(obj => {
let longListOfStrings = [];
for (let i = 0; i < 130; i++) {
longListOfStrings.push(i.toString());
}
longListOfStrings.push(obj.id);
let q = new Parse.Query('MyClass');
q.containedIn('objectId', longListOfStrings);
q.containedIn('objectId', longListOfStrings);
return q.find();
}).then(results => {
expect(results.length).toEqual(1);
done();
});
});
}); });

View File

@@ -248,46 +248,50 @@ describe('Parse.Relation testing', () => {
}); });
}); });
it("queries on relation fields with multiple ins", (done) => { it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
var ChildObject = Parse.Object.extend("ChildObject"); let ChildObject = Parse.Object.extend("ChildObject");
var childObjects = []; let childObjects = [];
for (var i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i})); childObjects.push(new ChildObject({x: i}));
} }
Parse.Object.saveAll(childObjects).then(() => { Parse.Object.saveAll(childObjects).then(() => {
var ParentObject = Parse.Object.extend("ParentObject"); let ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject(); let parent = new ParentObject();
parent.set("x", 4); parent.set("x", 4);
var relation = parent.relation("child"); let parent1Children = parent.relation("child");
relation.add(childObjects[0]); parent1Children.add(childObjects[0]);
relation.add(childObjects[1]); parent1Children.add(childObjects[1]);
relation.add(childObjects[2]); parent1Children.add(childObjects[2]);
var parent2 = new ParentObject(); let parent2 = new ParentObject();
parent2.set("x", 3); parent2.set("x", 3);
var relation2 = parent2.relation("child"); let parent2Children = parent2.relation("child");
relation2.add(childObjects[4]); parent2Children.add(childObjects[4]);
relation2.add(childObjects[5]); parent2Children.add(childObjects[5]);
relation2.add(childObjects[6]); parent2Children.add(childObjects[6]);
var otherChild2 = parent2.relation("otherChild"); let parent2OtherChildren = parent2.relation("otherChild");
otherChild2.add(childObjects[0]); parent2OtherChildren.add(childObjects[0]);
otherChild2.add(childObjects[1]); parent2OtherChildren.add(childObjects[1]);
otherChild2.add(childObjects[2]); parent2OtherChildren.add(childObjects[2]);
var parents = []; return Parse.Object.saveAll([parent, parent2]);
parents.push(parent);
parents.push(parent2);
return Parse.Object.saveAll(parents);
}).then(() => { }).then(() => {
var query = new Parse.Query(ParentObject); let objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
var objects = []; objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
objects.push(childObjects[0]); objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
query.containedIn("child", objects); return objectsWithChild0InBothChildren.find();
query.containedIn("otherChild", [childObjects[0]]); }).then(objectsWithChild0InBothChildren => {
return query.find(); //No parent has child 0 in both it's "child" and "otherChild" field;
}).then((list) => { expect(objectsWithChild0InBothChildren.length).toEqual(0);
equal(list.length, 2, "There should be 2 results"); }).then(() => {
let objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
return objectsWithChild4andOtherChild1.find();
}).then(objects => {
// parent2 has child 4 and otherChild 1
expect(objects.length).toEqual(1);
done(); done();
}); });
}); });

View File

@@ -1,6 +1,8 @@
// A database adapter that works with data exported from the hosted // A database adapter that works with data exported from the hosted
// Parse database. // Parse database.
import intersect from 'intersect';
var mongodb = require('mongodb'); var mongodb = require('mongodb');
var Parse = require('parse/node').Parse; var Parse = require('parse/node').Parse;
@@ -492,18 +494,28 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
} }
}; };
DatabaseController.prototype.addInObjectIdsIds = function(ids, query) { DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
if (typeof query.objectId == 'string') { let idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
// Add equality op as we are sure let idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
// we had a constraint on that one let idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
query.objectId = {'$eq': query.objectId};
let allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
let idsIntersection = [];
if (totalLength > 125) {
idsIntersection = intersect.big(allIds);
} else {
idsIntersection = intersect(allIds);
} }
query.objectId = query.objectId || {};
let queryIn = [].concat(query.objectId['$in'] || [], ids || []); // Need to make sure we don't clobber existing $lt or other constraints on objectId.
// make a set and spread to remove duplicates // Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
// replace the $in operator as other constraints // is expected though.
// may be set if (!('objectId' in query) || typeof query.objectId === 'string') {
query.objectId['$in'] = [...new Set(queryIn)]; query.objectId = {};
}
query.objectId['$in'] = idsIntersection;
return query; return query;
} }
@@ -523,7 +535,7 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
// anything about users, ideally. Then, improve the format of the ACL // anything about users, ideally. Then, improve the format of the ACL
// arg to work like the others. // arg to work like the others.
DatabaseController.prototype.find = function(className, query, options = {}) { DatabaseController.prototype.find = function(className, query, options = {}) {
var mongoOptions = {}; let mongoOptions = {};
if (options.skip) { if (options.skip) {
mongoOptions.skip = options.skip; mongoOptions.skip = options.skip;
} }
@@ -531,45 +543,39 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
mongoOptions.limit = options.limit; mongoOptions.limit = options.limit;
} }
var isMaster = !('acl' in options); let isMaster = !('acl' in options);
var aclGroup = options.acl || []; let aclGroup = options.acl || [];
var acceptor = function(schema) { let acceptor = schema => schema.hasKeys(className, keysForQuery(query))
return schema.hasKeys(className, keysForQuery(query)); let schema = null;
}; return this.loadSchema(acceptor).then(s => {
var schema;
return this.loadSchema(acceptor).then((s) => {
schema = s; schema = s;
if (options.sort) { if (options.sort) {
mongoOptions.sort = {}; mongoOptions.sort = {};
for (var key in options.sort) { for (let key in options.sort) {
var mongoKey = transform.transformKey(schema, className, key); let mongoKey = transform.transformKey(schema, className, key);
mongoOptions.sort[mongoKey] = options.sort[key]; mongoOptions.sort[mongoKey] = options.sort[key];
} }
} }
if (!isMaster) { if (!isMaster) {
var op = 'find'; let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ?
var k = Object.keys(query); 'get' :
if (k.length == 1 && typeof query.objectId == 'string') { 'find';
op = 'get';
}
return schema.validatePermission(className, aclGroup, op); return schema.validatePermission(className, aclGroup, op);
} }
return Promise.resolve(); return Promise.resolve();
}).then(() => { })
return this.reduceRelationKeys(className, query); .then(() => this.reduceRelationKeys(className, query))
}).then(() => { .then(() => this.reduceInRelation(className, query, schema))
return this.reduceInRelation(className, query, schema); .then(() => this.adaptiveCollection(className))
}).then(() => { .then(collection => {
return this.adaptiveCollection(className); let mongoWhere = transform.transformWhere(schema, className, query);
}).then(collection => {
var mongoWhere = transform.transformWhere(schema, className, query);
if (!isMaster) { if (!isMaster) {
var orParts = [ let orParts = [
{"_rperm" : { "$exists": false }}, {"_rperm" : { "$exists": false }},
{"_rperm" : { "$in" : ["*"]}} {"_rperm" : { "$in" : ["*"]}}
]; ];
for (var acl of aclGroup) { for (let acl of aclGroup) {
orParts.push({"_rperm" : { "$in" : [acl]}}); orParts.push({"_rperm" : { "$in" : [acl]}});
} }
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]}; mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};

View File

@@ -187,13 +187,12 @@ export function transformKeyValue(schema, className, restKey, restValue, options
// Returns the mongo form of the query. // Returns the mongo form of the query.
// Throws a Parse.Error if the input query is invalid. // Throws a Parse.Error if the input query is invalid.
function transformWhere(schema, className, restWhere) { function transformWhere(schema, className, restWhere) {
var mongoWhere = {}; let mongoWhere = {};
if (restWhere['ACL']) { if (restWhere['ACL']) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
'Cannot query on ACL.');
} }
for (var restKey in restWhere) { for (let restKey in restWhere) {
var out = transformKeyValue(schema, className, restKey, restWhere[restKey], let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
{query: true, validate: true}); {query: true, validate: true});
mongoWhere[out.key] = out.value; mongoWhere[out.key] = out.value;
} }