feat(plugin): 级联删除 — relations OnDeleteStrategy 支持
delete 方法扩展为处理三种级联策略:Restrict(存在关联时拒绝删除)、 Nullify(置空外键字段)、Cascade(级联软删除关联记录)。 在软删除主记录之前按声明顺序处理所有关联关系。
This commit is contained in:
@@ -262,6 +262,65 @@ impl PluginDataService {
|
|||||||
_event_bus: &EventBus,
|
_event_bus: &EventBus,
|
||||||
) -> AppResult<()> {
|
) -> AppResult<()> {
|
||||||
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
|
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
|
||||||
|
|
||||||
|
// 解析 entity schema 获取 relations
|
||||||
|
let entity_def: crate::manifest::PluginEntity =
|
||||||
|
serde_json::from_value(info.schema_json.clone())
|
||||||
|
.map_err(|e| AppError::Internal(format!("解析 entity schema 失败: {}", e)))?;
|
||||||
|
|
||||||
|
let manifest_id = resolve_manifest_id(plugin_id, tenant_id, db).await?;
|
||||||
|
|
||||||
|
// 处理级联关系
|
||||||
|
for relation in &entity_def.relations {
|
||||||
|
let rel_table = DynamicTableManager::table_name(&manifest_id, &relation.entity);
|
||||||
|
let fk = sanitize_identifier(&relation.foreign_key);
|
||||||
|
|
||||||
|
match relation.on_delete {
|
||||||
|
crate::manifest::OnDeleteStrategy::Restrict => {
|
||||||
|
let check_sql = format!(
|
||||||
|
"SELECT 1 as chk FROM \"{}\" WHERE data->>'{}' = $1 AND tenant_id = $2 AND deleted_at IS NULL LIMIT 1",
|
||||||
|
rel_table, fk
|
||||||
|
);
|
||||||
|
#[derive(FromQueryResult)]
|
||||||
|
struct RefCheck { chk: Option<i32> }
|
||||||
|
let has_ref = RefCheck::find_by_statement(Statement::from_sql_and_values(
|
||||||
|
sea_orm::DatabaseBackend::Postgres,
|
||||||
|
check_sql,
|
||||||
|
[id.to_string().into(), tenant_id.into()],
|
||||||
|
)).one(db).await?;
|
||||||
|
if has_ref.is_some() {
|
||||||
|
return Err(AppError::Validation(format!(
|
||||||
|
"存在关联的 {} 记录,无法删除",
|
||||||
|
relation.entity
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::manifest::OnDeleteStrategy::Nullify => {
|
||||||
|
let nullify_sql = format!(
|
||||||
|
"UPDATE \"{}\" SET data = jsonb_set(data, '{{{}}}', 'null'), updated_at = NOW() WHERE data->>'{}' = $1 AND tenant_id = $2 AND deleted_at IS NULL",
|
||||||
|
rel_table, fk, fk
|
||||||
|
);
|
||||||
|
db.execute(Statement::from_sql_and_values(
|
||||||
|
sea_orm::DatabaseBackend::Postgres,
|
||||||
|
nullify_sql,
|
||||||
|
[id.to_string().into(), tenant_id.into()],
|
||||||
|
)).await?;
|
||||||
|
}
|
||||||
|
crate::manifest::OnDeleteStrategy::Cascade => {
|
||||||
|
let cascade_sql = format!(
|
||||||
|
"UPDATE \"{}\" SET deleted_at = NOW(), updated_at = NOW() WHERE data->>'{}' = $1 AND tenant_id = $2 AND deleted_at IS NULL",
|
||||||
|
rel_table, fk
|
||||||
|
);
|
||||||
|
db.execute(Statement::from_sql_and_values(
|
||||||
|
sea_orm::DatabaseBackend::Postgres,
|
||||||
|
cascade_sql,
|
||||||
|
[id.to_string().into(), tenant_id.into()],
|
||||||
|
)).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 软删除主记录
|
||||||
let (sql, values) = DynamicTableManager::build_delete_sql(&info.table_name, id, tenant_id);
|
let (sql, values) = DynamicTableManager::build_delete_sql(&info.table_name, id, tenant_id);
|
||||||
|
|
||||||
db.execute(Statement::from_sql_and_values(
|
db.execute(Statement::from_sql_and_values(
|
||||||
|
|||||||
Reference in New Issue
Block a user