fix(schema-diff): qualify MySQL deploy SQL target

This commit is contained in:
t8y2 2026-06-28 11:05:04 +08:00
parent 9a730ce188
commit 0c2d95b83e
4 changed files with 89 additions and 3 deletions

View File

@ -19,7 +19,7 @@ import { createConcurrencyLimiter, mapWithConcurrency, schemaDiffMetadataConcurr
import { normalizeSchemaDiffCompareOptions } from "@/types/schemaDiff";
import type { SchemaDiffCompareOptions, SchemaDiffConfig } from "@/types/schemaDiff";
import type { ObjectSourceKind, TableInfo } from "@/types/database";
import { buildDeploySqlForObjects, convertToSchemaDiffObjects, groupDiffObjects, type OperationGroup, type SchemaDiffObject, type DiffOperationType, type DiffObjectKind, type SchemaDiffPreparation, type TableSchemaDetail } from "@/lib/schemaDiff";
import { buildDeploySqlForObjects, convertToSchemaDiffObjects, groupDiffObjects, schemaDiffDeployTargetSchema, type OperationGroup, type SchemaDiffObject, type DiffOperationType, type DiffObjectKind, type SchemaDiffPreparation, type TableSchemaDetail } from "@/lib/schemaDiff";
import { compileSchemaDiffTableFilter, filterSchemaDiffTables } from "@/lib/schemaDiffTableFilter";
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
@ -390,7 +390,7 @@ async function handleCompare() {
sourceOwners: srcOwners,
targetOwners: tgtOwners,
databaseType: dbType,
targetSchema: targetSchema.value,
targetSchema: schemaDiffDeployTargetSchema(dbType, targetDatabase.value, targetSchema.value),
ignoreComments: ignoreComments.value,
cascadeDelete: opts?.cascadeDelete ?? false,
compareColumnOrder: opts.compareColumnOrder,

View File

@ -117,6 +117,20 @@ export interface SchemaDiffPreparation {
syncSql: string;
}
const MYSQL_LIKE_SCHEMA_DIFF_TARGET_TYPES = new Set<DatabaseType>(["mysql", "doris", "starrocks", "goldendb", "sundb", "databend", "gbase"]);
export function schemaDiffDeployTargetSchema(databaseType: DatabaseType | undefined, targetDatabase: string, targetSchema?: string): string | undefined {
const schema = targetSchema?.trim();
if (schema) return schema;
const database = targetDatabase.trim();
if (databaseType && MYSQL_LIKE_SCHEMA_DIFF_TARGET_TYPES.has(databaseType) && database) {
return database;
}
return undefined;
}
// Unified object type for UI display
export type DiffOperationType = "modify" | "create" | "delete" | "none";
export type DiffObjectKind = "table" | "view" | "function" | "sequence" | "rule" | "owner" | "index" | "trigger" | "foreignKey";

View File

@ -957,6 +957,8 @@ fn column_def(col: &ColumnInfo, db_type: DatabaseType) -> String {
fn qualified_name(name: &str, db_type: DatabaseType, schema: Option<&str>) -> String {
schema
.map(str::trim)
.filter(|schema| !schema.is_empty())
.map(|schema| format!("{}.{}", quote_id(schema, db_type), quote_id(name, db_type)))
.unwrap_or_else(|| quote_id(name, db_type))
}
@ -1660,6 +1662,69 @@ mod tests {
);
}
#[test]
fn mysql_schema_sync_sql_qualifies_tables_with_target_database() {
let diffs = vec![TableDiff {
diff_type: "modified".to_string(),
object_type: None,
name: "notify_channel_config".to_string(),
columns: Some(vec![ColumnDiff {
diff_type: "modified".to_string(),
name: "config_json".to_string(),
source: Some(column("config_json", "json", Some("渠道配置"))),
target: Some(column("config_json", "json", Some("Config"))),
changes: vec!["comment: Config → 渠道配置".to_string()],
}]),
indexes: None,
foreign_keys: None,
triggers: None,
ddl: None,
target_ddl: None,
source_table_comment: None,
target_table_comment: None,
sync_sql: None,
}];
assert_eq!(
generate_schema_sync_sql(&diffs, &[], &[], &[], &[], DatabaseType::Mysql, Some("target_db"), false),
[
"-- Alter table: notify_channel_config",
"ALTER TABLE `target_db`.`notify_channel_config`",
" MODIFY COLUMN `config_json` json NOT NULL COMMENT '渠道配置';",
]
.join("\n")
);
}
#[test]
fn blank_target_schema_does_not_generate_empty_qualifier() {
let diffs = vec![TableDiff {
diff_type: "modified".to_string(),
object_type: None,
name: "notify_channel_config".to_string(),
columns: Some(vec![ColumnDiff {
diff_type: "modified".to_string(),
name: "config_json".to_string(),
source: Some(column("config_json", "json", Some("渠道配置"))),
target: Some(column("config_json", "json", Some("Config"))),
changes: vec!["comment: Config → 渠道配置".to_string()],
}]),
indexes: None,
foreign_keys: None,
triggers: None,
ddl: None,
target_ddl: None,
source_table_comment: None,
target_table_comment: None,
sync_sql: None,
}];
let sql = generate_schema_sync_sql(&diffs, &[], &[], &[], &[], DatabaseType::Mysql, Some(" "), false);
assert!(sql.contains("ALTER TABLE `notify_channel_config`"));
assert!(!sql.contains("``."));
}
#[test]
fn ignore_comments_skips_column_and_table_comment_diffs() {
let options = SchemaDiffPreparationOptions {

View File

@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import { buildDeploySqlForObjects, convertToSchemaDiffObjects, type TableDiff } from "../../apps/desktop/src/lib/schemaDiff.ts";
import { buildDeploySqlForObjects, convertToSchemaDiffObjects, schemaDiffDeployTargetSchema, type TableDiff } from "../../apps/desktop/src/lib/schemaDiff.ts";
test("uses generated sync SQL for modified table deployment", () => {
const tableDiffs: TableDiff[] = [
@ -44,3 +44,10 @@ test("falls back to source DDL when object sync SQL is unavailable", () => {
assert.equal(buildDeploySqlForObjects(objects), "-- Create table: users\nCREATE TABLE `users` (`id` int);\n");
});
test("uses mysql target database as schema diff deploy qualifier", () => {
assert.equal(schemaDiffDeployTargetSchema("mysql", "target_db", ""), "target_db");
assert.equal(schemaDiffDeployTargetSchema("mysql", "target_db", " "), "target_db");
assert.equal(schemaDiffDeployTargetSchema("mysql", "target_db", "explicit_schema"), "explicit_schema");
assert.equal(schemaDiffDeployTargetSchema("sqlite", "main", ""), undefined);
});