valence_core/
admin_entity_delete.rs1use crate::deletion::dag::table_skips_pending_deletion_filter;
4use crate::deletion::dag::DeletionDag;
5use crate::deletion::{dispatch, DeletionRequest, DeletionService};
6use crate::error::{Error, Result};
7use crate::ownership;
8use crate::privacy::{PrivacyEvaluator, PrivacyOperation};
9use crate::query::QueryCore;
10use crate::runtime::Valence;
11use crate::schema::SchemaRegistry;
12
13pub async fn queue_delete_entity(table: &str, id: &str, v: &Valence) -> Result<()> {
14 if table_skips_pending_deletion_filter(table) {
15 return Err(Error::Validation(format!(
16 "queued delete is not supported for table {table:?}"
17 )));
18 }
19
20 let registry = SchemaRegistry::global();
21 let schema = registry
22 .get_schema(table)
23 .ok_or_else(|| Error::NotFound(format!("unknown table {table}")))?;
24
25 let existing = match QueryCore::get_record_json(table, id, v).await? {
26 Some(j) => j,
27 None => return Ok(()),
28 };
29
30 PrivacyEvaluator::check_entity_read(schema, &existing, v).await?;
31 PrivacyEvaluator::check_entity_access(schema, PrivacyOperation::Delete, &existing, v).await?;
32
33 let bare = ownership::normalize_record_id_for_ownership(id);
34 if let Ok(Some(ownership)) =
35 ownership::OwnershipService::get_ownership_json(table, &bare, v).await
36 {
37 if ownership.get("status").and_then(|s| s.as_str()) == Some("pending_deletion") {
38 return Ok(());
39 }
40 }
41
42 let dag = DeletionDag::compute(table, &bare, v).await?;
43 if !dag.restrict_violations.is_empty() {
44 #[cfg(feature = "instrumentation")]
45 for v in &dag.restrict_violations {
46 crate::instrumentation::record_restrict_blocked(
47 table,
48 &bare,
49 &v.connection_name,
50 v.blocking_record_count.max(0) as usize,
51 );
52 }
53 return Err(Error::Validation(format!(
54 "delete restricted: {:?}",
55 dag.restrict_violations
56 )));
57 }
58
59 ownership::OwnershipService::mark_pending_deletion(table, &bare, v).await?;
60
61 let actor_json = serde_json::to_value(v.actor()).unwrap_or(serde_json::Value::Null);
62 let run_id = DeletionService::create_run(table, &bare, actor_json.clone(), v).await?;
63 #[cfg(feature = "instrumentation")]
64 {
65 let max_depth = dag.nodes.iter().map(|n| n.depth).max().unwrap_or(0) as usize;
66 crate::instrumentation::record_run_queued(table, &bare, dag.nodes.len(), max_depth);
67 }
68 dispatch(DeletionRequest {
69 run_id,
70 root_table: table.to_string(),
71 root_record_id: bare,
72 actor_json,
73 })
74 .await?;
75
76 Ok(())
77}