fix(mongo): preserve document IDs during editing

This commit is contained in:
vrustx 2026-07-16 00:47:02 +08:00 committed by GitHub
parent 295b67dd36
commit d9076f4689
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 44 additions and 30 deletions

View File

@ -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<str
stmts.push(`POST /${coll}/_update/${elasticsearchPathIdPreview(String(id))}${elasticsearchRoutingPreview(routing)}\n${stringifyDocumentStoreValue({ doc: updateDoc.$set ?? updateDoc }, "elasticsearch", 2)}`);
} else {
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns, documents.value[rowIdx]);
stmts.push(`db.${coll}.updateOne({_id: ${mongoIdPreview(documents.value[rowIdx]?._id ?? id)}}, ${formatMongoShellLiteral(updateDoc)})`);
stmts.push(`db.${coll}.updateOne({_id: ${formatMongoShellLiteral(documents.value[rowIdx]?._id ?? id)}}, ${formatMongoShellLiteral(updateDoc)})`);
}
}
@ -420,7 +414,7 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
const routing = documentRoutingFromGridRow(row, columns);
stmts.push(`DELETE /${coll}/_doc/${elasticsearchPathIdPreview(String(id))}${elasticsearchRoutingPreview(routing)}`);
} else {
stmts.push(`db.${coll}.deleteOne({_id: ${mongoIdPreview(documents.value[rowIdx]?._id ?? id)}})`);
stmts.push(`db.${coll}.deleteOne({_id: ${formatMongoShellLiteral(documents.value[rowIdx]?._id ?? id)}})`);
}
}

View File

@ -382,11 +382,6 @@ type MongoQueryGridChanges = {
columns: string[];
rows: MongoInputValue[][];
};
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 mongoCollectionExpression(collection: string): string {
return `db.getCollection(${JSON.stringify(collection)})`;
}
@ -427,7 +422,7 @@ const mongoQueryResultSaveHandler = computed<CustomSaveHandler | undefined>(() =
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;
};

View File

@ -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<string, unknown>
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<string, unknown>).$numberLong === "string";
}

View File

@ -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));

View File

@ -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\\"}"');
});

View File

@ -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", () => {