fix(schema): use sync sql for modified schema diff
This commit is contained in:
parent
dac97b83a6
commit
7e5fd88e63
|
|
@ -18,7 +18,7 @@ import { getSchemaDiffOptionsForDbType } from "@/lib/schemaDiffOptions";
|
|||
import { getDefaultOptionsForDbType } from "@/types/schemaDiff";
|
||||
import type { SchemaDiffCompareOptions, SchemaDiffConfig } from "@/types/schemaDiff";
|
||||
import type { ObjectSourceKind } from "@/types/database";
|
||||
import { convertToSchemaDiffObjects, groupDiffObjects, type OperationGroup, type SchemaDiffObject, type DiffOperationType, type DiffObjectKind, type SchemaDiffPreparation } from "@/lib/schemaDiff";
|
||||
import { buildDeploySqlForObjects, convertToSchemaDiffObjects, groupDiffObjects, type OperationGroup, type SchemaDiffObject, type DiffOperationType, type DiffObjectKind, type SchemaDiffPreparation } from "@/lib/schemaDiff";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
|
||||
|
|
@ -459,54 +459,7 @@ function handleToggleObjectSelection(objectId: string, selected: boolean) {
|
|||
}
|
||||
|
||||
function regenerateDeploySql() {
|
||||
// Only select top-level objects (exclude children)
|
||||
const selected = diffObjects.value.filter((o) => {
|
||||
const isTopLevel = !o.id.startsWith("col-") && !o.id.startsWith("idx-") && !o.id.startsWith("fk-") && !o.id.startsWith("trg-");
|
||||
return o.selected && o.operationType !== "none" && isTopLevel;
|
||||
});
|
||||
|
||||
if (selected.length === 0) {
|
||||
deploySql.value = "-- No objects selected";
|
||||
return;
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
for (const obj of selected) {
|
||||
if (obj.operationType === "create") {
|
||||
if (obj.sourceDdl) {
|
||||
lines.push(`-- Create ${obj.objectKind}: ${obj.name}`);
|
||||
lines.push(obj.sourceDdl);
|
||||
lines.push("");
|
||||
}
|
||||
} else if (obj.operationType === "delete") {
|
||||
lines.push(`-- Drop ${obj.objectKind}: ${obj.name}`);
|
||||
const dropSql = generateDropSql(obj);
|
||||
lines.push(dropSql);
|
||||
lines.push("");
|
||||
} else if (obj.operationType === "modify") {
|
||||
if (obj.sourceDdl) {
|
||||
lines.push(`-- Modify ${obj.objectKind}: ${obj.name}`);
|
||||
lines.push(obj.sourceDdl);
|
||||
lines.push("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deploySql.value = lines.join("\n") || "-- No DDL available for selected objects";
|
||||
}
|
||||
|
||||
function generateDropSql(obj: SchemaDiffObject): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
table: "TABLE",
|
||||
view: "VIEW",
|
||||
function: "FUNCTION",
|
||||
sequence: "SEQUENCE",
|
||||
rule: "RULE",
|
||||
owner: "OWNED BY",
|
||||
};
|
||||
const sqlType = typeMap[obj.objectKind] || obj.objectKind.toUpperCase();
|
||||
return `DROP ${sqlType} IF EXISTS ${obj.name};`;
|
||||
deploySql.value = buildDeploySqlForObjects(diffObjects.value);
|
||||
}
|
||||
|
||||
async function handleExecuteScript() {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ export interface TableDiff {
|
|||
targetDdl?: string;
|
||||
sourceTableComment?: string | null;
|
||||
targetTableComment?: string | null;
|
||||
syncSql?: string;
|
||||
}
|
||||
|
||||
export interface TableSchemaDetail {
|
||||
|
|
@ -202,6 +203,7 @@ export function convertToSchemaDiffObjects(tableDiffs: TableDiff[], functionDiff
|
|||
selected: opType !== "none",
|
||||
sourceDdl: diff.ddl,
|
||||
targetDdl: diff.targetDdl,
|
||||
deploySql: diff.syncSql,
|
||||
changes: diff.columns?.flatMap((c) => c.changes || []),
|
||||
children: [
|
||||
...(diff.columns?.map((c) => ({
|
||||
|
|
@ -310,6 +312,61 @@ export function convertToSchemaDiffObjects(tableDiffs: TableDiff[], functionDiff
|
|||
return objects;
|
||||
}
|
||||
|
||||
export function buildDeploySqlForObjects(objects: SchemaDiffObject[]): string {
|
||||
const selected = objects.filter((o) => {
|
||||
const isTopLevel = !o.id.startsWith("col-") && !o.id.startsWith("idx-") && !o.id.startsWith("fk-") && !o.id.startsWith("trg-");
|
||||
return o.selected && o.operationType !== "none" && isTopLevel;
|
||||
});
|
||||
|
||||
if (selected.length === 0) {
|
||||
return "-- No objects selected";
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
for (const obj of selected) {
|
||||
if (obj.deploySql?.trim()) {
|
||||
lines.push(obj.deploySql.trim());
|
||||
lines.push("");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (obj.operationType === "create") {
|
||||
if (obj.sourceDdl) {
|
||||
lines.push(`-- Create ${obj.objectKind}: ${obj.name}`);
|
||||
lines.push(obj.sourceDdl);
|
||||
lines.push("");
|
||||
}
|
||||
} else if (obj.operationType === "delete") {
|
||||
lines.push(`-- Drop ${obj.objectKind}: ${obj.name}`);
|
||||
const dropSql = generateDropSql(obj);
|
||||
lines.push(dropSql);
|
||||
lines.push("");
|
||||
} else if (obj.operationType === "modify") {
|
||||
if (obj.sourceDdl) {
|
||||
lines.push(`-- Modify ${obj.objectKind}: ${obj.name}`);
|
||||
lines.push(obj.sourceDdl);
|
||||
lines.push("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n") || "-- No DDL available for selected objects";
|
||||
}
|
||||
|
||||
function generateDropSql(obj: SchemaDiffObject): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
table: "TABLE",
|
||||
view: "VIEW",
|
||||
function: "FUNCTION",
|
||||
sequence: "SEQUENCE",
|
||||
rule: "RULE",
|
||||
owner: "OWNED BY",
|
||||
};
|
||||
const sqlType = typeMap[obj.objectKind] || obj.objectKind.toUpperCase();
|
||||
return `DROP ${sqlType} IF EXISTS ${obj.name};`;
|
||||
}
|
||||
|
||||
export interface ObjectTypeGroup {
|
||||
kind: DiffObjectKind;
|
||||
label: string;
|
||||
|
|
|
|||
|
|
@ -143,6 +143,8 @@ pub struct TableDiff {
|
|||
pub source_table_comment: Option<Option<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target_table_comment: Option<Option<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sync_sql: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -213,11 +215,26 @@ pub struct SchemaDiffPreparation {
|
|||
}
|
||||
|
||||
pub fn prepare_schema_diff(options: SchemaDiffPreparationOptions) -> SchemaDiffPreparation {
|
||||
let diffs = diff_schema(&options);
|
||||
let mut diffs = diff_schema(&options);
|
||||
let function_diffs = diff_functions(&options.source_functions, &options.target_functions);
|
||||
let sequence_diffs = diff_sequences(&options.source_sequences, &options.target_sequences);
|
||||
let rule_diffs = diff_rules(&options.source_rules, &options.target_rules);
|
||||
let owner_diffs = diff_owners(&options.source_owners, &options.target_owners);
|
||||
for diff in &mut diffs {
|
||||
let sync_sql = generate_schema_sync_sql(
|
||||
std::slice::from_ref(diff),
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
options.database_type,
|
||||
options.target_schema.as_deref(),
|
||||
options.cascade_delete,
|
||||
);
|
||||
if !sync_sql.is_empty() {
|
||||
diff.sync_sql = Some(sync_sql);
|
||||
}
|
||||
}
|
||||
let sync_sql = generate_schema_sync_sql(
|
||||
&diffs,
|
||||
&function_diffs,
|
||||
|
|
@ -283,6 +300,7 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec<TableDiff> {
|
|||
triggers: None,
|
||||
source_table_comment: None,
|
||||
target_table_comment: None,
|
||||
sync_sql: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -300,6 +318,7 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec<TableDiff> {
|
|||
target_ddl: target_details.get(name_clone.as_str()).and_then(|detail| detail.ddl.clone()),
|
||||
source_table_comment: None,
|
||||
target_table_comment: None,
|
||||
sync_sql: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -317,6 +336,7 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec<TableDiff> {
|
|||
target_ddl: None,
|
||||
source_table_comment: None,
|
||||
target_table_comment: None,
|
||||
sync_sql: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -334,6 +354,7 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec<TableDiff> {
|
|||
target_ddl: target_details.get(name_clone.as_str()).and_then(|detail| detail.ddl.clone()),
|
||||
source_table_comment: None,
|
||||
target_table_comment: None,
|
||||
sync_sql: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -368,6 +389,7 @@ fn diff_schema(options: &SchemaDiffPreparationOptions) -> Vec<TableDiff> {
|
|||
target_ddl: target_details.get(name_clone.as_str()).and_then(|detail| detail.ddl.clone()),
|
||||
source_table_comment: if has_diff { comment_changed.then_some(source_comment) } else { None },
|
||||
target_table_comment: if has_diff { comment_changed.then_some(target_comment) } else { None },
|
||||
sync_sql: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1536,6 +1558,7 @@ mod tests {
|
|||
target_ddl: None,
|
||||
source_table_comment: None,
|
||||
target_table_comment: None,
|
||||
sync_sql: None,
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -1570,6 +1593,7 @@ mod tests {
|
|||
target_ddl: None,
|
||||
source_table_comment: Some(Some("用户表".to_string())),
|
||||
target_table_comment: Some(Some("Users".to_string())),
|
||||
sync_sql: None,
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -1637,6 +1661,60 @@ mod tests {
|
|||
assert!(result.sync_sql.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_schema_diff_attaches_per_table_sync_sql() {
|
||||
let options = SchemaDiffPreparationOptions {
|
||||
source_tables: vec![TableInfo {
|
||||
name: "users".to_string(),
|
||||
table_type: "BASE TABLE".to_string(),
|
||||
comment: None,
|
||||
parent_schema: None,
|
||||
parent_name: None,
|
||||
}],
|
||||
target_tables: vec![TableInfo {
|
||||
name: "users".to_string(),
|
||||
table_type: "BASE TABLE".to_string(),
|
||||
comment: None,
|
||||
parent_schema: None,
|
||||
parent_name: None,
|
||||
}],
|
||||
source_details: vec![TableSchemaDetail {
|
||||
name: "users".to_string(),
|
||||
columns: vec![column("name", "varchar(128)", None)],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
ddl: Some("CREATE TABLE `users` (`name` varchar(128));".to_string()),
|
||||
}],
|
||||
target_details: vec![TableSchemaDetail {
|
||||
name: "users".to_string(),
|
||||
columns: vec![column("name", "varchar(64)", None)],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
ddl: Some("CREATE TABLE `users` (`name` varchar(64));".to_string()),
|
||||
}],
|
||||
source_functions: Vec::new(),
|
||||
target_functions: Vec::new(),
|
||||
source_sequences: Vec::new(),
|
||||
target_sequences: Vec::new(),
|
||||
source_rules: Vec::new(),
|
||||
target_rules: Vec::new(),
|
||||
source_owners: Vec::new(),
|
||||
target_owners: Vec::new(),
|
||||
database_type: DatabaseType::Mysql,
|
||||
target_schema: None,
|
||||
ignore_comments: false,
|
||||
cascade_delete: false,
|
||||
};
|
||||
|
||||
let result = prepare_schema_diff(options);
|
||||
let table_sync_sql = result.diffs[0].sync_sql.as_deref().unwrap_or_default();
|
||||
|
||||
assert!(table_sync_sql.contains("ALTER TABLE `users`"));
|
||||
assert!(!table_sync_sql.contains("CREATE TABLE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualifies_generated_schema_sync_sql_with_target_schema() {
|
||||
let diffs = vec![TableDiff {
|
||||
|
|
@ -1683,6 +1761,7 @@ mod tests {
|
|||
target_ddl: None,
|
||||
source_table_comment: None,
|
||||
target_table_comment: None,
|
||||
sync_sql: None,
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
import { buildDeploySqlForObjects, convertToSchemaDiffObjects, type TableDiff } from "../../apps/desktop/src/lib/schemaDiff.ts";
|
||||
|
||||
test("uses generated sync SQL for modified table deployment", () => {
|
||||
const tableDiffs: TableDiff[] = [
|
||||
{
|
||||
type: "modified",
|
||||
objectType: "table",
|
||||
name: "users",
|
||||
ddl: "CREATE TABLE `users` (`name` varchar(64));",
|
||||
syncSql: "-- Alter table: users\nALTER TABLE `users`\n MODIFY COLUMN `name` varchar(128) NOT NULL;",
|
||||
columns: [
|
||||
{
|
||||
type: "modified",
|
||||
name: "name",
|
||||
changes: ["type: varchar(64) -> varchar(128)"],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const objects = convertToSchemaDiffObjects(tableDiffs);
|
||||
const deploySql = buildDeploySqlForObjects(objects);
|
||||
|
||||
assert.equal(
|
||||
deploySql,
|
||||
"-- Alter table: users\nALTER TABLE `users`\n MODIFY COLUMN `name` varchar(128) NOT NULL;\n",
|
||||
);
|
||||
assert.equal(deploySql.includes("CREATE TABLE"), false);
|
||||
});
|
||||
|
||||
test("falls back to source DDL when object sync SQL is unavailable", () => {
|
||||
const tableDiffs: TableDiff[] = [
|
||||
{
|
||||
type: "added",
|
||||
objectType: "table",
|
||||
name: "users",
|
||||
ddl: "CREATE TABLE `users` (`id` int);",
|
||||
},
|
||||
];
|
||||
|
||||
const objects = convertToSchemaDiffObjects(tableDiffs);
|
||||
|
||||
assert.equal(buildDeploySqlForObjects(objects), "-- Create table: users\nCREATE TABLE `users` (`id` int);\n");
|
||||
});
|
||||
Loading…
Reference in New Issue