From dfd4d3b7ff375f0cef3d398ddbacba4e733db005 Mon Sep 17 00:00:00 2001 From: dadong <102886401+mr-dadong@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:17:41 +0800 Subject: [PATCH] feat(import): add SQL script import with dialect-aware parsing --- .../components/import/TableImportDialog.vue | 37 +- apps/desktop/src/i18n/locales/en.ts | 2 + apps/desktop/src/i18n/locales/es.ts | 2 + apps/desktop/src/i18n/locales/it.ts | 2 + apps/desktop/src/i18n/locales/ja.ts | 2 + apps/desktop/src/i18n/locales/ko.ts | 2 + apps/desktop/src/i18n/locales/pt-BR.ts | 2 + apps/desktop/src/i18n/locales/zh-CN.ts | 2 + apps/desktop/src/i18n/locales/zh-TW.ts | 2 + .../lib/__tests__/table/tableImport.spec.ts | 20 + apps/desktop/src/lib/backend/tauri.ts | 3 +- apps/desktop/src/lib/table/tableImport.ts | 7 +- crates/dbx-core/src/table_import.rs | 720 ++++++++++++++++++ docs/content/docs/table-import.cn.mdx | 9 +- docs/content/docs/table-import.mdx | 9 +- 15 files changed, 809 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/components/import/TableImportDialog.vue b/apps/desktop/src/components/import/TableImportDialog.vue index 70b3507aa7..73cef8bcd8 100644 --- a/apps/desktop/src/components/import/TableImportDialog.vue +++ b/apps/desktop/src/components/import/TableImportDialog.vue @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { AlertTriangle, ArrowLeft, ArrowRight, Check, CheckCircle2, FileJson, FileSpreadsheet, FileText, FileUp, Loader2, RefreshCw, Square, Upload, X } from "@lucide/vue"; +import { AlertTriangle, ArrowLeft, ArrowRight, Check, CheckCircle2, FileCode, FileJson, FileSpreadsheet, FileText, FileUp, Loader2, RefreshCw, Square, Upload, X } from "@lucide/vue"; import { useConnectionStore } from "@/stores/connectionStore"; import { useSettingsStore } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; @@ -109,6 +109,7 @@ const formatOptions: Array<{ value: api.TableImportSourceFormat; icon: any; labe { value: "delimited", icon: FileText, labelKey: "tableImport.formatDelimited", descriptionKey: "tableImport.formatDelimitedDescription" }, { value: "json", icon: FileJson, labelKey: "tableImport.formatJson", descriptionKey: "tableImport.formatJsonDescription" }, { value: "excel", icon: FileSpreadsheet, labelKey: "tableImport.formatExcel", descriptionKey: "tableImport.formatExcelDescription" }, + { value: "sql", icon: FileCode, labelKey: "tableImport.formatSql", descriptionKey: "tableImport.formatSqlDescription" }, ]; const encodingOptions: Array<{ value: api.TableImportTextEncoding; labelKey: string }> = [ @@ -275,6 +276,7 @@ function detectFormat(name: string): api.TableImportSourceFormat { if (lower.endsWith(".txt")) return "delimited"; if (lower.endsWith(".json")) return "json"; if (lower.endsWith(".xls") || lower.endsWith(".xlsx") || lower.endsWith(".xlsm")) return "excel"; + if (lower.endsWith(".sql")) return "sql"; return "csv"; } @@ -317,12 +319,14 @@ function taskParseOptions(format: api.TableImportSourceFormat, sheetName = ""): emptyStringAsNull: emptyStringAsNull.value, sheetName, jsonShape: jsonShape.value, + databaseType: structureDatabaseType.value, }); } function importParseOptions(format: api.TableImportSourceFormat, currentPreview: api.TableImportPreview, sheetName = ""): api.TableImportParseOptions { const options = taskParseOptions(format, sheetName); - if (isDelimitedFormat(format) && currentPreview.totalRowsExact !== false && options.encoding === "auto" && currentPreview.effectiveEncoding) { + // SQL 脚本与分隔文本一样依赖文本编码检测,导入时沿用预览阶段确定的编码避免二次检测结果不一致 + if ((isDelimitedFormat(format) || format === "sql") && currentPreview.totalRowsExact !== false && options.encoding === "auto" && currentPreview.effectiveEncoding) { options.encoding = currentPreview.effectiveEncoding; } return options; @@ -615,10 +619,11 @@ async function selectFile() { const selected = await open({ multiple: targetMode.value === "create", filters: [ - { name: "Data files", extensions: ["csv", "tsv", "txt", "json", "xlsx", "xlsm", "xls"] }, + { name: "Data files", extensions: ["csv", "tsv", "txt", "json", "xlsx", "xlsm", "xls", "sql"] }, { name: "Text", extensions: ["csv", "tsv", "txt"] }, { name: "JSON", extensions: ["json"] }, { name: "Excel", extensions: ["xlsx", "xlsm", "xls"] }, + { name: "SQL", extensions: ["sql"] }, ], }); if (!selected) return; @@ -939,7 +944,8 @@ async function reloadBatchPreviewsForEncoding() { errorMessage.value = ""; try { for (const task of batchTasks.value) { - if (!isDelimitedFormat(task.format)) continue; + // SQL 脚本同样是文本源,编码变化时需要重新预览 + if (!isDelimitedFormat(task.format) && task.format !== "sql") continue; const reusableSource = task.preview.sourceRef ? task.preview.filePath : task.source; const nextPreview = await api.previewTableImportFile(reusableSource, { sourceRef: task.preview.sourceRef || null, @@ -1018,7 +1024,7 @@ watch(rawProgressPercent, (percent) => {