From 341b9b9e0b538cfd0f946e1177a58e5d58f40395 Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:46:33 +0200 Subject: [PATCH] Merge pull request #29487 from overleaf/ii-subscpriptions-v1-id-index [web] Update v1_id index to be unique GitOrigin-RevId: 99ba8f10a1b34f7717f57cd5495d4159019343d1 --- ...110238_update_subscription_v1_id_index.mjs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tools/migrations/20251030110238_update_subscription_v1_id_index.mjs diff --git a/tools/migrations/20251030110238_update_subscription_v1_id_index.mjs b/tools/migrations/20251030110238_update_subscription_v1_id_index.mjs new file mode 100644 index 0000000000..e29c28d289 --- /dev/null +++ b/tools/migrations/20251030110238_update_subscription_v1_id_index.mjs @@ -0,0 +1,90 @@ +import Helpers from './lib/helpers.mjs' + +const tags = ['saas'] + +const originalIndexes = [ + { + key: { v1_id: 1 }, + name: 'v1_id_1', + sparse: true, + }, +] +const newIndexes = [ + { + key: { v1_id: 1 }, + name: 'v1_id_3', + unique: true, + sparse: true, + }, +] + +async function removeNullV1Ids(collection) { + // Remove the v1_id field from documents where it's null + const result = await collection.updateMany( + { v1_id: { $type: 'null' } }, + { $unset: { v1_id: 1 } } + ) + + console.log( + `Removed \`{ v1_id: null }\` field from ${result.modifiedCount} documents` + ) +} + +async function assertNoDuplicateV1Ids(collection) { + const duplicates = await collection + .aggregate([ + { $match: { v1_id: { $exists: true, $ne: null } } }, + { + $group: { + _id: '$v1_id', + count: { $sum: 1 }, + docs: { $push: '$_id' }, + }, + }, + { $match: { count: { $gt: 1 } } }, + ]) + .toArray() + + if (duplicates.length > 0) { + const duplicateDetails = duplicates.map(dup => ({ + v1_id: dup._id, + count: dup.count, + docs: dup.docs, + })) + throw new Error( + `Duplicate v1_id values found. Migration aborted to prevent data loss. Details: ${JSON.stringify( + duplicateDetails, + null, + 2 + )}` + ) + } +} + +const migrate = async client => { + const { db } = client + + // pre‑check (keep old index intact if failing) + await assertNoDuplicateV1Ids(db.subscriptions) + await removeNullV1Ids(db.subscriptions) + await Helpers.addIndexesToCollection(db.subscriptions, newIndexes) + await Helpers.dropIndexesFromCollection( + db.subscriptions, + // drop the 20251010082205 index too which wasn't working properly + originalIndexes.concat({ name: 'v1_id_2' }) + ) +} + +const rollback = async client => { + const { db } = client + + // recreate the original non-unique sparse index + await Helpers.addIndexesToCollection(db.subscriptions, originalIndexes) + await Helpers.dropIndexesFromCollection(db.subscriptions, newIndexes) +} + +export default { + tags, + migrate, + rollback, +}