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:
53
src/Adapters/Storage/Mongo/MongoCollection.js
Normal file
53
src/Adapters/Storage/Mongo/MongoCollection.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user