Skip to content

Commit

Permalink
Merge pull request #909 from ParsePlatform/nlutsenko.databaseController
Browse files Browse the repository at this point in the history
Move DatabaseController and Schema fully to adaptive mongo collection.
  • Loading branch information
nlutsenko committed Mar 8, 2016
2 parents 8d10447 + 49eb9df commit 241cd8c
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 119 deletions.
14 changes: 11 additions & 3 deletions src/Adapters/Storage/Mongo/MongoCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ export default class MongoCollection {
return this._mongoCollection.findAndModify(query, [], update, { new: true }).then(document => {
// Value is the object where mongo returns multiple fields.
return document.value;
})
});
}

insertOne(object) {
return this._mongoCollection.insertOne(object);
}

// Atomically updates data in the database for a single (first) object that matched the query
Expand All @@ -64,6 +68,10 @@ export default class MongoCollection {
return this._mongoCollection.update(query, update, { upsert: true });
}

updateOne(query, update) {
return this._mongoCollection.updateOne(query, update);
}

updateMany(query, update) {
return this._mongoCollection.updateMany(query, update);
}
Expand All @@ -83,8 +91,8 @@ export default class MongoCollection {
return this._mongoCollection.deleteOne(query);
}

remove(query) {
return this._mongoCollection.remove(query);
deleteMany(query) {
return this._mongoCollection.deleteMany(query);
}

drop() {
Expand Down
150 changes: 71 additions & 79 deletions src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ DatabaseController.prototype.connect = function() {
return this.adapter.connect();
};

// Returns a promise for a Mongo collection.
// Generally just for internal use.
DatabaseController.prototype.collection = function(className) {
if (!Schema.classNameIsValid(className)) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME,
'invalid className: ' + className);
}
return this.adapter.collection(this.collectionPrefix + className);
};

DatabaseController.prototype.adaptiveCollection = function(className) {
return this.adapter.adaptiveCollection(this.collectionPrefix + className);
};
Expand All @@ -54,15 +44,23 @@ function returnsTrue() {
return true;
}

DatabaseController.prototype.validateClassName = function(className) {
if (!Schema.classNameIsValid(className)) {
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
return Promise.reject(error);
}
return Promise.resolve();
};

// Returns a promise for a schema object.
// If we are provided a acceptor, then we run it on the schema.
// If the schema isn't accepted, we reload it at most once.
DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {

if (!this.schemaPromise) {
this.schemaPromise = this.collection('_SCHEMA').then((coll) => {
this.schemaPromise = this.adaptiveCollection('_SCHEMA').then(collection => {
delete this.schemaPromise;
return Schema.load(coll);
return Schema.load(collection);
});
return this.schemaPromise;
}
Expand All @@ -71,9 +69,9 @@ DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {
if (acceptor(schema)) {
return schema;
}
this.schemaPromise = this.collection('_SCHEMA').then((coll) => {
this.schemaPromise = this.adaptiveCollection('_SCHEMA').then(collection => {
delete this.schemaPromise;
return Schema.load(coll);
return Schema.load(collection);
});
return this.schemaPromise;
});
Expand Down Expand Up @@ -230,30 +228,28 @@ DatabaseController.prototype.handleRelationUpdates = function(className,

// Adds a relation.
// Returns a promise that resolves successfully iff the add was successful.
DatabaseController.prototype.addRelation = function(key, fromClassName,
fromId, toId) {
var doc = {
DatabaseController.prototype.addRelation = function(key, fromClassName, fromId, toId) {
let doc = {
relatedId: toId,
owningId: fromId
owningId : fromId
};
var className = '_Join:' + key + ':' + fromClassName;
return this.collection(className).then((coll) => {
return coll.update(doc, doc, {upsert: true});
let className = `_Join:${key}:${fromClassName}`;
return this.adaptiveCollection(className).then((coll) => {
return coll.upsertOne(doc, doc);
});
};

// Removes a relation.
// Returns a promise that resolves successfully iff the remove was
// successful.
DatabaseController.prototype.removeRelation = function(key, fromClassName,
fromId, toId) {
DatabaseController.prototype.removeRelation = function(key, fromClassName, fromId, toId) {
var doc = {
relatedId: toId,
owningId: fromId
};
var className = '_Join:' + key + ':' + fromClassName;
return this.collection(className).then((coll) => {
return coll.remove(doc);
let className = `_Join:${key}:${fromClassName}`;
return this.adaptiveCollection(className).then(coll => {
return coll.deleteOne(doc);
});
};

Expand All @@ -269,40 +265,36 @@ DatabaseController.prototype.destroy = function(className, query, options = {})
var aclGroup = options.acl || [];

var schema;
return this.loadSchema().then((s) => {
schema = s;
if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'delete');
}
return Promise.resolve();
}).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()
.then(s => {
schema = s;
if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'delete');
}
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
}

return coll.remove(mongoWhere);
}).then((resp) => {
//Check _Session to avoid changing password failed without any session.
if (resp.result.n === 0 && className !== "_Session") {
return Promise.reject(
new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'));
return Promise.resolve();
})
.then(() => this.adaptiveCollection(className))
.then(collection => {
let mongoWhere = transform.transformWhere(schema, className, query);

}
}, (error) => {
throw error;
});
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 collection.deleteMany(mongoWhere);
})
.then(resp => {
//Check _Session to avoid changing password failed without any session.
// TODO: @nlutsenko Stop relying on `result.n`
if (resp.result.n === 0 && className !== "_Session") {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
});
};

// Inserts an object into the database.
Expand All @@ -312,21 +304,21 @@ DatabaseController.prototype.create = function(className, object, options) {
var isMaster = !('acl' in options);
var aclGroup = options.acl || [];

return this.loadSchema().then((s) => {
schema = s;
if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'create');
}
return Promise.resolve();
}).then(() => {

return this.handleRelationUpdates(className, null, object);
}).then(() => {
return this.collection(className);
}).then((coll) => {
var mongoObject = transform.transformCreate(schema, className, object);
return coll.insert([mongoObject]);
});
return this.validateClassName(className)
.then(() => this.loadSchema())
.then(s => {
schema = s;
if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'create');
}
return Promise.resolve();
})
.then(() => this.handleRelationUpdates(className, null, object))
.then(() => this.adaptiveCollection(className))
.then(coll => {
var mongoObject = transform.transformCreate(schema, className, object);
return coll.insertOne(mongoObject);
});
};

// Runs a mongo query on the database.
Expand Down Expand Up @@ -386,14 +378,14 @@ DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
// equal-to-pointer constraints on relation fields.
// Returns a promise that resolves when query is mutated
DatabaseController.prototype.reduceInRelation = function(className, query, schema) {

// Search for an in-relation or equal-to-relation
// Make it sequential for now, not sure of paralleization side effects
if (query['$or']) {
let ors = query['$or'];
return Promise.all(ors.map((aQuery, index) => {
return this.reduceInRelation(className, aQuery, schema).then((aQuery) => {
query['$or'][index] = aQuery;
query['$or'][index] = aQuery;
})
}));
}
Expand All @@ -413,14 +405,14 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
relatedIds = [query[key].objectId];
}
return this.owningIds(className, key, relatedIds).then((ids) => {
delete query[key];
delete query[key];
this.addInObjectIdsIds(ids, query);
return Promise.resolve(query);
});
}
return Promise.resolve(query);
})

return Promise.all(promises).then(() => {
return Promise.resolve(query);
})
Expand All @@ -429,13 +421,13 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
// Modifies query so that it no longer has $relatedTo
// Returns a promise that resolves when query is mutated
DatabaseController.prototype.reduceRelationKeys = function(className, query) {

if (query['$or']) {
return Promise.all(query['$or'].map((aQuery) => {
return this.reduceRelationKeys(className, aQuery);
}));
}

var relatedTo = query['$relatedTo'];
if (relatedTo) {
return this.relatedIds(
Expand Down
2 changes: 1 addition & 1 deletion src/Controllers/HooksController.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class HooksController {

_removeHooks(query) {
return this.getCollection().then(collection => {
return collection.remove(query);
return collection.deleteMany(query);
}).then(() => {
return {};
});
Expand Down
41 changes: 22 additions & 19 deletions src/Routers/SchemasRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function getAllSchemas(req) {
return req.config.database.adaptiveCollection('_SCHEMA')
.then(collection => collection.find({}))
.then(schemas => schemas.map(Schema.mongoSchemaToSchemaAPIResponse))
.then(schemas => ({ response: { results: schemas }}));
.then(schemas => ({ response: { results: schemas } }));
}

function getOneSchema(req) {
Expand Down Expand Up @@ -65,7 +65,7 @@ function modifySchema(req) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${req.params.className} does not exist.`);
}

let existingFields = Object.assign(schema.data[className], {_id: className});
let existingFields = Object.assign(schema.data[className], { _id: className });
Object.keys(submittedFields).forEach(name => {
let field = submittedFields[name];
if (existingFields[name] && field.__op !== 'Delete') {
Expand All @@ -83,24 +83,27 @@ function modifySchema(req) {
}

// Finally we have checked to make sure the request is valid and we can start deleting fields.
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
let deletionPromises = [];
Object.keys(submittedFields).forEach(submittedFieldName => {
if (submittedFields[submittedFieldName].__op === 'Delete') {
let promise = schema.deleteField(submittedFieldName, className, req.config.database);
deletionPromises.push(promise);
// Do all deletions first, then add fields to avoid duplicate geopoint error.
let deletePromises = [];
let insertedFields = [];
Object.keys(submittedFields).forEach(fieldName => {
if (submittedFields[fieldName].__op === 'Delete') {
const promise = schema.deleteField(fieldName, className, req.config.database);
deletePromises.push(promise);
} else {
insertedFields.push(fieldName);
}
});

return Promise.all(deletionPromises)
.then(() => new Promise((resolve, reject) => {
schema.collection.update({_id: className}, mongoObject.result, {w: 1}, (err, docs) => {
if (err) {
reject(err);
}
resolve({ response: Schema.mongoSchemaToSchemaAPIResponse(mongoObject.result)});
})
}));
return Promise.all(deletePromises) // Delete Everything
.then(() => schema.reloadData()) // Reload our Schema, so we have all the new values
.then(() => {
let promises = insertedFields.map(fieldName => {
const mongoType = mongoObject.result[fieldName];
return schema.validateField(className, fieldName, mongoType);
});
return Promise.all(promises);
})
.then(() => ({ response: Schema.mongoSchemaToSchemaAPIResponse(mongoObject.result) }));
});
}

Expand Down Expand Up @@ -140,7 +143,7 @@ function deleteSchema(req) {
// We've dropped the collection now, so delete the item from _SCHEMA
// and clear the _Join collections
return req.config.database.adaptiveCollection('_SCHEMA')
.then(coll => coll.findOneAndDelete({_id: req.params.className}))
.then(coll => coll.findOneAndDelete({ _id: req.params.className }))
.then(document => {
if (document === null) {
//tried to delete non-existent class
Expand Down
Loading

0 comments on commit 241cd8c

Please sign in to comment.