From c11b0b160d9d48ded2c4440ad54b6e69f2cbcffe Mon Sep 17 00:00:00 2001 From: miracle Date: Thu, 23 Jul 2026 17:06:31 +0800 Subject: [PATCH] fix(mongodb): preserve int64 filter values --- .../com/dbx/agent/mongodb/MongoAgent.java | 31 ++++++++++++++++++- .../com/dbx/agent/mongodb/MongoAgentTest.java | 21 +++++++++++++ .../components/document/DocumentBrowser.vue | 2 ++ .../src/lib/app/documentStoreProvider.ts | 15 ++++++++- packages/app-tests/documentBrowser.test.ts | 6 ++++ .../app-tests/documentStoreProvider.test.ts | 5 +++ 6 files changed, 78 insertions(+), 2 deletions(-) diff --git a/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java b/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java index cf2453e49d..a8ce75a443 100644 --- a/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java +++ b/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java @@ -857,7 +857,36 @@ public final class MongoAgent { } static JsonObject bsonToExtendedJson(Document doc) { - return JsonParser.parseString(doc.toJson(EXTENDED_JSON_SETTINGS)).getAsJsonObject(); + JsonObject relaxed = JsonParser.parseString(doc.toJson(EXTENDED_JSON_SETTINGS)).getAsJsonObject(); + return preserveUnsafeLongsForJsonClients(doc, relaxed).getAsJsonObject(); + } + + private static JsonElement preserveUnsafeLongsForJsonClients(Object bsonValue, JsonElement relaxedValue) { + if ( + bsonValue instanceof Long longValue && + (longValue < -JS_MAX_SAFE_INTEGER || longValue > JS_MAX_SAFE_INTEGER) + ) { + JsonObject wrapper = new JsonObject(); + wrapper.addProperty("$numberLong", longValue.toString()); + return wrapper; + } + if (bsonValue instanceof Document document && relaxedValue.isJsonObject()) { + JsonObject object = relaxedValue.getAsJsonObject(); + for (Map.Entry entry : document.entrySet()) { + if (object.has(entry.getKey())) { + object.add( + entry.getKey(), + preserveUnsafeLongsForJsonClients(entry.getValue(), object.get(entry.getKey())) + ); + } + } + } else if (bsonValue instanceof List values && relaxedValue.isJsonArray()) { + JsonArray array = relaxedValue.getAsJsonArray(); + for (int index = 0; index < Math.min(values.size(), array.size()); index++) { + array.set(index, preserveUnsafeLongsForJsonClients(values.get(index), array.get(index))); + } + } + return relaxedValue; } static Object convertValue(Object value) { diff --git a/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java b/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java index d09faa1194..6e5165784c 100644 --- a/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java +++ b/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java @@ -552,6 +552,27 @@ class MongoAgentTest { ); } + @Test + void bsonToExtendedJsonWrapsUnsafeLongsForJsonClients() { + Document doc = new Document("_id", 144_115_205_316_939_462L) + .append("nested", new Document("sequence", -144_115_205_316_939_462L)) + .append("items", List.of(144_115_205_316_939_462L)) + .append("safe", 42L); + + JsonObject json = MongoAgent.bsonToExtendedJson(doc); + + assertEquals("144115205316939462", json.getAsJsonObject("_id").get("$numberLong").getAsString()); + assertEquals( + "-144115205316939462", + json.getAsJsonObject("nested").getAsJsonObject("sequence").get("$numberLong").getAsString() + ); + assertEquals( + "144115205316939462", + json.getAsJsonArray("items").get(0).getAsJsonObject().get("$numberLong").getAsString() + ); + assertEquals(42L, json.get("safe").getAsLong()); + } + @Test void documentForWriteParsesMongoShellIsoDateStrings() { Document doc = MongoAgent.documentForWrite("{\"$set\":{\"CreateDate\":\"ISODate(\\\"2026-06-10T13:59:31.287Z\\\")\"}}"); diff --git a/apps/desktop/src/components/document/DocumentBrowser.vue b/apps/desktop/src/components/document/DocumentBrowser.vue index ba35ab7b41..c67a9f05da 100644 --- a/apps/desktop/src/components/document/DocumentBrowser.vue +++ b/apps/desktop/src/components/document/DocumentBrowser.vue @@ -1787,6 +1787,7 @@ defineExpose({ focusSearch }); rows="1" class="document-query-input flex-1 min-w-0 text-xs bg-transparent outline-none placeholder:text-muted-foreground/60 font-mono" placeholder="{}" + @keydown.enter.exact.prevent="applyFilter" @keydown.ctrl.enter.prevent="applyFilter" @keydown.meta.enter.prevent="applyFilter" /> @@ -1825,6 +1826,7 @@ defineExpose({ focusSearch }); rows="1" class="document-query-input flex-1 min-w-0 text-xs bg-transparent outline-none placeholder:text-muted-foreground/60 font-mono" placeholder="{}" + @keydown.enter.exact.prevent="applyFilter" @keydown.ctrl.enter.prevent="applyFilter" @keydown.meta.enter.prevent="applyFilter" /> diff --git a/apps/desktop/src/lib/app/documentStoreProvider.ts b/apps/desktop/src/lib/app/documentStoreProvider.ts index de858c89f9..6f300194c7 100644 --- a/apps/desktop/src/lib/app/documentStoreProvider.ts +++ b/apps/desktop/src/lib/app/documentStoreProvider.ts @@ -1,4 +1,5 @@ import type { ComposerTranslation } from "vue-i18n"; +import { normalizeJsonArgument } from "@dbx-app/mongo-shell"; import type { DatabaseType } from "@/types/database"; import { quoteUnquotedObjectKeys } from "@/lib/mongo/mongoShellCommand"; import { formatMongoShellLiteral } from "@/lib/mongo/mongoDocumentValues"; @@ -531,11 +532,23 @@ function isDigit(value: string | undefined): boolean { export function parseDocumentFilterInput(input: string, options: DocumentFilterParseOptions = {}): Record { const trimmed = input.trim(); if (!trimmed) return {}; - const safe = quoteUnquotedObjectKeys(trimmed); + const safe = normalizeDocumentQueryObjectInput(trimmed, options.kind); const parsed = parseJsonPreservingLargeIntegers(safe, options); return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record) : {}; } +type DocumentQueryInputNormalizer = (input: string) => string | null; + +const documentQueryInputNormalizers: Record = { + mongodb: normalizeJsonArgument, + elasticsearch: quoteUnquotedObjectKeys, +}; + +function normalizeDocumentQueryObjectInput(input: string, kind?: DocumentStoreKind): string { + const normalize = kind ? documentQueryInputNormalizers[kind] : quoteUnquotedObjectKeys; + return normalize(input) ?? input; +} + export function currentDocumentFilterJson(input: string, structured: Record | null, kind?: DocumentStoreKind): string | undefined { const manual = parseDocumentFilterInput(input, { kind }); const filter = structured ? (Object.keys(manual).length ? { $and: [manual, structured] } : structured) : manual; diff --git a/packages/app-tests/documentBrowser.test.ts b/packages/app-tests/documentBrowser.test.ts index 8ef9ad875c..1815292263 100644 --- a/packages/app-tests/documentBrowser.test.ts +++ b/packages/app-tests/documentBrowser.test.ts @@ -61,3 +61,9 @@ test("document save uses shared identity plan and write helpers", () => { assert.doesNotMatch(source, /async function rekeyDocumentStoreDocument/); assert.doesNotMatch(source, /async function replaceDocumentStoreDocument/); }); + +test("document query inputs apply on Enter and reserve Shift+Enter for newlines", () => { + const source = documentBrowserSource(); + assert.equal(source.match(/@keydown\.enter\.exact\.prevent="applyFilter"/g)?.length, 2); + assert.doesNotMatch(source, /@keydown\.shift\.enter\.prevent="applyFilter"/); +}); diff --git a/packages/app-tests/documentStoreProvider.test.ts b/packages/app-tests/documentStoreProvider.test.ts index 65b15c7941..2cc7c57933 100644 --- a/packages/app-tests/documentStoreProvider.test.ts +++ b/packages/app-tests/documentStoreProvider.test.ts @@ -211,6 +211,11 @@ test("uses elemMatch only for AND conditions on the same array object", () => { test("formats document query object input", () => { assert.equal(formatDocumentQueryInput('{profile:{city:"上海"},status:"active"}', "mongodb"), ["{", ' "profile": {', ' "city": "上海"', " },", ' "status": "active"', "}"].join("\n")); }); +test("normalizes Mongo shell syntax in manual document filters", () => { + assert.equal(currentDocumentFilterJson("{id:NumberLong('144115205316939462')}", null, "mongodb"), JSON.stringify({ id: { $numberLong: "144115205316939462" } })); + assert.equal(currentDocumentFilterJson("{owner:ObjectId('507f1f77bcf86cd799439011'),status:'active'}", null, "mongodb"), JSON.stringify({ owner: { $oid: "507f1f77bcf86cd799439011" }, status: "active" })); +}); + test("preserves MongoDB int64 document filter values", () => { const id = "2048938405781032962"; const firstUnsafeInteger = "9007199254740993";