mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(mongodb): keep Int32 and Double types when editing numbers
This commit is contained in:
@@ -1577,7 +1577,7 @@ async function gridSave(changes: DocumentGridChanges) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, cols, documents.value[rowIdx]);
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, cols, mongoTypedSourceDocument(rowIdx));
|
||||
if (Object.keys(updateDoc).length === 0) continue;
|
||||
const documentId = documents.value[rowIdx]?._id ?? id;
|
||||
await api.documentUpdateDocument(props.connectionId, props.database, props.collection, serializeMongoDocumentId(documentId), JSON.stringify(updateDoc));
|
||||
@@ -1648,6 +1648,11 @@ function buildPathIdentityUpdatedDocument(document: JsonRecord, changes: Map<num
|
||||
return updated;
|
||||
}
|
||||
|
||||
/** The loaded document with its BSON number types intact (canonical extended JSON), so edits keep Int32/Double fields typed. */
|
||||
function mongoTypedSourceDocument(rowIdx: number): JsonRecord | undefined {
|
||||
return (mongoCopyDocumentsAvailable.value ? copyDocuments.value[rowIdx] : undefined) ?? documents.value[rowIdx];
|
||||
}
|
||||
|
||||
function buildMongoGridInsertDocument(row: MongoInputValue[], columns: string[], meta?: GridNewRowMeta): Record<string, unknown> {
|
||||
const sourceIndex = meta?.sourceIndex;
|
||||
const sourceDocument = sourceIndex === undefined ? undefined : copyDocuments.value[sourceIndex];
|
||||
@@ -1725,7 +1730,7 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
|
||||
stmts.push(kind === "solr" ? formatSolrDocumentOperationPreview({ action: "update", core: coll, id: documentId, document: writeDocument }) : formatMeilisearchDocumentOperationPreview({ action: "update", index: coll, id: documentId, document: writeDocument }));
|
||||
}
|
||||
} else {
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns, documents.value[rowIdx]);
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns, mongoTypedSourceDocument(rowIdx));
|
||||
stmts.push(`db.${coll}.updateOne({_id: ${formatMongoShellLiteral(documents.value[rowIdx]?._id ?? id)}}, ${formatMongoShellLiteral(updateDoc)})`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,9 +404,44 @@ function parseMongoExistingFieldInputValue(raw: Exclude<MongoInputValue, null>,
|
||||
if (typeof originalValue === "string") {
|
||||
return typeof raw === "string" ? raw : String(raw);
|
||||
}
|
||||
const typedNumber = mongoTypedNumberInputValue(raw, mongoDocumentNumericValueType(originalValue));
|
||||
if (typedNumber !== undefined) return typedNumber;
|
||||
return parseMongoDocumentInputValue(raw);
|
||||
}
|
||||
|
||||
const MONGO_DECIMAL_INPUT_PATTERN = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
||||
const MIN_BSON_INT32 = -2147483648n;
|
||||
const MAX_BSON_INT32 = 2147483647n;
|
||||
|
||||
/**
|
||||
* Re-wraps a number typed over an existing typed BSON number in the original
|
||||
* wrapper. A bare JSON number is always saved as Int64, so without this an
|
||||
* edited Int32 or Double silently changes type. Input the original type cannot
|
||||
* hold (a fraction for Int32, an Int32 overflow) falls back to normal parsing.
|
||||
*/
|
||||
function mongoTypedNumberInputValue(raw: Exclude<MongoInputValue, null>, numericType: string | undefined): unknown {
|
||||
if (!numericType || numericType === "number" || typeof raw === "boolean") return undefined;
|
||||
const text = String(raw).trim();
|
||||
if (!MONGO_DECIMAL_INPUT_PATTERN.test(text)) return undefined;
|
||||
switch (numericType) {
|
||||
case "int32":
|
||||
case "int64": {
|
||||
if (!MONGO_INTEGER_PATTERN.test(text)) return undefined;
|
||||
const integer = BigInt(text);
|
||||
if (numericType === "int32") return integer >= MIN_BSON_INT32 && integer <= MAX_BSON_INT32 ? { $numberInt: integer.toString() } : undefined;
|
||||
return integer >= MIN_BSON_INT64 && integer <= MAX_BSON_INT64 ? { $numberLong: integer.toString() } : undefined;
|
||||
}
|
||||
case "double": {
|
||||
const double = Number(text);
|
||||
return Number.isFinite(double) ? { $numberDouble: String(double) } : undefined;
|
||||
}
|
||||
case "decimal128":
|
||||
return { $numberDecimal: text };
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function mongoDocumentFieldValue(document: unknown, field: string): unknown {
|
||||
if (!document || typeof document !== "object" || Array.isArray(document)) return undefined;
|
||||
return (document as Record<string, unknown>)[field];
|
||||
|
||||
+42
@@ -169,6 +169,48 @@ test("preserves existing Mongo strings that resemble typed literals", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps the BSON number type of edited Mongo fields", () => {
|
||||
const original = {
|
||||
_id: { $oid: "65f000000000000000000001" },
|
||||
count: { $numberInt: "1" },
|
||||
total: { $numberLong: "2" },
|
||||
ratio: { $numberDouble: "1.5" },
|
||||
price: { $numberDecimal: "9.99" },
|
||||
};
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, "200"],
|
||||
[2, 7],
|
||||
[3, "3"],
|
||||
[4, "10.50"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(buildMongoUpdateDocument(changes, ["_id", "count", "total", "ratio", "price"], original), {
|
||||
$set: {
|
||||
count: { $numberInt: "200" },
|
||||
total: { $numberLong: "7" },
|
||||
ratio: { $numberDouble: "3" },
|
||||
price: { $numberDecimal: "10.50" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("falls back to inferred types when an edit no longer fits the Mongo number type", () => {
|
||||
const original = { _id: "1", count: { $numberInt: "1" }, other: { $numberInt: "1" }, label: { $numberInt: "1" } };
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, "2147483648"],
|
||||
[2, "1.5"],
|
||||
[3, "abc"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(buildMongoUpdateDocument(changes, ["_id", "count", "other", "label"], original), {
|
||||
$set: {
|
||||
count: 2147483648,
|
||||
other: 1.5,
|
||||
label: "abc",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps JSON inference for fields without an existing Mongo type", () => {
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[1, '{"enabled":true}'],
|
||||
|
||||
Reference in New Issue
Block a user