Skip to content

Instantly share code, notes, and snippets.

@rightson
Created October 1, 2024 01:09
Show Gist options
  • Save rightson/d86d59a61c47ee631a007e9d9b1449ab to your computer and use it in GitHub Desktop.
Save rightson/d86d59a61c47ee631a007e9d9b1449ab to your computer and use it in GitHub Desktop.
MongoDB Database Copy Script (alternative to mongodump/mongorestore)
// MongoDB Refactored Copy Script with Modular Functions and Index Copying
// This script copies all collections and their indexes from a source database to a target database without using mongodump.
// === Configuration ===
// Source MongoDB connection details
const sourceConfig = {
host: "source_host", // e.g., "localhost"
port: 27017, // e.g., 27017
username: "sourceUser", // Source DB username
password: "sourcePassword", // Source DB password
authDb: "admin", // Authentication database for source (often "admin" or the db itself)
dbName: "sourceDatabase" // Source database name to copy
};
// Target MongoDB connection details
const targetConfig = {
host: "target_host", // e.g., "localhost"
port: 27017, // e.g., 27017
username: "targetUser", // Target DB username (if authentication is required)
password: "targetPassword", // Target DB password
authDb: "admin", // Authentication database for target (if authentication is required)
dbName: "targetDatabase" // Target database name where data will be copied
};
// Batch size for bulk insert operations
const BATCH_SIZE = 1000;
// === Helper Functions ===
/**
* Constructs a MongoDB URI with proper encoding for username and password.
* @param {Object} config - Configuration object containing connection details.
* @returns {String} - Constructed MongoDB URI.
*/
function buildMongoURI(config) {
// Encode username and password to handle special characters
const encodedUsername = encodeURIComponent(config.username);
const encodedPassword = encodeURIComponent(config.password);
// Construct the URI with authentication
return `mongodb://${encodedUsername}:${encodedPassword}@${config.host}:${config.port}/${config.dbName}?authSource=${config.authDb}`;
}
/**
* Connects to a MongoDB instance using the provided URI.
* @param {String} uri - MongoDB connection URI.
* @param {String} dbName - Database name to connect to.
* @returns {Object} - Connected MongoDB database object.
*/
function connectToDatabase(uri, dbName) {
try {
const conn = new Mongo(uri);
const db = conn.getDB(dbName);
// Verify connection
db.runCommand({ ping: 1 });
log(`Successfully connected to database: ${dbName}`);
return { conn, db };
} catch (e) {
log(`Failed to connect to database '${dbName}': ${e}`);
quit(1);
}
}
/**
* Retrieves all collection names from a given database, excluding system collections.
* @param {Object} db - MongoDB database object.
* @returns {Array} - List of collection names.
*/
function getCollections(db) {
return db.getCollectionNames().filter(function(collName) {
return !collName.startsWith("system.");
});
}
/**
* Copies all indexes from a source collection to a target collection.
* @param {Object} sourceCollection - Source MongoDB collection object.
* @param {Object} targetCollection - Target MongoDB collection object.
*/
function copyIndexes(sourceCollection, targetCollection) {
log(` Copying indexes for collection: ${sourceCollection.getName()}`);
const indexes = sourceCollection.getIndexes();
indexes.forEach(function(index) {
// Skip the default _id index as MongoDB automatically creates it
if (index.name === "_id_") {
log(` Skipping default _id index.`);
return;
}
// Prepare index options by excluding fields not needed for creation
const indexOptions = Object.assign({}, index);
delete indexOptions.v;
delete indexOptions.ns;
delete indexOptions.key;
delete indexOptions.name;
try {
targetCollection.createIndex(index.key, indexOptions);
log(` Created index: ${index.name}`);
} catch (e) {
log(` Error creating index '${index.name}': ${e}`);
}
});
}
/**
* Copies all documents from a source collection to a target collection in batches.
* @param {Object} sourceCollection - Source MongoDB collection object.
* @param {Object} targetCollection - Target MongoDB collection object.
*/
function copyCollectionData(sourceCollection, targetCollection) {
log(` Starting data copy for collection: ${sourceCollection.getName()}`);
const cursor = sourceCollection.find();
let batch = [];
let totalInserted = 0;
while (cursor.hasNext()) {
const doc = cursor.next();
batch.push(doc);
if (batch.length === BATCH_SIZE) {
try {
targetCollection.insertMany(batch);
totalInserted += batch.length;
log(` Inserted ${totalInserted} documents so far...`);
batch = [];
} catch (e) {
log(` Error inserting batch: ${e}`);
}
}
}
// Insert any remaining documents in the batch
if (batch.length > 0) {
try {
targetCollection.insertMany(batch);
totalInserted += batch.length;
log(` Inserted a total of ${totalInserted} documents into '${sourceCollection.getName()}'.`);
} catch (e) {
log(` Error inserting final batch: ${e}`);
}
}
log(` Completed data copy for collection: ${sourceCollection.getName()}`);
}
/**
* Logs messages with a timestamp for better traceability.
* @param {String} message - Message to log.
*/
function log(message) {
const timestamp = new Date().toISOString();
print(`[${timestamp}] ${message}`);
}
// === Main Execution Flow ===
(function() {
// Build connection URIs
const sourceUri = buildMongoURI(sourceConfig);
const targetUri = buildMongoURI(targetConfig);
// Connect to source and target databases
log("Connecting to source MongoDB...");
const { conn: sourceConn, db: sourceDB } = connectToDatabase(sourceUri, sourceConfig.dbName);
log("Connecting to target MongoDB...");
const { conn: targetConn, db: targetDB } = connectToDatabase(targetUri, targetConfig.dbName);
// Retrieve collections from source database
const collections = getCollections(sourceDB);
log(`\nFound ${collections.length} collections to copy from '${sourceConfig.dbName}' to '${targetConfig.dbName}'.`);
// Iterate through each collection and perform copy operations
collections.forEach(function(collName) {
log(`\nProcessing collection: ${collName}`);
const sourceCollection = sourceDB.getCollection(collName);
const targetCollection = targetDB.getCollection(collName);
// Optional: Drop the target collection if it exists to start fresh
if (targetDB.getCollectionNames().includes(collName)) {
log(` Dropping existing collection '${collName}' in target database.`);
targetCollection.drop();
}
// Create the collection in the target database
try {
targetDB.createCollection(collName);
log(` Created collection '${collName}' in target database.`);
} catch (e) {
log(` Error creating collection '${collName}': ${e}`);
}
// Copy indexes
copyIndexes(sourceCollection, targetCollection);
// Copy documents
copyCollectionData(sourceCollection, targetCollection);
log(`Finished processing collection: ${collName}`);
});
log(`\nDatabase copy from '${sourceConfig.dbName}' to '${targetConfig.dbName}' completed successfully.`);
// Close the connections
sourceConn.close();
targetConn.close();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment