fix(structure): initialize dialogs and edit clickhouse columns

This commit is contained in:
t8y2 committed 2026-05-19 13:31:42 +08:00
1 parent 5b3b69f7fb
commit 69ee7f2d8b
18 files changed
+325 -120

No files matched your search

@@ -28,13 +28,17 @@ const passphrase = ref("");
const passphraseConfirm = ref("");
const error = ref("");
watch(dialogOpen, (open) => {
if (open) {
passphrase.value = "";
passphraseConfirm.value = "";
error.value = "";
}
});
watch(
dialogOpen,
(open) => {
if (open) {
passphrase.value = "";
passphraseConfirm.value = "";
error.value = "";
}
},
{ immediate: true },
);
function confirm() {
if (!passphrase.value) {
@@ -667,17 +667,21 @@ function resetForm() {
resetTestState();
}
watch(open, (value) => {
if (!value) {
resetForm();
return;
}
if (!props.editConfig) {
resetForm();
}
void loadJdbcDrivers();
void loadAgentDrivers();
});
watch(
open,
(value) => {
if (!value) {
resetForm();
return;
}
if (!props.editConfig) {
resetForm();
}
void loadJdbcDrivers();
void loadAgentDrivers();
},
{ immediate: true },
);
watch(canUseSsh, (value) => {
if (!value && configTab.value === "ssh") {
@@ -661,9 +661,13 @@ function stopDrag() {
window.removeEventListener("mouseup", stopDrag);
}
watch(open, (value) => {
if (value) void initialize();
});
watch(
open,
(value) => {
if (value) void initialize();
},
{ immediate: true },
);
watch(
() => visibleTables.value.map((table) => table.name).join("\n"),
@@ -412,21 +412,25 @@ watch(sourceTable, (table, previous) => {
}
});
watch(targetTable, () => clearResult());
watch(open, async (value) => {
if (!value) return;
result.value = null;
syncSql.value = "";
if (props.prefillConnectionId) {
sourceConnectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId, "source");
if (props.prefillDatabase) sourceDatabase.value = props.prefillDatabase;
if (props.prefillDatabase) await loadSchemas("source", props.prefillSchema);
if (props.prefillTable) {
await loadTables("source");
if (sourceTables.value.includes(props.prefillTable)) sourceTable.value = props.prefillTable;
watch(
open,
async (value) => {
if (!value) return;
result.value = null;
syncSql.value = "";
if (props.prefillConnectionId) {
sourceConnectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId, "source");
if (props.prefillDatabase) sourceDatabase.value = props.prefillDatabase;
if (props.prefillDatabase) await loadSchemas("source", props.prefillSchema);
if (props.prefillTable) {
await loadTables("source");
if (sourceTables.value.includes(props.prefillTable)) sourceTable.value = props.prefillTable;
}
}
}
});
},
{ immediate: true },
);
</script>
<template>
@@ -356,23 +356,27 @@ watch(targetDatabase, (database) => {
watch(sourceSchema, () => resetResult());
watch(targetSchema, () => resetResult());
watch(open, async (val) => {
if (val) {
step.value = "select";
diffs.value = [];
syncErrors.value = [];
executedCount.value = 0;
executeTotal.value = 0;
if (props.prefillConnectionId) {
sourceConnectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId, "source");
if (props.prefillDatabase) {
sourceDatabase.value = props.prefillDatabase;
await loadSchemas("source", props.prefillSchema);
watch(
open,
async (val) => {
if (val) {
step.value = "select";
diffs.value = [];
syncErrors.value = [];
executedCount.value = 0;
executeTotal.value = 0;
if (props.prefillConnectionId) {
sourceConnectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId, "source");
if (props.prefillDatabase) {
sourceDatabase.value = props.prefillDatabase;
await loadSchemas("source", props.prefillSchema);
}
}
}
}
});
},
{ immediate: true },
);
</script>
<template>
@@ -292,21 +292,25 @@ watch(schema, (value) => {
if (value) loadTables(preferredTable).catch((e) => toast(String(e), 5000));
});
watch(open, async (val) => {
if (val) {
resetState();
pendingPrefillTable.value = props.prefillTable ?? "";
if (props.prefillConnectionId) {
skipConnectionWatch.value = true;
connectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId);
if (props.prefillDatabase) {
database.value = props.prefillDatabase;
await loadSchemas(props.prefillSchema);
watch(
open,
async (val) => {
if (val) {
resetState();
pendingPrefillTable.value = props.prefillTable ?? "";
if (props.prefillConnectionId) {
skipConnectionWatch.value = true;
connectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId);
if (props.prefillDatabase) {
database.value = props.prefillDatabase;
await loadSchemas(props.prefillSchema);
}
}
}
}
});
},
{ immediate: true },
);
</script>
<template>
@@ -231,12 +231,16 @@ async function cancelImport() {
await api.cancelTableImport(importId.value);
}
watch(open, (value) => {
if (value) {
resetState();
void loadTargetColumns();
}
});
watch(
open,
(value) => {
if (value) {
resetState();
void loadTargetColumns();
}
},
{ immediate: true },
);
</script>
<template>
@@ -119,15 +119,19 @@ const filteredItems = computed(() => {
const filteredCounts = computed(() => summarizeLineageCounts(filteredItems.value));
watch(dialogOpen, (open) => {
if (open) {
confidenceFilter.value = "all";
searchText.value = "";
void loadLineage();
} else {
cancelLoad();
}
});
watch(
dialogOpen,
(open) => {
if (open) {
confidenceFilter.value = "all";
searchText.value = "";
void loadLineage();
} else {
cancelLoad();
}
},
{ immediate: true },
);
function cancelLoad() {
cancelled.value = true;
@@ -95,13 +95,17 @@ const progressLabel = computed(() =>
t("databaseSearch.progress", { done: progressDone.value, total: progressTotal.value }),
);
watch(dialogOpen, (open) => {
if (open) {
resetSearchState();
} else {
stopSearch();
}
});
watch(
dialogOpen,
(open) => {
if (open) {
resetSearchState();
} else {
stopSearch();
}
},
{ immediate: true },
);
function resetSearchState() {
running.value = false;
@@ -376,13 +376,17 @@ watch(sqlConnections, () => {
connectionId.value = resolveInitialConnectionId();
});
watch(open, (value) => {
if (!value) return;
resetState();
if (connectionId.value) {
loadDatabasesForConnection(connectionId.value);
}
});
watch(
open,
(value) => {
if (!value) return;
resetState();
if (connectionId.value) {
loadDatabasesForConnection(connectionId.value);
}
},
{ immediate: true },
);
</script>
<template>
@@ -159,19 +159,23 @@ watch(targetConnectionId, (id) => {
loadDatabases(id, "target");
});
watch(open, async (val) => {
if (val) {
resetState();
if (props.prefillConnectionId) {
skipSourceWatch.value = true;
sourceConnectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId, "source");
if (props.prefillDatabase) {
sourceDatabase.value = props.prefillDatabase;
watch(
open,
async (val) => {
if (val) {
resetState();
if (props.prefillConnectionId) {
skipSourceWatch.value = true;
sourceConnectionId.value = props.prefillConnectionId;
await loadDatabases(props.prefillConnectionId, "source");
if (props.prefillDatabase) {
sourceDatabase.value = props.prefillDatabase;
}
}
}
}
});
},
{ immediate: true },
);
function resetState() {
sourceConnectionId.value = "";
@@ -167,6 +167,12 @@ const clickhouseCapabilities = capabilities({
createTable: true,
addColumn: true,
dropColumn: true,
renameColumn: true,
alterExistingColumn: true,
alterType: true,
alterNullability: true,
alterDefault: true,
comment: true,
});
const capabilityByType: Partial<Record<DatabaseType, TableStructureCapabilities>> = {
@@ -85,9 +85,23 @@ function normalizeDefault(value: string | null | undefined): string {
return trimmed.toLowerCase() === "null" ? "" : trimmed;
}
function unwrapClickHouseNullableType(dataType: string): string {
const match = dataType.trim().match(/^Nullable\s*\((.*)\)$/is);
return match ? match[1].trim() : dataType.trim();
}
function clickHouseColumnType(column: EditableStructureColumn): string {
const dataType = column.dataType.trim();
if (column.isNullable) {
return /^Nullable\s*\(/i.test(dataType) ? dataType : `Nullable(${dataType})`;
}
return unwrapClickHouseNullableType(dataType);
}
function columnDefinition(databaseType: StructureSqlFlavor, column: EditableStructureColumn): string {
const parts = [quoteIdent(databaseType, column.name), column.dataType.trim()];
if (!column.isNullable && !isOracleLike(databaseType)) parts.push("NOT NULL");
const dataType = databaseType === "clickhouse" ? clickHouseColumnType(column) : column.dataType.trim();
const parts = [quoteIdent(databaseType, column.name), dataType];
if (!column.isNullable && !isOracleLike(databaseType) && databaseType !== "clickhouse") parts.push("NOT NULL");
const defaultValue = normalizeDefault(column.defaultValue);
if (defaultValue) parts.push(`DEFAULT ${defaultValue}`);
if (databaseType === "mysql" && clean(column.comment)) {
@@ -127,6 +141,11 @@ function buildAddColumnSql(databaseType: StructureSqlFlavor, table: string, colu
`COMMENT ON COLUMN ${table}.${quoteIdent(databaseType, column.name)} IS ${quoteString(clean(column.comment))};`,
);
}
if (databaseType === "clickhouse" && clean(column.comment)) {
statements.push(
`ALTER TABLE ${table} COMMENT COLUMN ${quoteIdent(databaseType, column.name)} ${quoteString(clean(column.comment))};`,
);
}
return statements;
}
@@ -240,6 +259,48 @@ function buildH2ExistingColumnSql(table: string, column: EditableStructureColumn
return statements;
}
function buildClickHouseExistingColumnSql(table: string, column: EditableStructureColumn): string[] {
const original = column.original;
if (!original) return [];
const statements: string[] = [];
let currentName = original.name;
if (column.name !== original.name) {
statements.push(
`ALTER TABLE ${table} RENAME COLUMN ${quoteIdent("clickhouse", original.name)} TO ${quoteIdent("clickhouse", column.name)};`,
);
currentName = column.name;
}
if (
clickHouseColumnType(column) !== original.data_type.trim() ||
normalizeDefault(column.defaultValue) !== originalDefault(column)
) {
const defaultValue = normalizeDefault(column.defaultValue);
if (defaultValue) {
statements.push(
`ALTER TABLE ${table} MODIFY COLUMN ${quoteIdent("clickhouse", currentName)} ${clickHouseColumnType(column)} DEFAULT ${defaultValue};`,
);
} else if (originalDefault(column)) {
statements.push(`ALTER TABLE ${table} MODIFY COLUMN ${quoteIdent("clickhouse", currentName)} REMOVE DEFAULT;`);
if (clickHouseColumnType(column) !== original.data_type.trim()) {
statements.push(
`ALTER TABLE ${table} MODIFY COLUMN ${quoteIdent("clickhouse", currentName)} ${clickHouseColumnType(column)};`,
);
}
} else {
statements.push(
`ALTER TABLE ${table} MODIFY COLUMN ${quoteIdent("clickhouse", currentName)} ${clickHouseColumnType(column)};`,
);
}
}
if (clean(column.comment) !== originalComment(column)) {
statements.push(
`ALTER TABLE ${table} COMMENT COLUMN ${quoteIdent("clickhouse", currentName)} ${quoteString(clean(column.comment))};`,
);
}
return statements;
}
function buildSqliteExistingColumnSql(table: string, column: EditableStructureColumn, warnings: string[]): string[] {
const original = column.original;
if (!original) return [];
@@ -321,6 +382,8 @@ function buildColumnSql(options: BuildTableStructureChangeSqlOptions, warnings:
statements.push(...buildOracleLikeExistingColumnSql(dialect, table, column));
} else if (dialect === "h2") {
statements.push(...buildH2ExistingColumnSql(table, column));
} else if (dialect === "clickhouse") {
statements.push(...buildClickHouseExistingColumnSql(table, column));
} else if (dialect === "sqlite") {
statements.push(...buildSqliteExistingColumnSql(table, column, warnings));
} else {
@@ -463,8 +526,9 @@ export function buildCreateTableSql(options: BuildTableStructureChangeSqlOptions
const pkColumns = activeColumns.filter((c) => c.isPrimaryKey);
const colDefs = activeColumns.map((col) => {
const parts = [quoteIdent(dialect, col.name), col.dataType.trim()];
if (!col.isNullable && !col.isPrimaryKey) parts.push("NOT NULL");
const dataType = dialect === "clickhouse" ? clickHouseColumnType(col) : col.dataType.trim();
const parts = [quoteIdent(dialect, col.name), dataType];
if (!col.isNullable && !col.isPrimaryKey && dialect !== "clickhouse") parts.push("NOT NULL");
const defaultValue = normalizeDefault(col.defaultValue);
if (defaultValue) parts.push(`DEFAULT ${defaultValue}`);
if (dialect === "mysql" && capabilities.comment && clean(col.comment)) {
@@ -489,6 +553,15 @@ export function buildCreateTableSql(options: BuildTableStructureChangeSqlOptions
}
}
}
if (capabilities.comment && dialect === "clickhouse") {
for (const col of activeColumns) {
if (clean(col.comment)) {
statements.push(
`ALTER TABLE ${table} COMMENT COLUMN ${quoteIdent(dialect, col.name)} ${quoteString(clean(col.comment))};`,
);
}
}
}
for (const index of options.indexes.filter((idx) => !idx.markedForDrop && !idx.isPrimary)) {
const name = clean(index.name);