fix(mongodb): preserve JSON-looking string values
This commit is contained in:
parent
66d7b965dc
commit
5ceca7fb3e
|
|
@ -305,7 +305,7 @@ async function gridSave(changes: DocumentGridChanges) {
|
|||
continue;
|
||||
}
|
||||
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, cols);
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, cols, documents.value[rowIdx]);
|
||||
if (Object.keys(updateDoc).length === 0) continue;
|
||||
await api.documentUpdateDocument(props.connectionId, props.database, props.collection, String(id), JSON.stringify(updateDoc));
|
||||
}
|
||||
|
|
@ -382,7 +382,7 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
|
|||
const routing = documentRoutingFromGridRow(row, columns);
|
||||
stmts.push(`POST /${coll}/_update/${elasticsearchPathIdPreview(String(id))}${elasticsearchRoutingPreview(routing)}\n${JSON.stringify({ doc: updateDoc.$set ?? updateDoc }, null, 2)}`);
|
||||
} else {
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns);
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns, documents.value[rowIdx]);
|
||||
stmts.push(`db.${coll}.updateOne({_id: ${mongoIdPreview(id)}}, ${formatMongoShellLiteral(updateDoc)})`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ const mongoQueryResultSaveHandler = computed<CustomSaveHandler | undefined>(() =
|
|||
const row = changes.rows[rowIdx];
|
||||
const id = row?.[idColIdx];
|
||||
if (id === null || id === undefined || String(id).trim() === "") continue;
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns);
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns, tab.result?.mongo_documents?.[rowIdx]);
|
||||
if (Object.keys(updateDoc).length === 0) continue;
|
||||
await api.mongoUpdateDocument(tab.connectionId, tab.database, target.collection, String(id), JSON.stringify(updateDoc));
|
||||
}
|
||||
|
|
@ -405,7 +405,7 @@ const mongoQueryResultSaveHandler = computed<CustomSaveHandler | undefined>(() =
|
|||
const row = changes.rows[rowIdx];
|
||||
const id = row?.[idColIdx];
|
||||
if (id === null || id === undefined || String(id).trim() === "") continue;
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns);
|
||||
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(id)}}, ${formatMongoShellLiteral(updateDoc)})`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,20 @@ export function parseMongoDocumentInputValue(raw: MongoInputValue): unknown {
|
|||
return raw;
|
||||
}
|
||||
|
||||
function parseMongoExistingFieldInputValue(raw: Exclude<MongoInputValue, null>, originalValue: unknown): unknown {
|
||||
// Objects and arrays are serialized into grid text too, so the raw document
|
||||
// is the only reliable way to distinguish them from JSON-shaped BSON strings.
|
||||
if (typeof originalValue === "string") {
|
||||
return typeof raw === "string" ? raw : String(raw);
|
||||
}
|
||||
return parseMongoDocumentInputValue(raw);
|
||||
}
|
||||
|
||||
function mongoDocumentFieldValue(document: unknown, field: string): unknown {
|
||||
if (!document || typeof document !== "object" || Array.isArray(document)) return undefined;
|
||||
return (document as Record<string, unknown>)[field];
|
||||
}
|
||||
|
||||
function legacyMongoDateDisplayToExtendedJson(value: string): { $date: string } | null {
|
||||
const match = value.match(LEGACY_MONGO_DATE_DISPLAY_PATTERN);
|
||||
if (!match) return null;
|
||||
|
|
@ -38,7 +52,7 @@ function legacyMongoDateDisplayToExtendedJson(value: string): { $date: string }
|
|||
return { $date: `${date}T${time}.${millis.padEnd(3, "0")}Z` };
|
||||
}
|
||||
|
||||
export function buildMongoUpdateDocument(changes: Map<number, MongoInputValue>, columns: string[]): Record<string, unknown> {
|
||||
export function buildMongoUpdateDocument(changes: Map<number, MongoInputValue>, columns: string[], originalDocument?: unknown): Record<string, unknown> {
|
||||
const setFields: Record<string, unknown> = {};
|
||||
const unsetFields: Record<string, unknown> = {};
|
||||
for (const [colIdx, newVal] of changes) {
|
||||
|
|
@ -47,7 +61,7 @@ export function buildMongoUpdateDocument(changes: Map<number, MongoInputValue>,
|
|||
if (newVal === null) {
|
||||
unsetFields[col] = "";
|
||||
} else {
|
||||
setFields[col] = parseMongoDocumentInputValue(newVal);
|
||||
setFields[col] = parseMongoExistingFieldInputValue(newVal, mongoDocumentFieldValue(originalDocument, col));
|
||||
}
|
||||
}
|
||||
const doc: Record<string, unknown> = {};
|
||||
|
|
@ -66,7 +80,7 @@ export function applyMongoGridChangesToDocument(document: unknown, changes: Map<
|
|||
if (newVal === null) {
|
||||
delete updated[column];
|
||||
} else {
|
||||
updated[column] = parseMongoDocumentInputValue(newVal);
|
||||
updated[column] = parseMongoExistingFieldInputValue(newVal, updated[column]);
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,70 @@ test("builds Mongo grid updates with set and unset operators", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test("preserves JSON-shaped strings when updating existing Mongo fields", () => {
|
||||
const original = {
|
||||
_id: "1",
|
||||
answer: '{"action":"New","values":[1]}',
|
||||
tagsText: '["draft"]',
|
||||
profile: { role: "admin" },
|
||||
};
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, '{\n "action": "Updated",\n "values": [\n 1,\n 2\n ]\n}'],
|
||||
[2, '[\n "published"\n]'],
|
||||
[3, '{"role":"maintainer"}'],
|
||||
]);
|
||||
|
||||
const update = buildMongoUpdateDocument(changes, ["_id", "answer", "tagsText", "profile"], original);
|
||||
|
||||
assert.deepEqual(update, {
|
||||
$set: {
|
||||
answer: '{\n "action": "Updated",\n "values": [\n 1,\n 2\n ]\n}',
|
||||
tagsText: '[\n "published"\n]',
|
||||
profile: { role: "maintainer" },
|
||||
},
|
||||
});
|
||||
assert.equal(formatMongoShellLiteral(update), '{"$set":{"answer":"{\\n \\"action\\": \\"Updated\\",\\n \\"values\\": [\\n 1,\\n 2\\n ]\\n}","tagsText":"[\\n \\"published\\"\\n]","profile":{"role":"maintainer"}}}');
|
||||
});
|
||||
|
||||
test("preserves existing Mongo strings that resemble typed literals", () => {
|
||||
const original = {
|
||||
_id: "1",
|
||||
numericText: "42",
|
||||
booleanText: "true",
|
||||
dateText: 'ISODate("2026-01-01T00:00:00.000Z")',
|
||||
quotedText: '"literal"',
|
||||
};
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, "43"],
|
||||
[2, "false"],
|
||||
[3, 'ISODate("2026-02-01T00:00:00.000Z")'],
|
||||
[4, '"changed"'],
|
||||
]);
|
||||
|
||||
assert.deepEqual(buildMongoUpdateDocument(changes, ["_id", "numericText", "booleanText", "dateText", "quotedText"], original), {
|
||||
$set: {
|
||||
numericText: "43",
|
||||
booleanText: "false",
|
||||
dateText: 'ISODate("2026-02-01T00:00:00.000Z")',
|
||||
quotedText: '"changed"',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps JSON inference for fields without an existing Mongo type", () => {
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, '{"enabled":true}'],
|
||||
[2, "42"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(buildMongoUpdateDocument(changes, ["_id", "newObject", "newNumber"], { _id: "1" }), {
|
||||
$set: {
|
||||
newObject: { enabled: true },
|
||||
newNumber: 42,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("applies saved Mongo grid changes to the raw preview document", () => {
|
||||
const original = {
|
||||
_id: "1",
|
||||
|
|
@ -62,6 +126,24 @@ test("applies saved Mongo grid changes to the raw preview document", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test("applies Mongo grid edits without converting existing JSON strings", () => {
|
||||
const original = {
|
||||
_id: "1",
|
||||
answer: '{"action":"New"}',
|
||||
profile: { role: "admin" },
|
||||
};
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, '{\n "action": "Updated"\n}'],
|
||||
[2, '{"role":"maintainer"}'],
|
||||
]);
|
||||
|
||||
assert.deepEqual(applyMongoGridChangesToDocument(original, changes, ["_id", "answer", "profile"]), {
|
||||
_id: "1",
|
||||
answer: '{\n "action": "Updated"\n}',
|
||||
profile: { role: "maintainer" },
|
||||
});
|
||||
});
|
||||
|
||||
test("builds Mongo inserts with parsed date values", () => {
|
||||
assert.deepEqual(buildMongoInsertDocument(["ignored", 'new Date("2026-06-10T13:59:31.287Z")'], ["_id", "createdAt"]), {
|
||||
createdAt: { $date: "2026-06-10T13:59:31.287Z" },
|
||||
|
|
|
|||
Loading…
Reference in New Issue