Last active
November 5, 2024 14:30
-
-
Save mstaicu/1f9fcb142e25905eebf1509ad0a119a5 to your computer and use it in GitHub Desktop.
MongoDB document migrate script from ObjectId to UUID
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import mongoose from "mongoose"; | |
import { randomUUID } from "node:crypto"; | |
const relatedCollections = { | |
users: [ | |
{ collectionName: "posts", foreignKey: "userId" }, | |
], | |
}; | |
async function dropIndexes(collectionName, db) { | |
const collection = db.collection(collectionName); | |
await collection.dropIndexes(); | |
console.log(`Indexes dropped for collection: ${collectionName}`); | |
} | |
async function recreateIndexes() { | |
const models = Object.values(mongoose.models); | |
for (const model of models) { | |
await model.syncIndexes(); | |
console.log(`Indexes re-synced for model: ${model.modelName}`); | |
} | |
} | |
async function migrateCollectionWithReferences(collectionName, db) { | |
const collection = db.collection(collectionName); | |
await dropIndexes(collectionName, db); | |
const documents = await collection.find({}).toArray(); | |
const idMapping = {}; | |
console.log(`Migrating collection: ${collectionName}`); | |
// Step 1: Update collection IDs to UUIDs | |
for (const doc of documents) { | |
const oldId = doc._id; | |
if ( | |
mongoose.Types.ObjectId.isValid(oldId) && | |
oldId instanceof mongoose.Types.ObjectId | |
) { | |
const newId = randomUUID(); | |
const { _id, ...docData } = doc; | |
await collection.insertOne({ _id: newId, ...docData }); | |
idMapping[oldId.toString()] = newId; | |
} | |
} | |
// Step 2: Update references in related collections | |
const collectionRelatedCollections = relatedCollections[collectionName]; | |
if ( | |
!collectionRelatedCollections || | |
!Array.isArray(collectionRelatedCollections) || | |
!collectionRelatedCollections.length | |
) { | |
console.error(`No related collections for ${collectionName}`); | |
} | |
for (let collection of collectionRelatedCollections) { | |
const { collectionName, foreignKey } = collection; | |
if (!collectionName) { | |
console.error( | |
`Error: collectionName ${collectionName} is null or undefined` | |
); | |
continue; | |
} | |
const relatedCollection = db.collection(collectionName); | |
const relatedDocuments = await relatedCollection.find({}).toArray(); | |
const keyPath = foreignKey.split("."); | |
console.log(`Updating references in related collection: ${collectionName}`); | |
for (const relatedDoc of relatedDocuments) { | |
let updated = false; | |
/** | |
* Mutating the object in place, passing itself and object property values by reference | |
*/ | |
let updatedDoc = { ...relatedDoc }; | |
const updateNestedReference = (path, obj) => { | |
if (path.length === 1) { | |
const [targetField] = path; | |
const objectId = obj[targetField]; | |
if (objectId && mongoose.Types.ObjectId.isValid(objectId)) { | |
const newReferenceId = idMapping[objectId.toString()]; | |
if (newReferenceId) { | |
obj[targetField] = newReferenceId; | |
updated = true; | |
} | |
} | |
} else { | |
/** | |
* Treating the foreign key as a Stack data structure | |
*/ | |
const [currentPath, ...remainingPath] = path; | |
const currentPathValue = obj[currentPath]; | |
if (Array.isArray(currentPathValue)) { | |
currentPathValue = currentPathValue.map((subObj) => | |
updateNestedReference(remainingPath, subObj) | |
); | |
} else if (currentPathValue) { | |
currentPathValue = updateNestedReference( | |
remainingPath, | |
currentPathValue | |
); | |
} | |
} | |
return obj; | |
}; | |
updatedDoc = updateNestedReference(keyPath, updatedDoc); | |
if (updated) { | |
await relatedCollection.updateOne( | |
{ _id: relatedDoc._id }, | |
{ $set: updatedDoc } | |
); | |
} | |
} | |
} | |
// Step 3: Delete old documents with ObjectId _id | |
for (const oldId in idMapping) { | |
await collection.deleteOne({ _id: new mongoose.Types.ObjectId(oldId) }); | |
} | |
} | |
await mongoose.connect( | |
"mongodb://my-database:27019/collection", | |
{ | |
bufferCommands: false, | |
autoIndex: false, | |
} | |
); | |
const db = mongoose.connection.db; | |
const collections = await db.listCollections().toArray(); | |
const allCollectionNames = collections.map((col) => col.name); | |
for (const collectionName of allCollectionNames) { | |
await migrateCollectionWithReferences(collectionName, db); | |
} | |
await recreateIndexes(db); | |
console.log("Migration completed successfully."); | |
await mongoose.disconnect(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment