Do not throw error if user provide a pointer like index onMongo (#6923)

* Do not throw error if user provide a pointer like index on mongo

* Add test
This commit is contained in:
Antoine Cormouls
2020-10-01 23:58:23 +02:00
committed by GitHub
parent 66f7af90c3
commit 929c4e1b0d
2 changed files with 183 additions and 105 deletions

View File

@@ -2540,7 +2540,7 @@ describe('schemas', () => {
it('unset field in beforeSave should not stop object creation', done => { it('unset field in beforeSave should not stop object creation', done => {
const hook = { const hook = {
method: function(req) { method: function (req) {
if (req.object.get('undesiredField')) { if (req.object.get('undesiredField')) {
req.object.unset('undesiredField'); req.object.unset('undesiredField');
} }
@@ -3110,6 +3110,77 @@ describe('schemas', () => {
}); });
}); });
it_only_db('mongo')(
'lets you add index with with pointer like structure',
done => {
request({
url: 'http://localhost:8378/1/schemas/NewClass',
method: 'POST',
headers: masterKeyHeaders,
json: true,
body: {},
}).then(() => {
request({
url: 'http://localhost:8378/1/schemas/NewClass',
method: 'PUT',
headers: masterKeyHeaders,
json: true,
body: {
fields: {
aPointer: { type: 'Pointer', targetClass: 'NewClass' },
},
indexes: {
pointer: { _p_aPointer: 1 },
},
},
}).then(response => {
expect(
dd(response.data, {
className: 'NewClass',
fields: {
ACL: { type: 'ACL' },
createdAt: { type: 'Date' },
updatedAt: { type: 'Date' },
objectId: { type: 'String' },
aPointer: { type: 'Pointer', targetClass: 'NewClass' },
},
classLevelPermissions: defaultClassLevelPermissions,
indexes: {
_id_: { _id: 1 },
pointer: { _p_aPointer: 1 },
},
})
).toEqual(undefined);
request({
url: 'http://localhost:8378/1/schemas/NewClass',
headers: masterKeyHeaders,
json: true,
}).then(response => {
expect(response.data).toEqual({
className: 'NewClass',
fields: {
ACL: { type: 'ACL' },
createdAt: { type: 'Date' },
updatedAt: { type: 'Date' },
objectId: { type: 'String' },
aPointer: { type: 'Pointer', targetClass: 'NewClass' },
},
classLevelPermissions: defaultClassLevelPermissions,
indexes: {
_id_: { _id: 1 },
pointer: { _p_aPointer: 1 },
},
});
config.database.adapter.getIndexes('NewClass').then(indexes => {
expect(indexes.length).toEqual(2);
done();
});
});
});
});
}
);
it('lets you add multiple indexes', done => { it('lets you add multiple indexes', done => {
request({ request({
url: 'http://localhost:8378/1/schemas/NewClass', url: 'http://localhost:8378/1/schemas/NewClass',

View File

@@ -34,12 +34,12 @@ const ReadPreference = mongodb.ReadPreference;
const MongoSchemaCollectionName = '_SCHEMA'; const MongoSchemaCollectionName = '_SCHEMA';
const storageAdapterAllCollections = (mongoAdapter) => { const storageAdapterAllCollections = mongoAdapter => {
return mongoAdapter return mongoAdapter
.connect() .connect()
.then(() => mongoAdapter.database.collections()) .then(() => mongoAdapter.database.collections())
.then((collections) => { .then(collections => {
return collections.filter((collection) => { return collections.filter(collection => {
if (collection.namespace.match(/\.system\./)) { if (collection.namespace.match(/\.system\./)) {
return false; return false;
} }
@@ -164,7 +164,7 @@ export class MongoStorageAdapter implements StorageAdapter {
const encodedUri = formatUrl(parseUrl(this._uri)); const encodedUri = formatUrl(parseUrl(this._uri));
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions) this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions)
.then((client) => { .then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client // Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
// Fortunately, we can get back the options and use them to select the proper DB. // Fortunately, we can get back the options and use them to select the proper DB.
// https://github.com/mongodb/node-mongodb-native/blob/2c35d76f08574225b8db02d7bef687123e6bb018/lib/mongo_client.js#L885 // https://github.com/mongodb/node-mongodb-native/blob/2c35d76f08574225b8db02d7bef687123e6bb018/lib/mongo_client.js#L885
@@ -183,7 +183,7 @@ export class MongoStorageAdapter implements StorageAdapter {
this.client = client; this.client = client;
this.database = database; this.database = database;
}) })
.catch((err) => { .catch(err => {
delete this.connectionPromise; delete this.connectionPromise;
return Promise.reject(err); return Promise.reject(err);
}); });
@@ -212,14 +212,14 @@ export class MongoStorageAdapter implements StorageAdapter {
_adaptiveCollection(name: string) { _adaptiveCollection(name: string) {
return this.connect() return this.connect()
.then(() => this.database.collection(this._collectionPrefix + name)) .then(() => this.database.collection(this._collectionPrefix + name))
.then((rawCollection) => new MongoCollection(rawCollection)) .then(rawCollection => new MongoCollection(rawCollection))
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
_schemaCollection(): Promise<MongoSchemaCollection> { _schemaCollection(): Promise<MongoSchemaCollection> {
return this.connect() return this.connect()
.then(() => this._adaptiveCollection(MongoSchemaCollectionName)) .then(() => this._adaptiveCollection(MongoSchemaCollectionName))
.then((collection) => new MongoSchemaCollection(collection)); .then(collection => new MongoSchemaCollection(collection));
} }
classExists(name: string) { classExists(name: string) {
@@ -229,20 +229,20 @@ export class MongoStorageAdapter implements StorageAdapter {
.listCollections({ name: this._collectionPrefix + name }) .listCollections({ name: this._collectionPrefix + name })
.toArray(); .toArray();
}) })
.then((collections) => { .then(collections => {
return collections.length > 0; return collections.length > 0;
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
setClassLevelPermissions(className: string, CLPs: any): Promise<void> { setClassLevelPermissions(className: string, CLPs: any): Promise<void> {
return this._schemaCollection() return this._schemaCollection()
.then((schemaCollection) => .then(schemaCollection =>
schemaCollection.updateSchema(className, { schemaCollection.updateSchema(className, {
$set: { '_metadata.class_permissions': CLPs }, $set: { '_metadata.class_permissions': CLPs },
}) })
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
setIndexesWithSchemaFormat( setIndexesWithSchemaFormat(
@@ -259,7 +259,7 @@ export class MongoStorageAdapter implements StorageAdapter {
} }
const deletePromises = []; const deletePromises = [];
const insertedIndexes = []; const insertedIndexes = [];
Object.keys(submittedIndexes).forEach((name) => { Object.keys(submittedIndexes).forEach(name => {
const field = submittedIndexes[name]; const field = submittedIndexes[name];
if (existingIndexes[name] && field.__op !== 'Delete') { if (existingIndexes[name] && field.__op !== 'Delete') {
throw new Parse.Error( throw new Parse.Error(
@@ -278,8 +278,13 @@ export class MongoStorageAdapter implements StorageAdapter {
deletePromises.push(promise); deletePromises.push(promise);
delete existingIndexes[name]; delete existingIndexes[name];
} else { } else {
Object.keys(field).forEach((key) => { Object.keys(field).forEach(key => {
if (!Object.prototype.hasOwnProperty.call(fields, key)) { if (
!Object.prototype.hasOwnProperty.call(
fields,
key.indexOf('_p_') === 0 ? key.replace('_p_', '') : key
)
) {
throw new Parse.Error( throw new Parse.Error(
Parse.Error.INVALID_QUERY, Parse.Error.INVALID_QUERY,
`Field ${key} does not exist, cannot add index.` `Field ${key} does not exist, cannot add index.`
@@ -300,17 +305,17 @@ export class MongoStorageAdapter implements StorageAdapter {
return Promise.all(deletePromises) return Promise.all(deletePromises)
.then(() => insertPromise) .then(() => insertPromise)
.then(() => this._schemaCollection()) .then(() => this._schemaCollection())
.then((schemaCollection) => .then(schemaCollection =>
schemaCollection.updateSchema(className, { schemaCollection.updateSchema(className, {
$set: { '_metadata.indexes': existingIndexes }, $set: { '_metadata.indexes': existingIndexes },
}) })
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
setIndexesFromMongo(className: string) { setIndexesFromMongo(className: string) {
return this.getIndexes(className) return this.getIndexes(className)
.then((indexes) => { .then(indexes => {
indexes = indexes.reduce((obj, index) => { indexes = indexes.reduce((obj, index) => {
if (index.key._fts) { if (index.key._fts) {
delete index.key._fts; delete index.key._fts;
@@ -322,13 +327,13 @@ export class MongoStorageAdapter implements StorageAdapter {
obj[index.name] = index.key; obj[index.name] = index.key;
return obj; return obj;
}, {}); }, {});
return this._schemaCollection().then((schemaCollection) => return this._schemaCollection().then(schemaCollection =>
schemaCollection.updateSchema(className, { schemaCollection.updateSchema(className, {
$set: { '_metadata.indexes': indexes }, $set: { '_metadata.indexes': indexes },
}) })
); );
}) })
.catch((err) => this.handleError(err)) .catch(err => this.handleError(err))
.catch(() => { .catch(() => {
// Ignore if collection not found // Ignore if collection not found
return Promise.resolve(); return Promise.resolve();
@@ -351,8 +356,8 @@ export class MongoStorageAdapter implements StorageAdapter {
schema.fields schema.fields
) )
.then(() => this._schemaCollection()) .then(() => this._schemaCollection())
.then((schemaCollection) => schemaCollection.insertSchema(mongoObject)) .then(schemaCollection => schemaCollection.insertSchema(mongoObject))
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
addFieldIfNotExists( addFieldIfNotExists(
@@ -361,11 +366,11 @@ export class MongoStorageAdapter implements StorageAdapter {
type: any type: any
): Promise<void> { ): Promise<void> {
return this._schemaCollection() return this._schemaCollection()
.then((schemaCollection) => .then(schemaCollection =>
schemaCollection.addFieldIfNotExists(className, fieldName, type) schemaCollection.addFieldIfNotExists(className, fieldName, type)
) )
.then(() => this.createIndexesIfNeeded(className, fieldName, type)) .then(() => this.createIndexesIfNeeded(className, fieldName, type))
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
@@ -373,8 +378,8 @@ export class MongoStorageAdapter implements StorageAdapter {
deleteClass(className: string) { deleteClass(className: string) {
return ( return (
this._adaptiveCollection(className) this._adaptiveCollection(className)
.then((collection) => collection.drop()) .then(collection => collection.drop())
.catch((error) => { .catch(error => {
// 'ns not found' means collection was already gone. Ignore deletion attempt. // 'ns not found' means collection was already gone. Ignore deletion attempt.
if (error.message == 'ns not found') { if (error.message == 'ns not found') {
return; return;
@@ -383,17 +388,17 @@ export class MongoStorageAdapter implements StorageAdapter {
}) })
// We've dropped the collection, now remove the _SCHEMA document // We've dropped the collection, now remove the _SCHEMA document
.then(() => this._schemaCollection()) .then(() => this._schemaCollection())
.then((schemaCollection) => .then(schemaCollection =>
schemaCollection.findAndDeleteSchema(className) schemaCollection.findAndDeleteSchema(className)
) )
.catch((err) => this.handleError(err)) .catch(err => this.handleError(err))
); );
} }
deleteAllClasses(fast: boolean) { deleteAllClasses(fast: boolean) {
return storageAdapterAllCollections(this).then((collections) => return storageAdapterAllCollections(this).then(collections =>
Promise.all( Promise.all(
collections.map((collection) => collections.map(collection =>
fast ? collection.deleteMany({}) : collection.drop() fast ? collection.deleteMany({}) : collection.drop()
) )
) )
@@ -421,7 +426,7 @@ export class MongoStorageAdapter implements StorageAdapter {
// Returns a Promise. // Returns a Promise.
deleteFields(className: string, schema: SchemaType, fieldNames: string[]) { deleteFields(className: string, schema: SchemaType, fieldNames: string[]) {
const mongoFormatNames = fieldNames.map((fieldName) => { const mongoFormatNames = fieldNames.map(fieldName => {
if (schema.fields[fieldName].type === 'Pointer') { if (schema.fields[fieldName].type === 'Pointer') {
return `_p_${fieldName}`; return `_p_${fieldName}`;
} else { } else {
@@ -429,7 +434,7 @@ export class MongoStorageAdapter implements StorageAdapter {
} }
}); });
const collectionUpdate = { $unset: {} }; const collectionUpdate = { $unset: {} };
mongoFormatNames.forEach((name) => { mongoFormatNames.forEach(name => {
collectionUpdate['$unset'][name] = null; collectionUpdate['$unset'][name] = null;
}); });
@@ -439,18 +444,20 @@ export class MongoStorageAdapter implements StorageAdapter {
}); });
const schemaUpdate = { $unset: {} }; const schemaUpdate = { $unset: {} };
fieldNames.forEach((name) => { fieldNames.forEach(name => {
schemaUpdate['$unset'][name] = null; schemaUpdate['$unset'][name] = null;
schemaUpdate['$unset'][`_metadata.fields_options.${name}`] = null; schemaUpdate['$unset'][`_metadata.fields_options.${name}`] = null;
}); });
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => collection.updateMany(collectionFilter, collectionUpdate)) .then(collection =>
collection.updateMany(collectionFilter, collectionUpdate)
)
.then(() => this._schemaCollection()) .then(() => this._schemaCollection())
.then((schemaCollection) => .then(schemaCollection =>
schemaCollection.updateSchema(className, schemaUpdate) schemaCollection.updateSchema(className, schemaUpdate)
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Return a promise for all schemas known to this adapter, in Parse format. In case the // Return a promise for all schemas known to this adapter, in Parse format. In case the
@@ -458,10 +465,10 @@ export class MongoStorageAdapter implements StorageAdapter {
// rejection reason are TBD. // rejection reason are TBD.
getAllClasses(): Promise<StorageClass[]> { getAllClasses(): Promise<StorageClass[]> {
return this._schemaCollection() return this._schemaCollection()
.then((schemasCollection) => .then(schemasCollection =>
schemasCollection._fetchAllSchemasFrom_SCHEMA() schemasCollection._fetchAllSchemasFrom_SCHEMA()
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Return a promise for the schema with the given name, in Parse format. If // Return a promise for the schema with the given name, in Parse format. If
@@ -469,10 +476,10 @@ export class MongoStorageAdapter implements StorageAdapter {
// undefined as the reason. // undefined as the reason.
getClass(className: string): Promise<StorageClass> { getClass(className: string): Promise<StorageClass> {
return this._schemaCollection() return this._schemaCollection()
.then((schemasCollection) => .then(schemasCollection =>
schemasCollection._fetchOneSchemaFrom_SCHEMA(className) schemasCollection._fetchOneSchemaFrom_SCHEMA(className)
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema, // TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema,
@@ -491,10 +498,10 @@ export class MongoStorageAdapter implements StorageAdapter {
schema schema
); );
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.insertOne(mongoObject, transactionalSession) collection.insertOne(mongoObject, transactionalSession)
) )
.catch((error) => { .catch(error => {
if (error.code === 11000) { if (error.code === 11000) {
// Duplicate value // Duplicate value
const err = new Parse.Error( const err = new Parse.Error(
@@ -514,7 +521,7 @@ export class MongoStorageAdapter implements StorageAdapter {
} }
throw error; throw error;
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Remove all objects that match the given Parse Query. // Remove all objects that match the given Parse Query.
@@ -528,11 +535,11 @@ export class MongoStorageAdapter implements StorageAdapter {
) { ) {
schema = convertParseSchemaToMongoSchema(schema); schema = convertParseSchemaToMongoSchema(schema);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => { .then(collection => {
const mongoWhere = transformWhere(className, query, schema); const mongoWhere = transformWhere(className, query, schema);
return collection.deleteMany(mongoWhere, transactionalSession); return collection.deleteMany(mongoWhere, transactionalSession);
}) })
.catch((err) => this.handleError(err)) .catch(err => this.handleError(err))
.then( .then(
({ result }) => { ({ result }) => {
if (result.n === 0) { if (result.n === 0) {
@@ -564,10 +571,10 @@ export class MongoStorageAdapter implements StorageAdapter {
const mongoUpdate = transformUpdate(className, update, schema); const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema); const mongoWhere = transformWhere(className, query, schema);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.updateMany(mongoWhere, mongoUpdate, transactionalSession) collection.updateMany(mongoWhere, mongoUpdate, transactionalSession)
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Atomically finds and updates an object based on query. // Atomically finds and updates an object based on query.
@@ -583,16 +590,14 @@ export class MongoStorageAdapter implements StorageAdapter {
const mongoUpdate = transformUpdate(className, update, schema); const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema); const mongoWhere = transformWhere(className, query, schema);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection._mongoCollection.findOneAndUpdate(mongoWhere, mongoUpdate, { collection._mongoCollection.findOneAndUpdate(mongoWhere, mongoUpdate, {
returnOriginal: false, returnOriginal: false,
session: transactionalSession || undefined, session: transactionalSession || undefined,
}) })
) )
.then((result) => .then(result => mongoObjectToParseObject(className, result.value, schema))
mongoObjectToParseObject(className, result.value, schema) .catch(error => {
)
.catch((error) => {
if (error.code === 11000) { if (error.code === 11000) {
throw new Parse.Error( throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE, Parse.Error.DUPLICATE_VALUE,
@@ -601,7 +606,7 @@ export class MongoStorageAdapter implements StorageAdapter {
} }
throw error; throw error;
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Hopefully we can get rid of this. It's only used for config and hooks. // Hopefully we can get rid of this. It's only used for config and hooks.
@@ -616,10 +621,10 @@ export class MongoStorageAdapter implements StorageAdapter {
const mongoUpdate = transformUpdate(className, update, schema); const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema); const mongoWhere = transformWhere(className, query, schema);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.upsertOne(mongoWhere, mongoUpdate, transactionalSession) collection.upsertOne(mongoWhere, mongoUpdate, transactionalSession)
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
@@ -667,7 +672,7 @@ export class MongoStorageAdapter implements StorageAdapter {
readPreference = this._parseReadPreference(readPreference); readPreference = this._parseReadPreference(readPreference);
return this.createTextIndexesIfNeeded(className, query, schema) return this.createTextIndexesIfNeeded(className, query, schema)
.then(() => this._adaptiveCollection(className)) .then(() => this._adaptiveCollection(className))
.then((collection) => .then(collection =>
collection.find(mongoWhere, { collection.find(mongoWhere, {
skip, skip,
limit, limit,
@@ -680,15 +685,15 @@ export class MongoStorageAdapter implements StorageAdapter {
explain, explain,
}) })
) )
.then((objects) => { .then(objects => {
if (explain) { if (explain) {
return objects; return objects;
} }
return objects.map((object) => return objects.map(object =>
mongoObjectToParseObject(className, object, schema) mongoObjectToParseObject(className, object, schema)
); );
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
ensureIndex( ensureIndex(
@@ -697,20 +702,22 @@ export class MongoStorageAdapter implements StorageAdapter {
fieldNames: string[], fieldNames: string[],
indexName: ?string, indexName: ?string,
caseInsensitive: boolean = false, caseInsensitive: boolean = false,
options?: Object = {}, options?: Object = {}
): Promise<any> { ): Promise<any> {
schema = convertParseSchemaToMongoSchema(schema); schema = convertParseSchemaToMongoSchema(schema);
const indexCreationRequest = {}; const indexCreationRequest = {};
const mongoFieldNames = fieldNames.map((fieldName) => const mongoFieldNames = fieldNames.map(fieldName =>
transformKey(className, fieldName, schema) transformKey(className, fieldName, schema)
); );
mongoFieldNames.forEach((fieldName) => { mongoFieldNames.forEach(fieldName => {
indexCreationRequest[fieldName] = options.indexType !== undefined ? options.indexType : 1; indexCreationRequest[fieldName] =
options.indexType !== undefined ? options.indexType : 1;
}); });
const defaultOptions: Object = { background: true, sparse: true }; const defaultOptions: Object = { background: true, sparse: true };
const indexNameOptions: Object = indexName ? { name: indexName } : {}; const indexNameOptions: Object = indexName ? { name: indexName } : {};
const ttlOptions: Object = options.ttl !== undefined ? { expireAfterSeconds: options.ttl } : {}; const ttlOptions: Object =
options.ttl !== undefined ? { expireAfterSeconds: options.ttl } : {};
const caseInsensitiveOptions: Object = caseInsensitive const caseInsensitiveOptions: Object = caseInsensitive
? { collation: MongoCollection.caseInsensitiveCollation() } ? { collation: MongoCollection.caseInsensitiveCollation() }
: {}; : {};
@@ -723,16 +730,16 @@ export class MongoStorageAdapter implements StorageAdapter {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then( .then(
(collection) => collection =>
new Promise((resolve, reject) => new Promise((resolve, reject) =>
collection._mongoCollection.createIndex( collection._mongoCollection.createIndex(
indexCreationRequest, indexCreationRequest,
indexOptions, indexOptions,
(error) => (error ? reject(error) : resolve()) error => (error ? reject(error) : resolve())
) )
) )
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
@@ -747,17 +754,17 @@ export class MongoStorageAdapter implements StorageAdapter {
) { ) {
schema = convertParseSchemaToMongoSchema(schema); schema = convertParseSchemaToMongoSchema(schema);
const indexCreationRequest = {}; const indexCreationRequest = {};
const mongoFieldNames = fieldNames.map((fieldName) => const mongoFieldNames = fieldNames.map(fieldName =>
transformKey(className, fieldName, schema) transformKey(className, fieldName, schema)
); );
mongoFieldNames.forEach((fieldName) => { mongoFieldNames.forEach(fieldName => {
indexCreationRequest[fieldName] = 1; indexCreationRequest[fieldName] = 1;
}); });
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection._ensureSparseUniqueIndexInBackground(indexCreationRequest) collection._ensureSparseUniqueIndexInBackground(indexCreationRequest)
) )
.catch((error) => { .catch(error => {
if (error.code === 11000) { if (error.code === 11000) {
throw new Parse.Error( throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE, Parse.Error.DUPLICATE_VALUE,
@@ -766,18 +773,18 @@ export class MongoStorageAdapter implements StorageAdapter {
} }
throw error; throw error;
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Used in tests // Used in tests
_rawFind(className: string, query: QueryType) { _rawFind(className: string, query: QueryType) {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.find(query, { collection.find(query, {
maxTimeMS: this._maxTimeMS, maxTimeMS: this._maxTimeMS,
}) })
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// Executes a count. // Executes a count.
@@ -791,14 +798,14 @@ export class MongoStorageAdapter implements StorageAdapter {
schema = convertParseSchemaToMongoSchema(schema); schema = convertParseSchemaToMongoSchema(schema);
readPreference = this._parseReadPreference(readPreference); readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.count(transformWhere(className, query, schema, true), { collection.count(transformWhere(className, query, schema, true), {
maxTimeMS: this._maxTimeMS, maxTimeMS: this._maxTimeMS,
readPreference, readPreference,
hint, hint,
}) })
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
distinct( distinct(
@@ -813,22 +820,22 @@ export class MongoStorageAdapter implements StorageAdapter {
const transformField = transformKey(className, fieldName, schema); const transformField = transformKey(className, fieldName, schema);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.distinct( collection.distinct(
transformField, transformField,
transformWhere(className, query, schema) transformWhere(className, query, schema)
) )
) )
.then((objects) => { .then(objects => {
objects = objects.filter((obj) => obj != null); objects = objects.filter(obj => obj != null);
return objects.map((object) => { return objects.map(object => {
if (isPointerField) { if (isPointerField) {
return transformPointerString(schema, fieldName, object); return transformPointerString(schema, fieldName, object);
} }
return mongoObjectToParseObject(className, object, schema); return mongoObjectToParseObject(className, object, schema);
}); });
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
aggregate( aggregate(
@@ -840,7 +847,7 @@ export class MongoStorageAdapter implements StorageAdapter {
explain?: boolean explain?: boolean
) { ) {
let isPointerField = false; let isPointerField = false;
pipeline = pipeline.map((stage) => { pipeline = pipeline.map(stage => {
if (stage.$group) { if (stage.$group) {
stage.$group = this._parseAggregateGroupArgs(schema, stage.$group); stage.$group = this._parseAggregateGroupArgs(schema, stage.$group);
if ( if (
@@ -870,7 +877,7 @@ export class MongoStorageAdapter implements StorageAdapter {
}); });
readPreference = this._parseReadPreference(readPreference); readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => .then(collection =>
collection.aggregate(pipeline, { collection.aggregate(pipeline, {
readPreference, readPreference,
maxTimeMS: this._maxTimeMS, maxTimeMS: this._maxTimeMS,
@@ -878,8 +885,8 @@ export class MongoStorageAdapter implements StorageAdapter {
explain, explain,
}) })
) )
.then((results) => { .then(results => {
results.forEach((result) => { results.forEach(result => {
if (Object.prototype.hasOwnProperty.call(result, '_id')) { if (Object.prototype.hasOwnProperty.call(result, '_id')) {
if (isPointerField && result._id) { if (isPointerField && result._id) {
result._id = result._id.split('$')[1]; result._id = result._id.split('$')[1];
@@ -898,12 +905,12 @@ export class MongoStorageAdapter implements StorageAdapter {
}); });
return results; return results;
}) })
.then((objects) => .then(objects =>
objects.map((object) => objects.map(object =>
mongoObjectToParseObject(className, object, schema) mongoObjectToParseObject(className, object, schema)
) )
) )
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
// This function will recursively traverse the pipeline and convert any Pointer or Date columns. // This function will recursively traverse the pipeline and convert any Pointer or Date columns.
@@ -929,7 +936,7 @@ export class MongoStorageAdapter implements StorageAdapter {
if (pipeline === null) { if (pipeline === null) {
return null; return null;
} else if (Array.isArray(pipeline)) { } else if (Array.isArray(pipeline)) {
return pipeline.map((value) => this._parseAggregateArgs(schema, value)); return pipeline.map(value => this._parseAggregateArgs(schema, value));
} else if (typeof pipeline === 'object') { } else if (typeof pipeline === 'object') {
const returnValue = {}; const returnValue = {};
for (const field in pipeline) { for (const field in pipeline) {
@@ -1004,7 +1011,7 @@ export class MongoStorageAdapter implements StorageAdapter {
// updatedAt or objectId and change it accordingly. // updatedAt or objectId and change it accordingly.
_parseAggregateGroupArgs(schema: any, pipeline: any): any { _parseAggregateGroupArgs(schema: any, pipeline: any): any {
if (Array.isArray(pipeline)) { if (Array.isArray(pipeline)) {
return pipeline.map((value) => return pipeline.map(value =>
this._parseAggregateGroupArgs(schema, value) this._parseAggregateGroupArgs(schema, value)
); );
} else if (typeof pipeline === 'object') { } else if (typeof pipeline === 'object') {
@@ -1084,14 +1091,14 @@ export class MongoStorageAdapter implements StorageAdapter {
createIndex(className: string, index: any) { createIndex(className: string, index: any) {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => collection._mongoCollection.createIndex(index)) .then(collection => collection._mongoCollection.createIndex(index))
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
createIndexes(className: string, indexes: any) { createIndexes(className: string, indexes: any) {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => collection._mongoCollection.createIndexes(indexes)) .then(collection => collection._mongoCollection.createIndexes(indexes))
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
createIndexesIfNeeded(className: string, fieldName: string, type: any) { createIndexesIfNeeded(className: string, fieldName: string, type: any) {
@@ -1129,7 +1136,7 @@ export class MongoStorageAdapter implements StorageAdapter {
textIndex, textIndex,
existingIndexes, existingIndexes,
schema.fields schema.fields
).catch((error) => { ).catch(error => {
if (error.code === 85) { if (error.code === 85) {
// Index exist with different options // Index exist with different options
return this.setIndexesFromMongo(className); return this.setIndexesFromMongo(className);
@@ -1142,31 +1149,31 @@ export class MongoStorageAdapter implements StorageAdapter {
getIndexes(className: string) { getIndexes(className: string) {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => collection._mongoCollection.indexes()) .then(collection => collection._mongoCollection.indexes())
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
dropIndex(className: string, index: any) { dropIndex(className: string, index: any) {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => collection._mongoCollection.dropIndex(index)) .then(collection => collection._mongoCollection.dropIndex(index))
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
dropAllIndexes(className: string) { dropAllIndexes(className: string) {
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then((collection) => collection._mongoCollection.dropIndexes()) .then(collection => collection._mongoCollection.dropIndexes())
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
updateSchemaWithIndexes(): Promise<any> { updateSchemaWithIndexes(): Promise<any> {
return this.getAllClasses() return this.getAllClasses()
.then((classes) => { .then(classes => {
const promises = classes.map((schema) => { const promises = classes.map(schema => {
return this.setIndexesFromMongo(schema.className); return this.setIndexesFromMongo(schema.className);
}); });
return Promise.all(promises); return Promise.all(promises);
}) })
.catch((err) => this.handleError(err)); .catch(err => this.handleError(err));
} }
createTransactionalSession(): Promise<any> { createTransactionalSession(): Promise<any> {