fix(mongo): preserve document IDs during editing
This commit is contained in:
1 parent
295b67dd36
commit
d9076f4689
6 files changed
+44
-30
No files matched your search
@@ -367,12 +367,6 @@ function buildElasticsearchInsertDocument(row: MongoInputValue[], columns: strin
|
|||||||
return doc;
|
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 {
|
function elasticsearchPathIdPreview(id: string): string {
|
||||||
return encodeURIComponent(id);
|
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)}`);
|
stmts.push(`POST /${coll}/_update/${elasticsearchPathIdPreview(String(id))}${elasticsearchRoutingPreview(routing)}\n${stringifyDocumentStoreValue({ doc: updateDoc.$set ?? updateDoc }, "elasticsearch", 2)}`);
|
||||||
} else {
|
} else {
|
||||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns, documents.value[rowIdx]);
|
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);
|
const routing = documentRoutingFromGridRow(row, columns);
|
||||||
stmts.push(`DELETE /${coll}/_doc/${elasticsearchPathIdPreview(String(id))}${elasticsearchRoutingPreview(routing)}`);
|
stmts.push(`DELETE /${coll}/_doc/${elasticsearchPathIdPreview(String(id))}${elasticsearchRoutingPreview(routing)}`);
|
||||||
} else {
|
} 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)}})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -382,11 +382,6 @@ type MongoQueryGridChanges = {
|
|||||||
columns: string[];
|
columns: string[];
|
||||||
rows: MongoInputValue[][];
|
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 {
|
function mongoCollectionExpression(collection: string): string {
|
||||||
return `db.getCollection(${JSON.stringify(collection)})`;
|
return `db.getCollection(${JSON.stringify(collection)})`;
|
||||||
}
|
}
|
||||||
@@ -427,7 +422,7 @@ const mongoQueryResultSaveHandler = computed<CustomSaveHandler | undefined>(() =
|
|||||||
if (id === null || id === undefined || String(id).trim() === "") continue;
|
if (id === null || id === undefined || String(id).trim() === "") continue;
|
||||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns, tab.result?.mongo_documents?.[rowIdx]);
|
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns, tab.result?.mongo_documents?.[rowIdx]);
|
||||||
if (Object.keys(updateDoc).length === 0) continue;
|
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;
|
return stmts;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export function serializeMongoDocumentId(value: unknown): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function mongoDocumentIdForGrid(value: unknown): MongoInputValue {
|
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;
|
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,3 @@ function isMongoExtendedJsonId(value: unknown): value is Record<string, unknown>
|
|||||||
const keys = Object.keys(object);
|
const keys = Object.keys(object);
|
||||||
return keys.length === 1 && (typeof object.$numberLong === "string" || typeof object.$oid === "string");
|
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";
|
|
||||||
}
|
|
||||||
@@ -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 {
|
fn bson_document_field_to_json(key: &str, bson: &Bson) -> serde_json::Value {
|
||||||
if key == "_id" {
|
if key == "_id" {
|
||||||
if let Bson::Int64(value) = bson {
|
match bson {
|
||||||
return serde_json::json!({ "$numberLong": value.to_string() });
|
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)
|
bson_to_json(bson)
|
||||||
@@ -1870,6 +1872,16 @@ mod tests {
|
|||||||
assert_eq!(value["snowflake"], serde_json::json!("2048938405781032962"));
|
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]
|
#[test]
|
||||||
fn bson_to_json_keeps_safe_int64_as_number() {
|
fn bson_to_json_keeps_safe_int64_as_number() {
|
||||||
let value = bson_to_json(&Bson::Int64(42));
|
let value = bson_to_json(&Bson::Int64(42));
|
||||||
|
|||||||
+9
-4
@@ -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", () => {
|
test("formats extended JSON object ids as Mongo shell ObjectId literals", () => {
|
||||||
assert.equal(formatMongoShellLiteral({ $oid: "6743e4bfa3f6f84bc3fff6c8" }), 'ObjectId("6743e4bfa3f6f84bc3fff6c8")');
|
assert.equal(formatMongoShellLiteral({ $oid: "6743e4bfa3f6f84bc3fff6c8" }), 'ObjectId("6743e4bfa3f6f84bc3fff6c8")');
|
||||||
|
assert.equal(formatMongoShellLiteral("6743e4bfa3f6f84bc3fff6c8"), '"6743e4bfa3f6f84bc3fff6c8"');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("serializes typed Mongo document ids while keeping their grid display compact", () => {
|
test("serializes typed Mongo document ids while keeping their grid display compact", () => {
|
||||||
const id = { $numberLong: "2048938405781032962" };
|
const longId = { $numberLong: "2048938405781032962" };
|
||||||
assert.equal(serializeMongoDocumentId(id), '{"$numberLong":"2048938405781032962"}');
|
const objectId = { $oid: "6743e4bfa3f6f84bc3fff6c8" };
|
||||||
assert.equal(mongoDocumentIdForGrid(id), "2048938405781032962");
|
assert.equal(serializeMongoDocumentId(longId), '{"$numberLong":"2048938405781032962"}');
|
||||||
assert.equal(serializeMongoDocumentId({ $oid: "6743e4bfa3f6f84bc3fff6c8" }), '{"$oid":"6743e4bfa3f6f84bc3fff6c8"}');
|
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("2048938405781032962"), '__dbx_mongo_string_id__"2048938405781032962"');
|
||||||
assert.equal(serializeMongoDocumentId('{"$numberLong":"2048938405781032962"}'), '__dbx_mongo_string_id__"{\\"$numberLong\\":\\"2048938405781032962\\"}"');
|
assert.equal(serializeMongoDocumentId('{"$numberLong":"2048938405781032962"}'), '__dbx_mongo_string_id__"{\\"$numberLong\\":\\"2048938405781032962\\"}"');
|
||||||
});
|
});
|
||||||
|
|||||||
+17
-5
@@ -626,12 +626,24 @@ test("mongoDocumentsToQueryResult turns mongo documents into grid rows", () => {
|
|||||||
assert.equal(result.truncated, true);
|
assert.equal(result.truncated, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("mongoDocumentsToQueryResult displays typed int64 ids without losing raw type metadata", () => {
|
test("mongoDocumentsToQueryResult displays ids without losing raw type metadata", () => {
|
||||||
const id = { $numberLong: "2048938405781032962" };
|
const documents = [
|
||||||
const result = mongoDocumentsToQueryResult([{ _id: id, name: "snowflake" }], 1, 1);
|
{ _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.rows, [
|
||||||
assert.deepEqual(result.mongo_documents, [{ _id: id, name: "snowflake" }]);
|
["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", () => {
|
test("buildMongoUpdateDocument ignores _id and preserves typed values", () => {
|
||||||
|
|||||||
Reference in new issue
Block a user