Merge pull request #943 from ParsePlatform/nlutsenko.schemaCollection
Add new schema collection type and replace all usages of direct mongo collection for schema operations.
This commit is contained in:
@@ -76,17 +76,6 @@ export default class MongoCollection {
|
|||||||
return this._mongoCollection.updateMany(query, update);
|
return this._mongoCollection.updateMany(query, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteOne(query) {
|
deleteOne(query) {
|
||||||
return this._mongoCollection.deleteOne(query);
|
return this._mongoCollection.deleteOne(query);
|
||||||
}
|
}
|
||||||
|
|||||||
58
src/Adapters/Storage/Mongo/MongoSchemaCollection.js
Normal file
58
src/Adapters/Storage/Mongo/MongoSchemaCollection.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
import MongoCollection from './MongoCollection';
|
||||||
|
|
||||||
|
function _mongoSchemaQueryFromNameQuery(name: string, query) {
|
||||||
|
return _mongoSchemaObjectFromNameFields(name, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mongoSchemaObjectFromNameFields(name: string, fields) {
|
||||||
|
let object = { _id: name };
|
||||||
|
if (fields) {
|
||||||
|
Object.keys(fields).forEach(key => {
|
||||||
|
object[key] = fields[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MongoSchemaCollection {
|
||||||
|
_collection: MongoCollection;
|
||||||
|
|
||||||
|
constructor(collection: MongoCollection) {
|
||||||
|
this._collection = collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllSchemas() {
|
||||||
|
return this._collection._rawFind({});
|
||||||
|
}
|
||||||
|
|
||||||
|
findSchema(name: string) {
|
||||||
|
return this._collection._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 }).then(results => {
|
||||||
|
return results[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
findAndDeleteSchema(name: string) {
|
||||||
|
// arguments: query, sort
|
||||||
|
return this._collection._mongoCollection.findAndRemove(_mongoSchemaQueryFromNameQuery(name), []).then(document => {
|
||||||
|
// Value is the object where mongo returns multiple fields.
|
||||||
|
return document.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(name: string, fields) {
|
||||||
|
let mongoObject = _mongoSchemaObjectFromNameFields(name, fields);
|
||||||
|
return this._collection.insertOne(mongoObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSchema(name: string, update) {
|
||||||
|
return this._collection.updateOne(_mongoSchemaQueryFromNameQuery(name), update);
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertSchema(name: string, query: string, update) {
|
||||||
|
return this._collection.upsertOne(_mongoSchemaQueryFromNameQuery(name, query), update);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
|
||||||
import MongoCollection from './MongoCollection';
|
import MongoCollection from './MongoCollection';
|
||||||
|
import MongoSchemaCollection from './MongoSchemaCollection';
|
||||||
|
|
||||||
let mongodb = require('mongodb');
|
let mongodb = require('mongodb');
|
||||||
let MongoClient = mongodb.MongoClient;
|
let MongoClient = mongodb.MongoClient;
|
||||||
|
|
||||||
|
const MongoSchemaCollectionName = '_SCHEMA';
|
||||||
|
|
||||||
export class MongoStorageAdapter {
|
export class MongoStorageAdapter {
|
||||||
// Private
|
// Private
|
||||||
_uri: string;
|
_uri: string;
|
||||||
@@ -38,6 +41,12 @@ export class MongoStorageAdapter {
|
|||||||
.then(rawCollection => new MongoCollection(rawCollection));
|
.then(rawCollection => new MongoCollection(rawCollection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
schemaCollection(collectionPrefix: string) {
|
||||||
|
return this.connect()
|
||||||
|
.then(() => this.adaptiveCollection(collectionPrefix + MongoSchemaCollectionName))
|
||||||
|
.then(collection => new MongoSchemaCollection(collection));
|
||||||
|
}
|
||||||
|
|
||||||
collectionExists(name: string) {
|
collectionExists(name: string) {
|
||||||
return this.connect().then(() => {
|
return this.connect().then(() => {
|
||||||
return this.database.listCollections({ name: name }).toArray();
|
return this.database.listCollections({ name: name }).toArray();
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ DatabaseController.prototype.adaptiveCollection = function(className) {
|
|||||||
return this.adapter.adaptiveCollection(this.collectionPrefix + className);
|
return this.adapter.adaptiveCollection(this.collectionPrefix + className);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DatabaseController.prototype.schemaCollection = function() {
|
||||||
|
return this.adapter.schemaCollection(this.collectionPrefix);
|
||||||
|
};
|
||||||
|
|
||||||
DatabaseController.prototype.collectionExists = function(className) {
|
DatabaseController.prototype.collectionExists = function(className) {
|
||||||
return this.adapter.collectionExists(this.collectionPrefix + className);
|
return this.adapter.collectionExists(this.collectionPrefix + className);
|
||||||
};
|
};
|
||||||
@@ -59,7 +63,7 @@ DatabaseController.prototype.validateClassName = function(className) {
|
|||||||
DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {
|
DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {
|
||||||
|
|
||||||
if (!this.schemaPromise) {
|
if (!this.schemaPromise) {
|
||||||
this.schemaPromise = this.adaptiveCollection('_SCHEMA').then(collection => {
|
this.schemaPromise = this.schemaCollection().then(collection => {
|
||||||
delete this.schemaPromise;
|
delete this.schemaPromise;
|
||||||
return Schema.load(collection);
|
return Schema.load(collection);
|
||||||
});
|
});
|
||||||
@@ -70,7 +74,7 @@ DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {
|
|||||||
if (acceptor(schema)) {
|
if (acceptor(schema)) {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
this.schemaPromise = this.adaptiveCollection('_SCHEMA').then(collection => {
|
this.schemaPromise = this.schemaCollection().then(collection => {
|
||||||
delete this.schemaPromise;
|
delete this.schemaPromise;
|
||||||
return Schema.load(collection);
|
return Schema.load(collection);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,23 +15,22 @@ function classNameMismatchResponse(bodyClass, pathClass) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAllSchemas(req) {
|
function getAllSchemas(req) {
|
||||||
return req.config.database.adaptiveCollection('_SCHEMA')
|
return req.config.database.schemaCollection()
|
||||||
.then(collection => collection.find({}))
|
.then(collection => collection.getAllSchemas())
|
||||||
.then(schemas => schemas.map(Schema.mongoSchemaToSchemaAPIResponse))
|
.then(schemas => schemas.map(Schema.mongoSchemaToSchemaAPIResponse))
|
||||||
.then(schemas => ({ response: { results: schemas } }));
|
.then(schemas => ({ response: { results: schemas } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOneSchema(req) {
|
function getOneSchema(req) {
|
||||||
const className = req.params.className;
|
const className = req.params.className;
|
||||||
return req.config.database.adaptiveCollection('_SCHEMA')
|
return req.config.database.schemaCollection()
|
||||||
.then(collection => collection.find({ '_id': className }, { limit: 1 }))
|
.then(collection => collection.findSchema(className))
|
||||||
.then(results => {
|
.then(mongoSchema => {
|
||||||
if (results.length != 1) {
|
if (!mongoSchema) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||||
}
|
}
|
||||||
return results[0];
|
return { response: Schema.mongoSchemaToSchemaAPIResponse(mongoSchema) };
|
||||||
})
|
});
|
||||||
.then(schema => ({ response: Schema.mongoSchemaToSchemaAPIResponse(schema) }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSchema(req) {
|
function createSchema(req) {
|
||||||
@@ -142,8 +141,8 @@ 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.adaptiveCollection('_SCHEMA')
|
return req.config.database.schemaCollection()
|
||||||
.then(coll => coll.findOneAndDelete({ _id: req.params.className }))
|
.then(coll => coll.findAndDeleteSchema(req.params.className))
|
||||||
.then(document => {
|
.then(document => {
|
||||||
if (document === null) {
|
if (document === null) {
|
||||||
//tried to delete non-existent class
|
//tried to delete non-existent class
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ class Schema {
|
|||||||
reloadData() {
|
reloadData() {
|
||||||
this.data = {};
|
this.data = {};
|
||||||
this.perms = {};
|
this.perms = {};
|
||||||
return this._collection.find({}).then(results => {
|
return this._collection.getAllSchemas().then(results => {
|
||||||
for (let obj of results) {
|
for (let obj of results) {
|
||||||
let className = null;
|
let className = null;
|
||||||
let classData = {};
|
let classData = {};
|
||||||
@@ -231,7 +231,7 @@ class Schema {
|
|||||||
return Promise.reject(mongoObject);
|
return Promise.reject(mongoObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._collection.insertOne(mongoObject.result)
|
return this._collection.addSchema(className, mongoObject.result)
|
||||||
.then(result => result.ops[0])
|
.then(result => result.ops[0])
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.code === 11000) { //Mongo's duplicate key error
|
if (error.code === 11000) { //Mongo's duplicate key error
|
||||||
@@ -268,7 +268,7 @@ class Schema {
|
|||||||
'schema is frozen, cannot add: ' + className);
|
'schema is frozen, cannot add: ' + className);
|
||||||
}
|
}
|
||||||
// We don't have this class. Update the schema
|
// We don't have this class. Update the schema
|
||||||
return this._collection.insertOne({ _id: className }).then(() => {
|
return this._collection.addSchema(className).then(() => {
|
||||||
// The schema update succeeded. Reload the schema
|
// The schema update succeeded. Reload the schema
|
||||||
return this.reloadData();
|
return this.reloadData();
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -288,14 +288,13 @@ class Schema {
|
|||||||
|
|
||||||
// Sets the Class-level permissions for a given className, which must exist.
|
// Sets the Class-level permissions for a given className, which must exist.
|
||||||
setPermissions(className, perms) {
|
setPermissions(className, perms) {
|
||||||
var query = {_id: className};
|
|
||||||
var update = {
|
var update = {
|
||||||
_metadata: {
|
_metadata: {
|
||||||
class_permissions: perms
|
class_permissions: perms
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
update = {'$set': update};
|
update = {'$set': update};
|
||||||
return this._collection.updateOne(query, update).then(() => {
|
return this._collection.updateSchema(className, update).then(() => {
|
||||||
// The update succeeded. Reload the schema
|
// The update succeeded. Reload the schema
|
||||||
return this.reloadData();
|
return this.reloadData();
|
||||||
});
|
});
|
||||||
@@ -353,12 +352,12 @@ class Schema {
|
|||||||
// We don't have this field. Update the schema.
|
// We don't have this field. Update the schema.
|
||||||
// Note that we use the $exists guard and $set to avoid race
|
// Note that we use the $exists guard and $set to avoid race
|
||||||
// conditions in the database. This is important!
|
// conditions in the database. This is important!
|
||||||
var query = { _id: className };
|
let query = {};
|
||||||
query[key] = { '$exists': false };
|
query[key] = { '$exists': false };
|
||||||
var update = {};
|
var update = {};
|
||||||
update[key] = type;
|
update[key] = type;
|
||||||
update = {'$set': update};
|
update = {'$set': update};
|
||||||
return this._collection.upsertOne(query, update).then(() => {
|
return this._collection.upsertSchema(className, query, update).then(() => {
|
||||||
// The update succeeded. Reload the schema
|
// The update succeeded. Reload the schema
|
||||||
return this.reloadData();
|
return this.reloadData();
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -428,7 +427,7 @@ class Schema {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
// Save the _SCHEMA object
|
// Save the _SCHEMA object
|
||||||
.then(() => this._collection.updateOne({ _id: className }, { $unset: { [fieldName]: null } }));
|
.then(() => this._collection.updateSchema(className, { $unset: { [fieldName]: null } }));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user