feat(plugin): 级联删除 — relations OnDeleteStrategy 支持
delete 方法扩展为处理三种级联策略:Restrict(存在关联时拒绝删除)、 Nullify(置空外键字段)、Cascade(级联软删除关联记录)。 在软删除主记录之前按声明顺序处理所有关联关系。
This commit is contained in:
@@ -262,6 +262,65 @@ impl PluginDataService {
|
||||
_event_bus: &EventBus,
|
||||
) -> AppResult<()> {
|
||||
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);
|
||||
|
||||
db.execute(Statement::from_sql_and_values(
|
||||
|
||||
Reference in New Issue
Block a user