DBController refactoring (#1228)
* Moves transform to MongoTransform - Adds ACL query injection in MongoTransform * Removes adaptiveCollection from DatabaseController - All collections manipulations are now handled by a DBController - Adds optional flags to configure an unsafe databaseController for direct access - Adds ability to configure RestWrite with multiple writes - Moves some transfirmations to MongoTransform as they output specific code * Renames Unsafe to WithoutValidation
This commit is contained in:
@@ -7,18 +7,27 @@ var mongodb = require('mongodb');
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
var Schema = require('./../Schema');
|
||||
var transform = require('./../transform');
|
||||
const deepcopy = require('deepcopy');
|
||||
|
||||
function DatabaseController(adapter) {
|
||||
function DatabaseController(adapter, { skipValidation } = {}) {
|
||||
this.adapter = adapter;
|
||||
|
||||
// We don't want a mutable this.schema, because then you could have
|
||||
// one request that uses different schemas for different parts of
|
||||
// it. Instead, use loadSchema to get a schema.
|
||||
this.schemaPromise = null;
|
||||
|
||||
this.skipValidation = !!skipValidation;
|
||||
this.connect();
|
||||
|
||||
Object.defineProperty(this, 'transform', {
|
||||
get: function() {
|
||||
return adapter.transform;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
DatabaseController.prototype.WithoutValidation = function() {
|
||||
return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true});
|
||||
}
|
||||
|
||||
// Connects to the database. Returns a promise that resolves when the
|
||||
@@ -27,10 +36,6 @@ DatabaseController.prototype.connect = function() {
|
||||
return this.adapter.connect();
|
||||
};
|
||||
|
||||
DatabaseController.prototype.adaptiveCollection = function(className) {
|
||||
return this.adapter.adaptiveCollection(className);
|
||||
};
|
||||
|
||||
DatabaseController.prototype.schemaCollection = function() {
|
||||
return this.adapter.schemaCollection();
|
||||
};
|
||||
@@ -44,6 +49,9 @@ DatabaseController.prototype.dropCollection = function(className) {
|
||||
};
|
||||
|
||||
DatabaseController.prototype.validateClassName = function(className) {
|
||||
if (this.skipValidation) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!Schema.classNameIsValid(className)) {
|
||||
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
|
||||
return Promise.reject(error);
|
||||
@@ -113,7 +121,7 @@ DatabaseController.prototype.validateObject = function(className, object, query,
|
||||
// Filters out any data that shouldn't be on this REST-formatted object.
|
||||
DatabaseController.prototype.untransformObject = function(
|
||||
schema, isMaster, aclGroup, className, mongoObject) {
|
||||
var object = transform.untransformObject(schema, className, mongoObject);
|
||||
var object = this.transform.untransformObject(schema, className, mongoObject);
|
||||
|
||||
if (className !== '_User') {
|
||||
return object;
|
||||
@@ -137,7 +145,7 @@ DatabaseController.prototype.untransformObject = function(
|
||||
// acl: a list of strings. If the object to be updated has an ACL,
|
||||
// one of the provided strings must provide the caller with
|
||||
// write permissions.
|
||||
DatabaseController.prototype.update = function(className, query, update, options) {
|
||||
DatabaseController.prototype.update = function(className, query, update, options = {}) {
|
||||
|
||||
const originalUpdate = update;
|
||||
// Make a copy of the object, so we don't mutate the incoming data.
|
||||
@@ -158,26 +166,29 @@ DatabaseController.prototype.update = function(className, query, update, options
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => this.handleRelationUpdates(className, query.objectId, update))
|
||||
.then(() => this.adaptiveCollection(className))
|
||||
.then(() => this.adapter.adaptiveCollection(className))
|
||||
.then(collection => {
|
||||
var mongoWhere = transform.transformWhere(schema, className, query);
|
||||
var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation});
|
||||
if (options.acl) {
|
||||
var writePerms = [
|
||||
{_wperm: {'$exists': false}}
|
||||
];
|
||||
for (var entry of options.acl) {
|
||||
writePerms.push({_wperm: {'$in': [entry]}});
|
||||
}
|
||||
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
|
||||
mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl);
|
||||
}
|
||||
mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.skipValidation});
|
||||
if (options.many) {
|
||||
return collection.updateMany(mongoWhere, mongoUpdate);
|
||||
}else if (options.upsert) {
|
||||
return collection.upsertOne(mongoWhere, mongoUpdate);
|
||||
} else {
|
||||
return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
|
||||
}
|
||||
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.'));
|
||||
}
|
||||
if (this.skipValidation) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
return sanitizeDatabaseResult(originalUpdate, result);
|
||||
});
|
||||
};
|
||||
@@ -256,7 +267,7 @@ DatabaseController.prototype.addRelation = function(key, fromClassName, fromId,
|
||||
owningId : fromId
|
||||
};
|
||||
let className = `_Join:${key}:${fromClassName}`;
|
||||
return this.adaptiveCollection(className).then((coll) => {
|
||||
return this.adapter.adaptiveCollection(className).then((coll) => {
|
||||
return coll.upsertOne(doc, doc);
|
||||
});
|
||||
};
|
||||
@@ -270,7 +281,7 @@ DatabaseController.prototype.removeRelation = function(key, fromClassName, fromI
|
||||
owningId: fromId
|
||||
};
|
||||
let className = `_Join:${key}:${fromClassName}`;
|
||||
return this.adaptiveCollection(className).then(coll => {
|
||||
return this.adapter.adaptiveCollection(className).then(coll => {
|
||||
return coll.deleteOne(doc);
|
||||
});
|
||||
};
|
||||
@@ -295,18 +306,11 @@ DatabaseController.prototype.destroy = function(className, query, options = {})
|
||||
}
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => this.adaptiveCollection(className))
|
||||
.then(() => this.adapter.adaptiveCollection(className))
|
||||
.then(collection => {
|
||||
let mongoWhere = transform.transformWhere(schema, className, query);
|
||||
|
||||
let mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation});
|
||||
if (options.acl) {
|
||||
var writePerms = [
|
||||
{ _wperm: { '$exists': false } }
|
||||
];
|
||||
for (var entry of options.acl) {
|
||||
writePerms.push({ _wperm: { '$in': [entry] } });
|
||||
}
|
||||
mongoWhere = { '$and': [mongoWhere, { '$or': writePerms }] };
|
||||
mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl);
|
||||
}
|
||||
return collection.deleteMany(mongoWhere);
|
||||
})
|
||||
@@ -321,7 +325,7 @@ DatabaseController.prototype.destroy = function(className, query, options = {})
|
||||
|
||||
// Inserts an object into the database.
|
||||
// Returns a promise that resolves successfully iff the object saved.
|
||||
DatabaseController.prototype.create = function(className, object, options) {
|
||||
DatabaseController.prototype.create = function(className, object, options = {}) {
|
||||
// Make a copy of the object, so we don't mutate the incoming data.
|
||||
let originalObject = object;
|
||||
object = deepcopy(object);
|
||||
@@ -340,9 +344,9 @@ DatabaseController.prototype.create = function(className, object, options) {
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => this.handleRelationUpdates(className, null, object))
|
||||
.then(() => this.adaptiveCollection(className))
|
||||
.then(() => this.adapter.adaptiveCollection(className))
|
||||
.then(coll => {
|
||||
var mongoObject = transform.transformCreate(schema, className, object);
|
||||
var mongoObject = this.transform.transformCreate(schema, className, object);
|
||||
return coll.insertOne(mongoObject);
|
||||
})
|
||||
.then(result => {
|
||||
@@ -371,7 +375,7 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a
|
||||
// to avoid Mongo-format dependencies.
|
||||
// Returns a promise that resolves to a list of items.
|
||||
DatabaseController.prototype.mongoFind = function(className, query, options = {}) {
|
||||
return this.adaptiveCollection(className)
|
||||
return this.adapter.adaptiveCollection(className)
|
||||
.then(collection => collection.find(query, options));
|
||||
};
|
||||
|
||||
@@ -404,7 +408,7 @@ function keysForQuery(query) {
|
||||
// Returns a promise for a list of related ids given an owning id.
|
||||
// className here is the owning className.
|
||||
DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
||||
return this.adaptiveCollection(joinTableName(className, key))
|
||||
return this.adapter.adaptiveCollection(joinTableName(className, key))
|
||||
.then(coll => coll.find({owningId : owningId}))
|
||||
.then(results => results.map(r => r.relatedId));
|
||||
};
|
||||
@@ -412,7 +416,7 @@ DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
||||
// Returns a promise for a list of owning ids given some related ids.
|
||||
// className here is the owning className.
|
||||
DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
|
||||
return this.adaptiveCollection(joinTableName(className, key))
|
||||
return this.adapter.adaptiveCollection(joinTableName(className, key))
|
||||
.then(coll => coll.find({ relatedId: { '$in': relatedIds } }))
|
||||
.then(results => results.map(r => r.owningId));
|
||||
};
|
||||
@@ -597,7 +601,7 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
if (options.sort) {
|
||||
mongoOptions.sort = {};
|
||||
for (let key in options.sort) {
|
||||
let mongoKey = transform.transformKey(schema, className, key);
|
||||
let mongoKey = this.transform.transformKey(schema, className, key);
|
||||
mongoOptions.sort[mongoKey] = options.sort[key];
|
||||
}
|
||||
}
|
||||
@@ -612,18 +616,11 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
})
|
||||
.then(() => this.reduceRelationKeys(className, query))
|
||||
.then(() => this.reduceInRelation(className, query, schema))
|
||||
.then(() => this.adaptiveCollection(className))
|
||||
.then(() => this.adapter.adaptiveCollection(className))
|
||||
.then(collection => {
|
||||
let mongoWhere = transform.transformWhere(schema, className, query);
|
||||
let mongoWhere = this.transform.transformWhere(schema, className, query);
|
||||
if (!isMaster) {
|
||||
let orParts = [
|
||||
{"_rperm" : { "$exists": false }},
|
||||
{"_rperm" : { "$in" : ["*"]}}
|
||||
];
|
||||
for (let acl of aclGroup) {
|
||||
orParts.push({"_rperm" : { "$in" : [acl]}});
|
||||
}
|
||||
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};
|
||||
mongoWhere = this.transform.addReadACL(mongoWhere, aclGroup);
|
||||
}
|
||||
if (options.count) {
|
||||
delete mongoOptions.limit;
|
||||
@@ -640,6 +637,25 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
});
|
||||
};
|
||||
|
||||
DatabaseController.prototype.deleteSchema = function(className) {
|
||||
return this.collectionExists(className)
|
||||
.then(exist => {
|
||||
if (!exist) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.adapter.adaptiveCollection(className)
|
||||
.then(collection => {
|
||||
return collection.count()
|
||||
.then(count => {
|
||||
if (count > 0) {
|
||||
throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
|
||||
}
|
||||
return collection.drop();
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function joinTableName(className, key) {
|
||||
return `_Join:${key}:${className}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user