1 parent
a740e2e9ce
commit
bd83ee189b
11 files changed
+263
-27
No files matched your search
@@ -4,7 +4,7 @@ import { useI18n } from "vue-i18n";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import type { GenerateResult, TableGenerateConfig } from "@/lib/dataGrid/dataGenerate";
|
||||
import { displayGeneratedValue, findGeneratorKey, formatGeneratedValue, generateTableData, defaultGeneratorParams, supportsGeneratedMultiRowValues } from "@/lib/dataGrid/dataGenerate";
|
||||
import { displayGeneratedValue, findGeneratorKey, formatGeneratedValue, generateTableData, defaultGeneratorParams, supportsGeneratedMultiRowValues, UniqueValueGenerationError } from "@/lib/dataGrid/dataGenerate";
|
||||
import { qualifiedTableName, quoteTableIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
@@ -61,6 +61,7 @@ interface GeneratedTableResult extends GenerateResult {
|
||||
schema: string;
|
||||
}
|
||||
const generatedResults = ref<GeneratedTableResult[]>([]);
|
||||
const generationError = ref("");
|
||||
|
||||
const connectionName = computed(() => (props.prefillConnectionId ? store.getConfig(props.prefillConnectionId)?.name : ""));
|
||||
|
||||
@@ -396,25 +397,31 @@ async function fetchMaxValues(cfg: TableGenerateConfig): Promise<Record<string,
|
||||
|
||||
async function doGenerate() {
|
||||
if (!Object.values(checkedTables).some(Boolean)) return;
|
||||
generationError.value = "";
|
||||
const results: GeneratedTableResult[] = [];
|
||||
const order = tableOrder.value.length > 0 ? tableOrder.value : Object.keys(configs);
|
||||
for (const key of order) {
|
||||
if (!checkedTables[key]) continue;
|
||||
const cfg = configs[key];
|
||||
let columns = cfg.columns.filter((col) => checkedColumns[colKey(cfg.schema, cfg.tableName, col.columnName)] !== false);
|
||||
if (columns.length === 0) continue;
|
||||
const aiStarts = await fetchMaxValues(cfg);
|
||||
if (Object.keys(aiStarts).length > 0) {
|
||||
columns = columns.map((col) => {
|
||||
const start = aiStarts[col.columnName];
|
||||
if (start !== undefined) {
|
||||
return { ...col, generatorKey: "sequence", generatorParams: { startValue: start, increment: 1 } };
|
||||
}
|
||||
return col;
|
||||
});
|
||||
try {
|
||||
const order = tableOrder.value.length > 0 ? tableOrder.value : Object.keys(configs);
|
||||
for (const key of order) {
|
||||
if (!checkedTables[key]) continue;
|
||||
const cfg = configs[key];
|
||||
let columns = cfg.columns.filter((col) => checkedColumns[colKey(cfg.schema, cfg.tableName, col.columnName)] !== false);
|
||||
if (columns.length === 0) continue;
|
||||
const aiStarts = await fetchMaxValues(cfg);
|
||||
if (Object.keys(aiStarts).length > 0) {
|
||||
columns = columns.map((col) => {
|
||||
const start = aiStarts[col.columnName];
|
||||
if (start !== undefined) {
|
||||
return { ...col, generatorKey: "sequence", generatorParams: { startValue: start, increment: 1 } };
|
||||
}
|
||||
return col;
|
||||
});
|
||||
}
|
||||
const result = generateTableData({ ...cfg, columns }, dbType.value);
|
||||
results.push({ tableName: cfg.tableName, schema: cfg.schema, ...result });
|
||||
}
|
||||
const result = generateTableData({ ...cfg, columns }, dbType.value);
|
||||
results.push({ tableName: cfg.tableName, schema: cfg.schema, ...result });
|
||||
} catch (error) {
|
||||
generationError.value = generationErrorMessage(error);
|
||||
return;
|
||||
}
|
||||
generatedResults.value = results;
|
||||
previewTableIndex.value = 0;
|
||||
@@ -438,8 +445,20 @@ async function regenerate() {
|
||||
return col;
|
||||
});
|
||||
}
|
||||
const result = generateTableData({ ...cfg, columns }, dbType.value);
|
||||
generatedResults.value[previewTableIndex.value] = { tableName: cfg.tableName, schema: cfg.schema, ...result };
|
||||
generationError.value = "";
|
||||
try {
|
||||
const result = generateTableData({ ...cfg, columns }, dbType.value);
|
||||
generatedResults.value[previewTableIndex.value] = { tableName: cfg.tableName, schema: cfg.schema, ...result };
|
||||
} catch (error) {
|
||||
generationError.value = generationErrorMessage(error);
|
||||
}
|
||||
}
|
||||
|
||||
function generationErrorMessage(error: unknown): string {
|
||||
if (error instanceof UniqueValueGenerationError) {
|
||||
return t("dataGenerate.uniqueExhausted", { table: error.tableName, column: error.columnName, attempts: error.attempts });
|
||||
}
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
function copyAllSql() {
|
||||
@@ -498,7 +517,7 @@ function allSqlStatements(): string[] {
|
||||
}
|
||||
|
||||
async function startInsert() {
|
||||
if (executing.value) return;
|
||||
if (executing.value || generationError.value) return;
|
||||
const cid = props.prefillConnectionId;
|
||||
const db = props.prefillDatabase;
|
||||
if (!cid || !db) return;
|
||||
@@ -807,6 +826,8 @@ async function onFileSelected(event: Event) {
|
||||
<span class="ml-1 font-medium">{{ connectionName || props.prefillDatabase }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="generationError" class="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive" role="alert">{{ generationError }}</div>
|
||||
|
||||
<template v-if="currentStep === 'config'">
|
||||
<div class="flex gap-4 min-h-[400px]">
|
||||
<!-- left: schema tree -->
|
||||
@@ -1024,7 +1045,7 @@ async function onFileSelected(event: Event) {
|
||||
</template>
|
||||
<template v-else-if="currentStep === 'preview'">
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs" @click="currentStep = 'config'">{{ t("dataGenerate.prevStep") }}</Button>
|
||||
<Button variant="default" size="sm" class="h-7 text-xs" :disabled="executing" @click="startInsert">
|
||||
<Button variant="default" size="sm" class="h-7 text-xs" :disabled="executing || !!generationError" @click="startInsert">
|
||||
<Loader2 v-if="executing" class="mr-1 h-3 w-3 animate-spin" />
|
||||
{{ t("dataGenerate.startInsert") }}
|
||||
</Button>
|
||||
|
||||
@@ -4819,6 +4819,7 @@ export default {
|
||||
includeDefault: "Include DEFAULT",
|
||||
includeNull: "Include NULL",
|
||||
unique: "Set unique",
|
||||
uniqueExhausted: "Could not generate a distinct value for {table}.{column} within {attempts} attempts. Increase the available value range or generate fewer rows.",
|
||||
disableLinks: "Disable data links between columns",
|
||||
imageMode: "Mode",
|
||||
generate: "Generate",
|
||||
|
||||
@@ -4600,6 +4600,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "Include DEFAULT",
|
||||
includeNull: "Include NULL",
|
||||
unique: "Set unique",
|
||||
uniqueExhausted: "No se pudo generar un valor distinto para {table}.{column} en {attempts} intentos. Amplía el rango de valores o genera menos filas.",
|
||||
disableLinks: "Disable data links between columns",
|
||||
imageMode: "Mode",
|
||||
generate: "Generate",
|
||||
|
||||
@@ -4598,6 +4598,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "Include DEFAULT",
|
||||
includeNull: "Include NULL",
|
||||
unique: "Set unique",
|
||||
uniqueExhausted: "Impossibile generare un valore distinto per {table}.{column} in {attempts} tentativi. Amplia l'intervallo dei valori o genera meno righe.",
|
||||
disableLinks: "Disable data links between columns",
|
||||
imageMode: "Mode",
|
||||
generate: "Generate",
|
||||
|
||||
@@ -4637,6 +4637,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "DEFAULTを含める",
|
||||
includeNull: "NULLを含める",
|
||||
unique: "一意に設定",
|
||||
uniqueExhausted: "{attempts} 回の試行で {table}.{column} の重複しない値を生成できませんでした。値の範囲を広げるか、生成行数を減らしてください。",
|
||||
disableLinks: "列間のデータリンクを無効化",
|
||||
imageMode: "モード",
|
||||
generate: "生成",
|
||||
|
||||
@@ -4318,6 +4318,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "DEFAULT 포함",
|
||||
includeNull: "NULL 포함",
|
||||
unique: "고유 설정",
|
||||
uniqueExhausted: "{attempts}번 시도했지만 {table}.{column}의 중복되지 않는 값을 생성하지 못했습니다. 값 범위를 늘리거나 생성 행 수를 줄이세요.",
|
||||
disableLinks: "컬럼 간 데이터 연결 비활성화",
|
||||
imageMode: "모드",
|
||||
generate: "생성",
|
||||
|
||||
@@ -4600,6 +4600,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "Include DEFAULT",
|
||||
includeNull: "Include NULL",
|
||||
unique: "Set unique",
|
||||
uniqueExhausted: "Não foi possível gerar um valor distinto para {table}.{column} em {attempts} tentativas. Aumente o intervalo de valores ou gere menos linhas.",
|
||||
disableLinks: "Disable data links between columns",
|
||||
imageMode: "Mode",
|
||||
generate: "Generate",
|
||||
|
||||
@@ -4742,6 +4742,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "包含默认值",
|
||||
includeNull: "包含 NULL",
|
||||
unique: "设置唯一",
|
||||
uniqueExhausted: "无法在 {attempts} 次尝试内为表 {table} 的字段 {column} 生成不重复值,请增大可选值范围或减少生成行数。",
|
||||
disableLinks: "禁用字段之间的数据链接",
|
||||
imageMode: "模式",
|
||||
generate: "生成",
|
||||
|
||||
@@ -3945,6 +3945,7 @@ export default withEnglishFallback({
|
||||
includeDefault: "包含預設值",
|
||||
includeNull: "包含 NULL",
|
||||
unique: "設定唯一",
|
||||
uniqueExhausted: "無法在 {attempts} 次嘗試內為資料表 {table} 的欄位 {column} 產生不重複值,請增加可選值範圍或減少產生列數。",
|
||||
disableLinks: "禁用欄位之間的資料連結",
|
||||
imageMode: "模式",
|
||||
generate: "生成",
|
||||
|
||||
@@ -1774,6 +1774,29 @@ export interface GenerateResult {
|
||||
statements: string[];
|
||||
}
|
||||
|
||||
const MAX_UNIQUE_GENERATION_ATTEMPTS = 100;
|
||||
|
||||
export class UniqueValueGenerationError extends Error {
|
||||
constructor(
|
||||
public readonly tableName: string,
|
||||
public readonly columnName: string,
|
||||
public readonly attempts: number,
|
||||
) {
|
||||
super(`Unable to generate a unique value for column "${columnName}" in table "${tableName}" after ${attempts} attempts.`);
|
||||
this.name = "UniqueValueGenerationError";
|
||||
}
|
||||
}
|
||||
|
||||
function generatedValueIdentity(value: unknown): string {
|
||||
if (isGeneratedSqlExpression(value)) return `sql:${value.sql}`;
|
||||
if (value === null) return "null";
|
||||
if (value === undefined) return "undefined";
|
||||
if (typeof value === "number") return `number:${value}`;
|
||||
if (typeof value === "boolean") return `boolean:${value}`;
|
||||
if (typeof value === "string") return `string:${value}`;
|
||||
return `${typeof value}:${JSON.stringify(value)}`;
|
||||
}
|
||||
|
||||
export function supportsGeneratedMultiRowValues(databaseType?: DatabaseType): boolean {
|
||||
return databaseType !== "oracle" && databaseType !== "oceanbase-oracle" && databaseType !== "iris";
|
||||
}
|
||||
@@ -1810,15 +1833,34 @@ export function generateTableData(config: TableGenerateConfig, databaseType?: Da
|
||||
const shouldAddTbname = isTdengineStable && !hasTbnameColumn;
|
||||
const tdengineChildTableName = shouldAddTbname ? generateTdengineChildTableName() : null;
|
||||
const tagValues = new Map<string, unknown>();
|
||||
const uniqueValues = config.columns.map((column) => (column.generatorParams?.unique ? new Set<string>() : null));
|
||||
const colNames = shouldAddTbname ? ["tbname", ...config.columns.map((c) => c.columnName)] : config.columns.map((c) => c.columnName);
|
||||
const rows: unknown[][] = [];
|
||||
|
||||
for (let i = 0; i < config.rowCount; i++) {
|
||||
const row = config.columns.map((col) => {
|
||||
const row = config.columns.map((col, columnIndex) => {
|
||||
if (isTdengineStable && col.isTag && tagValues.has(col.columnName)) {
|
||||
return tagValues.get(col.columnName);
|
||||
}
|
||||
const value = generateValue(col.columnName, col.dataType, col.generatorKey, i, col.generatorParams, col.isAutoIncrement ? null : col.columnDefault);
|
||||
let value: unknown;
|
||||
if (col.generatorParams?.unique) {
|
||||
const seen = uniqueValues[columnIndex]!;
|
||||
let found = false;
|
||||
for (let attempt = 0; attempt < MAX_UNIQUE_GENERATION_ATTEMPTS; attempt++) {
|
||||
value = generateValue(col.columnName, col.dataType, col.generatorKey, i, col.generatorParams, col.isAutoIncrement ? null : col.columnDefault);
|
||||
const identity = generatedValueIdentity(value);
|
||||
if (!seen.has(identity)) {
|
||||
seen.add(identity);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw new UniqueValueGenerationError(config.tableName, col.columnName, MAX_UNIQUE_GENERATION_ATTEMPTS);
|
||||
}
|
||||
} else {
|
||||
value = generateValue(col.columnName, col.dataType, col.generatorKey, i, col.generatorParams, col.isAutoIncrement ? null : col.columnDefault);
|
||||
}
|
||||
if (isTdengineStable && col.isTag) {
|
||||
tagValues.set(col.columnName, value);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user