feat(mongodb): support db.collection.renameCollection()

This commit is contained in:
Loc Nguyen authored and GitHub committed 2026-09-18 14:12:39 +08:00
1 parent 392138f59a
commit 2db28fc236
16 files changed
+239 -17

No files matched your search

@@ -108,6 +108,7 @@ const COLLECTION_METHODS = [
{ label: "createIndex", detail: "Create an index", apply: "createIndex({ ${field}: 1 })" },
{ label: "dropIndex", detail: "Drop one index", apply: 'dropIndex("${indexName}")' },
{ label: "dropIndexes", detail: "Drop collection indexes", apply: "dropIndexes()" },
{ label: "renameCollection", detail: "Rename the collection", apply: 'renameCollection("${newName}")' },
{ label: "drop", detail: "Drop the collection", apply: "drop()" },
] as const;
@@ -136,6 +137,7 @@ const COLLECTION_METHOD_BOOST: Record<(typeof COLLECTION_METHODS)[number]["label
dataSize: 75,
storageSize: 70,
totalIndexSize: 65,
renameCollection: 55,
dropIndex: 50,
dropIndexes: 45,
drop: 30,
@@ -50,6 +50,7 @@ const COLLECTION_METHOD_SHAPES: Record<string, MongoMethodShape> = {
updateMany: { expects: "a filter, an update, and optional options", roles: ["filter", "update", "options"] },
replaceOne: { expects: "a filter, a replacement document, and optional options", roles: ["filter", "replacement", "options"] },
bulkWrite: { expects: "an array of operations and optional options", roles: ["operations", "options"] },
renameCollection: { expects: "the new collection name", roles: ["newName"] },
deleteOne: { expects: "a filter", roles: ["filter"] },
deleteMany: { expects: "a filter", roles: ["filter"] },
findOneAndUpdate: { expects: "a filter, an update, and optional options", roles: ["filter", "update", "options"] },
@@ -80,6 +81,7 @@ const SUPPORTED_COLLECTION_METHODS = [
"updateMany",
"replaceOne",
"bulkWrite",
"renameCollection",
"deleteOne",
"deleteMany",
"findOneAndUpdate",
@@ -168,6 +170,12 @@ function diagnoseMongoCommand(source: string): string | null {
return `${method}() returns a result, not a cursor, so nothing can be chained after it: "${tail.length > 40 ? `${tail.slice(0, 40)}…` : tail}".`;
}
if (tail) return `Unexpected text after ${method}(...): "${tail.length > 40 ? `${tail.slice(0, 40)}…` : tail}".`;
if (method === "renameCollection") {
if (args.length > 1) return "renameCollection() dropTarget is not supported; drop the target collection first.";
const newName = args[0] ? /^(["'])([^"']*)\1$/.exec(args[0].trim())?.[2] : undefined;
if (newName !== undefined && (!newName.trim() || newName.includes("$"))) return "renameCollection() requires a valid collection name.";
if (newName !== undefined && shape.groups?.collection === newName) return "renameCollection() new name must differ from the current name.";
}
if (method === "bulkWrite" && args[0]) {
const operations = normalizeJsonArgument(args[0]);
const problem = operations ? validateBulkWriteOperations(operations) : null;
@@ -287,7 +295,7 @@ export interface MongoDistinctCommand {
filter?: string;
}
type MongoWriteKind = "runCommand" | "insert" | "update" | "replace" | "bulkWrite" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
type MongoWriteKind = "runCommand" | "insert" | "update" | "replace" | "bulkWrite" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "renameCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
export type MongoCommand =
| ({ kind: "find" } & MongoFindCommand)
@@ -312,6 +320,7 @@ export type MongoCommand =
| { kind: "dropIndex"; collection: string; index: string }
| { kind: "dropIndexes"; collection: string; indexes?: string }
| { kind: "dropCollection"; collection: string }
| { kind: "renameCollection"; collection: string; newName: string }
| { kind: "findOneAndUpdate"; collection: string; filter: string; update: string; options?: string }
| { kind: "findOneAndReplace"; collection: string; filter: string; replacement: string; options?: string }
| { kind: "findOneAndDelete"; collection: string; filter: string; options?: string };
@@ -889,6 +898,17 @@ export function parseMongoWriteCommand(input: string): MongoWriteCommand | null
return indexes !== null ? { kind: "dropIndexes", collection: dropIndexes.collection, ...(indexes ? { indexes } : {}) } : null;
}
const renameCollection = parseCollectionMethodTarget(source, "renameCollection");
if (renameCollection) {
const args = parseMethodArgs(source, renameCollection.methodCallIndex);
// A second argument would be dropTarget, which deletes an existing collection of the
// new name; it is rejected (see the diagnostics) rather than silently ignored.
if (!args || args.length !== 1) return null;
const newName = /^(["'])([^"'$]+)\1$/.exec(args[0]!.trim())?.[2]?.trim();
if (!newName || newName === renameCollection.collection) return null;
return { kind: "renameCollection", collection: renameCollection.collection, newName };
}
const dropCollection = parseCollectionMethodTarget(source, "drop");
if (dropCollection) {
const args = parseMethodArgs(source, dropCollection.methodCallIndex);
@@ -1184,6 +1204,15 @@ export function mongoUseToQueryResult(database: string, executionTimeMs: number)
};
}
export function mongoScalarToQueryResult(column: string, value: string, executionTimeMs: number): QueryResult {
return {
columns: [column],
rows: [[value]],
affected_rows: 1,
execution_time_ms: Math.max(0, Math.round(executionTimeMs)),
};
}
export function mongoVersionToQueryResult(version: string, executionTimeMs: number): QueryResult {
return {
columns: ["version"],
+5
View File
@@ -33,6 +33,7 @@ import {
mongoUseToQueryResult,
mongoVersionToQueryResult,
mongoBulkWriteToQueryResult,
mongoScalarToQueryResult,
mongoWriteToQueryResult,
splitMongoCommandRanges,
type MongoAggregateSafetyOptions,
@@ -6745,6 +6746,7 @@ export const useQueryStore = defineStore("query", () => {
case "createUser":
case "dropIndex":
case "dropIndexes":
case "renameCollection":
case "dropCollection": {
if (options?.mongoSafety) {
const safety = evaluateMongoWriteSafety(mongoCommand, options.mongoSafety);
@@ -6782,6 +6784,9 @@ export const useQueryStore = defineStore("query", () => {
} finally {
await refreshLoadedMongoIndexesAfterMutation(executionConnectionId, currentDatabase, mongoCommand.collection, traceId);
}
} else if (mongoCommand.kind === "renameCollection") {
await api.mongoRenameCollection(executionConnectionId, currentDatabase, mongoCommand.collection, mongoCommand.newName);
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoScalarToQueryResult("renamed", `${mongoCommand.collection} -> ${mongoCommand.newName}`, performance.now() - commandStartedAt))));
} else if (mongoCommand.kind === "dropCollection") {
await api.mongoDropCollection(executionConnectionId, currentDatabase, mongoCommand.collection);
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoWriteToQueryResult(1, performance.now() - commandStartedAt))));