fix(mongodb): defer result copy statement generation
This commit is contained in:
1 parent
71575c32de
commit
6ace11b097
5 files changed
+125
-67
No files matched your search
@@ -266,14 +266,12 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
vi.mocked(buildDataGridCopyInsertStatement).mockReturnValueOnce(pending.promise);
|
||||
const state = useDataGridExport(options);
|
||||
|
||||
const prefetch = state.prefetchSelectionAsInsertStatement("merged");
|
||||
const copy = state.copySelectionAsInsert("merged");
|
||||
await vi.waitFor(() => expect(buildDataGridCopyInsertStatement).toHaveBeenCalledTimes(1));
|
||||
expect(state.copySelectionAsInsert("merged")).toBe(false);
|
||||
expect(copyToClipboard).not.toHaveBeenCalled();
|
||||
pending.resolve("INSERT INTO users (name, note) VALUES ('Ada', 'math'), ('Grace', 'compiler');");
|
||||
await prefetch;
|
||||
await copy;
|
||||
expect(state.canCopyPreparedSelectionInsert("merged")).toBe(true);
|
||||
expect(state.copySelectionAsInsert("merged")).toBe(true);
|
||||
|
||||
expect(buildDataGridCopyInsertStatement).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -353,11 +351,47 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
},
|
||||
});
|
||||
|
||||
await state.prefetchSelectionAsInsertStatement();
|
||||
state.copySelectionAsInsert();
|
||||
await state.copySelectionAsInsert();
|
||||
|
||||
expect(copyToClipboard).toHaveBeenCalledWith(`db.getCollection("documents").insert({
|
||||
"booleanText": "true"
|
||||
});`);
|
||||
});
|
||||
|
||||
it("does not traverse Mongo documents while checking copy availability", async () => {
|
||||
let documentReads = 0;
|
||||
const originalDocument = Object.defineProperty({}, "payload", {
|
||||
enumerable: true,
|
||||
get() {
|
||||
documentReads++;
|
||||
return "large-value";
|
||||
},
|
||||
});
|
||||
const item = { ...row(["large-value"]), sourceIndex: 0 };
|
||||
const state = createMongoExportState({ columns: ["payload"], item, mongoDocuments: [originalDocument] });
|
||||
|
||||
expect(state.canCopyRowAsInsert.value).toBe(true);
|
||||
expect(documentReads).toBe(0);
|
||||
|
||||
const copy = state.copyRowAsInsert();
|
||||
expect(documentReads).toBe(0);
|
||||
expect(copyToClipboard).not.toHaveBeenCalled();
|
||||
|
||||
await copy;
|
||||
expect(documentReads).toBeGreaterThan(0);
|
||||
expect(copyToClipboard).toHaveBeenCalledWith(expect.stringContaining('"payload": "large-value"'));
|
||||
});
|
||||
|
||||
it("preserves oversized Mongo documents without running the formatter", async () => {
|
||||
const payload = "x".repeat(1_100_000);
|
||||
const item = { ...row([payload]), sourceIndex: 0 };
|
||||
const state = createMongoExportState({ columns: ["payload"], item, mongoDocuments: [{ payload }] });
|
||||
|
||||
await state.copyRowAsInsert();
|
||||
|
||||
const copied = vi.mocked(copyToClipboard).mock.calls[0]?.[0] ?? "";
|
||||
expect(copied).toHaveLength(payload.length + 'db.getCollection("documents").insert({"payload":""});'.length);
|
||||
expect(copied.startsWith('db.getCollection("documents").insert({"payload":"')).toBe(true);
|
||||
expect(copied.endsWith('"});')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -453,17 +453,23 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const key = insertCopyKey(excludePrimaryKeys, insertMode);
|
||||
const current = insertCopyCache(excludePrimaryKeys, insertMode);
|
||||
if (current.ready && current.key === key) return current.text;
|
||||
if (current.loading && current.key === key && current.promise) return current.promise;
|
||||
|
||||
const data: CopyInsertData = {
|
||||
columns: columns.value,
|
||||
sourceColumns: sourceColumns.value,
|
||||
columnTypes: columnTypes.value?.map((type) => type ?? undefined),
|
||||
rows,
|
||||
};
|
||||
if (databaseType.value === "mongodb") {
|
||||
// Mongo documents can approach the 16 MiB BSON limit. Yield before the
|
||||
// synchronous shell serialization so menu close/rendering is never held up.
|
||||
await yieldToMainThread();
|
||||
return buildCopyInsertStatement(data, excludePrimaryKeys, insertMode);
|
||||
}
|
||||
|
||||
const key = insertCopyKey(excludePrimaryKeys, insertMode);
|
||||
const current = insertCopyCache(excludePrimaryKeys, insertMode);
|
||||
if (current.ready && current.key === key) return current.text;
|
||||
if (current.loading && current.key === key && current.promise) return current.promise;
|
||||
const promise = Promise.resolve().then(async () => {
|
||||
const statement = await buildCopyInsertStatement(data, excludePrimaryKeys, insertMode);
|
||||
const latest = insertCopyCache(excludePrimaryKeys, insertMode);
|
||||
@@ -537,6 +543,12 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (databaseType.value === "mongodb") {
|
||||
// Keep context-menu work bounded; serialize the selected Mongo fields only
|
||||
// after the user explicitly invokes the copy command.
|
||||
await yieldToMainThread();
|
||||
return buildCopyInsertStatement(data, false, insertMode);
|
||||
}
|
||||
const key = selectionInsertCopyKey(insertMode);
|
||||
const current = selectionInsertCopyCache(insertMode);
|
||||
if (current.ready && current.key === key) return current.text;
|
||||
@@ -584,10 +596,16 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
return cache.ready && cache.key === selectionInsertCopyKey(insertMode);
|
||||
}
|
||||
|
||||
function copySelectionAsInsert(insertMode: DataGridCopyInsertMode = "merged"): boolean {
|
||||
if (!canCopyPreparedSelectionInsert(insertMode)) return false;
|
||||
void copyText(selectionInsertCopyCache(insertMode).text);
|
||||
return true;
|
||||
async function copySelectionAsInsert(insertMode: DataGridCopyInsertMode = "merged"): Promise<boolean> {
|
||||
try {
|
||||
const statement = await prepareSelectionAsInsertStatement(insertMode);
|
||||
if (!statement) return false;
|
||||
await copyText(statement);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
toast(t("grid.copyFailed", { message: error?.message || String(error) }), 5000);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function prefetchRowAsUpdateStatement() {
|
||||
@@ -1564,6 +1582,10 @@ function formatMongoCopyInsertStatement(statement: string | undefined): string |
|
||||
}
|
||||
}
|
||||
|
||||
function yieldToMainThread(): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
function compactLocalTimestamp(date = new Date()): string {
|
||||
const yy = String(date.getFullYear() % 100).padStart(2, "0");
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
|
||||
Reference in new issue
Block a user