feat(mongodb): support generic runCommand execution

This commit is contained in:
Guoyu Su authored and GitHub committed 2026-08-12 21:05:13 +08:00
1 parent 2d00620fac
commit 9b24d962bb
27 files changed
+580 -18

No files matched your search

+1
View File
@@ -690,6 +690,7 @@ export const mongoDistinct = forward("mongoDistinct");
export const mongoCollectionStats = forward("mongoCollectionStats");
export const mongoCreateIndex = forward("mongoCreateIndex");
export const mongoCreateUser = forward("mongoCreateUser");
export const mongoRunCommand = forward("mongoRunCommand");
export const mongoDropIndexes = forward("mongoDropIndexes");
export const documentInsertDocument = forward("documentInsertDocument");
export const mongoInsertDocument = forward("mongoInsertDocument");
+9
View File
@@ -3583,6 +3583,15 @@ export async function mongoCreateUser(connectionId: string, database: string, us
});
}
export async function mongoRunCommand(connectionId: string, database: string, commandJson: string, executionId?: string): Promise<MongoDocumentResult> {
return post("/api/mongo/run-command", {
connectionId,
database,
commandJson,
executionId,
});
}
export async function mongoDropIndexes(connectionId: string, database: string, collection: string, indexesJson?: string, single = false): Promise<MongoDropIndexesResult> {
return post("/api/mongo/drop-indexes", {
connectionId,
+9
View File
@@ -3527,6 +3527,15 @@ export async function mongoCreateUser(connectionId: string, database: string, us
});
}
export async function mongoRunCommand(connectionId: string, database: string, commandJson: string, executionId?: string): Promise<MongoDocumentResult> {
return invoke("mongo_run_command", {
connectionId,
database,
commandJson,
executionId,
});
}
export async function mongoDropIndexes(connectionId: string, database: string, collection: string, indexesJson?: string, single = false): Promise<MongoDropIndexesResult> {
return invoke("mongo_drop_indexes", {
connectionId,
@@ -67,6 +67,10 @@ export interface MongoCreateUserCommand {
writeConcernJson?: string;
}
export interface MongoRunCommand {
commandJson: string;
}
export type MongoCollectionStatsMetric = "stats" | "dataSize" | "storageSize" | "totalIndexSize";
export interface MongoCollectionStatsCommand {
@@ -81,7 +85,7 @@ export interface MongoDistinctCommand {
filter?: string;
}
type MongoWriteKind = "insert" | "update" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
type MongoWriteKind = "runCommand" | "insert" | "update" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
export type MongoCommand =
| ({ kind: "find" } & MongoFindCommand)
@@ -94,6 +98,7 @@ export type MongoCommand =
| ({ kind: "collectionStats" } & MongoCollectionStatsCommand)
| ({ kind: "use" } & MongoUseCommand)
| ({ kind: "createUser" } & MongoCreateUserCommand)
| ({ kind: "runCommand" } & MongoRunCommand)
| { kind: "insert"; collection: string; docsJson: string }
| { kind: "update"; collection: string; filter: string; update: string; options?: string; many: boolean }
| { kind: "delete"; collection: string; filter: string; many: boolean }
@@ -499,6 +504,21 @@ export function parseMongoCreateUserCommand(input: string): MongoCreateUserComma
};
}
export function parseMongoRunCommand(input: string): MongoRunCommand | null {
const source = input.trim().replace(/;$/, "").trim();
const match = /^db\s*\.\s*runCommand\s*\(/i.exec(source);
if (!match) return null;
const openIndex = source.indexOf("(", match.index);
const closeIndex = findMatchingParen(source, openIndex);
if (closeIndex < 0 || source.slice(closeIndex + 1).trim()) return null;
const args = splitTopLevel(source.slice(openIndex + 1, closeIndex));
if (args.length !== 1) return null;
const commandJson = parseMongoObjectArgument(args[0]);
if (!commandJson) return null;
const command = JSON.parse(commandJson) as Record<string, unknown>;
return Object.keys(command).length > 0 ? { commandJson: JSON.stringify(command) } : null;
}
export function parseMongoWriteCommand(input: string): MongoWriteCommand | null {
const source = input.trim().replace(/;$/, "").trim();
const insertOne = parseCollectionMethodTarget(source, "insertOne");
@@ -607,6 +627,10 @@ export function parseMongoCommand(input: string): ParsedMongoCommand | null {
const createUser = parseMongoCreateUserCommand(source);
return createUser ? { kind: "createUser", ...createUser } : null;
},
(source) => {
const runCommand = parseMongoRunCommand(source);
return runCommand ? { kind: "runCommand", ...runCommand } : null;
},
(source) => {
// Legacy Mongo shell uses count()/find().count(); keep accepting it
// while mapping to DBX's countDocuments-compatible result path.
+20
View File
@@ -4204,6 +4204,26 @@ export const useQueryStore = defineStore("query", () => {
});
break;
}
case "runCommand": {
if (options?.mongoSafety) {
const safety = evaluateMongoWriteSafety(mongoCommand, options.mongoSafety);
if (!safety.allowed) throw new Error(safety.reason);
}
queryExecutionLog("info", "mongo-run-command:start", {
traceId,
database: currentDatabase,
});
const result = await api.mongoRunCommand(executionConnectionId, currentDatabase, mongoCommand.commandJson, executionId);
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total, result.extended_documents, result.total_is_exact !== false))));
mongoEditTarget = undefined;
queryExecutionLog("info", "mongo-run-command:done", {
traceId,
database: currentDatabase,
rowCount: result.documents.length,
elapsed: elapsed(),
});
break;
}
case "insert":
case "update":
case "delete":