fix(mongodb): preserve long ids in filters and updates
This commit is contained in:
9 files changed
+325
-31
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import type { QueryResult } from "@/types/database";
|
||||
import { mongoDocumentIdForGrid } from "@/lib/mongo/mongoDocumentValues";
|
||||
|
||||
export interface MongoFindCommand {
|
||||
collection: string;
|
||||
@@ -638,11 +639,12 @@ function normalizeJsonArgument(value: string): string | null {
|
||||
if (!trimmed) return "{}";
|
||||
// Rewrite mongo shell constructors that are not valid JSON into the extended
|
||||
// JSON the backend understands (mongo_driver::json_value_to_bson): ObjectId(x)
|
||||
// -> {"$oid":x} and ISODate(x)/new Date(x) -> {"$date":x}. Without this a
|
||||
// -> {"$oid":x}, NumberLong(x) -> {"$numberLong":x}, and
|
||||
// ISODate(x)/new Date(x) -> {"$date":x}. Without this a
|
||||
// filter such as { createdAt: { $gte: ISODate("...") } } fails JSON.parse,
|
||||
// the command is left unrecognized and falls through to the SQL executor,
|
||||
// which rejects it with "Use MongoDB-specific commands".
|
||||
const withExtendedJson = trimmed.replace(/ObjectId\s*\(\s*["']([^"']+)["']\s*\)/g, '{"$oid":"$1"}').replace(/(?:ISODate|new\s+Date)\s*\(\s*["']([^"']+)["']\s*\)/g, '{"$date":"$1"}');
|
||||
const withExtendedJson = replaceMongoShellConstructors(trimmed);
|
||||
const preprocessed = quoteUnquotedObjectKeys(convertSingleQuotedStrings(withExtendedJson));
|
||||
try {
|
||||
JSON.parse(preprocessed);
|
||||
@@ -652,6 +654,41 @@ function normalizeJsonArgument(value: string): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
function replaceMongoShellConstructors(source: string): string {
|
||||
const constructor = /^(ObjectId|NumberLong|ISODate)\s*\(\s*["']([^"']+)["']\s*\)|^(ObjectId|NumberLong)\s*\(\s*(-?\d+)\s*\)|^(?:new\s+Date)\s*\(\s*["']([^"']+)["']\s*\)/;
|
||||
let result = "";
|
||||
let index = 0;
|
||||
while (index < source.length) {
|
||||
const quote = source[index];
|
||||
if (quote === '"' || quote === "'") {
|
||||
const start = index++;
|
||||
while (index < source.length) {
|
||||
if (source[index] === "\\") index += 2;
|
||||
else if (source[index] === quote) {
|
||||
index++;
|
||||
break;
|
||||
} else index++;
|
||||
}
|
||||
result += source.slice(start, index);
|
||||
continue;
|
||||
}
|
||||
const match = source.slice(index).match(constructor);
|
||||
if (!match) {
|
||||
result += source[index++];
|
||||
continue;
|
||||
}
|
||||
if (match[1]) {
|
||||
result += match[1] === "ObjectId" ? `{"$oid":"${match[2]}"}` : match[1] === "NumberLong" ? `{"$numberLong":"${match[2]}"}` : `{"$date":"${match[2]}"}`;
|
||||
} else if (match[3]) {
|
||||
result += match[3] === "NumberLong" ? `{"$numberLong":"${match[4]}"}` : `{"$oid":"${match[4]}"}`;
|
||||
} else {
|
||||
result += `{"$date":"${match[5]}"}`;
|
||||
}
|
||||
index += match[0].length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseMethodArgs(source: string, methodCallIndex: number): string[] | null {
|
||||
const openIndex = source.indexOf("(", methodCallIndex);
|
||||
const closeIndex = findMatchingParen(source, openIndex);
|
||||
@@ -1171,5 +1208,5 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
function toCellValue(value: unknown): string | number | boolean | null {
|
||||
if (value === undefined || value === null) return null;
|
||||
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
||||
return JSON.stringify(value);
|
||||
return mongoDocumentIdForGrid(value);
|
||||
}
|
||||
Reference in new issue
Block a user