uqa_sql/schema/
truncate.rs1use crate::SQLError;
10use std::collections::BTreeSet;
11
12pub trait TruncateCatalog {
14 fn try_resolve_visible_relation_kind(
15 &self,
16 name: &str,
17 ) -> Result<Option<(String, &'static str)>, SQLError>;
18 fn is_partitioned(&self, table: &str) -> Result<bool, String>;
19 fn hierarchy_scan_tables(
20 &self,
21 table: &str,
22 descendants: bool,
23 ) -> Result<Vec<String>, SQLError>;
24 fn referrers_to(&self, table: &str) -> Result<Vec<String>, String>;
25}
26
27#[cfg(test)]
28mod tests;
29
30#[derive(Debug)]
31pub struct TruncateTargets {
32 pub all: BTreeSet<String>,
33 pub trigger_order: Vec<String>,
34 pub privilege_targets: BTreeSet<String>,
35}
36
37pub fn resolve_truncate_targets(
38 catalog: &dyn TruncateCatalog,
39 tables: &[crate::ast::TruncateTarget],
40 cascade: bool,
41) -> Result<TruncateTargets, SQLError> {
42 let mut targets = BTreeSet::new();
43 let mut trigger_targets = Vec::new();
44 let mut privilege_targets = BTreeSet::new();
45 for requested in tables {
46 let Some((table, "table")) = catalog.try_resolve_visible_relation_kind(&requested.table)?
47 else {
48 return Err(SQLError::Unsupported(format!(
49 "TRUNCATE TABLE: relation `{}` does not exist",
50 requested.table
51 )));
52 };
53 privilege_targets.insert(table.clone());
54 let partitioned = catalog
55 .is_partitioned(&table)
56 .map_err(|err| SQLError::Internal(format!("read table hierarchy: {err}")))?;
57 if !requested.include_descendants && partitioned {
58 return Err(SQLError::Routine {
59 sqlstate: "42809".into(),
60 message: "cannot truncate only a partitioned table".into(),
61 });
62 }
63 for target in catalog.hierarchy_scan_tables(&table, requested.include_descendants)? {
64 if targets.insert(target.clone()) {
65 trigger_targets.push(target);
66 }
67 }
68 }
69 if cascade {
70 let mut cursor = 0;
71 while let Some(table) = trigger_targets.get(cursor).cloned() {
72 cursor += 1;
73 for referrer in catalog
74 .referrers_to(&table)
75 .map_err(|err| SQLError::Internal(format!("read foreign keys: {err}")))?
76 {
77 if targets.insert(referrer.clone()) {
78 privilege_targets.insert(referrer.clone());
79 trigger_targets.push(referrer);
80 }
81 }
82 }
83 }
84 Ok(TruncateTargets {
85 all: targets,
86 trigger_order: trigger_targets,
87 privilege_targets,
88 })
89}
90
91pub fn validate_truncate_references(
92 catalog: &dyn TruncateCatalog,
93 targets: &TruncateTargets,
94) -> Result<(), SQLError> {
95 for table in &targets.all {
96 if let Some(referrer) = catalog
97 .referrers_to(table)
98 .map_err(|err| SQLError::Internal(format!("read foreign keys: {err}")))?
99 .into_iter()
100 .find(|referrer| !targets.all.contains(referrer))
101 {
102 return Err(SQLError::TypeMismatch(format!(
103 "cannot truncate `{table}` because `{referrer}` references it; truncate both tables or use CASCADE"
104 )));
105 }
106 }
107 Ok(())
108}
109
110pub fn truncate_dependency_order(
112 catalog: &dyn TruncateCatalog,
113 targets: &TruncateTargets,
114) -> Result<Vec<String>, SQLError> {
115 let mut ordered = Vec::with_capacity(targets.all.len());
116 let mut visiting = BTreeSet::new();
117 let mut visited = BTreeSet::new();
118 for table in &targets.trigger_order {
119 visit_truncate_target(
120 catalog,
121 table,
122 &targets.all,
123 &mut visiting,
124 &mut visited,
125 &mut ordered,
126 )?;
127 }
128 Ok(ordered)
129}
130
131fn visit_truncate_target(
132 catalog: &dyn TruncateCatalog,
133 table: &str,
134 targets: &BTreeSet<String>,
135 visiting: &mut BTreeSet<String>,
136 visited: &mut BTreeSet<String>,
137 ordered: &mut Vec<String>,
138) -> Result<(), SQLError> {
139 if visited.contains(table) || !visiting.insert(table.to_string()) {
140 return Ok(());
141 }
142 for referrer in catalog
143 .referrers_to(table)
144 .map_err(|err| SQLError::Internal(format!("read foreign keys: {err}")))?
145 {
146 if targets.contains(&referrer) {
147 visit_truncate_target(catalog, &referrer, targets, visiting, visited, ordered)?;
148 }
149 }
150 visiting.remove(table);
151 if visited.insert(table.to_string()) {
152 ordered.push(table.to_string());
153 }
154 Ok(())
155}