diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ed371030..4f54fdaf7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### master [Full Changelog](https://github.com/parse-community/parse-server/compare/4.3.0...master) +- IMPROVE: Optimized deletion of class field from schema by using an index if available to do an index scan instead of a collection scan. [#6815](https://github.com/parse-community/parse-server/issues/6815). Thanks to [Manuel Trezza](https://github.com/mtrezza). ### 4.3.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/4.2.0...4.3.0) diff --git a/spec/MongoStorageAdapter.spec.js b/spec/MongoStorageAdapter.spec.js index 4509e3685d..239f50b888 100644 --- a/spec/MongoStorageAdapter.spec.js +++ b/spec/MongoStorageAdapter.spec.js @@ -351,6 +351,43 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { expect(postIndexPlan.executionStats.executionStages.stage).toBe('FETCH'); }); + it('should delete field without index', async () => { + const database = Config.get(Parse.applicationId).database; + const obj = new Parse.Object('MyObject'); + obj.set("test", 1); + await obj.save(); + const schemaBeforeDeletion = await new Parse.Schema('MyObject').get(); + await database.adapter.deleteFields( + "MyObject", + schemaBeforeDeletion, + ["test"] + ); + const schemaAfterDeletion = await new Parse.Schema('MyObject').get(); + expect(schemaBeforeDeletion.fields.test).toBeDefined(); + expect(schemaAfterDeletion.fields.test).toBeUndefined(); + }); + + it('should delete field with index', async () => { + const database = Config.get(Parse.applicationId).database; + const obj = new Parse.Object('MyObject'); + obj.set("test", 1); + await obj.save(); + const schemaBeforeDeletion = await new Parse.Schema('MyObject').get(); + await database.adapter.ensureIndex( + 'MyObject', + schemaBeforeDeletion, + ['test'] + ); + await database.adapter.deleteFields( + "MyObject", + schemaBeforeDeletion, + ["test"] + ); + const schemaAfterDeletion = await new Parse.Schema('MyObject').get(); + expect(schemaBeforeDeletion.fields.test).toBeDefined(); + expect(schemaAfterDeletion.fields.test).toBeUndefined(); + }); + if ( semver.satisfies(process.env.MONGODB_VERSION, '>=4.0.4') && process.env.MONGODB_TOPOLOGY === 'replicaset' && diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 9c08d3a073..9e75f5bb8a 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -433,6 +433,11 @@ export class MongoStorageAdapter implements StorageAdapter { collectionUpdate['$unset'][name] = null; }); + const collectionFilter = { $or: [] }; + mongoFormatNames.forEach(name => { + collectionFilter['$or'].push({ [name]: { $exists: true } }); + }); + const schemaUpdate = { $unset: {} }; fieldNames.forEach((name) => { schemaUpdate['$unset'][name] = null; @@ -440,7 +445,7 @@ export class MongoStorageAdapter implements StorageAdapter { }); return this._adaptiveCollection(className) - .then((collection) => collection.updateMany({}, collectionUpdate)) + .then((collection) => collection.updateMany(collectionFilter, collectionUpdate)) .then(() => this._schemaCollection()) .then((schemaCollection) => schemaCollection.updateSchema(className, schemaUpdate)