mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
fix(mongo): stop suggesting extended json keys as fields
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { mongoExtendedJsonValueType } from "@/lib/mongo/mongoDocumentValues";
|
||||
import { ACCUMULATORS, COMMON_OPERATORS, EXPRESSION_OPERATORS, PIPELINE_STAGES, PUSH_MODIFIERS, QUERY_OPERATORS, STAGE_OPTION_KEYS, UPDATE_OPERATORS, UPDATE_OPERATOR_LABELS, VALUE_SNIPPETS, mongoOperatorItemType, type MongoOperatorSpec } from "@/lib/mongo/mongoCompletionTables";
|
||||
|
||||
/**
|
||||
@@ -1015,6 +1016,9 @@ function extractActiveCollection(text: string, cursor: number): string | undefin
|
||||
|
||||
function collectFieldTypes(value: unknown, prefix: string, out: Map<string, Set<string>>, depth: number) {
|
||||
if (depth > 4 || value == null || typeof value !== "object") return;
|
||||
// A wrapper such as {$oid: "..."} is one BSON value. Walking into it would offer
|
||||
// `_id.$oid`, a path that exists only in transport and matches nothing on the server.
|
||||
if (mongoExtendedJsonValueType(value)) return;
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value.slice(0, 3)) collectFieldTypes(item, prefix, out, depth + 1);
|
||||
return;
|
||||
@@ -1031,7 +1035,7 @@ function describeMongoValueType(value: unknown): string {
|
||||
if (value == null) return "null";
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value instanceof Date) return "date";
|
||||
return typeof value === "object" ? "object" : typeof value;
|
||||
return mongoExtendedJsonValueType(value) ?? (typeof value === "object" ? "object" : typeof value);
|
||||
}
|
||||
|
||||
function quoteMongoFieldName(field: string, prefix: string): string {
|
||||
|
||||
@@ -15,7 +15,38 @@ export const MONGO_DOCUMENT_GRID_NULL = `${MONGO_DOCUMENT_GRID_PREFIX}null`;
|
||||
const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
||||
const MIN_BSON_INT64 = -9223372036854775808n;
|
||||
const MAX_BSON_INT64 = 9223372036854775807n;
|
||||
const MONGO_EXTENDED_JSON_VALUE_KEYS = new Set(["$binary", "$code", "$date", "$dbPointer", "$maxKey", "$minKey", "$numberDecimal", "$numberDouble", "$numberInt", "$numberLong", "$oid", "$regularExpression", "$symbol", "$timestamp", "$undefined", "$uuid"]);
|
||||
/** Extended JSON wrapper key -> the BSON scalar it stands for. */
|
||||
const MONGO_EXTENDED_JSON_VALUE_TYPES = new Map([
|
||||
["$binary", "binary"],
|
||||
["$code", "javascript"],
|
||||
["$date", "date"],
|
||||
["$dbPointer", "dbPointer"],
|
||||
["$maxKey", "maxKey"],
|
||||
["$minKey", "minKey"],
|
||||
["$numberDecimal", "decimal128"],
|
||||
["$numberDouble", "double"],
|
||||
["$numberInt", "int32"],
|
||||
["$numberLong", "int64"],
|
||||
["$oid", "objectId"],
|
||||
["$regularExpression", "regex"],
|
||||
["$symbol", "symbol"],
|
||||
["$timestamp", "timestamp"],
|
||||
["$undefined", "undefined"],
|
||||
["$uuid", "uuid"],
|
||||
]);
|
||||
const MONGO_EXTENDED_JSON_VALUE_KEYS = new Set(MONGO_EXTENDED_JSON_VALUE_TYPES.keys());
|
||||
|
||||
/**
|
||||
* The BSON scalar an extended JSON wrapper stands for, or undefined when the value is
|
||||
* a plain object. `{$oid: "..."}` is how the driver ships an ObjectId over JSON; it is
|
||||
* one value, not a subdocument with a `$oid` field.
|
||||
*/
|
||||
export function mongoExtendedJsonValueType(value: unknown): string | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
||||
const keys = Object.keys(value as Record<string, unknown>);
|
||||
if (keys.length === 2 && keys.includes("$code") && keys.includes("$scope")) return "javascript";
|
||||
return keys.length === 1 ? MONGO_EXTENDED_JSON_VALUE_TYPES.get(keys[0] ?? "") : undefined;
|
||||
}
|
||||
const MONGO_EXTENDED_JSON_NUMERIC_TYPES = new Map([
|
||||
["$numberInt", "int32"],
|
||||
["$numberLong", "int64"],
|
||||
|
||||
+29
@@ -466,6 +466,35 @@ test("snippet templates use placeholder syntax CodeMirror actually honours", ()
|
||||
}
|
||||
});
|
||||
|
||||
test("treats extended JSON wrappers as scalars, not subdocuments", () => {
|
||||
// The driver ships BSON scalars as extended JSON. Walking into them would offer
|
||||
// `_id.$oid`, which is valid syntax that matches nothing on the server.
|
||||
const inferred = inferMongoCompletionFields([
|
||||
{
|
||||
_id: { $oid: "6743e4bfa3f6f84bc3fff6c8" },
|
||||
created_at: { $date: "2025-01-01T00:00:00Z" },
|
||||
count: { $numberLong: "42" },
|
||||
raw: { $binary: { base64: "AQID", subType: "00" } },
|
||||
profile: { email: "a@example.com" },
|
||||
},
|
||||
]);
|
||||
|
||||
for (const phantom of ["_id.$oid", "created_at.$date", "count.$numberLong", "raw.$binary"]) {
|
||||
assert.equal(
|
||||
inferred.some((field) => field.name === phantom),
|
||||
false,
|
||||
phantom,
|
||||
);
|
||||
}
|
||||
|
||||
assert.ok(inferred.find((field) => field.name === "_id" && field.type === "objectId"));
|
||||
assert.ok(inferred.find((field) => field.name === "created_at" && field.type === "date"));
|
||||
assert.ok(inferred.find((field) => field.name === "count" && field.type === "int64"));
|
||||
assert.ok(inferred.find((field) => field.name === "raw" && field.type === "binary"));
|
||||
// Genuine subdocuments are still walked.
|
||||
assert.ok(inferred.find((field) => field.name === "profile.email" && field.type === "string"));
|
||||
});
|
||||
|
||||
test("infers dotted MongoDB fields from sampled documents", () => {
|
||||
const inferred = inferMongoCompletionFields([
|
||||
{ _id: "1", profile: { email: "a@example.com" }, tags: ["a"] },
|
||||
|
||||
Reference in New Issue
Block a user