From d9076f468950c6949a61777ec0dd994711afec00 Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Thu, 16 Jul 2026 00:47:02 +0800 Subject: [PATCH] fix(mongo): preserve document IDs during editing --- .../components/document/DocumentBrowser.vue | 10 ++------- .../src/components/layout/ContentArea.vue | 7 +----- .../src/lib/mongo/mongoDocumentValues.ts | 6 +---- crates/dbx-core/src/db/mongo_driver.rs | 16 ++++++++++++-- .../app-tests/mongoDocumentValues.test.ts | 13 +++++++---- packages/app-tests/mongoShellCommand.test.ts | 22 ++++++++++++++----- 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/apps/desktop/src/components/document/DocumentBrowser.vue b/apps/desktop/src/components/document/DocumentBrowser.vue index a369a6f90..858641440 100644 --- a/apps/desktop/src/components/document/DocumentBrowser.vue +++ b/apps/desktop/src/components/document/DocumentBrowser.vue @@ -367,12 +367,6 @@ function buildElasticsearchInsertDocument(row: MongoInputValue[], columns: strin return doc; } -function mongoIdPreview(val: unknown): string { - if (val === null || val === undefined) return "null"; - if (typeof val === "string" && /^[a-fA-F0-9]{24}$/.test(val)) return `ObjectId("${val}")`; - return formatMongoShellLiteral(val); -} - function elasticsearchPathIdPreview(id: string): string { return encodeURIComponent(id); } @@ -408,7 +402,7 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise(() = if (id === null || id === undefined || String(id).trim() === "") continue; const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns, tab.result?.mongo_documents?.[rowIdx]); if (Object.keys(updateDoc).length === 0) continue; - stmts.push(`${mongoCollectionExpression(target.collection)}.updateOne({_id: ${mongoIdPreview(mongoQueryResultDocumentId(rowIdx, id))}}, ${formatMongoShellLiteral(updateDoc)})`); + stmts.push(`${mongoCollectionExpression(target.collection)}.updateOne({_id: ${formatMongoShellLiteral(mongoQueryResultDocumentId(rowIdx, id))}}, ${formatMongoShellLiteral(updateDoc)})`); } return stmts; }; diff --git a/apps/desktop/src/lib/mongo/mongoDocumentValues.ts b/apps/desktop/src/lib/mongo/mongoDocumentValues.ts index 80e394c9f..755e829e0 100644 --- a/apps/desktop/src/lib/mongo/mongoDocumentValues.ts +++ b/apps/desktop/src/lib/mongo/mongoDocumentValues.ts @@ -174,7 +174,7 @@ export function serializeMongoDocumentId(value: unknown): string { } export function mongoDocumentIdForGrid(value: unknown): MongoInputValue { - if (isMongoNumberLong(value)) return value.$numberLong; + if (isMongoExtendedJsonId(value)) return String(value.$numberLong ?? value.$oid); if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value; return JSON.stringify(value); } @@ -185,7 +185,3 @@ function isMongoExtendedJsonId(value: unknown): value is Record const keys = Object.keys(object); return keys.length === 1 && (typeof object.$numberLong === "string" || typeof object.$oid === "string"); } - -function isMongoNumberLong(value: unknown): value is { $numberLong: string } { - return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 1 && typeof (value as Record).$numberLong === "string"; -} diff --git a/crates/dbx-core/src/db/mongo_driver.rs b/crates/dbx-core/src/db/mongo_driver.rs index d4f4e471e..87853bf36 100644 --- a/crates/dbx-core/src/db/mongo_driver.rs +++ b/crates/dbx-core/src/db/mongo_driver.rs @@ -1341,8 +1341,10 @@ fn bson_to_json(bson: &Bson) -> serde_json::Value { fn bson_document_field_to_json(key: &str, bson: &Bson) -> serde_json::Value { if key == "_id" { - if let Bson::Int64(value) = bson { - return serde_json::json!({ "$numberLong": value.to_string() }); + match bson { + Bson::Int64(value) => return serde_json::json!({ "$numberLong": value.to_string() }), + Bson::ObjectId(value) => return serde_json::json!({ "$oid": value.to_hex() }), + _ => {} } } bson_to_json(bson) @@ -1870,6 +1872,16 @@ mod tests { assert_eq!(value["snowflake"], serde_json::json!("2048938405781032962")); } + #[test] + fn bson_to_json_preserves_object_id_type_for_updates() { + let oid = ObjectId::parse_str("507f1f77bcf86cd799439011").unwrap(); + let value = bson_to_json(&Bson::Document(doc! { + "_id": Bson::ObjectId(oid), + })); + + assert_eq!(value["_id"], serde_json::json!({ "$oid": "507f1f77bcf86cd799439011" })); + } + #[test] fn bson_to_json_keeps_safe_int64_as_number() { let value = bson_to_json(&Bson::Int64(42)); diff --git a/packages/app-tests/mongoDocumentValues.test.ts b/packages/app-tests/mongoDocumentValues.test.ts index ed9ec19e2..edc50b409 100644 --- a/packages/app-tests/mongoDocumentValues.test.ts +++ b/packages/app-tests/mongoDocumentValues.test.ts @@ -222,13 +222,18 @@ test("formats extended JSON dates as Mongo shell ISODate literals", () => { test("formats extended JSON object ids as Mongo shell ObjectId literals", () => { assert.equal(formatMongoShellLiteral({ $oid: "6743e4bfa3f6f84bc3fff6c8" }), 'ObjectId("6743e4bfa3f6f84bc3fff6c8")'); + assert.equal(formatMongoShellLiteral("6743e4bfa3f6f84bc3fff6c8"), '"6743e4bfa3f6f84bc3fff6c8"'); }); test("serializes typed Mongo document ids while keeping their grid display compact", () => { - const id = { $numberLong: "2048938405781032962" }; - assert.equal(serializeMongoDocumentId(id), '{"$numberLong":"2048938405781032962"}'); - assert.equal(mongoDocumentIdForGrid(id), "2048938405781032962"); - assert.equal(serializeMongoDocumentId({ $oid: "6743e4bfa3f6f84bc3fff6c8" }), '{"$oid":"6743e4bfa3f6f84bc3fff6c8"}'); + const longId = { $numberLong: "2048938405781032962" }; + const objectId = { $oid: "6743e4bfa3f6f84bc3fff6c8" }; + assert.equal(serializeMongoDocumentId(longId), '{"$numberLong":"2048938405781032962"}'); + assert.equal(mongoDocumentIdForGrid(longId), "2048938405781032962"); + assert.equal(serializeMongoDocumentId(objectId), '{"$oid":"6743e4bfa3f6f84bc3fff6c8"}'); + assert.equal(mongoDocumentIdForGrid(objectId), "6743e4bfa3f6f84bc3fff6c8"); + assert.equal(serializeMongoDocumentId(42), "42"); + assert.equal(serializeMongoDocumentId(42.5), "42.5"); assert.equal(serializeMongoDocumentId("2048938405781032962"), '__dbx_mongo_string_id__"2048938405781032962"'); assert.equal(serializeMongoDocumentId('{"$numberLong":"2048938405781032962"}'), '__dbx_mongo_string_id__"{\\"$numberLong\\":\\"2048938405781032962\\"}"'); }); diff --git a/packages/app-tests/mongoShellCommand.test.ts b/packages/app-tests/mongoShellCommand.test.ts index 1bc0d9a87..ef01d3a5e 100644 --- a/packages/app-tests/mongoShellCommand.test.ts +++ b/packages/app-tests/mongoShellCommand.test.ts @@ -626,12 +626,24 @@ test("mongoDocumentsToQueryResult turns mongo documents into grid rows", () => { assert.equal(result.truncated, true); }); -test("mongoDocumentsToQueryResult displays typed int64 ids without losing raw type metadata", () => { - const id = { $numberLong: "2048938405781032962" }; - const result = mongoDocumentsToQueryResult([{ _id: id, name: "snowflake" }], 1, 1); +test("mongoDocumentsToQueryResult displays ids without losing raw type metadata", () => { + const documents = [ + { _id: { $oid: "6743e4bfa3f6f84bc3fff6c8" }, name: "object id" }, + { _id: { $numberLong: "2048938405781032962" }, name: "int64" }, + { _id: 42, name: "int" }, + { _id: 42.5, name: "double" }, + { _id: "customer-42", name: "string" }, + ]; + const result = mongoDocumentsToQueryResult(documents, documents.length, documents.length); - assert.deepEqual(result.rows, [["2048938405781032962", "snowflake"]]); - assert.deepEqual(result.mongo_documents, [{ _id: id, name: "snowflake" }]); + assert.deepEqual(result.rows, [ + ["6743e4bfa3f6f84bc3fff6c8", "object id"], + ["2048938405781032962", "int64"], + [42, "int"], + [42.5, "double"], + ["customer-42", "string"], + ]); + assert.deepEqual(result.mongo_documents, documents); }); test("buildMongoUpdateDocument ignores _id and preserves typed values", () => {