Skip to content

Commit 241cd8c

Browse files
committed
Merge pull request #909 from ParsePlatform/nlutsenko.databaseController
Move DatabaseController and Schema fully to adaptive mongo collection.
2 parents 8d10447 + 49eb9df commit 241cd8c

File tree

5 files changed

+121
-119
lines changed

5 files changed

+121
-119
lines changed

src/Adapters/Storage/Mongo/MongoCollection.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ export default class MongoCollection {
5454
return this._mongoCollection.findAndModify(query, [], update, { new: true }).then(document => {
5555
// Value is the object where mongo returns multiple fields.
5656
return document.value;
57-
})
57+
});
58+
}
59+
60+
insertOne(object) {
61+
return this._mongoCollection.insertOne(object);
5862
}
5963

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

71+
updateOne(query, update) {
72+
return this._mongoCollection.updateOne(query, update);
73+
}
74+
6775
updateMany(query, update) {
6876
return this._mongoCollection.updateMany(query, update);
6977
}
@@ -83,8 +91,8 @@ export default class MongoCollection {
8391
return this._mongoCollection.deleteOne(query);
8492
}
8593

86-
remove(query) {
87-
return this._mongoCollection.remove(query);
94+
deleteMany(query) {
95+
return this._mongoCollection.deleteMany(query);
8896
}
8997

9098
drop() {

src/Controllers/DatabaseController.js

Lines changed: 71 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ DatabaseController.prototype.connect = function() {
2828
return this.adapter.connect();
2929
};
3030

31-
// Returns a promise for a Mongo collection.
32-
// Generally just for internal use.
33-
DatabaseController.prototype.collection = function(className) {
34-
if (!Schema.classNameIsValid(className)) {
35-
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME,
36-
'invalid className: ' + className);
37-
}
38-
return this.adapter.collection(this.collectionPrefix + className);
39-
};
40-
4131
DatabaseController.prototype.adaptiveCollection = function(className) {
4232
return this.adapter.adaptiveCollection(this.collectionPrefix + className);
4333
};
@@ -54,15 +44,23 @@ function returnsTrue() {
5444
return true;
5545
}
5646

47+
DatabaseController.prototype.validateClassName = function(className) {
48+
if (!Schema.classNameIsValid(className)) {
49+
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
50+
return Promise.reject(error);
51+
}
52+
return Promise.resolve();
53+
};
54+
5755
// Returns a promise for a schema object.
5856
// If we are provided a acceptor, then we run it on the schema.
5957
// If the schema isn't accepted, we reload it at most once.
6058
DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {
6159

6260
if (!this.schemaPromise) {
63-
this.schemaPromise = this.collection('_SCHEMA').then((coll) => {
61+
this.schemaPromise = this.adaptiveCollection('_SCHEMA').then(collection => {
6462
delete this.schemaPromise;
65-
return Schema.load(coll);
63+
return Schema.load(collection);
6664
});
6765
return this.schemaPromise;
6866
}
@@ -71,9 +69,9 @@ DatabaseController.prototype.loadSchema = function(acceptor = returnsTrue) {
7169
if (acceptor(schema)) {
7270
return schema;
7371
}
74-
this.schemaPromise = this.collection('_SCHEMA').then((coll) => {
72+
this.schemaPromise = this.adaptiveCollection('_SCHEMA').then(collection => {
7573
delete this.schemaPromise;
76-
return Schema.load(coll);
74+
return Schema.load(collection);
7775
});
7876
return this.schemaPromise;
7977
});
@@ -230,30 +228,28 @@ DatabaseController.prototype.handleRelationUpdates = function(className,
230228

231229
// Adds a relation.
232230
// Returns a promise that resolves successfully iff the add was successful.
233-
DatabaseController.prototype.addRelation = function(key, fromClassName,
234-
fromId, toId) {
235-
var doc = {
231+
DatabaseController.prototype.addRelation = function(key, fromClassName, fromId, toId) {
232+
let doc = {
236233
relatedId: toId,
237-
owningId: fromId
234+
owningId : fromId
238235
};
239-
var className = '_Join:' + key + ':' + fromClassName;
240-
return this.collection(className).then((coll) => {
241-
return coll.update(doc, doc, {upsert: true});
236+
let className = `_Join:${key}:${fromClassName}`;
237+
return this.adaptiveCollection(className).then((coll) => {
238+
return coll.upsertOne(doc, doc);
242239
});
243240
};
244241

245242
// Removes a relation.
246243
// Returns a promise that resolves successfully iff the remove was
247244
// successful.
248-
DatabaseController.prototype.removeRelation = function(key, fromClassName,
249-
fromId, toId) {
245+
DatabaseController.prototype.removeRelation = function(key, fromClassName, fromId, toId) {
250246
var doc = {
251247
relatedId: toId,
252248
owningId: fromId
253249
};
254-
var className = '_Join:' + key + ':' + fromClassName;
255-
return this.collection(className).then((coll) => {
256-
return coll.remove(doc);
250+
let className = `_Join:${key}:${fromClassName}`;
251+
return this.adaptiveCollection(className).then(coll => {
252+
return coll.deleteOne(doc);
257253
});
258254
};
259255

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

271267
var schema;
272-
return this.loadSchema().then((s) => {
273-
schema = s;
274-
if (!isMaster) {
275-
return schema.validatePermission(className, aclGroup, 'delete');
276-
}
277-
return Promise.resolve();
278-
}).then(() => {
279-
280-
return this.collection(className);
281-
}).then((coll) => {
282-
var mongoWhere = transform.transformWhere(schema, className, query);
283-
284-
if (options.acl) {
285-
var writePerms = [
286-
{_wperm: {'$exists': false}}
287-
];
288-
for (var entry of options.acl) {
289-
writePerms.push({_wperm: {'$in': [entry]}});
268+
return this.loadSchema()
269+
.then(s => {
270+
schema = s;
271+
if (!isMaster) {
272+
return schema.validatePermission(className, aclGroup, 'delete');
290273
}
291-
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
292-
}
293-
294-
return coll.remove(mongoWhere);
295-
}).then((resp) => {
296-
//Check _Session to avoid changing password failed without any session.
297-
if (resp.result.n === 0 && className !== "_Session") {
298-
return Promise.reject(
299-
new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
300-
'Object not found.'));
274+
return Promise.resolve();
275+
})
276+
.then(() => this.adaptiveCollection(className))
277+
.then(collection => {
278+
let mongoWhere = transform.transformWhere(schema, className, query);
301279

302-
}
303-
}, (error) => {
304-
throw error;
305-
});
280+
if (options.acl) {
281+
var writePerms = [
282+
{ _wperm: { '$exists': false } }
283+
];
284+
for (var entry of options.acl) {
285+
writePerms.push({ _wperm: { '$in': [entry] } });
286+
}
287+
mongoWhere = { '$and': [mongoWhere, { '$or': writePerms }] };
288+
}
289+
return collection.deleteMany(mongoWhere);
290+
})
291+
.then(resp => {
292+
//Check _Session to avoid changing password failed without any session.
293+
// TODO: @nlutsenko Stop relying on `result.n`
294+
if (resp.result.n === 0 && className !== "_Session") {
295+
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
296+
}
297+
});
306298
};
307299

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

315-
return this.loadSchema().then((s) => {
316-
schema = s;
317-
if (!isMaster) {
318-
return schema.validatePermission(className, aclGroup, 'create');
319-
}
320-
return Promise.resolve();
321-
}).then(() => {
322-
323-
return this.handleRelationUpdates(className, null, object);
324-
}).then(() => {
325-
return this.collection(className);
326-
}).then((coll) => {
327-
var mongoObject = transform.transformCreate(schema, className, object);
328-
return coll.insert([mongoObject]);
329-
});
307+
return this.validateClassName(className)
308+
.then(() => this.loadSchema())
309+
.then(s => {
310+
schema = s;
311+
if (!isMaster) {
312+
return schema.validatePermission(className, aclGroup, 'create');
313+
}
314+
return Promise.resolve();
315+
})
316+
.then(() => this.handleRelationUpdates(className, null, object))
317+
.then(() => this.adaptiveCollection(className))
318+
.then(coll => {
319+
var mongoObject = transform.transformCreate(schema, className, object);
320+
return coll.insertOne(mongoObject);
321+
});
330322
};
331323

332324
// Runs a mongo query on the database.
@@ -386,14 +378,14 @@ DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
386378
// equal-to-pointer constraints on relation fields.
387379
// Returns a promise that resolves when query is mutated
388380
DatabaseController.prototype.reduceInRelation = function(className, query, schema) {
389-
381+
390382
// Search for an in-relation or equal-to-relation
391383
// Make it sequential for now, not sure of paralleization side effects
392384
if (query['$or']) {
393385
let ors = query['$or'];
394386
return Promise.all(ors.map((aQuery, index) => {
395387
return this.reduceInRelation(className, aQuery, schema).then((aQuery) => {
396-
query['$or'][index] = aQuery;
388+
query['$or'][index] = aQuery;
397389
})
398390
}));
399391
}
@@ -413,14 +405,14 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
413405
relatedIds = [query[key].objectId];
414406
}
415407
return this.owningIds(className, key, relatedIds).then((ids) => {
416-
delete query[key];
408+
delete query[key];
417409
this.addInObjectIdsIds(ids, query);
418410
return Promise.resolve(query);
419411
});
420412
}
421413
return Promise.resolve(query);
422414
})
423-
415+
424416
return Promise.all(promises).then(() => {
425417
return Promise.resolve(query);
426418
})
@@ -429,13 +421,13 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
429421
// Modifies query so that it no longer has $relatedTo
430422
// Returns a promise that resolves when query is mutated
431423
DatabaseController.prototype.reduceRelationKeys = function(className, query) {
432-
424+
433425
if (query['$or']) {
434426
return Promise.all(query['$or'].map((aQuery) => {
435427
return this.reduceRelationKeys(className, aQuery);
436428
}));
437429
}
438-
430+
439431
var relatedTo = query['$relatedTo'];
440432
if (relatedTo) {
441433
return this.relatedIds(

src/Controllers/HooksController.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class HooksController {
7171

7272
_removeHooks(query) {
7373
return this.getCollection().then(collection => {
74-
return collection.remove(query);
74+
return collection.deleteMany(query);
7575
}).then(() => {
7676
return {};
7777
});

src/Routers/SchemasRouter.js

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function getAllSchemas(req) {
1818
return req.config.database.adaptiveCollection('_SCHEMA')
1919
.then(collection => collection.find({}))
2020
.then(schemas => schemas.map(Schema.mongoSchemaToSchemaAPIResponse))
21-
.then(schemas => ({ response: { results: schemas }}));
21+
.then(schemas => ({ response: { results: schemas } }));
2222
}
2323

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

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

8585
// Finally we have checked to make sure the request is valid and we can start deleting fields.
86-
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
87-
let deletionPromises = [];
88-
Object.keys(submittedFields).forEach(submittedFieldName => {
89-
if (submittedFields[submittedFieldName].__op === 'Delete') {
90-
let promise = schema.deleteField(submittedFieldName, className, req.config.database);
91-
deletionPromises.push(promise);
86+
// Do all deletions first, then add fields to avoid duplicate geopoint error.
87+
let deletePromises = [];
88+
let insertedFields = [];
89+
Object.keys(submittedFields).forEach(fieldName => {
90+
if (submittedFields[fieldName].__op === 'Delete') {
91+
const promise = schema.deleteField(fieldName, className, req.config.database);
92+
deletePromises.push(promise);
93+
} else {
94+
insertedFields.push(fieldName);
9295
}
9396
});
94-
95-
return Promise.all(deletionPromises)
96-
.then(() => new Promise((resolve, reject) => {
97-
schema.collection.update({_id: className}, mongoObject.result, {w: 1}, (err, docs) => {
98-
if (err) {
99-
reject(err);
100-
}
101-
resolve({ response: Schema.mongoSchemaToSchemaAPIResponse(mongoObject.result)});
102-
})
103-
}));
97+
return Promise.all(deletePromises) // Delete Everything
98+
.then(() => schema.reloadData()) // Reload our Schema, so we have all the new values
99+
.then(() => {
100+
let promises = insertedFields.map(fieldName => {
101+
const mongoType = mongoObject.result[fieldName];
102+
return schema.validateField(className, fieldName, mongoType);
103+
});
104+
return Promise.all(promises);
105+
})
106+
.then(() => ({ response: Schema.mongoSchemaToSchemaAPIResponse(mongoObject.result) }));
104107
});
105108
}
106109

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

0 commit comments

Comments
 (0)