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,18 +142,17 @@ 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)
.then(s => {
schema = s; schema = s;
if (!isMaster) { if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'update'); return schema.validatePermission(className, aclGroup, 'update');
} }
return Promise.resolve(); return Promise.resolve();
}).then(() => { })
.then(() => this.handleRelationUpdates(className, query.objectId, update))
return this.handleRelationUpdates(className, query.objectId, update); .then(() => this.adaptiveCollection(className))
}).then(() => { .then(collection => {
return this.collection(className);
}).then((coll) => {
var mongoWhere = transform.transformWhere(schema, className, query); var mongoWhere = transform.transformWhere(schema, className, query);
if (options.acl) { if (options.acl) {
var writePerms = [ var writePerms = [
@@ -164,26 +163,21 @@ DatabaseController.prototype.update = function(className, query, update, options
} }
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]}; mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
} }
mongoUpdate = transform.transformUpdate(schema, className, update); mongoUpdate = transform.transformUpdate(schema, className, update);
return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
return coll.findAndModify(mongoWhere, {}, mongoUpdate, {}); })
}).then((result) => { .then(result => {
if (!result.value) { if (!result) {
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'));
}
if (result.lastErrorObject.n != 1) {
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
'Object not found.')); 'Object not found.'));
} }
var response = {}; let response = {};
var inc = mongoUpdate['$inc']; let inc = mongoUpdate['$inc'];
if (inc) { if (inc) {
for (var key in inc) { Object.keys(inc).forEach(key => {
response[key] = (result.value[key] || 0) + inc[key]; response[key] = result[key];
} });
} }
return response; return response;
}); });

View File

@@ -40,48 +40,42 @@ export class UserController extends AdaptableController {
verifyEmail(username, token) { verifyEmail(username, token) {
return new Promise((resolve, reject) => {
// Trying to verify email when not enabled
if (!this.shouldVerifyEmails) { if (!this.shouldVerifyEmails) {
reject(); // Trying to verify email when not enabled
return; // TODO: Better error here.
return Promise.reject();
} }
var database = this.config.database; return this.config.database
.adaptiveCollection('_User')
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({
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];
});
}); });
} }
@@ -130,23 +124,15 @@ 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);
}
});
});
}); });
} }

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(() => {