perf: full-stack performance and security hardening

Frontend performance:
- Debounce DataGrid search (150ms) and replace O(n) row wrapping with index array
- Replace QueryStore deep watcher with targeted field-level persistence snapshot
- Extract shared nodeConfig computed in TreeItem (eliminates 15x redundant getConfig calls)
- Clear tab result data on close to free memory
- Add LRU eviction for completion caches (50 entry cap)
- Replace savedSql version string join with numeric counter

Memory leak fixes:
- Store Tauri event unlisten handles and clean up on unmount
- Clean up DataGrid search timer on unmount

Backend security & resource management:
- Add 50MB request body size limit
- Reject file uploads >100MB and SQL files >200MB
- Clean up SSE channels after transfer/import completes
- Recover from poisoned mutex instead of panicking
- Delete temporary upload files after preview
- Validate SQL file paths to prevent directory traversal
This commit is contained in:
t8y2 committed 2026-05-11 00:18:09 +08:00
1 parent 741783445d
commit f456ddd979
13 files changed
+230 -87

No files matched your search

+14
View File
@@ -984,6 +984,17 @@ export const useConnectionStore = defineStore("connection", () => {
return isSchemaAware(getConfig(connectionId)?.db_type);
}
const COMPLETION_CACHE_MAX = 50;
function evictOldestCacheEntries(cache: Record<string, unknown>, max: number) {
const keys = Object.keys(cache);
if (keys.length <= max) return;
const toRemove = keys.slice(0, keys.length - max);
for (const key of toRemove) {
delete cache[key];
}
}
async function listCompletionTables(connectionId: string, database: string): Promise<SqlCompletionTable[]> {
const cacheKey = `${connectionId}:${database}`;
if (completionTablesCache.value[cacheKey]) {
@@ -1009,6 +1020,7 @@ export const useConnectionStore = defineStore("connection", () => {
}),
);
completionTablesCache.value[cacheKey] = tableGroups.flat();
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
return completionTablesCache.value[cacheKey];
}
@@ -1017,6 +1029,7 @@ export const useConnectionStore = defineStore("connection", () => {
name: table.name,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
}));
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
return completionTablesCache.value[cacheKey];
}
@@ -1031,6 +1044,7 @@ export const useConnectionStore = defineStore("connection", () => {
await ensureConnected(connectionId);
const querySchema = schema || database;
completionColumnsCache.value[cacheKey] = await api.getColumns(connectionId, database, querySchema, table);
evictOldestCacheEntries(completionColumnsCache.value, COMPLETION_CACHE_MAX);
}
return completionColumnsCache.value[cacheKey].map((column) => ({