Skip to content

Commit f0ebb7b

Browse files
committed
Merge pull request #1295 from drew-gross/test-1259
Fixes #1271
2 parents 781e81d + 73bca3b commit f0ebb7b

File tree

5 files changed

+102
-74
lines changed

5 files changed

+102
-74
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"commander": "^2.9.0",
2727
"deepcopy": "^0.6.1",
2828
"express": "^4.13.4",
29+
"intersect": "^1.0.1",
2930
"lru-cache": "^4.0.0",
3031
"mailgun-js": "^0.7.7",
3132
"mime": "^1.3.4",

spec/ParseQuery.spec.js

+18
Original file line numberDiff line numberDiff line change
@@ -2228,4 +2228,22 @@ describe('Parse.Query testing', () => {
22282228
});
22292229
});
22302230
});
2231+
2232+
it('objectId containedIn with multiple large array', done => {
2233+
let obj = new Parse.Object('MyClass');
2234+
obj.save().then(obj => {
2235+
let longListOfStrings = [];
2236+
for (let i = 0; i < 130; i++) {
2237+
longListOfStrings.push(i.toString());
2238+
}
2239+
longListOfStrings.push(obj.id);
2240+
let q = new Parse.Query('MyClass');
2241+
q.containedIn('objectId', longListOfStrings);
2242+
q.containedIn('objectId', longListOfStrings);
2243+
return q.find();
2244+
}).then(results => {
2245+
expect(results.length).toEqual(1);
2246+
done();
2247+
});
2248+
});
22312249
});

spec/ParseRelation.spec.js

+37-33
Original file line numberDiff line numberDiff line change
@@ -248,46 +248,50 @@ describe('Parse.Relation testing', () => {
248248
});
249249
});
250250

251-
it("queries on relation fields with multiple ins", (done) => {
252-
var ChildObject = Parse.Object.extend("ChildObject");
253-
var childObjects = [];
254-
for (var i = 0; i < 10; i++) {
251+
it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
252+
let ChildObject = Parse.Object.extend("ChildObject");
253+
let childObjects = [];
254+
for (let i = 0; i < 10; i++) {
255255
childObjects.push(new ChildObject({x: i}));
256256
}
257257

258258
Parse.Object.saveAll(childObjects).then(() => {
259-
var ParentObject = Parse.Object.extend("ParentObject");
260-
var parent = new ParentObject();
259+
let ParentObject = Parse.Object.extend("ParentObject");
260+
let parent = new ParentObject();
261261
parent.set("x", 4);
262-
var relation = parent.relation("child");
263-
relation.add(childObjects[0]);
264-
relation.add(childObjects[1]);
265-
relation.add(childObjects[2]);
266-
var parent2 = new ParentObject();
262+
let parent1Children = parent.relation("child");
263+
parent1Children.add(childObjects[0]);
264+
parent1Children.add(childObjects[1]);
265+
parent1Children.add(childObjects[2]);
266+
let parent2 = new ParentObject();
267267
parent2.set("x", 3);
268-
var relation2 = parent2.relation("child");
269-
relation2.add(childObjects[4]);
270-
relation2.add(childObjects[5]);
271-
relation2.add(childObjects[6]);
272-
273-
var otherChild2 = parent2.relation("otherChild");
274-
otherChild2.add(childObjects[0]);
275-
otherChild2.add(childObjects[1]);
276-
otherChild2.add(childObjects[2]);
277-
278-
var parents = [];
279-
parents.push(parent);
280-
parents.push(parent2);
281-
return Parse.Object.saveAll(parents);
268+
let parent2Children = parent2.relation("child");
269+
parent2Children.add(childObjects[4]);
270+
parent2Children.add(childObjects[5]);
271+
parent2Children.add(childObjects[6]);
272+
273+
let parent2OtherChildren = parent2.relation("otherChild");
274+
parent2OtherChildren.add(childObjects[0]);
275+
parent2OtherChildren.add(childObjects[1]);
276+
parent2OtherChildren.add(childObjects[2]);
277+
278+
return Parse.Object.saveAll([parent, parent2]);
282279
}).then(() => {
283-
var query = new Parse.Query(ParentObject);
284-
var objects = [];
285-
objects.push(childObjects[0]);
286-
query.containedIn("child", objects);
287-
query.containedIn("otherChild", [childObjects[0]]);
288-
return query.find();
289-
}).then((list) => {
290-
equal(list.length, 2, "There should be 2 results");
280+
let objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
281+
objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
282+
objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
283+
return objectsWithChild0InBothChildren.find();
284+
}).then(objectsWithChild0InBothChildren => {
285+
//No parent has child 0 in both it's "child" and "otherChild" field;
286+
expect(objectsWithChild0InBothChildren.length).toEqual(0);
287+
}).then(() => {
288+
let objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
289+
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
290+
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
291+
return objectsWithChild4andOtherChild1.find();
292+
}).then(objects => {
293+
// parent2 has child 4 and otherChild 1
294+
expect(objects.length).toEqual(1);
291295
done();
292296
});
293297
});

src/Controllers/DatabaseController.js

+42-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// A database adapter that works with data exported from the hosted
22
// Parse database.
33

4+
import intersect from 'intersect';
5+
46
var mongodb = require('mongodb');
57
var Parse = require('parse/node').Parse;
68

@@ -492,18 +494,28 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
492494
}
493495
};
494496

495-
DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
496-
if (typeof query.objectId == 'string') {
497-
// Add equality op as we are sure
498-
// we had a constraint on that one
499-
query.objectId = {'$eq': query.objectId};
497+
DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
498+
let idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
499+
let idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
500+
let idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
501+
502+
let allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
503+
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
504+
505+
let idsIntersection = [];
506+
if (totalLength > 125) {
507+
idsIntersection = intersect.big(allIds);
508+
} else {
509+
idsIntersection = intersect(allIds);
500510
}
501-
query.objectId = query.objectId || {};
502-
let queryIn = [].concat(query.objectId['$in'] || [], ids || []);
503-
// make a set and spread to remove duplicates
504-
// replace the $in operator as other constraints
505-
// may be set
506-
query.objectId['$in'] = [...new Set(queryIn)];
511+
512+
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
513+
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
514+
// is expected though.
515+
if (!('objectId' in query) || typeof query.objectId === 'string') {
516+
query.objectId = {};
517+
}
518+
query.objectId['$in'] = idsIntersection;
507519

508520
return query;
509521
}
@@ -523,53 +535,47 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
523535
// anything about users, ideally. Then, improve the format of the ACL
524536
// arg to work like the others.
525537
DatabaseController.prototype.find = function(className, query, options = {}) {
526-
var mongoOptions = {};
538+
let mongoOptions = {};
527539
if (options.skip) {
528540
mongoOptions.skip = options.skip;
529541
}
530542
if (options.limit) {
531543
mongoOptions.limit = options.limit;
532544
}
533545

534-
var isMaster = !('acl' in options);
535-
var aclGroup = options.acl || [];
536-
var acceptor = function(schema) {
537-
return schema.hasKeys(className, keysForQuery(query));
538-
};
539-
var schema;
540-
return this.loadSchema(acceptor).then((s) => {
546+
let isMaster = !('acl' in options);
547+
let aclGroup = options.acl || [];
548+
let acceptor = schema => schema.hasKeys(className, keysForQuery(query))
549+
let schema = null;
550+
return this.loadSchema(acceptor).then(s => {
541551
schema = s;
542552
if (options.sort) {
543553
mongoOptions.sort = {};
544-
for (var key in options.sort) {
545-
var mongoKey = transform.transformKey(schema, className, key);
554+
for (let key in options.sort) {
555+
let mongoKey = transform.transformKey(schema, className, key);
546556
mongoOptions.sort[mongoKey] = options.sort[key];
547557
}
548558
}
549559

550560
if (!isMaster) {
551-
var op = 'find';
552-
var k = Object.keys(query);
553-
if (k.length == 1 && typeof query.objectId == 'string') {
554-
op = 'get';
555-
}
561+
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ?
562+
'get' :
563+
'find';
556564
return schema.validatePermission(className, aclGroup, op);
557565
}
558566
return Promise.resolve();
559-
}).then(() => {
560-
return this.reduceRelationKeys(className, query);
561-
}).then(() => {
562-
return this.reduceInRelation(className, query, schema);
563-
}).then(() => {
564-
return this.adaptiveCollection(className);
565-
}).then(collection => {
566-
var mongoWhere = transform.transformWhere(schema, className, query);
567+
})
568+
.then(() => this.reduceRelationKeys(className, query))
569+
.then(() => this.reduceInRelation(className, query, schema))
570+
.then(() => this.adaptiveCollection(className))
571+
.then(collection => {
572+
let mongoWhere = transform.transformWhere(schema, className, query);
567573
if (!isMaster) {
568-
var orParts = [
574+
let orParts = [
569575
{"_rperm" : { "$exists": false }},
570576
{"_rperm" : { "$in" : ["*"]}}
571577
];
572-
for (var acl of aclGroup) {
578+
for (let acl of aclGroup) {
573579
orParts.push({"_rperm" : { "$in" : [acl]}});
574580
}
575581
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};

src/transform.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,12 @@ export function transformKeyValue(schema, className, restKey, restValue, options
187187
// Returns the mongo form of the query.
188188
// Throws a Parse.Error if the input query is invalid.
189189
function transformWhere(schema, className, restWhere) {
190-
var mongoWhere = {};
190+
let mongoWhere = {};
191191
if (restWhere['ACL']) {
192-
throw new Parse.Error(Parse.Error.INVALID_QUERY,
193-
'Cannot query on ACL.');
192+
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
194193
}
195-
for (var restKey in restWhere) {
196-
var out = transformKeyValue(schema, className, restKey, restWhere[restKey],
194+
for (let restKey in restWhere) {
195+
let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
197196
{query: true, validate: true});
198197
mongoWhere[out.key] = out.value;
199198
}

0 commit comments

Comments
 (0)