feat(schema-diff): select fields and indexes for synchronization

This commit is contained in:
zhangyc authored and GitHub committed 2026-08-23 12:24:47 +08:00
1 parent d8bc5153d2
commit 726f86e24c
32 files changed
+1185 -413

No files matched your search

+92
View File
@@ -397,6 +397,17 @@ pub struct SchemaDiffPreparation {
pub dependency_graph: Option<DependencyGraph>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SchemaSyncSqlPlan {
pub sync_sql: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub rollback_sync_sql: Option<String>,
pub rollback_completeness: RollbackCompleteness,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub missing_rollback_objects: Vec<MissingRollbackObject>,
}
fn default_rollback_complete() -> RollbackCompleteness {
RollbackCompleteness::Complete
}
@@ -3839,6 +3850,50 @@ pub fn generate_schema_sync_sql(
.0
}
#[allow(clippy::too_many_arguments)]
pub fn generate_schema_sync_sql_plan(
diffs: &[TableDiff],
function_diffs: &[FunctionDiff],
sequence_diffs: &[SequenceDiff],
rule_diffs: &[RuleDiff],
owner_diffs: &[OwnerDiff],
db_type: DatabaseType,
schema: Option<&str>,
cascade_delete: bool,
source_dialect: Option<DialectKind>,
field_mappings: &[FieldMapping],
enable_rollback: bool,
) -> SchemaSyncSqlPlan {
let (sync_sql, _) = generate_schema_sync_sql_inner(
diffs,
function_diffs,
sequence_diffs,
rule_diffs,
owner_diffs,
db_type,
schema,
cascade_delete,
source_dialect,
field_mappings,
);
let (rollback_sync_sql, missing_rollback_objects) = if enable_rollback {
let dependency_graph = DependencyGraph { nodes: HashMap::new(), topological_order: Vec::new() };
let rollback_graph = RollbackGraph::from_forward_diffs(diffs, &[], &dependency_graph);
let (sql, missing) = generate_rollback_sync_sql_with_missing(&rollback_graph, db_type, schema, cascade_delete);
(Some(sql), missing)
} else {
(None, Vec::new())
};
let rollback_completeness = if missing_rollback_objects.is_empty() {
RollbackCompleteness::Complete
} else {
RollbackCompleteness::Incomplete
};
SchemaSyncSqlPlan { sync_sql, rollback_sync_sql, rollback_completeness, missing_rollback_objects }
}
fn generate_schema_sync_sql_inner(
diffs: &[TableDiff],
function_diffs: &[FunctionDiff],
@@ -5952,6 +6007,43 @@ mod tests {
assert!(!table_sync_sql.contains("CREATE TABLE"));
}
#[test]
fn schema_sync_plan_builds_matching_forward_and_rollback_sql_for_selected_children() {
let selected_diff = TableDiff {
diff_type: "modified".to_string(),
object_type: Some("table".to_string()),
name: "users".to_string(),
columns: Some(vec![ColumnDiff {
diff_type: "added".to_string(),
name: "nickname".to_string(),
source: Some(column("nickname", "varchar(64)", None)),
target: None,
changes: Vec::new(),
add_position: None,
}]),
..Default::default()
};
let plan = generate_schema_sync_sql_plan(
&[selected_diff],
&[],
&[],
&[],
&[],
DatabaseType::Mysql,
Some("shop"),
false,
None,
&[],
true,
);
assert!(plan.sync_sql.contains("ADD COLUMN `nickname`"), "{}", plan.sync_sql);
let rollback = plan.rollback_sync_sql.expect("rollback SQL");
assert!(rollback.contains("DROP COLUMN `nickname`"), "{rollback}");
assert_eq!(plan.rollback_completeness, RollbackCompleteness::Complete);
}
#[test]
fn qualifies_generated_schema_sync_sql_with_target_schema() {
let diffs = vec![TableDiff {
+1
View File
@@ -448,6 +448,7 @@ async fn main() {
.route("/dialect/data-types", get(routes::dialect::list_data_types))
.route("/schema-diff/prepare", post(routes::schema_diff::prepare_schema_diff))
.route("/schema-diff/generate-sync-sql", post(routes::schema_diff::generate_schema_sync_sql))
.route("/schema-diff/generate-sync-plan", post(routes::schema_diff::generate_schema_sync_plan))
.route(
"/schema/cache",
post(routes::schema_cache::save_schema_cache).get(routes::schema_cache::load_schema_cache),
+19
View File
@@ -14,6 +14,25 @@ pub struct GenerateSchemaSyncSqlRequest {
pub cascade_delete: Option<bool>,
pub source_dialect: Option<String>,
pub field_mappings: Option<Vec<dbx_core::schema_diff::FieldMapping>>,
pub enable_rollback: Option<bool>,
}
pub async fn generate_schema_sync_plan(
Json(req): Json<GenerateSchemaSyncSqlRequest>,
) -> Json<dbx_core::schema_diff::SchemaSyncSqlPlan> {
Json(dbx_core::schema_diff::generate_schema_sync_sql_plan(
&req.diffs,
req.function_diffs.as_deref().unwrap_or_default(),
req.sequence_diffs.as_deref().unwrap_or_default(),
req.rule_diffs.as_deref().unwrap_or_default(),
req.owner_diffs.as_deref().unwrap_or_default(),
req.database_type,
req.target_schema.as_deref(),
req.cascade_delete.unwrap_or(false),
req.source_dialect.as_deref().and_then(dbx_core::sql_dialect::descriptor::DialectKind::from_label),
req.field_mappings.as_deref().unwrap_or(&[]),
req.enable_rollback.unwrap_or(false),
))
}
pub async fn prepare_schema_diff(