Merge pull request #752 from ParsePlatform/nlutsenko.adapter.collection

Add MongoCollection wrapper and move few basic uses of collection to it.
This commit is contained in:
Nikita Lutsenko
2016-03-01 23:05:09 -08:00
4 changed files with 99 additions and 70 deletions

View File

@@ -0,0 +1,53 @@
let mongodb = require('mongodb');
let Collection = mongodb.Collection;
export default class MongoCollection {
_mongoCollection:Collection;
constructor(mongoCollection:Collection) {
this._mongoCollection = mongoCollection;
}
// Does a find with "smart indexing".
// Currently this just means, if it needs a geoindex and there is
// none, then build the geoindex.
// This could be improved a lot but it's not clear if that's a good
// idea. Or even if this behavior is a good idea.
find(query, { skip, limit, sort } = {}) {
return this._rawFind(query, { skip, limit, sort })
.catch(error => {
// Check for "no geoindex" error
if (error.code != 17007 ||
!error.message.match(/unable to find index for .geoNear/)) {
throw error;
}
// Figure out what key needs an index
let key = error.message.match(/field=([A-Za-z_0-9]+) /)[1];
if (!key) {
throw error;
}
var index = {};
index[key] = '2d';
//TODO: condiser moving index creation logic into Schema.js
return this._mongoCollection.createIndex(index)
// Retry, but just once.
.then(() => this._rawFind(query, { skip, limit, sort }));
});
}
_rawFind(query, { skip, limit, sort } = {}) {
return this._mongoCollection
.find(query, { skip, limit, sort })
.toArray();
}
count(query, { skip, limit, sort } = {}) {
return this._mongoCollection.count(query, { skip, limit, sort });
}
drop() {
return this._mongoCollection.drop();
}
}

View File

@@ -1,4 +1,6 @@
import MongoCollection from './MongoCollection';
let mongodb = require('mongodb'); let mongodb = require('mongodb');
let MongoClient = mongodb.MongoClient; let MongoClient = mongodb.MongoClient;
@@ -30,6 +32,12 @@ export class MongoStorageAdapter {
}); });
} }
adaptiveCollection(name: string) {
return this.connect()
.then(() => this.database.collection(name))
.then(rawCollection => new MongoCollection(rawCollection));
}
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();

View File

@@ -38,6 +38,10 @@ DatabaseController.prototype.collection = function(className) {
return this.rawCollection(className); return this.rawCollection(className);
}; };
DatabaseController.prototype.adaptiveCollection = function(className) {
return this.adapter.adaptiveCollection(this.collectionPrefix + className);
};
DatabaseController.prototype.collectionExists = function(className) { DatabaseController.prototype.collectionExists = function(className) {
return this.adapter.collectionExists(this.collectionPrefix + className); return this.adapter.collectionExists(this.collectionPrefix + className);
}; };
@@ -340,9 +344,8 @@ DatabaseController.prototype.create = function(className, object, options) {
// to avoid Mongo-format dependencies. // to avoid Mongo-format dependencies.
// Returns a promise that resolves to a list of items. // Returns a promise that resolves to a list of items.
DatabaseController.prototype.mongoFind = function(className, query, options = {}) { DatabaseController.prototype.mongoFind = function(className, query, options = {}) {
return this.collection(className).then((coll) => { return this.adaptiveCollection(className)
return coll.find(query, options).toArray(); .then(collection => collection.find(query, options));
});
}; };
// Deletes everything in the database matching the current collectionPrefix // Deletes everything in the database matching the current collectionPrefix
@@ -378,23 +381,17 @@ function keysForQuery(query) {
// Returns a promise for a list of related ids given an owning id. // Returns a promise for a list of related ids given an owning id.
// className here is the owning className. // className here is the owning className.
DatabaseController.prototype.relatedIds = function(className, key, owningId) { DatabaseController.prototype.relatedIds = function(className, key, owningId) {
var joinTable = '_Join:' + key + ':' + className; return this.adaptiveCollection(joinTableName(className, key))
return this.collection(joinTable).then((coll) => { .then(coll => coll.find({owningId : owningId}))
return coll.find({owningId: owningId}).toArray(); .then(results => results.map(r => r.relatedId));
}).then((results) => {
return results.map(r => r.relatedId);
});
}; };
// Returns a promise for a list of owning ids given some related ids. // Returns a promise for a list of owning ids given some related ids.
// className here is the owning className. // className here is the owning className.
DatabaseController.prototype.owningIds = function(className, key, relatedIds) { DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
var joinTable = '_Join:' + key + ':' + className; return this.adaptiveCollection(joinTableName(className, key))
return this.collection(joinTable).then((coll) => { .then(coll => coll.find({ relatedId: { '$in': relatedIds } }))
return coll.find({relatedId: {'$in': relatedIds}}).toArray(); .then(results => results.map(r => r.owningId));
}).then((results) => {
return results.map(r => r.owningId);
});
}; };
// Modifies query so that it no longer has $in on relation fields, or // Modifies query so that it no longer has $in on relation fields, or
@@ -443,38 +440,6 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
} }
}; };
// Does a find with "smart indexing".
// Currently this just means, if it needs a geoindex and there is
// none, then build the geoindex.
// This could be improved a lot but it's not clear if that's a good
// idea. Or even if this behavior is a good idea.
DatabaseController.prototype.smartFind = function(coll, where, options) {
return coll.find(where, options).toArray()
.then((result) => {
return result;
}, (error) => {
// Check for "no geoindex" error
if (!error.message.match(/unable to find index for .geoNear/) ||
error.code != 17007) {
throw error;
}
// Figure out what key needs an index
var key = error.message.match(/field=([A-Za-z_0-9]+) /)[1];
if (!key) {
throw error;
}
var index = {};
index[key] = '2d';
//TODO: condiser moving index creation logic into Schema.js
return coll.createIndex(index).then(() => {
// Retry, but just once.
return coll.find(where, options).toArray();
});
});
};
// Runs a query on the database. // Runs a query on the database.
// Returns a promise that resolves to a list of items. // Returns a promise that resolves to a list of items.
// Options: // Options:
@@ -528,8 +493,8 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
}).then(() => { }).then(() => {
return this.reduceInRelation(className, query, schema); return this.reduceInRelation(className, query, schema);
}).then(() => { }).then(() => {
return this.collection(className); return this.adaptiveCollection(className);
}).then((coll) => { }).then(collection => {
var mongoWhere = transform.transformWhere(schema, className, query); var mongoWhere = transform.transformWhere(schema, className, query);
if (!isMaster) { if (!isMaster) {
var orParts = [ var orParts = [
@@ -542,9 +507,9 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]}; mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};
} }
if (options.count) { if (options.count) {
return coll.count(mongoWhere, mongoOptions); return collection.count(mongoWhere, mongoOptions);
} else { } else {
return this.smartFind(coll, mongoWhere, mongoOptions) return collection.find(mongoWhere, mongoOptions)
.then((mongoResults) => { .then((mongoResults) => {
return mongoResults.map((r) => { return mongoResults.map((r) => {
return this.untransformObject( return this.untransformObject(
@@ -555,4 +520,8 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
}); });
}; };
function joinTableName(className, key) {
return `_Join:${key}:${className}`;
}
module.exports = DatabaseController; module.exports = DatabaseController;

View File

@@ -38,11 +38,10 @@ function mongoSchemaToSchemaAPIResponse(schema) {
} }
function getAllSchemas(req) { function getAllSchemas(req) {
return req.config.database.collection('_SCHEMA') return req.config.database.adaptiveCollection('_SCHEMA')
.then(coll => coll.find({}).toArray()) .then(collection => collection.find({}))
.then(schemas => ({response: { .then(schemas => schemas.map(mongoSchemaToSchemaAPIResponse))
results: schemas.map(mongoSchemaToSchemaAPIResponse) .then(schemas => ({ response: { results: schemas }}));
}}));
} }
function getOneSchema(req) { function getOneSchema(req) {
@@ -152,7 +151,7 @@ function deleteSchema(req) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, Schema.invalidClassNameMessage(req.params.className)); throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, Schema.invalidClassNameMessage(req.params.className));
} }
return req.config.database.collection(req.params.className) return req.config.database.adaptiveCollection(req.params.className)
.then(collection => { .then(collection => {
return collection.count() return collection.count()
.then(count => { .then(count => {
@@ -161,19 +160,19 @@ function deleteSchema(req) {
} }
return collection.drop(); return collection.drop();
}) })
.then(() => { })
// We've dropped the collection now, so delete the item from _SCHEMA .then(() => {
// and clear the _Join collections // We've dropped the collection now, so delete the item from _SCHEMA
return req.config.database.collection('_SCHEMA') // and clear the _Join collections
.then(coll => coll.findAndRemove({_id: req.params.className}, [])) return req.config.database.collection('_SCHEMA')
.then(doc => { .then(coll => coll.findAndRemove({_id: req.params.className}, []))
if (doc.value === null) { .then(doc => {
//tried to delete non-existent class if (doc.value === null) {
return Promise.resolve(); //tried to delete non-existent class
} return Promise.resolve();
return removeJoinTables(req.config.database, doc.value); }
}); return removeJoinTables(req.config.database, doc.value);
}) });
}) })
.then(() => { .then(() => {
// Success // Success