fix: only refresh sidebar tree when table comment actually changed

Previously the sidebar tree refresh was skipped entirely in EDIT mode because
the check was gated on activeTab.mode === 'data', which is never true when
the structure editor is open. Now we pass a commentChanged flag through the
event chain and only refresh the tree when the comment was modified.
This commit is contained in:
t8y2 2026-05-26 16:21:54 +08:00
parent 29462937d3
commit 551339cd22
6 changed files with 108 additions and 17 deletions

View File

@ -925,13 +925,19 @@ onUnmounted(() => {
"
@object-schema-change="(schema) => activeTab && queryStore.updateSchema(activeTab.id, schema)"
@structure-editor-saved="
activeTab &&
onStructureEditorSaved(onReloadData, toast, {
connectionId: activeTab.connectionId,
database: activeTab.database,
schema: activeTab.schema,
tableName: activeTab.structureTableName || '',
})
(commentChanged) =>
activeTab &&
onStructureEditorSaved(
onReloadData,
toast,
{
connectionId: activeTab.connectionId,
database: activeTab.database,
schema: activeTab.schema,
tableName: activeTab.structureTableName || '',
},
commentChanged,
)
"
@structure-editor-close="activeTab && queryStore.closeTab(activeTab.id)"
/>

View File

@ -83,7 +83,7 @@ const emit = defineEmits<{
clickTable: [tableName: string];
openObjectTable: [target: { tableName: string; schema?: string }];
objectSchemaChange: [schema: string | undefined];
structureEditorSaved: [];
structureEditorSaved: [commentChanged: boolean];
structureEditorClose: [];
}>();
@ -690,7 +690,7 @@ defineExpose({ focusSearch, refreshData });
:database="activeTab.database"
:schema="activeTab.schema"
:table-name="activeTab.structureTableName || ''"
@saved="emit('structureEditorSaved')"
@saved="(commentChanged) => emit('structureEditorSaved', commentChanged)"
@close="emit('structureEditorClose')"
/>
</template>

View File

@ -75,7 +75,7 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
saved: [];
saved: [commentChanged: boolean];
close: [];
}>();
@ -140,6 +140,8 @@ const indexColLabels = computed(() => [
const targetSchema = computed(() => props.schema || props.database || "");
const isCreateMode = computed(() => !props.tableName);
const newTableName = ref("");
const tableComment = ref("");
const originalTableComment = ref("");
const targetLabel = computed(() =>
buildStructureTargetLabel(
connection.value?.name,
@ -160,6 +162,8 @@ async function refreshSqlPreview() {
tableName: isCreateMode.value ? newTableName.value : props.tableName || "",
columns: columns.value,
indexes: indexes.value,
tableComment: tableComment.value,
originalTableComment: isCreateMode.value ? undefined : originalTableComment.value,
};
try {
const result = isCreateMode.value
@ -201,6 +205,8 @@ function resetState() {
foreignKeys.value = [];
triggers.value = [];
newTableName.value = "";
tableComment.value = "";
originalTableComment.value = "";
}
async function loadStructure(silent = false) {
@ -219,6 +225,16 @@ async function loadStructure(silent = false) {
indexes.value = createIndexDrafts(nextIndexes);
foreignKeys.value = nextForeignKeys;
triggers.value = nextTriggers;
try {
const tables = await api.listTables(props.connectionId, props.database, targetSchema.value);
const table = tables.find(
(t) => t.name.toLowerCase() === props.tableName!.toLowerCase() && t.table_type !== "VIEW",
);
originalTableComment.value = table?.comment || "";
tableComment.value = table?.comment || "";
} catch {
/* ignore — table comment is optional */
}
} catch (e: any) {
errorMessage.value = e?.message || String(e);
} finally {
@ -385,7 +401,7 @@ async function applyChanges() {
try {
await api.executeBatch(props.connectionId, props.database, pendingStatements.value);
toast(t("structureEditor.saved"), 2500);
emit("saved");
emit("saved", tableComment.value !== originalTableComment.value);
if (isCreateMode.value) {
emit("close");
} else {
@ -404,7 +420,7 @@ onMounted(() => {
});
watch(
[isCreateMode, databaseType, () => props.schema, () => props.tableName, newTableName, columns, indexes],
[isCreateMode, databaseType, () => props.schema, () => props.tableName, newTableName, tableComment, columns, indexes],
() => {
void refreshSqlPreview();
},
@ -444,6 +460,15 @@ watch(
/>
</div>
<div class="flex shrink-0 items-center gap-2">
<label class="shrink-0 text-[11px] font-medium text-muted-foreground">{{ t("structureEditor.comment") }}</label>
<Input
v-model="tableComment"
:placeholder="t('structureEditor.commentPlaceholder')"
class="h-6 max-w-[320px] text-[11px]"
/>
</div>
<div v-if="loading" class="flex min-h-0 flex-1 items-center justify-center gap-2 text-sm text-muted-foreground">
<Loader2 class="h-4 w-4 animate-spin" />
{{ t("common.loading") }}

View File

@ -123,6 +123,7 @@ export function useNavigationTargets(dialogs: {
reloadData: () => Promise<void>,
toast: (msg: string, duration?: number) => void,
context: { connectionId: string; database: string; schema?: string; tableName: string },
commentChanged?: boolean,
) {
if (!context.tableName) {
try {
@ -134,6 +135,15 @@ export function useNavigationTargets(dialogs: {
} catch {}
return;
}
if (commentChanged) {
try {
await connectionStore.refreshObjectListTreeNode(
context.connectionId,
context.database,
context.schema || undefined,
);
} catch {}
}
const activeTab = queryStore.tabs.find((t) => t.id === queryStore.activeTabId);
if (activeTab?.mode === "data" && activeTab.tableMeta?.tableName === context.tableName) {
try {
@ -148,11 +158,6 @@ export function useNavigationTargets(dialogs: {
columns,
primaryKeys: editablePrimaryKeys(connectionStore.getConfig(activeTab.connectionId)?.db_type, columns),
});
await connectionStore.refreshObjectListTreeNode(
activeTab.connectionId,
activeTab.database,
activeTab.tableMeta.schema,
);
await reloadData();
} catch (e: any) {
toast(e?.message || String(e), 5000);

View File

@ -33,6 +33,8 @@ export interface BuildTableStructureChangeSqlOptions {
tableName: string;
columns: EditableStructureColumn[];
indexes: EditableStructureIndex[];
tableComment?: string;
originalTableComment?: string;
}
export interface TableStructureChangeSql {

View File

@ -93,6 +93,10 @@ pub struct TableStructureSqlOptions {
pub columns: Vec<EditableStructureColumn>,
#[serde(default)]
pub indexes: Vec<EditableStructureIndex>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub table_comment: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_table_comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -291,9 +295,43 @@ pub fn build_table_structure_change_sql(options: TableStructureSqlOptions) -> Ta
let mut warnings = validate_draft(&options);
let mut statements = build_column_sql(&options, &mut warnings);
statements.extend(build_index_sql(&options, &mut warnings));
statements.extend(build_table_comment_sql(&options, &mut warnings));
TableStructureSqlResult { statements, warnings }
}
fn build_table_comment_sql(options: &TableStructureSqlOptions, warnings: &mut Vec<String>) -> Vec<String> {
let capabilities = capabilities_for(options.database_type);
if !capabilities.comment {
return Vec::new();
}
let new_comment = options.table_comment.as_deref().unwrap_or("");
let original_comment = options.original_table_comment.as_deref().unwrap_or("");
if clean(new_comment) == clean(original_comment) {
return Vec::new();
}
let dialect = capabilities.dialect;
let table = qualified_table(dialect, options.schema.as_deref(), &options.table_name);
let quoted = quote_string(&clean(new_comment));
match dialect {
StructureDialect::Mysql => {
vec![format!("ALTER TABLE {table} COMMENT = {quoted};")]
}
StructureDialect::Postgres | StructureDialect::Oracle | StructureDialect::H2 => {
vec![format!("COMMENT ON TABLE {table} IS {quoted};")]
}
StructureDialect::ClickHouse => {
vec![format!("ALTER TABLE {table} MODIFY COMMENT {quoted};")]
}
StructureDialect::SqlServer | StructureDialect::Sqlite | StructureDialect::DuckDb | _ => {
if !clean(new_comment).is_empty() {
warnings
.push(format!("Table comments are not supported for {} from this editor.", dialect_label(dialect)));
}
Vec::new()
}
}
}
pub fn build_create_table_sql(options: TableStructureSqlOptions) -> TableStructureSqlResult {
let mut warnings = Vec::new();
if clean(&options.table_name).is_empty() {
@ -338,6 +376,21 @@ pub fn build_create_table_sql(options: TableStructureSqlOptions) -> TableStructu
statements.push(format!("CREATE TABLE {table} (\n {}\n);", column_definitions.join(",\n ")));
if capabilities.comment {
let table_comment = clean(options.table_comment.as_deref().unwrap_or(""));
if !table_comment.is_empty() {
if dialect == StructureDialect::Mysql {
if let Some(last) = statements.last_mut() {
*last = last.replace(");", &format!(") COMMENT = {};", quote_string(&table_comment)));
}
} else if matches!(dialect, StructureDialect::Postgres | StructureDialect::Oracle | StructureDialect::H2) {
statements.push(format!("COMMENT ON TABLE {table} IS {};", quote_string(&table_comment)));
} else if dialect == StructureDialect::ClickHouse {
statements.push(format!("ALTER TABLE {table} MODIFY COMMENT {};", quote_string(&table_comment)));
}
}
}
if capabilities.comment
&& matches!(dialect, StructureDialect::Postgres | StructureDialect::Oracle | StructureDialect::H2)
{