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 });
}
// 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() {
return this._mongoCollection.drop();
}

View File

@@ -142,51 +142,45 @@ DatabaseController.prototype.update = function(className, query, update, options
var isMaster = !('acl' in options);
var aclGroup = options.acl || [];
var mongoUpdate, schema;
return this.loadSchema(acceptor).then((s) => {
schema = s;
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]}});
return this.loadSchema(acceptor)
.then(s => {
schema = s;
if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'update');
}
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
}
mongoUpdate = transform.transformUpdate(schema, className, update);
return coll.findAndModify(mongoWhere, {}, mongoUpdate, {});
}).then((result) => {
if (!result.value) {
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,
'Object not found.'));
}
var response = {};
var inc = mongoUpdate['$inc'];
if (inc) {
for (var key in inc) {
response[key] = (result.value[key] || 0) + inc[key];
return Promise.resolve();
})
.then(() => this.handleRelationUpdates(className, query.objectId, update))
.then(() => this.adaptiveCollection(className))
.then(collection => {
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 response;
});
mongoUpdate = transform.transformUpdate(schema, className, update);
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.

View File

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

View File

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