Skip to content

Instantly share code, notes, and snippets.

@mstaicu
Last active November 5, 2024 14:30
Show Gist options
  • Save mstaicu/1f9fcb142e25905eebf1509ad0a119a5 to your computer and use it in GitHub Desktop.
Save mstaicu/1f9fcb142e25905eebf1509ad0a119a5 to your computer and use it in GitHub Desktop.
MongoDB document migrate script from ObjectId to UUID
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