* Adds tests that reproduce the issue * Use values from keys to force include when needed
This commit is contained in:
@@ -2655,6 +2655,46 @@ describe('Parse.Query testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('select nested keys 2 level without include (issue #3185)', function(done) {
|
||||||
|
var Foobar = new Parse.Object('Foobar');
|
||||||
|
var BarBaz = new Parse.Object('Barbaz');
|
||||||
|
var Bazoo = new Parse.Object('Bazoo');
|
||||||
|
|
||||||
|
Bazoo.set('some', 'thing');
|
||||||
|
Bazoo.set('otherSome', 'value');
|
||||||
|
Bazoo.save().then(() => {
|
||||||
|
BarBaz.set('key', 'value');
|
||||||
|
BarBaz.set('otherKey', 'value');
|
||||||
|
BarBaz.set('bazoo', Bazoo);
|
||||||
|
return BarBaz.save();
|
||||||
|
}).then(() => {
|
||||||
|
Foobar.set('foo', 'bar');
|
||||||
|
Foobar.set('fizz', 'buzz');
|
||||||
|
Foobar.set('barBaz', BarBaz);
|
||||||
|
return Foobar.save();
|
||||||
|
}).then(function(savedFoobar){
|
||||||
|
var foobarQuery = new Parse.Query('Foobar');
|
||||||
|
foobarQuery.select(['fizz', 'barBaz.key', 'barBaz.bazoo.some']);
|
||||||
|
return foobarQuery.get(savedFoobar.id);
|
||||||
|
}).then((foobarObj) => {
|
||||||
|
equal(foobarObj.get('fizz'), 'buzz');
|
||||||
|
equal(foobarObj.get('foo'), undefined);
|
||||||
|
if (foobarObj.has('barBaz')) {
|
||||||
|
equal(foobarObj.get('barBaz').get('key'), 'value');
|
||||||
|
equal(foobarObj.get('barBaz').get('otherKey'), undefined);
|
||||||
|
if (foobarObj.get('barBaz').has('bazoo')) {
|
||||||
|
equal(foobarObj.get('barBaz').get('bazoo').get('some'), 'thing');
|
||||||
|
equal(foobarObj.get('barBaz').get('bazoo').get('otherSome'), undefined);
|
||||||
|
} else {
|
||||||
|
fail('bazoo should be set');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail('barBaz should be set');
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
it('properly handles nested ors', function(done) {
|
it('properly handles nested ors', function(done) {
|
||||||
var objects = [];
|
var objects = [];
|
||||||
while(objects.length != 4) {
|
while(objects.length != 4) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ var SchemaController = require('./Controllers/SchemaController');
|
|||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
const triggers = require('./triggers');
|
const triggers = require('./triggers');
|
||||||
|
|
||||||
|
const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt'];
|
||||||
// restOptions can include:
|
// restOptions can include:
|
||||||
// skip
|
// skip
|
||||||
// limit
|
// limit
|
||||||
@@ -52,15 +53,36 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
|
|||||||
// this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']]
|
// this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']]
|
||||||
this.include = [];
|
this.include = [];
|
||||||
|
|
||||||
|
// If we have keys, we probably want to force some includes (n-1 level)
|
||||||
|
// See issue: https://github.com/ParsePlatform/parse-server/issues/3185
|
||||||
|
if (restOptions.hasOwnProperty('keys')) {
|
||||||
|
const keysForInclude = restOptions.keys.split(',').filter((key) => {
|
||||||
|
// At least 2 components
|
||||||
|
return key.split(".").length > 1;
|
||||||
|
}).map((key) => {
|
||||||
|
// Slice the last component (a.b.c -> a.b)
|
||||||
|
// Otherwise we'll include one level too much.
|
||||||
|
return key.slice(0, key.lastIndexOf("."));
|
||||||
|
}).join(',');
|
||||||
|
|
||||||
|
// Concat the possibly present include string with the one from the keys
|
||||||
|
// Dedup / sorting is handle in 'include' case.
|
||||||
|
if (keysForInclude.length > 0) {
|
||||||
|
if (!restOptions.include || restOptions.include.length == 0) {
|
||||||
|
restOptions.include = keysForInclude;
|
||||||
|
} else {
|
||||||
|
restOptions.include += "," + keysForInclude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var option in restOptions) {
|
for (var option in restOptions) {
|
||||||
switch(option) {
|
switch(option) {
|
||||||
case 'keys':
|
case 'keys': {
|
||||||
this.keys = new Set(restOptions.keys.split(','));
|
const keys = restOptions.keys.split(',').concat(AlwaysSelectedKeys);
|
||||||
// Add the default
|
this.keys = Array.from(new Set(keys));
|
||||||
this.keys.add('objectId');
|
|
||||||
this.keys.add('createdAt');
|
|
||||||
this.keys.add('updatedAt');
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'count':
|
case 'count':
|
||||||
this.doCount = true;
|
this.doCount = true;
|
||||||
break;
|
break;
|
||||||
@@ -80,22 +102,26 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
|
|||||||
}
|
}
|
||||||
this.findOptions.sort = sortMap;
|
this.findOptions.sort = sortMap;
|
||||||
break;
|
break;
|
||||||
case 'include':
|
case 'include': {
|
||||||
var paths = restOptions.include.split(',');
|
const paths = restOptions.include.split(',');
|
||||||
var pathSet = {};
|
// Load the existing includes (from keys)
|
||||||
for (var path of paths) {
|
const pathSet = paths.reduce((memo, path) => {
|
||||||
// Add all prefixes with a .-split to pathSet
|
// Split each paths on . (a.b.c -> [a,b,c])
|
||||||
var parts = path.split('.');
|
// reduce to create all paths
|
||||||
for (var len = 1; len <= parts.length; len++) {
|
// ([a,b,c] -> {a: true, 'a.b': true, 'a.b.c': true})
|
||||||
pathSet[parts.slice(0, len).join('.')] = true;
|
return path.split('.').reduce((memo, path, index, parts) => {
|
||||||
}
|
memo[parts.slice(0, index+1).join('.')] = true;
|
||||||
}
|
return memo;
|
||||||
this.include = Object.keys(pathSet).sort((a, b) => {
|
}, memo);
|
||||||
return a.length - b.length;
|
}, {});
|
||||||
}).map((s) => {
|
|
||||||
|
this.include = Object.keys(pathSet).map((s) => {
|
||||||
return s.split('.');
|
return s.split('.');
|
||||||
|
}).sort((a, b) => {
|
||||||
|
return a.length - b.length; // Sort by number of components
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'redirectClassNameForKey':
|
case 'redirectClassNameForKey':
|
||||||
this.redirectKey = restOptions.redirectClassNameForKey;
|
this.redirectKey = restOptions.redirectClassNameForKey;
|
||||||
this.redirectClassName = null;
|
this.redirectClassName = null;
|
||||||
@@ -421,7 +447,7 @@ RestQuery.prototype.runFind = function(options = {}) {
|
|||||||
}
|
}
|
||||||
let findOptions = Object.assign({}, this.findOptions);
|
let findOptions = Object.assign({}, this.findOptions);
|
||||||
if (this.keys) {
|
if (this.keys) {
|
||||||
findOptions.keys = Array.from(this.keys).map((key) => {
|
findOptions.keys = this.keys.map((key) => {
|
||||||
return key.split('.')[0];
|
return key.split('.')[0];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user