fix(mongodb): defer result copy statement generation

This commit is contained in:
t8y2 committed 2026-07-21 17:11:58 +08:00
1 parent 71575c32de
commit 6ace11b097
5 files changed
+125 -67

No files matched your search

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { formatMongoShellText, MAX_MONGO_FORMAT_CHARS } from "@/lib/mongo/mongoFormatter";
describe("mongoFormatter", () => {
it("formats documents with many short fields without repeated whole-output rewrites", () => {
const fields = Array.from({ length: 20_000 }, (_, index) => `"field${index}":${index}`).join(",");
const query = `db.items.insert({${fields}});`;
const formatted = formatMongoShellText(query);
expect(formatted).toContain('"field0": 0');
expect(formatted).toContain('"field19999": 19999');
expect(formatted.length).toBeGreaterThan(query.length);
});
it("rejects input beyond the formatter safety limit", () => {
expect(() => formatMongoShellText("x".repeat(MAX_MONGO_FORMAT_CHARS + 1))).toThrow("MongoDB query is too large to format safely.");
});
});
+32 -16
View File
@@ -3,7 +3,9 @@ import { DEFAULT_SQL_FORMATTER_SETTINGS, type SqlFormatterSettings } from "@/lib
export const MAX_MONGO_FORMAT_CHARS = 1_000_000;
interface FormatState {
out: string;
out: string[];
lastChar: string;
lastNonWhitespace: string;
indentLevel: number;
atLineStart: boolean;
pendingSpace: boolean;
@@ -21,7 +23,7 @@ export function formatMongoShellText(text: string, settings: Partial<SqlFormatte
}
const indentUnit = settings.useTabs ? "\t" : " ".repeat(settings.tabWidth ?? DEFAULT_SQL_FORMATTER_SETTINGS.tabWidth);
const state: FormatState = { out: "", indentLevel: 0, atLineStart: true, pendingSpace: false, chainIndent: false, pendingChainCall: false, stack: [] };
const state: FormatState = { out: [], lastChar: "", lastNonWhitespace: "", indentLevel: 0, atLineStart: true, pendingSpace: false, chainIndent: false, pendingChainCall: false, stack: [] };
for (let index = 0; index < text.length; index++) {
const char = text[index] ?? "";
@@ -111,34 +113,34 @@ export function formatMongoShellText(text: string, settings: Partial<SqlFormatte
appendToken(state, char, indentUnit);
}
return cleanupFormattedMongoText(state.out);
return cleanupFormattedMongoText(state.out.join(""));
}
function appendToken(state: FormatState, token: string, indentUnit: string) {
if (state.atLineStart) {
state.out += indentUnit.repeat(Math.max(0, state.indentLevel + (state.chainIndent ? 1 : 0)));
appendOutput(state, indentUnit.repeat(Math.max(0, state.indentLevel + (state.chainIndent ? 1 : 0))));
state.atLineStart = false;
} else if (state.pendingSpace && shouldInsertPendingSpace(state.out, token)) {
state.out += " ";
} else if (state.pendingSpace && shouldInsertPendingSpace(state.lastNonWhitespace, token)) {
appendOutput(state, " ");
}
state.out += token;
appendOutput(state, token);
state.pendingSpace = false;
state.chainIndent = false;
}
function appendRaw(state: FormatState, token: string, indentUnit: string) {
if (state.atLineStart) {
state.out += indentUnit.repeat(Math.max(0, state.indentLevel + (state.chainIndent ? 1 : 0)));
appendOutput(state, indentUnit.repeat(Math.max(0, state.indentLevel + (state.chainIndent ? 1 : 0))));
state.atLineStart = false;
}
state.out += token;
appendOutput(state, token);
state.pendingSpace = false;
state.chainIndent = false;
}
function newline(state: FormatState, indentUnit: string, indentDelta = 0) {
trimTrailingSpaces(state);
if (!state.out.endsWith("\n")) state.out += "\n";
if (state.lastChar !== "\n") appendOutput(state, "\n");
state.indentLevel = Math.max(0, state.indentLevel + indentDelta);
state.atLineStart = true;
state.pendingSpace = false;
@@ -146,8 +148,7 @@ function newline(state: FormatState, indentUnit: string, indentDelta = 0) {
void indentUnit;
}
function shouldInsertPendingSpace(output: string, token: string): boolean {
const previous = lastNonWhitespace(output);
function shouldInsertPendingSpace(previous: string, token: string): boolean {
if (!previous) return false;
if ([".", "(", "[", "{"].includes(previous)) return false;
if ([".", ")", "]", "}", ",", ":"].includes(token)) return false;
@@ -258,13 +259,28 @@ function findPreviousNonWhitespace(text: string, start: number): number | null {
return null;
}
function lastNonWhitespace(text: string): string | null {
const index = findPreviousNonWhitespace(text, text.length - 1);
return index == null ? null : (text[index] ?? null);
function appendOutput(state: FormatState, text: string) {
if (!text) return;
state.out.push(text);
state.lastChar = text[text.length - 1] ?? state.lastChar;
const lastNonWhitespaceIndex = findPreviousNonWhitespace(text, text.length - 1);
if (lastNonWhitespaceIndex !== null) state.lastNonWhitespace = text[lastNonWhitespaceIndex] ?? state.lastNonWhitespace;
}
function trimTrailingSpaces(state: FormatState) {
state.out = state.out.replace(/[ \t]+$/g, "");
while (state.out.length > 0) {
const lastIndex = state.out.length - 1;
const chunk = state.out[lastIndex] ?? "";
const trimmed = chunk.replace(/[ \t]+$/g, "");
if (trimmed) {
state.out[lastIndex] = trimmed;
state.lastChar = trimmed[trimmed.length - 1] ?? "";
return;
}
state.out.pop();
}
state.lastChar = "";
state.lastNonWhitespace = "";
}
function cleanupFormattedMongoText(text: string): string {