Huge performance improvement on roles queries

This commit is contained in:
Florent Vilmart
2016-04-05 20:44:26 -04:00
parent 1bd804693c
commit cbbd66964a
2 changed files with 76 additions and 87 deletions

View File

@@ -114,7 +114,7 @@ describe('Parse Role testing', () => {
return Parse.Object.saveAll(roles, { useMasterKey: true }); return Parse.Object.saveAll(roles, { useMasterKey: true });
}).then( () => { }).then( () => {
auth = new Auth({config: new Config("test"), isMaster: true, user: user}); auth = new Auth({config: new Config("test"), isMaster: true, user: user});
getAllRolesSpy = spyOn(auth, "_getAllRoleNamesForId").and.callThrough(); getAllRolesSpy = spyOn(auth, "_getAllRolesNamesForRoleIds").and.callThrough();
return auth._loadRoles(); return auth._loadRoles();
}).then ( (roles) => { }).then ( (roles) => {
@@ -125,15 +125,14 @@ describe('Parse Role testing', () => {
}); });
// 1 Query for the initial setup // 1 Query for the initial setup
// 4 Queries for all the specific roles // 1 query for the parent roles
// 1 Query for the final $in expect(restExecute.calls.count()).toEqual(2);
expect(restExecute.calls.count()).toEqual(6);
// 4 One query for each of the roles // 1 call for the 1st layer of roles
// 3 Queries for the "RootRole" // 1 call for the 2nd layer
expect(getAllRolesSpy.calls.count()).toEqual(7); expect(getAllRolesSpy.calls.count()).toEqual(2);
done() done()
}).catch( () => { }).catch( (err) => {
fail("should succeed"); fail("should succeed");
done(); done();
}); });
@@ -206,11 +205,11 @@ describe('Parse Role testing', () => {
// For each role, fetch their sibling, what they inherit // For each role, fetch their sibling, what they inherit
// return with result and roleId for later comparison // return with result and roleId for later comparison
let promises = [admin, moderator, contentManager, superModerator].map((role) => { let promises = [admin, moderator, contentManager, superModerator].map((role) => {
return auth._getAllRoleNamesForId(role.id).then((result) => { return auth._getAllRolesNamesForRoleIds([role.id]).then((result) => {
return Parse.Promise.as({ return Parse.Promise.as({
id: role.id, id: role.id,
name: role.get('name'), name: role.get('name'),
roleIds: result roleNames: result
}); });
}) })
}); });
@@ -219,26 +218,25 @@ describe('Parse Role testing', () => {
}).then((results) => { }).then((results) => {
results.forEach((result) => { results.forEach((result) => {
let id = result.id; let id = result.id;
let roleIds = result.roleIds; let roleNames = result.roleNames;
if (id == admin.id) { if (id == admin.id) {
expect(roleIds.length).toBe(2); expect(roleNames.length).toBe(2);
expect(roleIds.indexOf(moderator.id)).not.toBe(-1); expect(roleNames.indexOf("Moderator")).not.toBe(-1);
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1); expect(roleNames.indexOf("ContentManager")).not.toBe(-1);
} else if (id == moderator.id) { } else if (id == moderator.id) {
expect(roleIds.length).toBe(1); expect(roleNames.length).toBe(1);
expect(roleIds.indexOf(contentManager.id)).toBe(0); expect(roleNames.indexOf("ContentManager")).toBe(0);
} else if (id == contentManager.id) { } else if (id == contentManager.id) {
expect(roleIds.length).toBe(0); expect(roleNames.length).toBe(0);
} else if (id == superModerator.id) { } else if (id == superModerator.id) {
expect(roleIds.length).toBe(3); expect(roleNames.length).toBe(3);
expect(roleIds.indexOf(moderator.id)).not.toBe(-1); expect(roleNames.indexOf("Moderator")).not.toBe(-1);
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1); expect(roleNames.indexOf("ContentManager")).not.toBe(-1);
expect(roleIds.indexOf(superContentManager.id)).not.toBe(-1); expect(roleNames.indexOf("SuperContentManager")).not.toBe(-1);
} }
}); });
done(); done();
}).fail((err) => { }).fail((err) => {
console.error(err);
done(); done();
}) })
@@ -259,7 +257,6 @@ describe('Parse Role testing', () => {
done(); done();
}); });
}, (e) => { }, (e) => {
console.log(e);
fail('should not have errored'); fail('should not have errored');
}); });
}); });
@@ -338,10 +335,13 @@ describe('Parse Role testing', () => {
fail('Customer user should not have been able to save.'); fail('Customer user should not have been able to save.');
done(); done();
}, (e) => { }, (e) => {
expect(e.code).toEqual(101); if (e) {
expect(e.code).toEqual(101);
} else {
fail('should return an error');
}
done(); done();
}) })
}); });
}); });

View File

@@ -114,30 +114,17 @@ Auth.prototype._loadRoles = function() {
this.rolePromise = null; this.rolePromise = null;
return Promise.resolve(this.userRoles); return Promise.resolve(this.userRoles);
} }
var rolesMap = results.reduce((m, r) => {
m.names.push(r.name);
m.ids.push(r.objectId);
return m;
}, {ids: [], names: []});
var roleIDs = results.map(r => r.objectId); // run the recursive finding
var promises = [Promise.resolve(roleIDs)]; return this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names)
var queriedRoles = {}; .then((roleNames) => {
for (var role of roleIDs) { this.userRoles = roleNames.map((r) => {
promises.push(this._getAllRoleNamesForId(role, queriedRoles)); return 'role:' + r;
}
return Promise.all(promises).then((results) => {
var allIDs = [];
for (var x of results) {
Array.prototype.push.apply(allIDs, x);
}
var restWhere = {
objectId: {
'$in': allIDs
}
};
var query = new RestQuery(this.config, master(this.config),
'_Role', restWhere, {});
return query.execute();
}).then((response) => {
var results = response.results;
this.userRoles = results.map((r) => {
return 'role:' + r.name;
}); });
this.fetchedRoles = true; this.fetchedRoles = true;
this.rolePromise = null; this.rolePromise = null;
@@ -146,50 +133,52 @@ Auth.prototype._loadRoles = function() {
}); });
}; };
// Given a role object id, get any other roles it is part of // Given a list of roleIds, find all the parent roles, returns a promise with all names
Auth.prototype._getAllRoleNamesForId = function(roleID, queriedRoles = {}) { Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) {
// Don't need to requery this role as it is already being queried for. let ins = roleIDs.filter((roleID) => {
if (queriedRoles[roleID] != null) { return queriedRoles[roleID] !== true;
return Promise.resolve([]); }).map((roleID) => {
// mark as queried
queriedRoles[roleID] = true;
return {
__type: 'Pointer',
className: '_Role',
objectId: roleID
}
});
// all roles are accounted for, return the names
if (ins.length == 0) {
return Promise.resolve([...new Set(names)]);
} }
queriedRoles[roleID] = true; // Build an OR query across all parentRoles
// As per documentation, a Role inherits AnotherRole let restWhere;
// if this Role is in the roles pointer of this AnotherRole if (ins.length == 1) {
// Let's find all the roles where this role is in a roles relation restWhere = { 'roles': ins[0] };
var rolePointer = { } else {
__type: 'Pointer', restWhere = { 'roles': { '$in': ins }}
className: '_Role', }
objectId: roleID let query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
};
var restWhere = {
'roles': rolePointer
};
var query = new RestQuery(this.config, master(this.config), '_Role',
restWhere, {});
return query.execute().then((response) => { return query.execute().then((response) => {
var results = response.results; var results = response.results;
// Nothing found
if (!results.length) { if (!results.length) {
return Promise.resolve([]); return Promise.resolve(names);
} }
var roleIDs = results.map(r => r.objectId); // Map the results with all Ids and names
let resultMap = results.reduce((memo, role) => {
// we found a list of roles where the roleID memo.names.push(role.name);
// is referenced in the roles relation, memo.ids.push(role.objectId);
// Get the roles where those found roles are also return memo;
// referenced the same way }, {ids: [], names: []});
var parentRolesPromises = roleIDs.map( (roleId) => { // store the new found names
return this._getAllRoleNamesForId(roleId, queriedRoles); names = names.concat(resultMap.names);
}); // find the next ones, circular roles will be cut
parentRolesPromises.push(Promise.resolve(roleIDs)); return this._getAllRolesNamesForRoleIds(resultMap.ids, names, queriedRoles)
return Promise.all(parentRolesPromises); }).then((names) => {
}).then(function(results){ return Promise.resolve([...new Set(names)])
// Flatten })
let roleIDs = results.reduce( (memo, result) => { }
return memo.concat(result);
}, []);
return Promise.resolve([...new Set(roleIDs)]);
});
};
module.exports = { module.exports = {
Auth: Auth, Auth: Auth,