From 9105df9a044dfdb84515842c0fcbe8f429bda5e0 Mon Sep 17 00:00:00 2001 From: zipg Date: Tue, 14 Jul 2026 18:03:43 +0800 Subject: [PATCH] fix(oracle): generate compatible insert statements --- .../generate/DataGenerateDialog.vue | 133 +++++++++++++----- apps/desktop/src/lib/dataGrid/dataGenerate.ts | 66 ++++++++- packages/app-tests/dataGenerate.test.ts | 69 ++++++++- 3 files changed, 222 insertions(+), 46 deletions(-) diff --git a/apps/desktop/src/components/generate/DataGenerateDialog.vue b/apps/desktop/src/components/generate/DataGenerateDialog.vue index bcb30cac92..b314663505 100644 --- a/apps/desktop/src/components/generate/DataGenerateDialog.vue +++ b/apps/desktop/src/components/generate/DataGenerateDialog.vue @@ -3,9 +3,9 @@ import { reactive, ref, computed, onMounted, watch, type ComponentPublicInstance import { useI18n } from "vue-i18n"; import { useConnectionStore } from "@/stores/connectionStore"; import * as api from "@/lib/backend/api"; -import type { TableGenerateConfig } from "@/lib/dataGrid/dataGenerate"; -import { displayGeneratedValue, findGeneratorKey, formatGeneratedValue, generateTableData, defaultGeneratorParams } from "@/lib/dataGrid/dataGenerate"; -import { quoteTableIdentifier } from "@/lib/table/tableSelectSql"; +import type { GenerateResult, TableGenerateConfig } from "@/lib/dataGrid/dataGenerate"; +import { displayGeneratedValue, findGeneratorKey, formatGeneratedValue, generateTableData, defaultGeneratorParams, supportsGeneratedMultiRowValues } from "@/lib/dataGrid/dataGenerate"; +import { qualifiedTableName, quoteTableIdentifier } from "@/lib/table/tableSelectSql"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; import { executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard"; @@ -56,7 +56,11 @@ const panelColumnName = ref(null); // Step state: config -> preview const currentStep = ref<"config" | "preview" | "result">("config"); -const generatedResults = ref<{ tableName: string; columns: string[]; rows: unknown[][]; sql: string }[]>([]); +interface GeneratedTableResult extends GenerateResult { + tableName: string; + schema: string; +} +const generatedResults = ref([]); const connectionName = computed(() => (props.prefillConnectionId ? store.getConfig(props.prefillConnectionId)?.name : "")); @@ -142,6 +146,9 @@ async function loadSchemas() { for (const c of cols) { checkedColumns[colKey(targetSchema, props.prefillTable, c.name)] = true; } + panelTableKey.value = key; + panelMode.value = "table"; + panelColumnName.value = null; break; } } catch { @@ -263,6 +270,26 @@ async function selectTable(schema: string, table: string, checked: boolean) { } } +async function activateTable(schema: string, table: string) { + const key = tableKey(schema, table); + if (!checkedTables[key]) { + await selectTable(schema, table, true); + } + await showTable(schema, table); +} + +async function updateTableSelection(schema: string, table: string, checked: boolean) { + const key = tableKey(schema, table); + await selectTable(schema, table, checked); + if (checked) { + await showTable(schema, table); + } else if (panelTableKey.value === key) { + panelTableKey.value = null; + panelMode.value = null; + panelColumnName.value = null; + } +} + function toggleColumn(schema: string, table: string, column: string) { const ck = colKey(schema, table, column); const key = tableKey(schema, table); @@ -325,7 +352,7 @@ function onPreviewColResizeStart(ci: number, event: MouseEvent) { document.addEventListener("pointerup", onUp); document.body.classList.add("select-none", "cursor-col-resize"); } -const currentPreview = computed(() => generatedResults.value[previewTableIndex.value] ?? { tableName: "", columns: [], rows: [], sql: "" }); +const currentPreview = computed(() => generatedResults.value[previewTableIndex.value] ?? { tableName: "", schema: "", columns: [], rows: [], sql: "", statements: [] }); function displayPreviewCell(cell: unknown): string { return displayGeneratedValue(cell); @@ -355,7 +382,8 @@ async function fetchMaxValues(cfg: TableGenerateConfig): Promise 0 ? tableOrder.value : Object.keys(configs); for (const key of order) { if (!checkedTables[key]) continue; @@ -373,7 +401,7 @@ async function doGenerate() { }); } const result = generateTableData({ ...cfg, columns }, dbType.value); - results.push({ tableName: cfg.tableName, columns: result.columns, rows: result.rows, sql: result.sql }); + results.push({ tableName: cfg.tableName, schema: cfg.schema, ...result }); } generatedResults.value = results; previewTableIndex.value = 0; @@ -382,7 +410,8 @@ async function doGenerate() { async function regenerate() { if (generatedResults.value.length === 0) return; - const key = Object.keys(configs).find((k) => configs[k].tableName === generatedResults.value[previewTableIndex.value]?.tableName); + const preview = generatedResults.value[previewTableIndex.value]; + const key = Object.keys(configs).find((k) => configs[k].tableName === preview?.tableName && configs[k].schema === preview?.schema); if (!key) return; const cfg = configs[key]; let columns = cfg.columns.filter((col) => checkedColumns[colKey(cfg.schema, cfg.tableName, col.columnName)] !== false); @@ -397,7 +426,7 @@ async function regenerate() { }); } const result = generateTableData({ ...cfg, columns }, dbType.value); - generatedResults.value[previewTableIndex.value] = { tableName: cfg.tableName, columns: result.columns, rows: result.rows, sql: result.sql }; + generatedResults.value[previewTableIndex.value] = { tableName: cfg.tableName, schema: cfg.schema, ...result }; } function copyAllSql() { @@ -422,21 +451,30 @@ const generateOptions = reactive({ useTransaction: true, extendedInsert: true, }); +const supportsExtendedInsert = computed(() => supportsGeneratedMultiRowValues(dbType.value)); +watch( + supportsExtendedInsert, + (supported) => { + if (!supported) generateOptions.extendedInsert = false; + }, + { immediate: true }, +); const optionsDialogOpen = ref(false); -function sqlStatementsForTable(r: { tableName: string; columns: string[]; rows: unknown[][]; sql: string }): string[] { +function sqlStatementsForTable(r: GeneratedTableResult): string[] { const stmts: string[] = []; + const targetTable = qualifiedTableName({ databaseType: dbType.value, schema: r.schema, tableName: r.tableName, database: props.prefillDatabase }); if (generateOptions.truncate) { - stmts.push(`TRUNCATE TABLE ${quoteTableIdentifier(dbType.value, r.tableName)};`); + stmts.push(`TRUNCATE TABLE ${targetTable};`); } - if (generateOptions.extendedInsert) { - stmts.push(r.sql); + if (generateOptions.extendedInsert || !supportsGeneratedMultiRowValues(dbType.value)) { + stmts.push(...r.statements); } else { const colList = r.columns.map((c) => quoteTableIdentifier(dbType.value, c)).join(", "); for (const row of r.rows) { - const vals = row.map(formatGeneratedValue).join(", "); - stmts.push(`INSERT INTO ${quoteTableIdentifier(dbType.value, r.tableName)} (${colList}) VALUES (${vals});`); + const vals = row.map((value) => formatGeneratedValue(value)).join(", "); + stmts.push(`INSERT INTO ${targetTable} (${colList}) VALUES (${vals});`); } } return stmts; @@ -462,37 +500,55 @@ async function startInsert() { execute: async () => { executing.value = true; const perTable: TableResult[] = []; + let stopAfterTable = false; for (const r of generatedResults.value) { const stmts = sqlStatementsForTable(r); const rowCount = r.rows.length; let ok = 0; let lastError = ""; - for (let si = 0; si < stmts.length; si++) { + + const executeAsStatementGroup = generateOptions.useTransaction && !supportsGeneratedMultiRowValues(dbType.value); + if (executeAsStatementGroup) { try { - if (generateOptions.useTransaction) { - await api.executeInTransaction(cid, db, [stmts[si]], props.prefillSchema); - } else { - await api.executeQuery(cid, db, stmts[si], props.prefillSchema); - } - if (generateOptions.extendedInsert) { - ok = rowCount; - } else if (!(generateOptions.truncate && si === 0)) { - ok++; - } + await api.executeInTransaction(cid, db, stmts, r.schema || props.prefillSchema); + ok = rowCount; } catch (e: unknown) { const msg = e instanceof Error ? e.message : String(e); console.error("[startInsert] SQL error:", msg); - if (!lastError) lastError = msg; - if (generateOptions.extendedInsert) { - ok = 0; + lastError = msg; + stopAfterTable = !generateOptions.continueOnError; + } + } else { + const usesMultiRowStatement = generateOptions.extendedInsert && supportsGeneratedMultiRowValues(dbType.value); + for (let si = 0; si < stmts.length; si++) { + try { + if (generateOptions.useTransaction) { + await api.executeInTransaction(cid, db, [stmts[si]], r.schema || props.prefillSchema); + } else { + await api.executeQuery(cid, db, stmts[si], r.schema || props.prefillSchema); + } + if (usesMultiRowStatement) { + ok = rowCount; + } else if (!(generateOptions.truncate && si === 0)) { + ok++; + } + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + console.error("[startInsert] SQL error:", msg); + if (!lastError) lastError = msg; + if (usesMultiRowStatement) ok = 0; + if (!generateOptions.continueOnError) { + stopAfterTable = true; + break; + } } - if (!generateOptions.continueOnError) break; } } perTable.push({ table: r.tableName, total: rowCount, ok, err: rowCount - ok, error: lastError || undefined }); if (ok > 0) { - store.invalidateMetadataCache(cid, db, props.prefillSchema || undefined, r.tableName); + store.invalidateMetadataCache(cid, db, r.schema || props.prefillSchema || undefined, r.tableName); } + if (stopAfterTable) break; } executeResults.value = perTable; currentStep.value = "result"; @@ -510,6 +566,7 @@ const tableOrder = ref([]); const orderableTables = computed(() => { return Object.keys(checkedTables).filter((k) => checkedTables[k]); }); +const hasSelectedTables = computed(() => orderableTables.value.length > 0); function openOrderDialog() { tableOrder.value = [...orderableTables.value]; @@ -763,14 +820,14 @@ async function onFileSelected(event: Event) {
-
- - + - + {{ tbl.name }} @@ -948,7 +1005,7 @@ async function onFileSelected(event: Event) { {{ t("dangerDialog.cancel") }} @@ -986,8 +1043,8 @@ async function onFileSelected(event: Event) { {{ t("dataGenerate.useTransaction") }} -