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.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 {