Merge pull request #759 from ParsePlatform/nlutsenko.storage.findAndDoMagic

Add findOneAndDelete, findOneAndModify to MongoCollection, move most of usages to it.
This commit is contained in:
Nikita Lutsenko
2016-03-02 14:36:01 -08:00
4 changed files with 102 additions and 99 deletions

View File

@@ -47,6 +47,29 @@ export default class MongoCollection {
return this._mongoCollection.count(query, { skip, limit, sort }); return this._mongoCollection.count(query, { skip, limit, sort });
} }
// Atomically finds and updates an object based on query.
// The result is the promise with an object that was in the database !AFTER! changes.
// Postgres Note: Translates directly to `UPDATE * SET * ... RETURNING *`, which will return data after the change is done.
findOneAndUpdate(query, update) {
// arguments: query, sort, update, options(optional)
// Setting `new` option to true makes it return the after document, not the before one.
return this._mongoCollection.findAndModify(query, [], update, { new: true }).then(document => {
// Value is the object where mongo returns multiple fields.
return document.value;
})
}
// Atomically find and delete an object based on query.
// The result is the promise with an object that was in the database before deleting.
// Postgres Note: Translates directly to `DELETE * FROM ... RETURNING *`, which will return data after delete is done.
findOneAndDelete(query) {
// arguments: query, sort
return this._mongoCollection.findAndRemove(query, []).then(document => {
// Value is the object where mongo returns multiple fields.
return document.value;
});
}
drop() { drop() {
return this._mongoCollection.drop(); return this._mongoCollection.drop();
} }

View File

@@ -142,51 +142,45 @@ DatabaseController.prototype.update = function(className, query, update, options
var isMaster = !('acl' in options); var isMaster = !('acl' in options);
var aclGroup = options.acl || []; var aclGroup = options.acl || [];
var mongoUpdate, schema; var mongoUpdate, schema;
return this.loadSchema(acceptor).then((s) => { return this.loadSchema(acceptor)
schema = s; .then(s => {
if (!isMaster) { schema = s;
return schema.validatePermission(className, aclGroup, 'update'); if (!isMaster) {
} return schema.validatePermission(className, aclGroup, 'update');
return Promise.resolve();
}).then(() => {
return this.handleRelationUpdates(className, query.objectId, update);
}).then(() => {
return this.collection(className);
}).then((coll) => {
var mongoWhere = transform.transformWhere(schema, className, query);
if (options.acl) {
var writePerms = [
{_wperm: {'$exists': false}}
];
for (var entry of options.acl) {
writePerms.push({_wperm: {'$in': [entry]}});
} }
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]}; return Promise.resolve();
} })
.then(() => this.handleRelationUpdates(className, query.objectId, update))
mongoUpdate = transform.transformUpdate(schema, className, update); .then(() => this.adaptiveCollection(className))
.then(collection => {
return coll.findAndModify(mongoWhere, {}, mongoUpdate, {}); var mongoWhere = transform.transformWhere(schema, className, query);
}).then((result) => { if (options.acl) {
if (!result.value) { var writePerms = [
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, {_wperm: {'$exists': false}}
'Object not found.')); ];
} for (var entry of options.acl) {
if (result.lastErrorObject.n != 1) { writePerms.push({_wperm: {'$in': [entry]}});
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, }
'Object not found.')); mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
}
var response = {};
var inc = mongoUpdate['$inc'];
if (inc) {
for (var key in inc) {
response[key] = (result.value[key] || 0) + inc[key];
} }
} mongoUpdate = transform.transformUpdate(schema, className, update);
return response; return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
}); })
.then(result => {
if (!result) {
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'));
}
let response = {};
let inc = mongoUpdate['$inc'];
if (inc) {
Object.keys(inc).forEach(key => {
response[key] = result[key];
});
}
return response;
});
}; };
// Processes relation-updating operations from a REST-format update. // Processes relation-updating operations from a REST-format update.

View File

@@ -40,49 +40,43 @@ export class UserController extends AdaptableController {
verifyEmail(username, token) { verifyEmail(username, token) {
if (!this.shouldVerifyEmails) {
return new Promise((resolve, reject) => {
// Trying to verify email when not enabled // Trying to verify email when not enabled
if (!this.shouldVerifyEmails) { // TODO: Better error here.
reject(); return Promise.reject();
return; }
}
return this.config.database
var database = this.config.database; .adaptiveCollection('_User')
.then(collection => {
database.collection('_User').then(coll => {
// Need direct database access because verification token is not a parse field // Need direct database access because verification token is not a parse field
return coll.findAndModify({ return collection.findOneAndUpdate({
username: username, username: username,
_email_verify_token: token, _email_verify_token: token
}, null, {$set: {emailVerified: true}}, (err, doc) => { }, {$set: {emailVerified: true}});
if (err || !doc.value) { })
reject(err); .then(document => {
} else { if (!document) {
resolve(doc.value); return Promise.reject();
} }
}); return document;
}); });
});
} }
checkResetTokenValidity(username, token) { checkResetTokenValidity(username, token) {
return new Promise((resolve, reject) => { return this.config.database.adaptiveCollection('_User')
return this.config.database.collection('_User').then(coll => { .then(collection => {
return coll.findOne({ return collection.find({
username: username, username: username,
_perishable_token: token, _perishable_token: token
}, (err, doc) => { }, { limit: 1 });
if (err || !doc) { })
reject(err); .then(results => {
} else { if (results.length != 1) {
resolve(doc); return Promise.reject();
} }
}); return results[0];
}); });
});
} }
getUserIfNeeded(user) { getUserIfNeeded(user) {
@@ -130,24 +124,16 @@ export class UserController extends AdaptableController {
} }
setPasswordResetToken(email) { setPasswordResetToken(email) {
var database = this.config.database; let token = randomString(25);
var token = randomString(25); return this.config.database
return new Promise((resolve, reject) => { .adaptiveCollection('_User')
return database.collection('_User').then(coll => { .then(collection => {
// Need direct database access because verification token is not a parse field // Need direct database access because verification token is not a parse field
return coll.findAndModify({ return collection.findOneAndUpdate(
email: email, { email: email}, // query
}, null, {$set: {_perishable_token: token}}, (err, doc) => { { $set: { _perishable_token: token } } // update
if (err || !doc.value) { );
console.error(err);
reject(err);
} else {
doc.value._perishable_token = token;
resolve(doc.value);
}
});
}); });
});
} }
sendPasswordResetEmail(email) { sendPasswordResetEmail(email) {

View File

@@ -164,14 +164,14 @@ function deleteSchema(req) {
.then(() => { .then(() => {
// We've dropped the collection now, so delete the item from _SCHEMA // We've dropped the collection now, so delete the item from _SCHEMA
// and clear the _Join collections // and clear the _Join collections
return req.config.database.collection('_SCHEMA') return req.config.database.adaptiveCollection('_SCHEMA')
.then(coll => coll.findAndRemove({_id: req.params.className}, [])) .then(coll => coll.findOneAndDelete({_id: req.params.className}))
.then(doc => { .then(document => {
if (doc.value === null) { if (document === null) {
//tried to delete non-existent class //tried to delete non-existent class
return Promise.resolve(); return Promise.resolve();
} }
return removeJoinTables(req.config.database, doc.value); return removeJoinTables(req.config.database, document);
}); });
}) })
.then(() => { .then(() => {