feat(export): configurable CSV field quoting mode

This commit is contained in:
zipg authored and GitHub committed 2026-09-03 17:51:50 +08:00
1 parent d421bd33fc
commit 203eab3dec
32 files changed
+442 -56

No files matched your search

@@ -90,6 +90,7 @@ import {
type TabPlacement,
type TabSortMode,
type UpdateDownloadSource,
type CsvQuoteMode,
type CustomThemeColors,
type CustomTheme,
type McpConnectionPolicy,
@@ -548,6 +549,7 @@ const editSidebarShowTooltips = ref(settingsStore.editorSettings.sidebarShowTool
const editSidebarIndent = ref(settingsStore.editorSettings.sidebarIndent);
const editSidebarFontSize = ref(settingsStore.editorSettings.sidebarFontSize);
const editExportBatchSize = ref(settingsStore.editorSettings.exportBatchSize);
const editCsvQuoteMode = ref<CsvQuoteMode>(settingsStore.editorSettings.csvQuoteMode);
const editGlobalDateTimeDisplayFormat = ref(settingsStore.editorSettings.globalDateTimeDisplayFormat);
const editGlobalDateTimeExportFormat = ref(settingsStore.editorSettings.globalDateTimeExportFormat);
const editGlobalDateTimeImportFormat = ref(settingsStore.editorSettings.globalDateTimeImportFormat);
@@ -698,6 +700,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
sidebarCopyTableNameIncludeSchema: editSidebarCopyTableNameIncludeSchema.value,
redisKeyTemplates: normalizeRedisKeyTemplates(editRedisKeyTemplates.value),
exportBatchSize: editExportBatchSize.value,
csvQuoteMode: editCsvQuoteMode.value,
globalDateTimeDisplayFormat: editGlobalDateTimeDisplayFormat.value,
globalDateTimeExportFormat: editGlobalDateTimeExportFormat.value,
globalDateTimeImportFormat: editGlobalDateTimeImportFormat.value,
@@ -1102,6 +1105,7 @@ function syncEditorSettingsDraftFromStore() {
editSidebarIndent.value = settingsStore.editorSettings.sidebarIndent;
editSidebarFontSize.value = settingsStore.editorSettings.sidebarFontSize;
editExportBatchSize.value = settingsStore.editorSettings.exportBatchSize;
editCsvQuoteMode.value = settingsStore.editorSettings.csvQuoteMode;
editGlobalDateTimeDisplayFormat.value = settingsStore.editorSettings.globalDateTimeDisplayFormat;
editGlobalDateTimeExportFormat.value = settingsStore.editorSettings.globalDateTimeExportFormat;
editGlobalDateTimeImportFormat.value = settingsStore.editorSettings.globalDateTimeImportFormat;
@@ -1398,6 +1402,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields);
editRedisKeyTemplates.value = normalizeRedisKeyTemplates(DEFAULT_EDITOR_SETTINGS.redisKeyTemplates).join("\n");
editExportBatchSize.value = DEFAULT_EDITOR_SETTINGS.exportBatchSize;
editCsvQuoteMode.value = DEFAULT_EDITOR_SETTINGS.csvQuoteMode;
editGlobalDateTimeDisplayFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeDisplayFormat;
editGlobalDateTimeExportFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeExportFormat;
editGlobalDateTimeImportFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeImportFormat;
@@ -1507,6 +1512,7 @@ function resetAllDefaults() {
editSidebarCopyTableNameIncludeSchema.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameIncludeSchema;
editRedisKeyTemplates.value = normalizeRedisKeyTemplates(DEFAULT_EDITOR_SETTINGS.redisKeyTemplates).join("\n");
editExportBatchSize.value = DEFAULT_EDITOR_SETTINGS.exportBatchSize;
editCsvQuoteMode.value = DEFAULT_EDITOR_SETTINGS.csvQuoteMode;
editGlobalDateTimeDisplayFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeDisplayFormat;
editGlobalDateTimeExportFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeExportFormat;
editGlobalDateTimeImportFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeImportFormat;
@@ -6518,6 +6524,23 @@ onUnmounted(() => {
<div class="text-sm font-medium text-muted-foreground">
{{ t("settings.exportSection") }}
</div>
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 space-y-0.5">
<Label for="csv-quote-mode">{{ t("settings.csvQuoteMode") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.csvQuoteModeDescription") }}
</p>
</div>
<Select v-model="editCsvQuoteMode">
<SelectTrigger id="csv-quote-mode" class="h-8 w-44 shrink-0">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">{{ t("settings.csvQuoteModeAll") }}</SelectItem>
<SelectItem value="necessary">{{ t("settings.csvQuoteModeNecessary") }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label>{{ t("settings.exportBatchSize") }}</Label>
<div class="flex items-center gap-3">
@@ -2156,7 +2156,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "
executePage: (sql) => api.executeQuery(props.connection.id, props.database, sql),
});
if (format === "csv") {
await api.exportQueryResultCsv(filePath, result.columns, result.rows);
await api.exportQueryResultCsv(filePath, result.columns, result.rows, settingsStore.editorSettings.csvQuoteMode);
} else {
const comments = result.columns.map((name) => columnInfos?.find((column) => column.name.toLocaleLowerCase() === name.toLocaleLowerCase())?.comment);
const headerOverrides = buildXlsxHeaderOverrides(result.columns, comments, headerMode);
@@ -2194,6 +2194,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "
tableName: row.name,
filePath,
format,
csvQuoteMode: settingsStore.editorSettings.csvQuoteMode,
columns,
columnComments: format === "xlsx" ? columnComments : undefined,
autoFilter: format === "xlsx" ? autoFilter : undefined,
@@ -756,7 +756,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
}
outputPath = path as string;
}
await api.exportQueryResultCsv(outputPath, result.columns, rows);
await api.exportQueryResultCsv(outputPath, result.columns, rows, useSettingsStore().editorSettings.csvQuoteMode);
if (needsFullExport && exportProgressState) {
exportProgressState.value = {
...exportProgressState.value,
@@ -795,7 +795,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
outputPath = path as string;
}
const result = await resultToExport(undefined, undefined, false);
await api.exportQueryResultCsv(outputPath, result.columns, result.rows);
await api.exportQueryResultCsv(outputPath, result.columns, result.rows, useSettingsStore().editorSettings.csvQuoteMode);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
@@ -1154,6 +1154,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
tableName: meta.tableName,
filePath: outputPath,
format,
csvQuoteMode: editorSettings.csvQuoteMode,
columns: columns.value,
columnTypes: columnTypes.value,
columnComments: format === "xlsx" ? buildXlsxHeaderOverrides(columns.value, visibleXlsxColumnComments.value, headerMode) : undefined,
@@ -1227,7 +1228,14 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
const columnComments = format === "xlsx" ? buildXlsxHeaderOverrides(allColumns.value, allXlsxColumnComments.value, headerMode) : undefined;
const request = baseRequest
? { ...baseRequest, dateTimeFormat: useSettingsStore().editorSettings.globalDateTimeExportFormat || undefined, numericColumnRightAlign: useSettingsStore().editorSettings.numericColumnRightAlign ?? true, columnComments, autoFilter: format === "xlsx" ? autoFilter : undefined }
? {
...baseRequest,
csvQuoteMode: useSettingsStore().editorSettings.csvQuoteMode,
dateTimeFormat: useSettingsStore().editorSettings.globalDateTimeExportFormat || undefined,
numericColumnRightAlign: useSettingsStore().editorSettings.numericColumnRightAlign ?? true,
columnComments,
autoFilter: format === "xlsx" ? autoFilter : undefined,
}
: undefined;
if (!request) throw new Error("Unable to build query result export request");
@@ -18,6 +18,7 @@ import { fetchTableDataForExport } from "@/lib/table/tableDataExport";
import XlsxHeaderDialog from "@/components/export/XlsxHeaderDialog.vue";
import { buildXlsxHeaderOverrides, hasXlsxHeaderComments, type XlsxExportOptions, type XlsxHeaderMode } from "@/lib/export/xlsxHeader";
import { isLoadingStructurePreview, showStructureDocCopyDialog, showStructurePreviewDialog, structureDocCopyText, structureDocCopyTitle, structurePreviewDefaultFileName, structurePreviewError, structurePreviewSql, structurePreviewTitle } from "@/components/sidebar/sidebarTreeDialogState";
import type { CsvQuoteMode } from "@/lib/export/csvQuoteMode";
type StructureCopyFormat = "tsv" | "markdown";
@@ -42,6 +43,7 @@ interface SidebarTableExportTarget {
identifierQuote?: string;
batchSize: number;
rowLimit: number | null;
csvQuoteMode: CsvQuoteMode;
fileNameBase?: string;
}
@@ -360,6 +362,7 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt
identifierQuote: connectionStore.connectionIdentifierQuote(connectionId),
batchSize: editorSettings.exportBatchSize,
rowLimit: editorSettings.exportRowLimitEnabled ? editorSettings.exportRowLimit : null,
csvQuoteMode: editorSettings.csvQuoteMode,
};
}
@@ -395,7 +398,7 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt
executePage: (sql) => api.executeQuery(connectionId, database, sql),
});
if (format === "csv") {
await api.exportQueryResultCsv(outputPath, result.columns, result.rows);
await api.exportQueryResultCsv(outputPath, result.columns, result.rows, target.csvQuoteMode);
} else {
const comments = result.columns.map((name) => exportColumnInfos?.find((column) => column.name.toLocaleLowerCase() === name.toLocaleLowerCase())?.comment);
const headerOverrides = buildXlsxHeaderOverrides(result.columns, comments, headerMode);
@@ -424,6 +427,7 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt
tableName: target.tableName,
filePath: outputPath,
format,
csvQuoteMode: target.csvQuoteMode,
columns: queryColumns,
columnComments,
autoFilter: format === "xlsx" ? autoFilter : undefined,
+4
View File
@@ -6855,6 +6855,10 @@ export default {
queryExportKeysetOptimizationEnabled: "Try keyset optimization",
queryExportKeysetOptimizationEnabledDescription: "Only applies to safely recognized single-table queries; complex queries automatically fall back.",
exportSection: "Export",
csvQuoteMode: "CSV field quotes",
csvQuoteModeDescription: "Choose how CSV fields use double quotes.",
csvQuoteModeAll: "All fields",
csvQuoteModeNecessary: "Only when needed",
performanceSection: "Performance",
shortcutExecuteSql: "Execute SQL",
shortcutExecuteSqlInNewResultTab: "Execute SQL in new result tab",
+4
View File
@@ -6472,6 +6472,10 @@ export default withEnglishFallback({
queryExportKeysetOptimizationEnabled: "Intentar optimización keyset",
queryExportKeysetOptimizationEnabledDescription: "Solo aplica a consultas de una sola tabla reconocidas de forma segura; las consultas complejas usan el método estándar automáticamente.",
exportSection: "Exportar",
csvQuoteMode: "Comillas de campos CSV",
csvQuoteModeDescription: "Elige cómo usar comillas dobles en los campos CSV.",
csvQuoteModeAll: "Todos los campos",
csvQuoteModeNecessary: "Solo cuando sea necesario",
performanceSection: "Rendimiento",
shortcutExecuteSql: "Ejecutar SQL",
shortcutExecuteSqlInNewResultTab: "Ejecutar SQL en una nueva pestaña de resultados",
+4
View File
@@ -6472,6 +6472,10 @@ export default withEnglishFallback({
queryExportKeysetOptimizationEnabled: "Prova ottimizzazione keyset",
queryExportKeysetOptimizationEnabledDescription: "Si applica solo a query a tabella singola riconosciute in sicurezza; le query complesse ricadono automaticamente.",
exportSection: "Esportazione",
csvQuoteMode: "Virgolette campi CSV",
csvQuoteModeDescription: "Scegli come usare le virgolette doppie nei campi CSV.",
csvQuoteModeAll: "Tutti i campi",
csvQuoteModeNecessary: "Solo quando necessario",
performanceSection: "Prestazioni",
shortcutExecuteSql: "Esegui SQL",
shortcutExecuteSqlInNewResultTab: "Esegui SQL in una nuova scheda dei risultati",
+4
View File
@@ -6480,6 +6480,10 @@ export default withEnglishFallback({
exportBatchSize: "エクスポートバッチサイズ",
exportBatchSizeDescription: "テーブルデータエクスポート時のバッチあたりの取得行数(100〜100000)。",
exportSection: "エクスポート",
csvQuoteMode: "CSV フィールドの引用符",
csvQuoteModeDescription: "CSV フィールドを二重引用符で囲む方法を選択します。",
csvQuoteModeAll: "すべてのフィールド",
csvQuoteModeNecessary: "必要な場合のみ",
performanceSection: "パフォーマンス",
shortcutExecuteSql: "SQLを実行",
shortcutExecuteSqlInNewResultTab: "新しい結果タブでSQLを実行",
+4
View File
@@ -6631,6 +6631,10 @@ export default withEnglishFallback({
shortcutUppercaseSelection: "선택 영역을 대문자로 변환",
shortcutLowercaseSelection: "선택 영역을 소문자로 변환",
shortcutExPasteSqlInCondition: "ExPaste: IN 조건으로 붙여넣기",
csvQuoteMode: "CSV 필드 따옴표",
csvQuoteModeDescription: "CSV 내보내기 시 필드에 큰따옴표를 사용하는 방식을 선택합니다.",
csvQuoteModeAll: "모든 필드",
csvQuoteModeNecessary: "필수 필드만",
},
driverStore: {
jreDirRemoveFailed: "이전 JRE 디렉터리 제거 실패: {path} (원래 오류: {error})",
+4
View File
@@ -6474,6 +6474,10 @@ export default withEnglishFallback({
queryExportKeysetOptimizationEnabled: "Tentar otimização keyset",
queryExportKeysetOptimizationEnabledDescription: "Aplica-se apenas a consultas de tabela única reconhecidas com segurança; consultas complexas recuam automaticamente.",
exportSection: "Exportar",
csvQuoteMode: "Aspas dos campos CSV",
csvQuoteModeDescription: "Escolha como usar aspas duplas nos campos CSV.",
csvQuoteModeAll: "Todos os campos",
csvQuoteModeNecessary: "Somente quando necessário",
performanceSection: "Desempenho",
shortcutExecuteSql: "Executar SQL",
shortcutExecuteSqlInNewResultTab: "Executar SQL em uma nova aba de resultados",
+4
View File
@@ -6836,6 +6836,10 @@ export default withEnglishFallback({
queryExportKeysetOptimizationEnabled: "尝试 keyset 优化",
queryExportKeysetOptimizationEnabledDescription: "仅对可安全识别的单表查询生效,复杂查询会自动回退。",
exportSection: "导出",
csvQuoteMode: "CSV 字段引号",
csvQuoteModeDescription: "选择 CSV 导出时字段使用双引号的方式。",
csvQuoteModeAll: "全部字段",
csvQuoteModeNecessary: "仅必要字段",
performanceSection: "性能",
shortcutExecuteSql: "执行 SQL",
shortcutExecuteSqlInNewResultTab: "在新结果标签页中执行 SQL",
+4
View File
@@ -6190,6 +6190,10 @@ export default withEnglishFallback({
updateDownloadSourceOfficial: "官方來源(建議)",
updateDownloadSourceCnb: "CNB",
exportSection: "匯出",
csvQuoteMode: "CSV 欄位引號",
csvQuoteModeDescription: "選擇 CSV 匯出時欄位使用雙引號的方式。",
csvQuoteModeAll: "全部欄位",
csvQuoteModeNecessary: "僅必要欄位",
performanceSection: "效能",
exportBatchSize: "匯出批次大小",
dateTimeSection: "日期與時間",
+3 -2
View File
@@ -153,6 +153,7 @@ import type {
ElasticsearchDeleteByQueryResult,
} from "@/lib/backend/tauri";
import type { QueryEditability } from "@/lib/sql/sqlAnalysis";
import type { CsvQuoteMode } from "@/lib/export/csvQuoteMode";
import { isTerminalTransferProgress } from "@/lib/backend/transferProgress";
import type {
DataGridColumnDistinctValuesSqlOptions,
@@ -2595,9 +2596,9 @@ export async function cancelQueryResultExport(exportId: string, executionId?: st
});
}
export async function exportQueryResultCsv(filePath: string, columns: string[], rows: readonly (readonly XlsxCellValue[])[]): Promise<void> {
export async function exportQueryResultCsv(filePath: string, columns: string[], rows: readonly (readonly XlsxCellValue[])[], csvQuoteMode: CsvQuoteMode = "all"): Promise<void> {
const { formatCsv } = await import("@/lib/export/exportFormats");
const content = formatCsv(columns, rows as (string | number | boolean | null)[][]);
const content = formatCsv(columns, rows as (string | number | boolean | null)[][], csvQuoteMode);
const fileName = filePath.split(/[\\/]/).pop() || "export.csv";
const blob = new Blob(["\uFEFF", content], {
type: "text/csv;charset=utf-8",
+6 -1
View File
@@ -8,6 +8,7 @@ import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
import { decodeMeilisearchDocumentPage, decodeMeilisearchSearchResult, type MeilisearchDocumentPage, type MeilisearchDocumentPageWire, type MeilisearchSearchResult, type MeilisearchSearchWireResult } from "@/lib/backend/meilisearchTransport";
import type { XuguTablespaceInfo } from "@/types/database";
import type { CreatedKey, EnqueuedTaskSummary, KeyCreateInput, KeyListItem, KeyPage, KeyUpdateInput, MeilisearchSystemOverview, MeilisearchTask, TaskListInput, TaskPage, TaskSelector } from "@/types/meilisearchManagement";
import type { CsvQuoteMode } from "@/lib/export/csvQuoteMode";
/** Normalize Tauri rejections once at the public backend boundary. */
async function invokeBackend<T>(command: string, args?: Record<string, unknown>): Promise<T> {
@@ -4839,6 +4840,7 @@ export interface TableExportRequest {
tableName: string;
filePath: string;
format: "csv" | "xlsx" | "json" | "markdown" | "sql" | "txt";
csvQuoteMode?: CsvQuoteMode;
columns?: string[];
columnTypes?: Array<string | null | undefined>;
columnComments?: Array<string | null> | null;
@@ -4862,6 +4864,7 @@ export interface TableCsvExportOptions {
columns?: string[];
pageSize?: number;
timeoutSecs?: number;
csvQuoteMode?: CsvQuoteMode;
}
export interface TableExportProgress {
@@ -4886,6 +4889,7 @@ export interface QueryResultExportRequest {
useAgentCursor: boolean;
filePath: string;
format: "csv" | "xlsx" | "txt" | "sql";
csvQuoteMode?: CsvQuoteMode;
includeSqlSheet?: boolean;
pageSize: number;
rowLimit?: number | null;
@@ -5029,12 +5033,13 @@ export async function recordDatabaseExportDestination(directory: string): Promis
await invoke("record_database_export_destination", { directory });
}
export async function exportQueryResultCsv(filePath: string, columns: string[], rows: readonly (readonly XlsxCellValue[])[]): Promise<void> {
export async function exportQueryResultCsv(filePath: string, columns: string[], rows: readonly (readonly XlsxCellValue[])[], csvQuoteMode: CsvQuoteMode = "all"): Promise<void> {
return invoke("export_query_result_csv", {
request: {
filePath,
columns,
rows,
csvQuoteMode,
},
});
}
@@ -0,0 +1,16 @@
export type CsvQuoteMode = "all" | "necessary";
export const DEFAULT_CSV_QUOTE_MODE: CsvQuoteMode = "all";
export function normalizeCsvQuoteMode(value: unknown): CsvQuoteMode {
return value === "necessary" ? "necessary" : DEFAULT_CSV_QUOTE_MODE;
}
export function csvFieldNeedsQuotes(value: string): boolean {
return /[",\r\n]/.test(value);
}
export function escapeCsvField(value: string, quoteMode: CsvQuoteMode): string {
if (quoteMode === "necessary" && !csvFieldNeedsQuotes(value)) return value;
return `"${value.replace(/"/g, '""')}"`;
}
+4 -4
View File
@@ -1,12 +1,12 @@
import type { DatabaseType, QueryResult } from "@/types/database";
import * as api from "@/lib/backend/api";
import { escapeCsvField, type CsvQuoteMode } from "@/lib/export/csvQuoteMode";
export type ExportCellValue = string | number | boolean | null;
export function formatCsv(columns: string[], rows: ExportCellValue[][]): string {
const esc = (v: string) => `"${v.replace(/"/g, '""')}"`;
const header = columns.map(esc).join(",");
const body = rows.map((row) => row.map((c) => (c === null ? "" : esc(String(c)))).join(",")).join("\n");
export function formatCsv(columns: string[], rows: ExportCellValue[][], quoteMode: CsvQuoteMode = "all"): string {
const header = columns.map((column) => escapeCsvField(column, quoteMode)).join(",");
const body = rows.map((row) => row.map((cell) => (cell === null ? "" : escapeCsvField(String(cell), quoteMode))).join(",")).join("\n");
return `${header}\n${body}`;
}
@@ -88,6 +88,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"sidebarCopyTableNameIncludeSchema",
"redisKeyTemplates",
"exportBatchSize",
"csvQuoteMode",
"exportRowLimitEnabled",
"exportRowLimit",
"queryExportKeysetOptimizationEnabled",
+2
View File
@@ -6975,6 +6975,7 @@ export const useQueryStore = defineStore("query", () => {
totalRows,
timeoutSecs: queryTimeoutSecsForConnection(conn, settingsStore.editorSettings.globalQueryTimeoutSecs),
keysetOptimizationEnabled: settings.queryExportKeysetOptimizationEnabled,
csvQuoteMode: settings.csvQuoteMode,
clientSessionId,
executionId: uuid(),
exportTableName: options.exportTableName,
@@ -7012,6 +7013,7 @@ export const useQueryStore = defineStore("query", () => {
totalRows: null,
timeoutSecs: queryTimeoutSecsForConnection(conn, settingsStore.editorSettings.globalQueryTimeoutSecs),
keysetOptimizationEnabled: settings.queryExportKeysetOptimizationEnabled,
csvQuoteMode: settings.csvQuoteMode,
clientSessionId: `${tabClientSessionId(tab, "export")}:${exportId}`,
executionId: uuid(),
numericColumnRightAlign: settings.numericColumnRightAlign,
+6 -1
View File
@@ -26,11 +26,12 @@ import { normalizeSqlVariableSyntaxOverrides, type SqlVariableSyntaxOverrides }
import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateFields } from "@/lib/table/tableColumnTemplates";
import { type DataTabReuseMode, DEFAULT_DATA_TAB_REUSE_MODE, normalizeDataTabReuseMode } from "@/lib/tabs/dataTabReuseMode";
import { normalizeCompletionTriggerMode, type SqlCompletionTriggerMode } from "@/lib/sql/sqlCompletionTriggerPolicy";
import { DEFAULT_CSV_QUOTE_MODE, normalizeCsvQuoteMode, type CsvQuoteMode } from "@/lib/export/csvQuoteMode";
import { configureMetadataRuntimeCache, METADATA_CACHE_DEFAULT_MEMORY_MB, normalizeMetadataCacheMemoryMb } from "@/lib/metadata/metadataRuntimeCache";
import type { AiApiStyle, AiAssistantMode, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiModelEffortPreference, AiProvider, AiReasoningLevel, AiTestConnectionResult } from "@/types/ai";
import type { SqlShortcutAction, SqlSnippet, TableInfoTab } from "@/types/database";
export type { AiApiStyle, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiProvider, AiReasoningLevel, AiTestConnectionResult, DataTabReuseMode, SavedSqlOpenTargetMode, SqlCompletionTriggerMode };
export type { AiApiStyle, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiProvider, AiReasoningLevel, AiTestConnectionResult, CsvQuoteMode, DataTabReuseMode, SavedSqlOpenTargetMode, SqlCompletionTriggerMode };
export interface DesktopSettings {
show_tray_icon: boolean;
@@ -789,6 +790,7 @@ export interface EditorSettings {
sqlShortcuts: SqlShortcutAction[];
tableColumnTemplateFields: string[];
exportBatchSize: number;
csvQuoteMode: CsvQuoteMode;
/** Global Redis key-search templates; overridden by non-empty connection templates. */
redisKeyTemplates: string[];
exportRowLimitEnabled: boolean;
@@ -1014,6 +1016,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
sqlShortcuts: [],
tableColumnTemplateFields: [...DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS],
exportBatchSize: 2000,
csvQuoteMode: DEFAULT_CSV_QUOTE_MODE,
redisKeyTemplates: [],
exportRowLimitEnabled: false,
exportRowLimit: 100000,
@@ -1484,6 +1487,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
sqlShortcuts: normalizeSqlShortcuts(settings.sqlShortcuts, existing?.sqlShortcuts),
tableColumnTemplateFields: normalizeTableColumnTemplateFields(settings.tableColumnTemplateFields),
exportBatchSize: typeof settings.exportBatchSize === "number" && settings.exportBatchSize >= 100 && settings.exportBatchSize <= 100000 ? Math.round(settings.exportBatchSize) : DEFAULT_EDITOR_SETTINGS.exportBatchSize,
csvQuoteMode: normalizeCsvQuoteMode(settings.csvQuoteMode),
redisKeyTemplates: normalizeRedisKeyTemplates(settings.redisKeyTemplates),
exportRowLimitEnabled: typeof settings.exportRowLimitEnabled === "boolean" ? settings.exportRowLimitEnabled : DEFAULT_EDITOR_SETTINGS.exportRowLimitEnabled,
exportRowLimit: typeof settings.exportRowLimit === "number" && settings.exportRowLimit >= 100 && settings.exportRowLimit <= 2147483647 ? Math.round(settings.exportRowLimit) : DEFAULT_EDITOR_SETTINGS.exportRowLimit,
@@ -2151,6 +2155,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.sqlShortcuts !== undefined) editorSettings.value.sqlShortcuts = normalizeSqlShortcuts(partial.sqlShortcuts);
if (partial.tableColumnTemplateFields !== undefined) editorSettings.value.tableColumnTemplateFields = normalizeTableColumnTemplateFields(partial.tableColumnTemplateFields);
if (partial.exportBatchSize !== undefined) editorSettings.value.exportBatchSize = Math.min(100000, Math.max(100, Math.round(partial.exportBatchSize)));
if (partial.csvQuoteMode !== undefined) editorSettings.value.csvQuoteMode = normalizeCsvQuoteMode(partial.csvQuoteMode);
if (partial.redisKeyTemplates !== undefined) editorSettings.value.redisKeyTemplates = normalizeRedisKeyTemplates(partial.redisKeyTemplates);
if (partial.exportRowLimitEnabled !== undefined) editorSettings.value.exportRowLimitEnabled = partial.exportRowLimitEnabled;
if (partial.exportRowLimit !== undefined) editorSettings.value.exportRowLimit = Math.min(2147483647, Math.max(100, Math.round(partial.exportRowLimit)));