feat(export): split large sql exports into zip parts

This commit is contained in:
КГБ Lieu authored and GitHub committed 2026-09-11 15:41:28 +08:00
1 parent 339c96cc81
commit 16866293a3
30 files changed
+933 -57

No files matched your search

@@ -68,6 +68,10 @@ const includeObjects = ref(true);
const includeCreateDatabase = ref(false);
const dropTableIfExists = ref(false);
const omitAutoIncrement = ref(false);
const splitSqlOutput = ref(false);
const splitSqlPartMaxMb = ref(100);
const MIN_SPLIT_SQL_PART_MB = 1;
const MAX_SPLIT_SQL_PART_MB = 4096;
// `AUTO_INCREMENT` stripping is a MySQL-only DDL transform (backend gates on
// db_type == mysql, which also covers MariaDB / TiDB / OceanBase-MySQL-mode).
const isMysqlFamily = computed(() => store.getConfig(connectionId.value)?.db_type === "mysql");
@@ -161,6 +165,12 @@ function joinExportPath(directory: string, fileName: string): string {
return `${directory.replace(/[\\/]+$/, "")}${separator}${fileName}`;
}
function normalizedSplitSqlPartMaxMb(): number {
const value = Number(splitSqlPartMaxMb.value);
if (!Number.isFinite(value)) return 100;
return Math.min(MAX_SPLIT_SQL_PART_MB, Math.max(MIN_SPLIT_SQL_PART_MB, Math.round(value)));
}
// Lenient exports write per-object failures into the SQL file as `-- ERROR`
// comments and still finish; completion must warn instead of reporting plain
// success (#8184).
@@ -329,8 +339,8 @@ async function startExport() {
const { save } = await import("@tauri-apps/plugin-dialog");
const safeName = sanitizeFileName(database.value || "database");
const path = await save({
defaultPath: `${safeName}.sql`,
filters: [{ name: "SQL", extensions: ["sql"] }],
defaultPath: `${safeName}.${splitSqlOutput.value ? "zip" : "sql"}`,
filters: [{ name: splitSqlOutput.value ? "ZIP" : "SQL", extensions: [splitSqlOutput.value ? "zip" : "sql"] }],
});
if (!path) return;
filePath = path;
@@ -392,6 +402,7 @@ async function startExport() {
omitAutoIncrement: omitAutoIncrement.value,
snapshotSessionId,
batchSize: 1000,
splitMaxMb: splitSqlOutput.value ? normalizedSplitSqlPartMaxMb() : undefined,
};
return runDatabaseExportUntilTerminal(request, (progress) => {
exportProgress.value = { ...progress };
@@ -508,7 +519,7 @@ async function startAllDatabasesExport() {
batchDatabaseIndex.value = index + 1;
const currentExportId = `${batchId}-${index + 1}`;
activeDatabaseExportId.value = currentExportId;
const filePath = isTauriRuntime() ? joinExportPath(directoryPath, `${sanitizeFileName(item.fileStem)}.sql`) : `__web_export_${currentExportId}.sql`;
const filePath = isTauriRuntime() ? joinExportPath(directoryPath, `${sanitizeFileName(item.fileStem)}.${splitSqlOutput.value ? "zip" : "sql"}`) : `__web_export_${currentExportId}.${splitSqlOutput.value ? "zip" : "sql"}`;
let currentDatabaseRowsExported = 0;
const terminal = await runWithDatabaseBackupSnapshot(
@@ -533,6 +544,7 @@ async function startAllDatabasesExport() {
omitAutoIncrement: omitAutoIncrement.value,
snapshotSessionId,
batchSize: 1000,
splitMaxMb: splitSqlOutput.value ? normalizedSplitSqlPartMaxMb() : undefined,
},
(progress) => {
const nextRowsExported = Math.max(0, progress.rowsExported);
@@ -651,6 +663,8 @@ function resetState() {
includeCreateDatabase.value = false;
dropTableIfExists.value = false;
omitAutoIncrement.value = false;
splitSqlOutput.value = false;
splitSqlPartMaxMb.value = 100;
isExporting.value = false;
exportProgress.value = null;
exportDone.value = false;
@@ -938,6 +952,27 @@ watch(
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
{{ t("databaseExport.includeObjects") }}
</div>
<div class="flex items-center justify-between gap-2 text-xs">
<button type="button" class="flex min-w-0 items-center gap-2 text-left" @click="splitSqlOutput = !splitSqlOutput">
<CheckSquare v-if="splitSqlOutput" class="w-3.5 h-3.5 text-primary shrink-0" />
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
<span>{{ t("databaseExport.splitSqlOutput") }}</span>
</button>
<Input
v-if="splitSqlOutput"
v-model.number="splitSqlPartMaxMb"
type="number"
inputmode="numeric"
:min="MIN_SPLIT_SQL_PART_MB"
:max="MAX_SPLIT_SQL_PART_MB"
class="h-7 w-24 text-xs"
:aria-label="t('databaseExport.splitSqlPartMaxMb')"
@blur="splitSqlPartMaxMb = normalizedSplitSqlPartMaxMb()"
/>
</div>
<p v-if="splitSqlOutput" class="pl-5 text-[11px] text-muted-foreground">
{{ t("databaseExport.splitSqlOutputDescription", { min: MIN_SPLIT_SQL_PART_MB, max: MAX_SPLIT_SQL_PART_MB }) }}
</p>
</div>
</div>
@@ -3,22 +3,36 @@ import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import type { SqlInsertMode } from "@/lib/export/sqlInsertMode";
import type { SqlExportOptions, SqlInsertMode } from "@/lib/export/sqlInsertMode";
const { t } = useI18n();
const open = defineModel<boolean>("open", { default: false });
const props = defineProps<{ allowSplit?: boolean }>();
const selected = ref<SqlInsertMode>("batch");
const splitSqlOutput = ref(false);
const splitSqlPartMaxMb = ref(100);
const MIN_SPLIT_SQL_PART_MB = 1;
const MAX_SPLIT_SQL_PART_MB = 4096;
let outcomeEmitted = false;
const emit = defineEmits<{
confirm: [mode: SqlInsertMode];
confirm: [options: SqlExportOptions];
cancel: [];
}>();
function normalizedSplitSqlPartMaxMb(): number {
const value = Number(splitSqlPartMaxMb.value);
if (!Number.isFinite(value)) return 100;
return Math.min(MAX_SPLIT_SQL_PART_MB, Math.max(MIN_SPLIT_SQL_PART_MB, Math.round(value)));
}
function onConfirm() {
outcomeEmitted = true;
open.value = false;
emit("confirm", selected.value);
emit("confirm", {
insertMode: selected.value,
splitMaxMb: props.allowSplit && splitSqlOutput.value ? normalizedSplitSqlPartMaxMb() : undefined,
});
}
function onCancel() {
@@ -55,6 +69,19 @@ function onOpenChange(value: boolean) {
<span class="mt-1 block text-xs text-muted-foreground">{{ t("grid.sqlInsertModeSingleDescription") }}</span>
</span>
</label>
<div v-if="props.allowSplit" class="space-y-2 rounded-md border p-3">
<label class="flex cursor-pointer items-center gap-2 text-sm">
<input v-model="splitSqlOutput" type="checkbox" class="h-4 w-4" data-sql-split-output />
{{ t("databaseExport.splitSqlOutput") }}
</label>
<div v-if="splitSqlOutput" class="flex items-center justify-between gap-3 pl-6">
<span class="text-xs text-muted-foreground">{{ t("databaseExport.splitSqlPartMaxMb") }}</span>
<input v-model.number="splitSqlPartMaxMb" type="number" :min="MIN_SPLIT_SQL_PART_MB" :max="MAX_SPLIT_SQL_PART_MB" class="h-8 w-24 rounded-md border bg-background px-2 text-sm" data-sql-split-max-mb />
</div>
<p v-if="splitSqlOutput" class="pl-6 text-xs text-muted-foreground">
{{ t("databaseExport.splitSqlOutputDescription", { min: MIN_SPLIT_SQL_PART_MB, max: MAX_SPLIT_SQL_PART_MB }) }}
</p>
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="onCancel">{{ t("common.cancel") }}</Button>
@@ -37,13 +37,13 @@ afterEach(() => {
i18n.global.locale.value = "en";
});
async function mountDialog(onConfirm = () => {}, onCancel = () => {}) {
async function mountDialog(onConfirm = () => {}, onCancel = () => {}, allowSplit = false) {
const container = document.createElement("div");
document.body.appendChild(container);
const app = createApp(
defineComponent({
setup() {
return () => h(SqlInsertModeDialog, { open: true, onConfirm, onCancel });
return () => h(SqlInsertModeDialog, { open: true, allowSplit, onConfirm, onCancel });
},
}),
);
@@ -68,7 +68,24 @@ describe("SqlInsertModeDialog", () => {
document.querySelector<HTMLButtonElement>("[data-sql-insert-mode-confirm]")?.click();
expect(onConfirm).toHaveBeenCalledOnce();
expect(onConfirm).toHaveBeenCalledWith("single");
expect(onConfirm).toHaveBeenCalledWith({ insertMode: "single", splitMaxMb: undefined });
});
it("enables split ZIP output only when the caller supports it", async () => {
const onConfirm = vi.fn();
await mountDialog(onConfirm, () => {}, true);
const split = document.querySelector<HTMLInputElement>("input[data-sql-split-output]");
expect(split).not.toBeNull();
split?.click();
await nextTick();
const size = document.querySelector<HTMLInputElement>("input[data-sql-split-max-mb]");
expect(size?.value).toBe("100");
if (size) size.value = "256";
size?.dispatchEvent(new Event("input", { bubbles: true }));
document.querySelector<HTMLButtonElement>("[data-sql-insert-mode-confirm]")?.click();
expect(onConfirm).toHaveBeenCalledWith({ insertMode: "batch", splitMaxMb: 256 });
});
it("renders the batch and single-row explanations", async () => {
@@ -2112,9 +2112,9 @@ async function exportData(row: ObjectBrowserRow, format: "csv" | "json" | "sql")
await exportDataLegacy(row, format);
return;
}
const insertMode = format === "sql" ? await showSqlInsertModeDialog() : undefined;
if (format === "sql" && insertMode === null) return;
await exportTableData(row, format, undefined, "name", true, insertMode ?? "batch");
const sqlExportOptions = format === "sql" ? await showSqlInsertModeDialog({ allowSplit: true }) : undefined;
if (format === "sql" && sqlExportOptions === null) return;
await exportTableData(row, format, undefined, "name", true, sqlExportOptions?.insertMode ?? "batch", sqlExportOptions?.splitMaxMb);
}
function showObjectBrowserXlsxHeaderDialog(hasComments: boolean): Promise<XlsxExportOptions | null> {
@@ -2157,17 +2157,18 @@ async function exportDataXlsx(row: ObjectBrowserRow) {
await exportTableData(row, "xlsx", columnInfos, exportOptions.headerMode, exportOptions.autoFilter);
}
async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "sql", columnInfos?: ColumnInfo[], headerMode: XlsxHeaderMode = "name", autoFilter = true, insertMode: SqlInsertMode = "batch") {
async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "sql", columnInfos?: ColumnInfo[], headerMode: XlsxHeaderMode = "name", autoFilter = true, insertMode: SqlInsertMode = "batch", splitMaxMb?: number) {
const schema = row.schema || selectedSchema.value;
const splitSqlOutput = format === "sql" && splitMaxMb !== undefined;
// Save dialog first
let filePath = "";
const defaultName = `${row.name}.${format}`;
const defaultName = `${row.name}.${splitSqlOutput ? "zip" : format}`;
if (isTauriRuntime()) {
try {
const { save } = await import("@tauri-apps/plugin-dialog");
const filter = format === "csv" ? { name: "CSV", extensions: ["csv"] } : format === "xlsx" ? { name: "Excel", extensions: ["xlsx"] } : { name: "SQL", extensions: ["sql"] };
const filter = format === "csv" ? { name: "CSV", extensions: ["csv"] } : format === "xlsx" ? { name: "Excel", extensions: ["xlsx"] } : splitSqlOutput ? { name: "ZIP", extensions: ["zip"] } : { name: "SQL", extensions: ["sql"] };
const path = await save({
defaultPath: defaultName,
filters: [filter],
@@ -2180,7 +2181,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "
}
} else {
const webExportId = generateDatabaseExportId();
filePath = `__web_export_${webExportId}.${format}`;
filePath = `__web_export_${webExportId}.${splitSqlOutput ? "zip" : format}`;
}
let task: ExportTask | null = null;
@@ -2231,7 +2232,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "
tableName: row.name,
filePath,
format,
...(format === "sql" ? { insertMode } : {}),
...(format === "sql" ? { insertMode, splitMaxMb } : {}),
csvQuoteMode: settingsStore.editorSettings.csvQuoteMode,
columns,
columnComments: format === "xlsx" ? columnComments : undefined,
@@ -30,9 +30,9 @@ describe("ObjectBrowser XLSX export", () => {
const exportData = functionBody("exportData");
const exportTableData = functionBody("exportTableData");
expect(exportData).toContain("await showSqlInsertModeDialog()");
expect(exportData.indexOf("await showSqlInsertModeDialog()")).toBeLessThan(exportData.indexOf("await exportTableData("));
expect(exportTableData).toContain('...(format === "sql" ? { insertMode } : {})');
expect(exportData).toContain("await showSqlInsertModeDialog({ allowSplit: true })");
expect(exportData.indexOf("await showSqlInsertModeDialog({ allowSplit: true })")).toBeLessThan(exportData.indexOf("await exportTableData("));
expect(exportTableData).toContain('...(format === "sql" ? { insertMode, splitMaxMb } : {})');
});
it("falls back to field-name headers when column metadata is unavailable", () => {
@@ -15,7 +15,7 @@ import { copyToClipboard } from "@/lib/common/clipboard";
import { clearDataGridClipboardCopy, rememberDataGridClipboardCopy } from "@/lib/dataGrid/dataGridClipboard";
import { buildDataGridCopyInsertStatement, type DataGridCopyInsertMode, type DataGridTableMeta } from "@/lib/dataGrid/dataGridSql";
import { formatSqlInsert, formatTsv } from "@/lib/export/exportFormats";
import { showSqlInsertModeDialog, type SqlInsertMode } from "@/lib/export/sqlInsertMode";
import { showSqlInsertModeDialog, type SqlExportOptions, type SqlInsertMode } from "@/lib/export/sqlInsertMode";
import { summarizeExportRows } from "@/lib/export/exportDiagnostics";
import { appendDebugLog, appendNativeProcessMemoryLog, getBrowserMemorySnapshot, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
import { uuid } from "@/lib/common/utils";
@@ -1164,7 +1164,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await exportAllResultsXlsxResult(true);
}
async function exportFullTableDataViaBackend(format: "csv" | "xlsx" | "json" | "markdown" | "sql" | "txt", rowIds?: number[], headerMode: XlsxHeaderMode = "name", autoFilter = true, insertMode?: SqlInsertMode): Promise<boolean> {
async function exportFullTableDataViaBackend(format: "csv" | "xlsx" | "json" | "markdown" | "sql" | "txt", rowIds?: number[], headerMode: XlsxHeaderMode = "name", autoFilter = true, sqlExportOptions?: SqlExportOptions): Promise<boolean> {
const meta = tableMeta.value;
// The backend table exporter currently builds two-part table names. External
// Doris/StarRocks catalogs need the data-tab paginator's three-part SQL.
@@ -1173,8 +1173,9 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
}
const fmt = FORMAT_META[format];
const extension = fmt?.ext ?? format;
const filterName = fmt?.label ?? format.toUpperCase();
const splitSqlOutput = format === "sql" && sqlExportOptions?.splitMaxMb !== undefined;
const extension = splitSqlOutput ? "zip" : (fmt?.ext ?? format);
const filterName = splitSqlOutput ? "ZIP" : (fmt?.label ?? format.toUpperCase());
let outputPath = exportFileName(meta.tableName || "export", extension, { preferFallback: true });
if (isTauriRuntime()) {
const { save } = await import("@tauri-apps/plugin-dialog");
@@ -1223,7 +1224,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
tableName: meta.tableName,
filePath: outputPath,
format,
...(format === "sql" && insertMode ? { insertMode } : {}),
...(format === "sql" && sqlExportOptions ? { insertMode: sqlExportOptions.insertMode, splitMaxMb: sqlExportOptions.splitMaxMb } : {}),
csvQuoteMode: editorSettings.csvQuoteMode,
columns: columns.value,
columnTypes: columnTypes.value,
@@ -1409,16 +1410,18 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
},
true,
);
const insertMode = await showSqlInsertModeDialog();
if (insertMode === null) {
const selectedSqlExportOptions = (await showSqlInsertModeDialog({ allowSplit: rowIds === undefined && context.value === "table-data" })) as SqlExportOptions | SqlInsertMode | null;
if (selectedSqlExportOptions === null) {
logExportStage("cancelled", { stage: "insert-mode-dialog" });
return;
}
logExportStage("mode-selected", { insertMode });
const sqlExportOptions = typeof selectedSqlExportOptions === "string" ? { insertMode: selectedSqlExportOptions } : selectedSqlExportOptions;
const insertMode = sqlExportOptions.insertMode;
logExportStage("mode-selected", { insertMode, splitMaxMb: sqlExportOptions.splitMaxMb });
try {
// Step 1: table-data context — existing backend table export
logExportStage("backend-export-start");
const handledByBackend = await exportFullTableDataViaBackend("sql", rowIds, "name", true, insertMode);
const handledByBackend = await exportFullTableDataViaBackend("sql", rowIds, "name", true, sqlExportOptions);
logExportStage("backend-export-finished", { handledByBackend });
if (handledByBackend) {
logExportStage("done", { path: "backend" });
@@ -1490,8 +1493,9 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
async function exportCurrentPageSql() {
await runExclusiveExport(async () => {
const insertMode = await showSqlInsertModeDialog();
if (insertMode === null) return;
const selectedSqlExportOptions = (await showSqlInsertModeDialog()) as SqlExportOptions | SqlInsertMode | null;
if (selectedSqlExportOptions === null) return;
const insertMode = typeof selectedSqlExportOptions === "string" ? selectedSqlExportOptions : selectedSqlExportOptions.insertMode;
try {
const result = await resultToExport(undefined, undefined, false, false);
const exportData = sqlInsertExportData(result);
+3
View File
@@ -7881,6 +7881,9 @@ export default withEnglishFallback({
omitAutoIncrement: "AUTO_INCREMENT parametrini daxil etmə (yeni quraşdırma skriptləri üçün)",
includeData: "Cədvəl məlumatları (INSERT)",
includeObjects: "Görünüşlər / prosedurlar / funksiyalar / ardıcıllıqlar",
splitSqlOutput: "SQL ixracını ZIP arxivinə böl",
splitSqlPartMaxMb: "Hər SQL hissəsi üçün maksimum ölçü (MB)",
splitSqlOutputDescription: "Nömrələnmiş SQL hissələri və manifestdən ibarət ZIP arxivi yaradır. Hissələr tam SQL ifadələri arasında bölünür ({min}–{max} MB).",
databaseSelection: "Verilənlər bazaları",
selectedDatabases: "{selected}/{total} seçilib",
selectAllDatabases: "Hamısını seç",
+3
View File
@@ -7879,6 +7879,9 @@ export default {
omitAutoIncrement: "Omit AUTO_INCREMENT (for fresh-install scripts)",
includeData: "Table data (INSERT)",
includeObjects: "Views / Procedures / Functions / Sequences",
splitSqlOutput: "Split SQL export into a ZIP archive",
splitSqlPartMaxMb: "Maximum size per SQL part (MB)",
splitSqlOutputDescription: "Creates a ZIP archive containing numbered SQL parts and a manifest. Parts are split between complete SQL statements ({min}–{max} MB).",
databaseSelection: "Databases",
selectedDatabases: "{selected}/{total} selected",
selectAllDatabases: "Select all",
+3
View File
@@ -7441,6 +7441,9 @@ export default withEnglishFallback({
omitAutoIncrement: "Omitir AUTO_INCREMENT (para scripts de instalación nueva)",
includeData: "Datos de tablas (INSERT)",
includeObjects: "Vistas / Procedimientos / Funciones / Secuencias",
splitSqlOutput: "Dividir la exportación SQL en un archivo ZIP",
splitSqlPartMaxMb: "Tamaño máximo por parte SQL (MB)",
splitSqlOutputDescription: "Crea un archivo ZIP con partes SQL numeradas y un manifiesto. Las partes se dividen entre sentencias SQL completas ({min}–{max} MB).",
databaseSelection: "Bases de datos",
selectedDatabases: "{selected}/{total} seleccionadas",
selectAllDatabases: "Seleccionar todo",
+3
View File
@@ -7442,6 +7442,9 @@ export default withEnglishFallback({
omitAutoIncrement: "Ometti AUTO_INCREMENT (per script di installazione pulita)",
includeData: "Dati tabella (INSERT)",
includeObjects: "Viste / Procedure / Funzioni / Sequenze",
splitSqlOutput: "Dividere l'esportazione SQL in un archivio ZIP",
splitSqlPartMaxMb: "Dimensione massima per parte SQL (MB)",
splitSqlOutputDescription: "Crea un archivio ZIP con parti SQL numerate e un manifest. Le parti vengono divise tra istruzioni SQL complete ({min}–{max} MB).",
databaseSelection: "Database",
selectedDatabases: "{selected}/{total} selezionati",
selectAllDatabases: "Seleziona tutti",
+3
View File
@@ -7494,6 +7494,9 @@ export default withEnglishFallback({
omitAutoIncrement: "AUTO_INCREMENT を省略(新規インストール用スクリプト向け)",
includeData: "テーブルデータ (INSERT)",
includeObjects: "ビュー / プロシージャ / 関数 / シーケンス",
splitSqlOutput: "SQLエクスポートをZIPアーカイブに分割",
splitSqlPartMaxMb: "SQLパートあたりの最大サイズ(MB)",
splitSqlOutputDescription: "番号付きSQLパートとマニフェストを含むZIPアーカイブを作成します。パートは完全なSQL文の間で分割されます({min}~{max} MB)。",
databaseSelection: "データベース",
selectedDatabases: "{selected}/{total}件選択中",
selectAllDatabases: "すべて選択",
+3
View File
@@ -7160,6 +7160,9 @@ export default withEnglishFallback({
omitAutoIncrement: "AUTO_INCREMENT 생략 (초기 설치 스크립트용)",
includeData: "테이블 데이터 (INSERT)",
includeObjects: "뷰 / 프로시저 / 함수 / 시퀀스",
splitSqlOutput: "SQL 내보내기를 ZIP 압축 파일로 분할",
splitSqlPartMaxMb: "SQL 파트당 최대 크기(MB)",
splitSqlOutputDescription: "번호가 지정된 SQL 파트와 매니페스트가 포함된 ZIP 압축 파일을 만듭니다. 파트는 완전한 SQL 문 사이에서 분할됩니다({min}–{max} MB).",
databaseSelection: "데이터베이스",
selectedDatabases: "{selected}/{total} 선택됨",
selectAllDatabases: "모두 선택",
+3
View File
@@ -7443,6 +7443,9 @@ export default withEnglishFallback({
omitAutoIncrement: "Omitir AUTO_INCREMENT (para scripts de instalação do zero)",
includeData: "Dados da tabela (INSERT)",
includeObjects: "Views / Procedures / Funções / Sequências",
splitSqlOutput: "Dividir exportação SQL em arquivo ZIP",
splitSqlPartMaxMb: "Tamanho máximo por parte SQL (MB)",
splitSqlOutputDescription: "Cria um arquivo ZIP com partes SQL numeradas e um manifesto. As partes são divididas entre instruções SQL completas ({min}–{max} MB).",
databaseSelection: "Bancos de dados",
selectedDatabases: "{selected}/{total} selecionados",
selectAllDatabases: "Selecionar todos",
+3
View File
@@ -7733,6 +7733,9 @@ export default withEnglishFallback({
omitAutoIncrement: "AUTO_INCREMENT'i atla (sıfırdan kurulum betikleri için)",
includeData: "Tablo verisi (INSERT)",
includeObjects: "Görünümler / Yordamlar / İşlevler / Diziler",
splitSqlOutput: "SQL dışa aktarmayı ZIP arşivine böl",
splitSqlPartMaxMb: "SQL parçası başına maksimum boyut (MB)",
splitSqlOutputDescription: "Numaralı SQL parçaları ve bir bildirim içeren bir ZIP arşivi oluşturur. Parçalar tam SQL ifadeleri arasında bölünür ({min}–{max} MB).",
databaseSelection: "Veritabanları",
selectedDatabases: "{selected}/{total} seçildi",
selectAllDatabases: "Tümünü seç",
+3
View File
@@ -7860,6 +7860,9 @@ export default withEnglishFallback({
omitAutoIncrement: "省略 AUTO_INCREMENT(用于全新安装的初始化脚本)",
includeData: "表数据 (INSERT)",
includeObjects: "视图 / 存储过程 / 函数 / 序列",
splitSqlOutput: "将 SQL 导出拆分为 ZIP 压缩包",
splitSqlPartMaxMb: "每个 SQL 分片的最大大小(MB)",
splitSqlOutputDescription: "创建包含编号 SQL 分片和清单的 ZIP 压缩包。分片仅在完整 SQL 语句之间拆分({min}–{max} MB)。",
databaseSelection: "选择数据库",
selectedDatabases: "已选择 {selected}/{total}",
selectAllDatabases: "全选",
+3
View File
@@ -6757,6 +6757,9 @@ export default withEnglishFallback({
omitAutoIncrement: "省略 AUTO_INCREMENT(用於全新安裝的初始化腳本)",
includeData: "資料表資料 (INSERT)",
includeObjects: "檢視 / 預存程序 / 函式 / 序列",
splitSqlOutput: "將 SQL 匯出分割為 ZIP 壓縮檔",
splitSqlPartMaxMb: "每個 SQL 分片的最大大小(MB)",
splitSqlOutputDescription: "建立包含編號 SQL 分片與資訊清單的 ZIP 壓縮檔。分片只會在完整 SQL 陳述式之間分割({min}–{max} MB)。",
databaseSelection: "選擇資料庫",
selectedDatabases: "已選擇 {selected}/{total}",
selectAllDatabases: "全選",
+2
View File
@@ -4891,6 +4891,7 @@ export interface DatabaseExportRequest {
outputCompression?: "none" | "gzip";
snapshotSessionId?: string;
batchSize: number;
splitMaxMb?: number;
}
export interface DatabaseBackupSnapshot {
@@ -4941,6 +4942,7 @@ export interface TableExportRequest {
dateTimeFormat?: string;
numericColumnRightAlign?: boolean;
autoFilter?: boolean;
splitMaxMb?: number;
}
export interface TableCsvExportOptions {
+10 -4
View File
@@ -4,17 +4,22 @@ import i18n from "@/i18n";
export type SqlInsertMode = "batch" | "single";
export interface SqlExportOptions {
insertMode: SqlInsertMode;
splitMaxMb?: number;
}
export const DEFAULT_SQL_INSERT_MODE: SqlInsertMode = "batch";
export function showSqlInsertModeDialog(): Promise<SqlInsertMode | null> {
if (typeof document === "undefined") return Promise.resolve(DEFAULT_SQL_INSERT_MODE);
export function showSqlInsertModeDialog(options: { allowSplit?: boolean } = {}): Promise<SqlExportOptions | null> {
if (typeof document === "undefined") return Promise.resolve({ insertMode: DEFAULT_SQL_INSERT_MODE });
return new Promise((resolve) => {
const container = document.createElement("div");
document.body.append(container);
let settled = false;
let app: ReturnType<typeof createApp> | null = null;
const finish = (value: SqlInsertMode | null) => {
const finish = (value: SqlExportOptions | null) => {
if (settled) return;
settled = true;
resolve(value);
@@ -23,7 +28,8 @@ export function showSqlInsertModeDialog(): Promise<SqlInsertMode | null> {
};
app = createApp(SqlInsertModeDialog, {
open: true,
onConfirm: (insertMode: SqlInsertMode) => finish(insertMode),
allowSplit: options.allowSplit === true,
onConfirm: (options: SqlExportOptions) => finish(options),
onCancel: () => finish(null),
});
app.use(i18n);