mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 08:13:34 +00:00
fix(mongodb): label sampled date and ObjectId fields with BSON types
This commit is contained in:
@@ -2331,10 +2331,13 @@ function collectFieldTypes(value: unknown, prefix: string, out: Map<string, Set<
|
||||
}
|
||||
}
|
||||
|
||||
const LEGACY_ISODATE_PATTERN = /^ISODate\("[^"]*"\)$/;
|
||||
|
||||
function describeMongoValueType(value: unknown): string {
|
||||
if (value == null) return "null";
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value instanceof Date) return "date";
|
||||
if (typeof value === "string" && LEGACY_ISODATE_PATTERN.test(value)) return "date";
|
||||
return mongoExtendedJsonValueType(value) ?? (typeof value === "object" ? "object" : typeof value);
|
||||
}
|
||||
|
||||
|
||||
+74
@@ -102,4 +102,78 @@ describe("connectionStore listMongoCompletionFields", () => {
|
||||
expect(cached).toEqual(fields);
|
||||
expect(api.mongoFindDocuments).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("infers types from extended_documents when returned by mongoAggregateDocuments", async () => {
|
||||
const api = {
|
||||
mongoAggregateDocuments: vi.fn().mockResolvedValue({
|
||||
documents: [{ _id: "65f0c0ffee00000000000001", createdAt: "2026-01-01T00:00:00Z", ref: "65f0c0ffee0000000000abcd" }],
|
||||
extended_documents: [{ _id: { $oid: "65f0c0ffee00000000000001" }, createdAt: { $date: "2026-01-01T00:00:00Z" }, ref: { $oid: "65f0c0ffee0000000000abcd" } }],
|
||||
}),
|
||||
mongoFindDocuments: vi.fn(),
|
||||
};
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => api);
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const config = mongoConnection();
|
||||
store.addEphemeralConnection(config);
|
||||
|
||||
const fields = await store.listMongoCompletionFields(config.id, "app", "users");
|
||||
|
||||
expect(fields).toEqual([
|
||||
{ name: "_id", type: "objectId" },
|
||||
{ name: "createdAt", type: "date" },
|
||||
{ name: "ref", type: "objectId" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("infers types from extended_documents when returned by mongoFindDocuments fallback", async () => {
|
||||
const api = {
|
||||
mongoAggregateDocuments: vi.fn().mockRejectedValue(new Error("Aggregation not supported")),
|
||||
mongoFindDocuments: vi.fn().mockResolvedValue({
|
||||
documents: [{ _id: "65f0c0ffee00000000000001", createdAt: "2026-01-01T00:00:00Z", ref: "65f0c0ffee0000000000abcd" }],
|
||||
extended_documents: [{ _id: { $oid: "65f0c0ffee00000000000001" }, createdAt: { $date: "2026-01-01T00:00:00Z" }, ref: { $oid: "65f0c0ffee0000000000abcd" } }],
|
||||
}),
|
||||
};
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => api);
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const config = mongoConnection();
|
||||
store.addEphemeralConnection(config);
|
||||
|
||||
const fields = await store.listMongoCompletionFields(config.id, "app", "users");
|
||||
|
||||
expect(fields).toEqual([
|
||||
{ name: "_id", type: "objectId" },
|
||||
{ name: "createdAt", type: "date" },
|
||||
{ name: "ref", type: "objectId" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to documents when extended_documents is absent or length mismatches", async () => {
|
||||
const api = {
|
||||
mongoAggregateDocuments: vi.fn().mockResolvedValue({
|
||||
documents: [{ _id: "1", fallbackField: "val" }],
|
||||
extended_documents: [],
|
||||
}),
|
||||
mongoFindDocuments: vi.fn(),
|
||||
};
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => api);
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const config = mongoConnection();
|
||||
store.addEphemeralConnection(config);
|
||||
|
||||
const fields = await store.listMongoCompletionFields(config.id, "app", "users");
|
||||
|
||||
expect(fields).toEqual([
|
||||
{ name: "_id", type: "string" },
|
||||
{ name: "fallbackField", type: "string" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8817,7 +8817,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
} catch {
|
||||
result = await api.mongoFindDocuments(connectionId, database, collection, 0, 100, "{}");
|
||||
}
|
||||
const fields = inferMongoCompletionFields(result.documents ?? []);
|
||||
const sampled = result.extended_documents?.length === result.documents.length ? result.extended_documents : result.documents;
|
||||
const fields = inferMongoCompletionFields(sampled ?? []);
|
||||
mongoCompletionFieldsCache.value[cacheKey] = fields;
|
||||
evictOldestCacheEntries(mongoCompletionFieldsCache.value, COMPLETION_CACHE_MAX);
|
||||
return fields;
|
||||
|
||||
+29
@@ -1779,6 +1779,35 @@ test("treats extended JSON wrappers as scalars, not subdocuments", () => {
|
||||
assert.ok(inferred.find((field) => field.name === "profile.email" && field.type === "string"));
|
||||
});
|
||||
|
||||
test("infers BSON types from extended JSON values and legacy ISODate strings", () => {
|
||||
const inferred = inferMongoCompletionFields([
|
||||
{
|
||||
createdAt: { $date: "2026-01-01T00:00:00Z" },
|
||||
ref: { $oid: "65f0c0ffee0000000000abcd" },
|
||||
price: { $numberDecimal: "1.5" },
|
||||
},
|
||||
]);
|
||||
|
||||
for (const phantom of ["createdAt.$date", "ref.$oid", "price.$numberDecimal"]) {
|
||||
assert.equal(
|
||||
inferred.some((field) => field.name === phantom),
|
||||
false,
|
||||
phantom,
|
||||
);
|
||||
}
|
||||
|
||||
assert.equal(inferred.find((field) => field.name === "createdAt")?.type, "date");
|
||||
assert.equal(inferred.find((field) => field.name === "ref")?.type, "objectId");
|
||||
assert.equal(inferred.find((field) => field.name === "price")?.type, "decimal128");
|
||||
|
||||
const legacyInferred = inferMongoCompletionFields([
|
||||
{
|
||||
createdAt: 'ISODate("2026-01-01T00:00:00Z")',
|
||||
},
|
||||
]);
|
||||
assert.equal(legacyInferred.find((field) => field.name === "createdAt")?.type, "date");
|
||||
});
|
||||
|
||||
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