From 45b20f08d230242120cfa0311a48bf49b161c04b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 10 Jan 2025 17:29:25 -0500 Subject: [PATCH] feat(model): make `syncIndexes()` not call `createIndex()` on indexes that already exist --- lib/model.js | 26 +++++++++++++------------- test/model.test.js | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/model.js b/lib/model.js index 8d38d9ee08..9738db0f68 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1256,7 +1256,7 @@ Model.syncIndexes = async function syncIndexes(options) { } } - const diffIndexesResult = await model.diffIndexes(); + const diffIndexesResult = await model.diffIndexes({ indexOptionsToCreate: true }); const dropped = await model.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop }); await model.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate }); @@ -1361,13 +1361,14 @@ Model.listSearchIndexes = async function listSearchIndexes(options) { * * const { toDrop, toCreate } = await Model.diffIndexes(); * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop - * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create + * toCreate; // Array of index specs containing the keys of indexes that `syncIndexes()` will create * * @param {Object} [options] + * @param {Boolean} [options.indexOptionsToCreate=false] If true, `toCreate` will include both the index spec and the index options, not just the index spec * @return {Promise} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`. */ -Model.diffIndexes = async function diffIndexes() { +Model.diffIndexes = async function diffIndexes(options) { if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') { throw new MongooseError('Model.syncIndexes() no longer accepts a callback'); } @@ -1389,13 +1390,14 @@ Model.diffIndexes = async function diffIndexes() { const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes()); const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes); - const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop); + const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options); return { toDrop, toCreate }; }; -function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) { +function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options) { const toCreate = []; + const indexOptionsToCreate = options?.indexOptionsToCreate ?? false; for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) { let found = false; @@ -1416,7 +1418,11 @@ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) { } if (!found) { - toCreate.push(schemaIndexKeysObject); + if (indexOptionsToCreate) { + toCreate.push([schemaIndexKeysObject, schemaIndexOptions]); + } else { + toCreate.push(schemaIndexKeysObject); + } } } @@ -1597,7 +1603,7 @@ Model.createIndexes = async function createIndexes(options) { */ function _ensureIndexes(model, options, callback) { - const indexes = model.schema.indexes(); + const indexes = Array.isArray(options?.toCreate) ? options.toCreate : model.schema.indexes(); let indexError; options = options || {}; @@ -1681,12 +1687,6 @@ function _ensureIndexes(model, options, callback) { indexOptions.background = options.background; } - if ('toCreate' in options) { - if (options.toCreate.length === 0) { - return done(); - } - } - // Just in case `createIndex()` throws a sync error let promise = null; try { diff --git a/test/model.test.js b/test/model.test.js index af518fe734..aa3a8bff98 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -5056,6 +5056,26 @@ describe('Model', function() { assert.strictEqual(indexes[1].background, false); }); + it('syncIndexes() does not call createIndex for indexes that already exist', async function() { + const opts = { autoIndex: false }; + const schema = new Schema({ name: String }, opts); + schema.index({ name: 1 }, { background: true }); + + const M = db.model('Test', schema); + await M.syncIndexes(); + + const indexes = await M.listIndexes(); + assert.deepEqual(indexes[1].key, { name: 1 }); + + sinon.stub(M.collection, 'createIndex').callsFake(() => Promise.resolve()); + try { + await M.syncIndexes(); + assert.equal(M.collection.createIndex.getCalls().length, 0); + } finally { + sinon.restore(); + } + }); + it('syncIndexes() supports hideIndexes (gh-14868)', async function() { const opts = { autoIndex: false }; const schema = new Schema({ name: String }, opts);