From ca0153acbb2872858a2447b6ea6de3b019faeda9 Mon Sep 17 00:00:00 2001
From: t8y2 <1156263951@qq.com>
Date: Tue, 2 Jun 2026 10:27:08 +0800
Subject: [PATCH] fix(diff): sync MySQL comment differences
---
.../src/components/diff/SchemaDiffDialog.vue | 17 ++
apps/desktop/src/i18n/locales/en.ts | 2 +
apps/desktop/src/i18n/locales/es.ts | 2 +
apps/desktop/src/i18n/locales/zh-CN.ts | 2 +
apps/desktop/src/i18n/locales/zh-TW.ts | 2 +
apps/desktop/src/lib/schemaDiff.ts | 1 +
crates/dbx-core/src/schema_diff.rs | 145 +++++++++++++++---
7 files changed, 152 insertions(+), 19 deletions(-)
diff --git a/apps/desktop/src/components/diff/SchemaDiffDialog.vue b/apps/desktop/src/components/diff/SchemaDiffDialog.vue
index 50a54715b3..98260584b0 100644
--- a/apps/desktop/src/components/diff/SchemaDiffDialog.vue
+++ b/apps/desktop/src/components/diff/SchemaDiffDialog.vue
@@ -54,6 +54,7 @@ const executedCount = ref(0);
const executeTotal = ref(0);
const syncErrors = ref<{ sql: string; error: string }[]>([]);
const syncSql = ref("");
+const ignoreComments = ref(false);
const highlightedSyncSql = computed(() => highlight(syncSql.value));
const allSelected = computed(() => diffs.value.length > 0 && diffs.value.every((d) => d.selected));
@@ -185,6 +186,7 @@ async function startCompare() {
targetDetails,
databaseType: targetConfig?.db_type || "mysql",
targetSchema: targetSchema.value,
+ ignoreComments: ignoreComments.value,
});
diffs.value = result.diffs.map((diff) => ({ ...diff, selected: true }));
@@ -346,6 +348,7 @@ watch(targetDatabase, (database) => {
});
watch(sourceSchema, () => resetResult());
watch(targetSchema, () => resetResult());
+watch(ignoreComments, () => resetResult());
watch(
open,
@@ -495,6 +498,13 @@ watch(
+
@@ -604,6 +614,13 @@ watch(
,
+
+ ;
+
+ {{ t("diff.comments") }}
+
{{ t("diff.newTable") }}
{{ t("diff.dropTable") }}
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 584e765e7d..842a06b6cb 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -1454,6 +1454,7 @@ export default {
selectConnection: "Select connection",
selectDatabase: "Select database",
selectSchema: "Select schema",
+ ignoreComments: "Ignore comment differences",
compare: "Compare",
comparing: "Comparing schemas...",
noDifferences: "No differences found",
@@ -1466,6 +1467,7 @@ export default {
indexes: "Indexes",
foreignKeys: "Foreign keys",
triggers: "Triggers",
+ comments: "Comments",
newTable: "New table",
dropTable: "Drop table",
generatedSql: "Sync SQL",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index e24f13cb4a..597601a472 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -1344,6 +1344,7 @@ export default {
selectConnection: "Seleccionar conexión",
selectDatabase: "Seleccionar base de datos",
selectSchema: "Seleccionar esquema",
+ ignoreComments: "Ignorar diferencias de comentarios",
compare: "Comparar",
comparing: "Comparando esquemas...",
noDifferences: "No se encontraron diferencias",
@@ -1356,6 +1357,7 @@ export default {
indexes: "Índices",
foreignKeys: "Claves foráneas",
triggers: "Disparadores",
+ comments: "Comentarios",
newTable: "Nueva tabla",
dropTable: "Eliminar tabla",
generatedSql: "SQL de sincronización",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 227f505e4e..548c1fc26d 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -1428,6 +1428,7 @@ export default {
selectConnection: "选择连接",
selectDatabase: "选择数据库",
selectSchema: "选择 Schema",
+ ignoreComments: "忽略注释差异",
compare: "开始比较",
comparing: "正在比较结构...",
noDifferences: "两个数据库结构完全一致",
@@ -1440,6 +1441,7 @@ export default {
indexes: "索引",
foreignKeys: "外键",
triggers: "触发器",
+ comments: "注释",
newTable: "新增表",
dropTable: "删除表",
generatedSql: "同步 SQL",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index 17d024c9a5..7521354a29 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -1428,6 +1428,7 @@ export default {
selectConnection: "選擇連線",
selectDatabase: "選擇資料庫",
selectSchema: "選擇 Schema",
+ ignoreComments: "忽略註解差異",
compare: "開始比較",
comparing: "正在比較結構……",
noDifferences: "兩個資料庫結構完全一致",
@@ -1440,6 +1441,7 @@ export default {
indexes: "索引",
foreignKeys: "外鍵",
triggers: "觸發器",
+ comments: "註解",
newTable: "新增資料表",
dropTable: "刪除資料表",
generatedSql: "同步 SQL",
diff --git a/apps/desktop/src/lib/schemaDiff.ts b/apps/desktop/src/lib/schemaDiff.ts
index a2ad576dd5..54d984e783 100644
--- a/apps/desktop/src/lib/schemaDiff.ts
+++ b/apps/desktop/src/lib/schemaDiff.ts
@@ -61,6 +61,7 @@ export interface SchemaDiffPreparationOptions {
targetDetails: TableSchemaDetail[];
databaseType: DatabaseType;
targetSchema?: string;
+ ignoreComments?: boolean;
}
export interface SchemaDiffPreparation {
diff --git a/crates/dbx-core/src/schema_diff.rs b/crates/dbx-core/src/schema_diff.rs
index 4f9e4686c3..d61b7d5f27 100644
--- a/crates/dbx-core/src/schema_diff.rs
+++ b/crates/dbx-core/src/schema_diff.rs
@@ -115,6 +115,8 @@ pub struct SchemaDiffPreparationOptions {
pub database_type: DatabaseType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_schema: Option
,
+ #[serde(default)]
+ pub ignore_comments: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -232,13 +234,14 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec {
for name in common {
let Some(source) = source_details.get(name.as_str()) else { continue };
let Some(target) = target_details.get(name.as_str()) else { continue };
- let column_diffs = diff_columns(&source.columns, &target.columns);
+ let column_diffs = diff_columns_with_options(&source.columns, &target.columns, options.ignore_comments);
let index_diffs = diff_indexes(&source.indexes, &target.indexes);
let foreign_key_diffs = diff_foreign_keys(&source.foreign_keys, &target.foreign_keys);
let trigger_diffs = diff_triggers(&source.triggers, &target.triggers);
let source_comment = source_table_comments.get(name.as_str()).cloned().unwrap_or(None);
let target_comment = target_table_comments.get(name.as_str()).cloned().unwrap_or(None);
- let comment_changed = source_comment.clone().unwrap_or_default() != target_comment.clone().unwrap_or_default();
+ let comment_changed = !options.ignore_comments
+ && source_comment.clone().unwrap_or_default() != target_comment.clone().unwrap_or_default();
if !column_diffs.is_empty()
|| !index_diffs.is_empty()
@@ -275,6 +278,10 @@ fn diff_names(source: &[String], target: &[String]) -> (Vec, Vec
}
pub fn diff_columns(source: &[ColumnInfo], target: &[ColumnInfo]) -> Vec {
+ diff_columns_with_options(source, target, false)
+}
+
+fn diff_columns_with_options(source: &[ColumnInfo], target: &[ColumnInfo], ignore_comments: bool) -> Vec {
let mut diffs = Vec::new();
let target_map: HashMap<&str, &ColumnInfo> = target.iter().map(|column| (column.name.as_str(), column)).collect();
let source_map: HashMap<&str, &ColumnInfo> = source.iter().map(|column| (column.name.as_str(), column)).collect();
@@ -301,8 +308,9 @@ pub fn diff_columns(source: &[ColumnInfo], target: &[ColumnInfo]) -> Vec String {
if let Some(default) = &col.column_default {
definition.push_str(&format!(" DEFAULT {default}"));
}
+ if matches!(db_type, DatabaseType::Mysql | DatabaseType::Doris | DatabaseType::StarRocks) {
+ if let Some(comment) = &col.comment {
+ definition.push_str(&format!(" COMMENT {}", comment_literal(comment)));
+ }
+ }
definition
}
@@ -755,21 +768,23 @@ pub fn generate_schema_sync_sql(diffs: &[TableDiff], db_type: DatabaseType, sche
lines.push(String::new());
}
- if let Some(columns) = &diff.columns {
- for column in columns {
- if let Some(source) = &column.source {
- if column.changes.iter().any(|change| change.starts_with("comment:")) {
- lines.push(column_comment_sql(
- &diff.name,
- &column.name,
- source.comment.as_deref().unwrap_or_default(),
- db_type,
- schema,
- ));
- }
- if column.diff_type == "added" {
- if let Some(comment) = &source.comment {
- lines.push(column_comment_sql(&diff.name, &column.name, comment, db_type, schema));
+ if !is_mysql {
+ if let Some(columns) = &diff.columns {
+ for column in columns {
+ if let Some(source) = &column.source {
+ if column.changes.iter().any(|change| change.starts_with("comment:")) {
+ lines.push(column_comment_sql(
+ &diff.name,
+ &column.name,
+ source.comment.as_deref().unwrap_or_default(),
+ db_type,
+ schema,
+ ));
+ }
+ if column.diff_type == "added" {
+ if let Some(comment) = &source.comment {
+ lines.push(column_comment_sql(&diff.name, &column.name, comment, db_type, schema));
+ }
}
}
}
@@ -862,6 +877,21 @@ mod tests {
}
}
+ fn column(name: &str, data_type: &str, comment: Option<&str>) -> ColumnInfo {
+ ColumnInfo {
+ name: name.to_string(),
+ data_type: data_type.to_string(),
+ is_nullable: false,
+ column_default: None,
+ is_primary_key: false,
+ extra: None,
+ comment: comment.map(str::to_string),
+ numeric_precision: None,
+ numeric_scale: None,
+ character_maximum_length: None,
+ }
+ }
+
#[test]
fn detects_modified_indexes_not_only_added_or_removed_indexes() {
let diffs = diff_indexes(
@@ -989,6 +1019,83 @@ mod tests {
);
}
+ #[test]
+ fn mysql_column_comment_changes_generate_modify_column_sql() {
+ let diffs = vec![TableDiff {
+ diff_type: "modified".to_string(),
+ object_type: None,
+ name: "users".to_string(),
+ columns: Some(vec![ColumnDiff {
+ diff_type: "modified".to_string(),
+ name: "name".to_string(),
+ source: Some(column("name", "varchar(64)", Some("用户姓名"))),
+ target: Some(column("name", "varchar(64)", Some("Name"))),
+ changes: vec!["comment: Name → 用户姓名".to_string()],
+ }]),
+ indexes: None,
+ foreign_keys: None,
+ triggers: None,
+ ddl: None,
+ source_table_comment: Some(Some("用户表".to_string())),
+ target_table_comment: Some(Some("Users".to_string())),
+ }];
+
+ assert_eq!(
+ generate_schema_sync_sql(&diffs, DatabaseType::Mysql, None),
+ [
+ "-- Alter table: users",
+ "ALTER TABLE `users`",
+ " MODIFY COLUMN `name` varchar(64) NOT NULL COMMENT '用户姓名';",
+ "",
+ "ALTER TABLE `users` COMMENT = '用户表';",
+ ]
+ .join("\n")
+ );
+ }
+
+ #[test]
+ fn ignore_comments_skips_column_and_table_comment_diffs() {
+ let options = SchemaDiffPreparationOptions {
+ source_tables: vec![TableInfo {
+ name: "users".to_string(),
+ table_type: "BASE TABLE".to_string(),
+ comment: Some("用户表".to_string()),
+ parent_schema: None,
+ parent_name: None,
+ }],
+ target_tables: vec![TableInfo {
+ name: "users".to_string(),
+ table_type: "BASE TABLE".to_string(),
+ comment: Some("Users".to_string()),
+ parent_schema: None,
+ parent_name: None,
+ }],
+ source_details: vec![TableSchemaDetail {
+ name: "users".to_string(),
+ columns: vec![column("name", "varchar(64)", Some("用户姓名"))],
+ indexes: Vec::new(),
+ foreign_keys: Vec::new(),
+ triggers: Vec::new(),
+ ddl: None,
+ }],
+ target_details: vec![TableSchemaDetail {
+ name: "users".to_string(),
+ columns: vec![column("name", "varchar(64)", Some("Name"))],
+ indexes: Vec::new(),
+ foreign_keys: Vec::new(),
+ triggers: Vec::new(),
+ ddl: None,
+ }],
+ database_type: DatabaseType::Mysql,
+ target_schema: None,
+ ignore_comments: true,
+ };
+
+ let result = prepare_schema_diff(options);
+ assert!(result.diffs.is_empty());
+ assert!(result.sync_sql.is_empty());
+ }
+
#[test]
fn qualifies_generated_schema_sync_sql_with_target_schema() {
let diffs = vec![TableDiff {