feat(mongodb): support replaceOne

This commit is contained in:
Loc Nguyen authored and GitHub committed 2026-09-16 12:18:54 +08:00
1 parent 72be4ad9eb
commit 9eafac706e
24 files changed
+496 -16

No files matched your search

+1
View File
@@ -815,6 +815,7 @@ export const mongoInsertDocuments = forward("mongoInsertDocuments");
export const documentUpdateDocument = forward("documentUpdateDocument");
export const mongoUpdateDocument = forward("mongoUpdateDocument");
export const mongoUpdateDocuments = forward("mongoUpdateDocuments");
export const mongoReplaceDocument = forward("mongoReplaceDocument");
export const documentDeleteDocument = forward("documentDeleteDocument");
export const documentSaveMeilisearchBatch = forward("documentSaveMeilisearchBatch");
export const meilisearchSearchDocuments = forward("meilisearchSearchDocuments");
+11
View File
@@ -4530,6 +4530,17 @@ export async function documentUpdateDocument(connectionId: string, database: str
});
}
export async function mongoReplaceDocument(connectionId: string, database: string, collection: string, filterJson: string, replacementJson: string, optionsJson?: string): Promise<{ affected_rows: number }> {
return post("/api/mongo/replace-document", {
connectionId,
database,
collection,
filterJson,
replacementJson,
optionsJson,
});
}
export async function mongoUpdateDocuments(connectionId: string, database: string, collection: string, filterJson: string, updateJson: string, many: boolean, optionsJson?: string): Promise<{ affected_rows: number }> {
return post("/api/mongo/update-documents", {
connectionId,
+12
View File
@@ -4440,6 +4440,18 @@ export async function documentUpdateDocument(connectionId: string, database: str
});
}
export async function mongoReplaceDocument(connectionId: string, database: string, collection: string, filterJson: string, replacementJson: string, optionsJson?: string): Promise<{ affected_rows: number }> {
const affectedRows = await invoke<number>("mongo_replace_document", {
connectionId,
database,
collection,
filterJson,
replacementJson,
optionsJson,
});
return { affected_rows: affectedRows };
}
export async function mongoUpdateDocuments(connectionId: string, database: string, collection: string, filterJson: string, updateJson: string, many: boolean, optionsJson?: string): Promise<{ affected_rows: number }> {
const affectedRows = await invoke<number>("mongo_update_documents", {
connectionId,
@@ -93,6 +93,7 @@ const COLLECTION_METHODS = [
{ label: "insertMany", detail: "Insert multiple documents", apply: "insertMany([{}])" },
{ label: "updateOne", detail: "Update one matching document", apply: "updateOne({}, { $set: {} })" },
{ label: "updateMany", detail: "Update all matching documents", apply: "updateMany({}, { $set: {} })" },
{ label: "replaceOne", detail: "Replace one matching document", apply: "replaceOne({}, {})" },
{ label: "deleteOne", detail: "Delete one matching document", apply: "deleteOne({})" },
{ label: "deleteMany", detail: "Delete all matching documents", apply: "deleteMany({})" },
{ label: "findOneAndUpdate", detail: "Atomically update and return a document", apply: "findOneAndUpdate({}, { $set: {} })" },
@@ -120,6 +121,7 @@ const COLLECTION_METHOD_BOOST: Record<(typeof COLLECTION_METHODS)[number]["label
insertMany: 170,
updateOne: 160,
updateMany: 150,
replaceOne: 145,
deleteOne: 140,
deleteMany: 130,
findOneAndUpdate: 120,
@@ -190,6 +192,7 @@ const METHOD_ARG_ROLES: Record<string, readonly MongoArgRole[]> = {
findOneAndDelete: ["filter", "options"],
updateOne: ["filter", "update", "options"],
updateMany: ["filter", "update", "options"],
replaceOne: ["filter", "replacement", "options"],
findOneAndUpdate: ["filter", "update", "options"],
findOneAndReplace: ["filter", "replacement", "options"],
insertOne: ["document", "options"],
@@ -48,6 +48,7 @@ const COLLECTION_METHOD_SHAPES: Record<string, MongoMethodShape> = {
update: { expects: "a filter, an update, and optional options", roles: ["filter", "update", "options"] },
updateOne: { expects: "a filter, an update, and optional options", roles: ["filter", "update", "options"] },
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"] },
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"] },
@@ -76,6 +77,7 @@ const SUPPORTED_COLLECTION_METHODS = [
"insertMany",
"updateOne",
"updateMany",
"replaceOne",
"deleteOne",
"deleteMany",
"findOneAndUpdate",
@@ -157,6 +159,11 @@ function diagnoseMongoCommand(source: string): string | null {
const tail = source.slice(closeIndex + 1).trim();
if (tail) return `Unexpected text after ${method}(...): "${tail.length > 40 ? `${tail.slice(0, 40)}…` : tail}".`;
if (method === "replaceOne" && args[1]) {
const replacement = parseMongoObjectArgument(args[1]);
const operator = replacement ? Object.keys(JSON.parse(replacement) as Record<string, unknown>).find((key) => key.startsWith("$")) : undefined;
if (operator) return `replaceOne() replaces the whole document, so it must not contain update operators such as ${operator}; use updateOne() to modify fields.`;
}
return `${method}() expects ${shapeSpec.expects}.`;
}
@@ -256,7 +263,7 @@ export interface MongoDistinctCommand {
filter?: string;
}
type MongoWriteKind = "runCommand" | "insert" | "update" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
type MongoWriteKind = "runCommand" | "insert" | "update" | "replace" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
export type MongoCommand =
| ({ kind: "find" } & MongoFindCommand)
@@ -273,6 +280,7 @@ export type MongoCommand =
| ({ kind: "runCommand" } & MongoRunCommand)
| { kind: "insert"; collection: string; docsJson: string }
| { kind: "update"; collection: string; filter: string; update: string; options?: string; many: boolean }
| { kind: "replace"; collection: string; filter: string; replacement: string; options?: string }
| { kind: "delete"; collection: string; filter: string; many: boolean }
| { kind: "createIndex"; collection: string; keys: string; options?: string }
| { kind: "dropIndex"; collection: string; index: string }
@@ -744,6 +752,20 @@ export function parseMongoWriteCommand(input: string): MongoWriteCommand | null
return value !== null && typeof value === "object" ? { kind: "insert", collection: insert.collection, docsJson: docs } : null;
}
const replaceOne = parseCollectionMethodTarget(source, "replaceOne");
if (replaceOne) {
const args = parseMethodArgs(source, replaceOne.methodCallIndex);
if (!args || args.length < 2 || args.length > 3) return null;
const filter = normalizeJsonArgument(args[0]);
const replacement = parseMongoObjectArgument(args[1]);
if (!filter || !replacement) return null;
// A replacement is a whole document; `{$set: ...}` here means updateOne() was intended.
if (Object.keys(JSON.parse(replacement) as Record<string, unknown>).some((key) => key.startsWith("$"))) return null;
const options = args[2]?.trim() ? normalizeJsonArgument(args[2]) : undefined;
if (args[2]?.trim() && !options) return null;
return { kind: "replace", collection: replaceOne.collection, filter, replacement, ...(options ? { options } : {}) };
}
for (const method of ["updateOne", "updateMany"] as const) {
const target = parseCollectionMethodTarget(source, method);
if (!target) continue;
@@ -1516,6 +1538,7 @@ function isNonEmptyRecord(value: unknown): value is Record<string, unknown> {
function mongoWriteFilter(command: MongoWriteCommand): string | null {
switch (command.kind) {
case "update":
case "replace":
case "delete":
case "findOneAndUpdate":
case "findOneAndReplace":
+4
View File
@@ -6642,6 +6642,7 @@ export const useQueryStore = defineStore("query", () => {
}
case "insert":
case "update":
case "replace":
case "delete":
case "createIndex":
case "createUser":
@@ -6665,6 +6666,9 @@ export const useQueryStore = defineStore("query", () => {
} else if (mongoCommand.kind === "update") {
const result = await api.mongoUpdateDocuments(executionConnectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.update, mongoCommand.many, mongoCommand.options);
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt))));
} else if (mongoCommand.kind === "replace") {
const result = await api.mongoReplaceDocument(executionConnectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.replacement, mongoCommand.options);
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt))));
} else if (mongoCommand.kind === "createIndex") {
const result = await api.mongoCreateIndex(executionConnectionId, currentDatabase, mongoCommand.collection, mongoCommand.keys, mongoCommand.options);
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoCreateIndexToQueryResult(result.name, performance.now() - commandStartedAt))));