uqa_sql/catalog/events/definition/
lookup.rs1use super::EventAnalysisContext;
9use crate::catalog::roles::RoleReference;
10use crate::{
11 ast::TableHierarchy,
12 catalog::events::{
13 reads::{EventCatalogReads, EventLookupState},
14 StoredTrigger,
15 },
16 SQLError,
17};
18use std::collections::BTreeMap;
19use uqa_core::RelationIdentity;
20
21pub trait EventPartitionCatalog {
22 fn contains_loaded_table(&self, relation: &RelationIdentity) -> bool;
23 fn table_names(&self) -> Result<Vec<String>, String>;
24 fn try_table_hierarchy(&self, name: &str) -> Result<TableHierarchy, String>;
25}
26#[derive(Clone, Copy)]
27pub struct EventLookupContext<'a> {
28 pub analysis: EventAnalysisContext<'a>,
29 pub partitions: &'a dyn EventPartitionCatalog,
30 pub registry: &'a dyn EventCatalogReads,
31 pub state: &'a dyn EventLookupState,
32}
33impl EventLookupContext<'_> {
34 pub fn partition_trigger_sources(
35 &self,
36 table: &str,
37 ) -> Result<Vec<RelationIdentity>, SQLError> {
38 let mut current = self.analysis.resolve_trigger_table(table)?;
39 let mut sources = vec![current.clone()];
40 if !self.partitions.contains_loaded_table(¤t) {
41 return Ok(sources);
42 }
43 loop {
44 let hierarchy = self
45 .partitions
46 .try_table_hierarchy(¤t.qualified_name())
47 .map_err(|error| {
48 SQLError::Internal(format!("read trigger partition hierarchy: {error}"))
49 })?;
50 if hierarchy.partition_bound.is_none() {
51 break;
52 }
53 let Some(parent) = hierarchy.parents.first() else {
54 return Err(SQLError::Internal(format!(
55 "partition `{}` has no parent",
56 current.qualified_name()
57 )));
58 };
59 current = RelationIdentity::from_legacy_name(parent).map_err(|error| {
60 SQLError::Internal(format!(
61 "decode trigger partition parent `{parent}`: {error}"
62 ))
63 })?;
64 sources.push(current.clone());
65 }
66 Ok(sources)
67 }
68 pub fn ensure_partition_trigger_name_available(
69 &self,
70 relation: &RelationIdentity,
71 name: &str,
72 replacing_local: bool,
73 ) -> Result<(), SQLError> {
74 let ancestor_sources = self
75 .partition_trigger_sources(&relation.qualified_name())?
76 .into_iter()
77 .skip(1)
78 .collect::<Vec<_>>();
79 let mut descendant_relations = Vec::new();
80 for table in self
81 .partitions
82 .table_names()
83 .map_err(|error| SQLError::Internal(format!("read trigger partitions: {error}")))?
84 {
85 if table == relation.qualified_name() {
86 continue;
87 }
88 let sources = self.partition_trigger_sources(&table)?;
89 if sources.iter().skip(1).any(|source| source == relation) {
90 descendant_relations.push(RelationIdentity::from_legacy_name(&table).map_err(
91 |error| {
92 SQLError::Internal(format!("decode trigger partition `{table}`: {error}"))
93 },
94 )?);
95 }
96 }
97 let triggers = self.registry.read_triggers();
98 for source in ancestor_sources {
99 if triggers
100 .get(&source)
101 .is_some_and(|entries| entries.contains_key(name))
102 {
103 return Err(super::duplicate_object(
104 "trigger",
105 name,
106 &relation.qualified_name(),
107 ));
108 }
109 }
110 for descendant in descendant_relations {
111 if triggers
112 .get(&descendant)
113 .is_some_and(|entries| entries.contains_key(name))
114 {
115 return Err(super::duplicate_object(
116 "trigger",
117 name,
118 &descendant.qualified_name(),
119 ));
120 }
121 }
122 if !replacing_local
123 && triggers
124 .get(relation)
125 .is_some_and(|entries| entries.contains_key(name))
126 {
127 return Err(super::duplicate_object(
128 "trigger",
129 name,
130 &relation.qualified_name(),
131 ));
132 }
133 Ok(())
134 }
135 pub fn rule_privilege_subject(&self, table: &str) -> Result<RoleReference, SQLError> {
136 let relation = self.analysis.resolve_rule_relation(table)?;
137 self.analysis
138 .catalog
139 .event_relation_owner(&relation)
140 .and_then(|(owner, _)| self.analysis.privileges.bound_role(owner))
141 }
142 pub fn constraint_trigger_by_constraint_name(
143 &self,
144 table: &str,
145 name: &str,
146 ) -> Result<Option<StoredTrigger>, SQLError> {
147 let relation = self.analysis.resolve_trigger_table(table)?;
148 Ok(self
149 .registry
150 .read_triggers()
151 .get(&relation)
152 .into_iter()
153 .flat_map(BTreeMap::values)
154 .find(|trigger| {
155 trigger.definition.constraint
156 && trigger
157 .constraint_name
158 .as_deref()
159 .unwrap_or(&trigger.definition.name)
160 == name
161 })
162 .cloned())
163 }
164}
165
166mod selections;