mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-22 15:30:41 +00:00
fix(diff): sync MySQL comment differences
This commit is contained in:
@@ -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(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<input id="schema-diff-ignore-comments" v-model="ignoreComments" type="checkbox" class="accent-primary" />
|
||||
<Label for="schema-diff-ignore-comments" class="cursor-pointer text-xs">
|
||||
{{ t("diff.ignoreComments") }}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<!-- Comparing -->
|
||||
<div v-if="step === 'comparing'" class="flex items-center justify-center py-8 text-sm text-muted-foreground">
|
||||
<Loader2 class="w-4 h-4 animate-spin mr-2" />
|
||||
@@ -604,6 +614,13 @@ watch(
|
||||
<span v-if="ti < d.triggers!.length - 1">, </span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="d.type === 'modified' && d.sourceTableComment !== undefined">
|
||||
<span
|
||||
v-if="d.columns?.length || d.indexes?.length || d.foreignKeys?.length || d.triggers?.length"
|
||||
>;
|
||||
</span>
|
||||
<span>{{ t("diff.comments") }}</span>
|
||||
</template>
|
||||
<span v-else-if="d.type === 'added'" class="text-green-500">{{ t("diff.newTable") }}</span>
|
||||
<span v-else-if="d.type === 'removed'" class="text-red-500">{{ t("diff.dropTable") }}</span>
|
||||
</td>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface SchemaDiffPreparationOptions {
|
||||
targetDetails: TableSchemaDetail[];
|
||||
databaseType: DatabaseType;
|
||||
targetSchema?: string;
|
||||
ignoreComments?: boolean;
|
||||
}
|
||||
|
||||
export interface SchemaDiffPreparation {
|
||||
|
||||
@@ -115,6 +115,8 @@ pub struct SchemaDiffPreparationOptions {
|
||||
pub database_type: DatabaseType,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub target_schema: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ignore_comments: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -232,13 +234,14 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec<TableDiff> {
|
||||
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<String>, Vec<String>
|
||||
}
|
||||
|
||||
pub fn diff_columns(source: &[ColumnInfo], target: &[ColumnInfo]) -> Vec<ColumnDiff> {
|
||||
diff_columns_with_options(source, target, false)
|
||||
}
|
||||
|
||||
fn diff_columns_with_options(source: &[ColumnInfo], target: &[ColumnInfo], ignore_comments: bool) -> Vec<ColumnDiff> {
|
||||
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<ColumnD
|
||||
source_column.column_default.as_deref().unwrap_or("NULL")
|
||||
));
|
||||
}
|
||||
if source_column.comment.as_deref().unwrap_or_default()
|
||||
!= target_column.comment.as_deref().unwrap_or_default()
|
||||
if !ignore_comments
|
||||
&& source_column.comment.as_deref().unwrap_or_default()
|
||||
!= target_column.comment.as_deref().unwrap_or_default()
|
||||
{
|
||||
changes.push(format!(
|
||||
"comment: {} → {}",
|
||||
@@ -551,6 +559,11 @@ fn column_def(col: &ColumnInfo, db_type: DatabaseType) -> 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 {
|
||||
|
||||
Reference in New Issue
Block a user