1use crate::analysis::facts::{SearchPathTarget, TableConstraintFact};
3use crate::analysis::graph::{DependencyEdge, DependencyGraph, DependencyKind};
4use crate::analysis::mutations::{
5 AlterTableActionMutation, AlterTypeActionMutation, Mutation, PersistenceMutation,
6};
7use crate::analysis::transaction::{StateChange, TransactionFrame};
8use crate::ast::identifiers::ObjectId;
9use crate::db::cache::DbCache;
10use crate::model::constraint::{ConstraintKind, ConstraintState};
11pub use crate::model::relation::RelationOverlay;
12use crate::model::relation::{ColumnAction, Persistence, Privilege, RelationKind, RelationState};
13use crate::model::sequence::{SequenceOverlay, SequenceState};
14use crate::model::trigger::TriggerOverlay;
15use crate::model::types::{TypeKind, TypeOverlay, TypeState};
16use std::collections::{HashMap, HashSet};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum Confidence {
20 Exact,
21 Tainted,
22}
23
24#[derive(Debug, PartialEq, Eq)]
25pub enum MutationResult {
26 Applied,
27 Skipped,
28 NotExecuted,
31 Conflict {
32 reason: String,
33 },
34}
35
36#[derive(Debug, Default, Clone)]
37pub struct CascadeResult {
38 pub dropped_relations: HashSet<ObjectId>,
39 pub dropped_indexes: HashSet<ObjectId>,
40 pub dropped_constraints: HashSet<(ObjectId, String)>,
41}
42
43#[derive(Clone)]
44pub struct LocalState {
45 pub relations: HashMap<ObjectId, RelationOverlay>,
46 pub types: HashMap<ObjectId, TypeOverlay>,
47 pub functions: HashMap<ObjectId, crate::model::function::FunctionOverlay>,
48 pub sequences: HashMap<ObjectId, SequenceOverlay>,
49 pub publications: HashMap<String, crate::model::replication::PublicationOverlay>,
50 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionOverlay>,
51 pub roles: HashMap<ObjectId, crate::model::role::RoleOverlay>,
52 pub triggers: HashMap<ObjectId, TriggerOverlay>,
53 pub constraints: HashMap<(ObjectId, String), ConstraintState>,
54 pub graph: DependencyGraph,
55 pub search_path: Vec<String>,
56 pub default_search_path: Vec<String>,
57 pub search_path_template: Vec<String>,
58 pub default_search_path_template: Vec<String>,
59 pub current_role: String,
62 pub current_role_known: bool,
65 pub persistent_current_role: String,
68 pub persistent_current_role_known: bool,
69 pub session_role: String,
73 pub session_role_known: bool,
76 pub persistent_session_role: String,
78 pub persistent_session_role_known: bool,
79 pub authenticated_role: String,
81 pub authenticated_role_known: bool,
82 pub roles_known: bool,
84 pub confidence: Confidence,
85 pub transactions: Vec<TransactionFrame>,
86 pub transaction_aborted: bool,
87 pub pending_validation: HashSet<(ObjectId, String)>,
88 pub generation_counter: u64,
89}
90
91#[derive(Clone, Debug)]
92pub struct PreState {
93 pub relations: HashMap<ObjectId, crate::model::relation::RelationState>,
94 pub functions: HashMap<ObjectId, crate::model::function::FunctionState>,
95 pub roles: HashMap<ObjectId, crate::model::role::RoleState>,
96 pub publications: HashMap<String, crate::model::replication::PublicationState>,
97 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionState>,
98 pub sequences: HashMap<ObjectId, crate::model::sequence::SequenceState>,
99 pub types: HashMap<ObjectId, crate::model::types::TypeState>,
100 pub indexes: Vec<crate::analysis::graph::DependencyEdge>,
101}
102
103#[derive(Clone)]
104pub struct AnalysisState {
105 pub pg_version_num: Option<u32>,
106 pub baseline_available: bool,
110 pub baseline_schemas: Option<HashSet<String>>,
114 pub baseline_relations: HashSet<ObjectId>,
115 pub baseline_indexes: HashSet<ObjectId>,
116 pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
117 pub baseline_fk_dependencies: HashSet<ObjectId>,
118 pub local: LocalState,
119}
120
121impl AnalysisState {
122 fn trigger_key(table_id: &ObjectId, name: &str) -> ObjectId {
123 ObjectId::new(&table_id.schema, format!("{}\0{name}", table_id.name))
127 }
128
129 pub fn new(cache: DbCache) -> Self {
130 Self::with_baseline(cache, true)
131 }
132
133 pub fn with_baseline(cache: DbCache, baseline_available: bool) -> Self {
134 let default_search_path = cache.search_path.clone();
135 let default_search_path_template = if cache.metadata.schemas.is_none() {
136 cache
137 .metadata
138 .source_search_path
139 .clone()
140 .unwrap_or_else(|| default_search_path.clone())
141 } else {
142 default_search_path.clone()
143 };
144 let current_role_known = cache.metadata.source_role.is_some();
145 let current_role = cache
146 .metadata
147 .source_role
148 .clone()
149 .unwrap_or_else(|| "postgres".to_string());
150 let session_role_known = cache.metadata.source_session_role.is_some();
151 let session_role = cache
152 .metadata
153 .source_session_role
154 .clone()
155 .unwrap_or_else(|| current_role.clone());
156 let authenticated_role = session_role.clone();
157 let authenticated_role_known = session_role_known;
158 let persistent_current_role = current_role.clone();
159 let persistent_current_role_known = current_role_known;
160 let persistent_session_role = session_role.clone();
161 let persistent_session_role_known = session_role_known;
162 let roles_known = cache.metadata.source_session_role.is_some();
163 let baseline_schemas = cache
164 .metadata
165 .schemas
166 .as_ref()
167 .map(|schemas| schemas.iter().cloned().collect());
168 let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
169 let mut baseline_relations = HashSet::new();
170 let mut baseline_indexes = HashSet::new();
171 let mut baseline_foreign_keys = HashSet::new();
172 let mut baseline_fk_dependencies = HashSet::new();
173 let mut triggers = HashMap::new();
174 let mut constraints = HashMap::new();
175 let mut types = HashMap::new();
176 let mut graph = DependencyGraph::new();
177
178 for (id, rel_state) in cache.baseline_relations() {
179 if rel_state.is_fk_dependency {
180 baseline_fk_dependencies.insert(id.clone());
181 }
182 relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
183 baseline_relations.insert(id.clone());
184 }
185
186 for (id, type_state) in &cache.types {
187 types.insert(id.clone(), TypeOverlay::Present(type_state.clone()));
188 }
189
190 for fk in cache.foreign_keys {
191 baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
192 graph.edges.push(DependencyEdge::new(
193 fk.from_table,
194 fk.to_table,
195 DependencyKind::ForeignKey {
196 constraint_name: Some(fk.constraint_name),
197 from_columns: Vec::new(),
198 to_columns: Vec::new(),
199 from_generation: 0,
200 },
201 ));
202 }
203
204 for idx in cache.indexes {
205 baseline_indexes.insert(idx.index_id.clone());
207 graph.edges.push(DependencyEdge::new(
208 idx.index_id,
209 idx.table_id,
210 DependencyKind::IndexOnRelation {
211 using_method: None,
212 has_predicate: false,
213 is_concurrent: false,
214 is_unique: false,
215 eligibility_known: false,
216 },
217 ));
218 }
219
220 for dependency in cache.dependencies {
221 if dependency.deptype != "view" {
222 continue;
223 }
224 let (Some(obj_schema), Some(obj_name), Some(ref_schema), Some(ref_name)) = (
225 dependency.obj_schema,
226 dependency.obj_name,
227 dependency.ref_schema,
228 dependency.ref_name,
229 ) else {
230 continue;
231 };
232 let dependent = ObjectId::new(obj_schema, obj_name);
233 let referenced = ObjectId::new(ref_schema, ref_name);
234 if dependent == referenced {
239 continue;
240 }
241 let is_view = relations.get(&dependent).is_some_and(|relation| {
242 matches!(
243 relation,
244 RelationOverlay::Present(state)
245 if matches!(
246 state.kind,
247 crate::model::relation::RelationKind::View
248 | crate::model::relation::RelationKind::MaterializedView
249 )
250 )
251 });
252 if is_view && relations.contains_key(&referenced) {
253 graph.edges.push(DependencyEdge::new(
254 dependent,
255 referenced,
256 DependencyKind::ViewDependency { view_generation: 0 },
257 ));
258 }
259 }
260
261 for constraint in cache.constraints {
262 constraints.insert(
263 (constraint.table_id.clone(), constraint.name.clone()),
264 constraint,
265 );
266 }
267
268 for t in cache.triggers {
269 let trigger_key = Self::trigger_key(&t.table_id, &t.trigger_id.name);
270 triggers.insert(
271 trigger_key.clone(),
272 TriggerOverlay::Present(crate::model::trigger::TriggerState {
273 name: t.trigger_id.name.clone(),
274 id: trigger_key.clone(),
275 table_id: t.table_id.clone(),
276 enabled_mode: t.enabled_mode,
277 generation: 0,
278 }),
279 );
280 graph.edges.push(DependencyEdge::new(
281 trigger_key.clone(),
282 t.table_id,
283 DependencyKind::TriggerOnTable {
284 trigger_id: trigger_key,
285 function_id: t.function_id,
286 },
287 ));
288 }
289
290 let mut functions: HashMap<ObjectId, crate::model::function::FunctionOverlay> =
291 HashMap::new();
292 for (id, func_state) in &cache.functions {
293 functions.insert(
294 id.clone(),
295 crate::model::function::FunctionOverlay::Present(func_state.clone()),
296 );
297 }
298
299 Self {
300 pg_version_num: cache.pg_version_num,
301 baseline_available,
302 baseline_schemas,
303 baseline_relations,
304 baseline_indexes,
305 baseline_foreign_keys,
306 baseline_fk_dependencies,
307 local: LocalState {
308 relations,
309 types,
310 functions,
311 sequences: HashMap::new(),
312 publications: HashMap::new(),
313 subscriptions: HashMap::new(),
314 roles: cache
315 .roles
316 .into_iter()
317 .map(|(id, role)| (id, crate::model::role::RoleOverlay::Present(role)))
318 .collect(),
319 triggers,
320 constraints,
321 graph,
322 search_path: default_search_path.clone(),
323 default_search_path,
324 search_path_template: default_search_path_template.clone(),
325 default_search_path_template,
326 current_role,
327 current_role_known,
328 persistent_current_role,
329 persistent_current_role_known,
330 session_role,
331 session_role_known,
332 persistent_session_role,
333 persistent_session_role_known,
334 authenticated_role,
335 authenticated_role_known,
336 roles_known,
337 confidence: Confidence::Exact,
338 transactions: Vec::new(),
339 transaction_aborted: false,
340 pending_validation: HashSet::new(),
341 generation_counter: 0,
342 },
343 }
344 }
345
346 pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
347 self.local.relations.get(id)
348 }
349
350 pub fn resolve_function_schema(
351 &self,
352 name: &crate::ast::identifiers::QualifiedName,
353 sig_str: &str,
354 ) -> String {
355 if let Some(schema) = &name.schema {
356 return schema.resolve();
357 }
358 for schema in &self.local.search_path {
359 let candidate = ObjectId::new(schema.clone(), sig_str.to_string());
360 if self.local.functions.contains_key(&candidate) {
361 return schema.clone();
362 }
363 }
364 self.local
365 .search_path
366 .first()
367 .cloned()
368 .unwrap_or_else(|| "public".to_string())
369 }
370
371 pub fn resolve_relation_id(&self, name: &crate::ast::identifiers::QualifiedName) -> ObjectId {
372 if let Some(schema) = &name.schema {
373 return ObjectId::new(schema.resolve(), name.name.resolve());
374 }
375 let resolved_name = name.name.resolve();
376 for schema in &self.local.search_path {
377 let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
378 if self.local.relations.contains_key(&candidate) {
379 candidate.inferred_schema = true;
380 return candidate;
381 }
382 }
383 let schema = self
384 .local
385 .search_path
386 .first()
387 .cloned()
388 .unwrap_or_else(|| "public".to_string());
389 let mut id = ObjectId::new(schema, resolved_name);
390 id.inferred_schema = true;
391 id
392 }
393
394 pub fn relation_is_present(&self, id: &ObjectId) -> bool {
395 matches!(
396 self.local.relations.get(id),
397 Some(RelationOverlay::Present(_))
398 )
399 }
400
401 pub fn baseline_covers_object(&self, id: &ObjectId) -> bool {
405 self.baseline_schemas
406 .as_ref()
407 .is_none_or(|schemas| schemas.contains(&id.schema))
408 }
409
410 pub fn baseline_scope_omits_displayed_object<'a>(
411 &self,
412 object_name: &'a str,
413 ) -> Option<&'a str> {
414 let schemas = self.baseline_schemas.as_ref()?;
415 let (schema, _) = object_name.split_once('.')?;
416 (!schemas.contains(schema)).then_some(schema)
417 }
418
419 fn sequence_is_present(&self, id: &ObjectId) -> bool {
420 matches!(
421 self.local.sequences.get(id),
422 Some(SequenceOverlay::Present(_))
423 )
424 }
425
426 fn type_is_present(&self, id: &ObjectId) -> bool {
427 matches!(self.local.types.get(id), Some(TypeOverlay::Present(_)))
428 }
429
430 fn index_is_present(&self, id: &ObjectId) -> bool {
431 self.local.graph.edges.iter().any(|edge| {
432 matches!(edge.kind, DependencyKind::IndexOnRelation { .. }) && edge.dependent == *id
433 })
434 }
435
436 fn next_generated_constraint_name(
437 &self,
438 table: &ObjectId,
439 name1: &str,
440 name2: Option<&str>,
441 label: &str,
442 ) -> String {
443 (0..)
444 .map(|suffix| {
445 let label = if suffix == 0 {
446 label.to_string()
447 } else {
448 format!("{label}{suffix}")
449 };
450 Self::postgres_object_name(name1, name2, &label)
451 })
452 .find(|candidate| {
453 !self
454 .local
455 .constraints
456 .contains_key(&(table.clone(), candidate.clone()))
457 })
458 .expect("constraint suffix space is unbounded")
459 }
460
461 fn postgres_object_name(name1: &str, name2: Option<&str>, label: &str) -> String {
462 const MAX_IDENTIFIER_BYTES: usize = 63;
463
464 fn truncate(value: &str, max_bytes: usize) -> &str {
465 let mut end = max_bytes.min(value.len());
466 while !value.is_char_boundary(end) {
467 end -= 1;
468 }
469 &value[..end]
470 }
471
472 let separators = usize::from(name2.is_some()) + 1;
473 let available = MAX_IDENTIFIER_BYTES.saturating_sub(label.len() + separators);
474 let mut name1_bytes = name1.len();
475 let mut name2_bytes = name2.map_or(0, str::len);
476 while name1_bytes + name2_bytes > available {
477 if name1_bytes > name2_bytes {
478 name1_bytes -= 1;
479 } else {
480 name2_bytes -= 1;
481 }
482 }
483
484 let name1 = truncate(name1, name1_bytes);
485 match name2 {
486 Some(name2) => format!("{name1}_{}_{}", truncate(name2, name2_bytes), label),
487 None => format!("{name1}_{label}"),
488 }
489 }
490
491 fn relation_namespace_is_taken(&self, id: &ObjectId) -> bool {
492 self.relation_is_present(id)
493 || self.sequence_is_present(id)
494 || self.index_is_present(id)
495 || self.type_is_present(id)
496 }
497
498 pub fn column_was_added_in_transaction(&self, table_id: &ObjectId, column: &str) -> bool {
499 if self.local.transactions.is_empty() {
500 return false;
501 }
502
503 for frame in &self.local.transactions {
505 for change in &frame.undo_log {
506 if let StateChange::RelationSnapshot { id, previous } = change
507 && id == table_id
508 {
509 match previous.as_ref() {
510 None | Some(RelationOverlay::Dropped) => {
511 return true;
512 }
513 Some(RelationOverlay::Present(r)) => {
514 let col_existed = r.columns.iter().any(|c| c.name == column);
515 return !col_existed;
516 }
517 }
518 }
519 }
520 }
521 false
522 }
523
524 pub fn capture_pre_state(&self) -> PreState {
525 let mut relations = HashMap::new();
526 for (id, overlay) in &self.local.relations {
527 if let RelationOverlay::Present(s) = overlay {
528 relations.insert(id.clone(), s.clone());
529 }
530 }
531
532 let mut functions = HashMap::new();
533 for (id, overlay) in &self.local.functions {
534 if let crate::model::function::FunctionOverlay::Present(s) = overlay {
535 functions.insert(id.clone(), s.clone());
536 }
537 }
538
539 let mut roles = HashMap::new();
540 for (name, overlay) in &self.local.roles {
541 if let crate::model::role::RoleOverlay::Present(s) = overlay {
542 roles.insert(name.clone(), s.clone());
543 }
544 }
545
546 let mut publications = HashMap::new();
547 for (name, overlay) in &self.local.publications {
548 if let crate::model::replication::PublicationOverlay::Present(s) = overlay {
549 publications.insert(name.clone(), s.clone());
550 }
551 }
552
553 let mut subscriptions = HashMap::new();
554 for (name, overlay) in &self.local.subscriptions {
555 if let crate::model::replication::SubscriptionOverlay::Present(s) = overlay {
556 subscriptions.insert(name.clone(), s.clone());
557 }
558 }
559
560 let mut sequences = HashMap::new();
561 for (id, overlay) in &self.local.sequences {
562 if let SequenceOverlay::Present(s) = overlay {
563 sequences.insert(id.clone(), s.clone());
564 }
565 }
566
567 let mut types = HashMap::new();
568 for (id, overlay) in &self.local.types {
569 if let TypeOverlay::Present(s) = overlay {
570 types.insert(id.clone(), s.clone());
571 }
572 }
573
574 let indexes = self
575 .local
576 .graph
577 .edges
578 .iter()
579 .filter(|e| matches!(e.kind, DependencyKind::IndexOnRelation { .. }))
580 .cloned()
581 .collect();
582
583 PreState {
584 relations,
585 functions,
586 roles,
587 publications,
588 subscriptions,
589 sequences,
590 types,
591 indexes,
592 }
593 }
594
595 pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
596 let mut result = CascadeResult::default();
597 let mut visited = HashSet::new();
598 self.walk_cascade(target_oid, &mut visited, &mut result);
599 result
600 }
601
602 fn walk_cascade(
603 &self,
604 current: &ObjectId,
605 visited: &mut HashSet<ObjectId>,
606 result: &mut CascadeResult,
607 ) {
608 let resolved_current = self.local.graph.resolve_rename(current).clone();
609
610 if !visited.insert(resolved_current.clone()) {
611 return;
612 }
613
614 result.dropped_relations.insert(resolved_current.clone());
615
616 for edge in &self.local.graph.edges {
617 match &edge.kind {
618 DependencyKind::ViewDependency { .. } => {
619 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
620 let resolved_view_id =
621 self.local.graph.resolve_rename(&edge.dependent).clone();
622 if !visited.contains(&resolved_view_id) {
623 self.walk_cascade(&resolved_view_id, visited, result);
624 }
625 }
626 }
627 DependencyKind::IndexOnRelation { .. } => {
628 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
629 result
630 .dropped_indexes
631 .insert(self.local.graph.resolve_rename(&edge.dependent).clone());
632 }
633 }
634 DependencyKind::ForeignKey {
635 constraint_name, ..
636 } => {
637 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current
638 && let Some(cname) = constraint_name
639 {
640 result.dropped_constraints.insert((
641 self.local.graph.resolve_rename(&edge.dependent).clone(),
642 cname.clone(),
643 ));
644 }
645 }
646 DependencyKind::PartitionOf
647 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current =>
648 {
649 let resolved_child = self.local.graph.resolve_rename(&edge.dependent).clone();
650 if !visited.contains(&resolved_child) {
651 self.walk_cascade(&resolved_child, visited, result);
652 }
653 }
654 _ => {}
655 }
656 }
657 }
658
659 fn resolve_grant_privileges(
660 spec: &crate::analysis::facts::PrivilegeSpec,
661 ) -> HashSet<Privilege> {
662 match spec {
663 crate::analysis::facts::PrivilegeSpec::All => vec![
664 Privilege::Select,
665 Privilege::Insert,
666 Privilege::Update,
667 Privilege::Delete,
668 Privilege::Truncate,
669 Privilege::References,
670 Privilege::Trigger,
671 ]
672 .into_iter()
673 .collect(),
674 crate::analysis::facts::PrivilegeSpec::List(list) => list
675 .iter()
676 .filter_map(|p| match p {
677 crate::analysis::facts::PrivilegeFact::Select => Some(Privilege::Select),
678 crate::analysis::facts::PrivilegeFact::Insert => Some(Privilege::Insert),
679 crate::analysis::facts::PrivilegeFact::Update => Some(Privilege::Update),
680 crate::analysis::facts::PrivilegeFact::Delete => Some(Privilege::Delete),
681 crate::analysis::facts::PrivilegeFact::Truncate => Some(Privilege::Truncate),
682 crate::analysis::facts::PrivilegeFact::References => {
683 Some(Privilege::References)
684 }
685 crate::analysis::facts::PrivilegeFact::Trigger => Some(Privilege::Trigger),
686 _ => None,
687 })
688 .collect(),
689 }
690 }
691
692 fn resolve_role_name(
693 role: &crate::analysis::facts::RoleFact,
694 current_role: &str,
695 session_role: &str,
696 ) -> Option<ObjectId> {
697 let name = match role {
698 crate::analysis::facts::RoleFact::Named { name, .. } => Some(name.clone()),
699 crate::analysis::facts::RoleFact::CurrentUser
700 | crate::analysis::facts::RoleFact::CurrentRole => Some(current_role.to_string()),
701 crate::analysis::facts::RoleFact::SessionUser => Some(session_role.to_string()),
702 crate::analysis::facts::RoleFact::Unknown => None,
703 }?;
704 Some(ObjectId::new("", name))
705 }
706
707 fn role_fact_identity(
708 &self,
709 role: &crate::analysis::facts::RoleFact,
710 ) -> Option<(String, bool)> {
711 match role {
712 crate::analysis::facts::RoleFact::Named { name, .. } => Some((name.clone(), true)),
713 crate::analysis::facts::RoleFact::CurrentUser
714 | crate::analysis::facts::RoleFact::CurrentRole => Some((
715 self.local.current_role.clone(),
716 self.local.current_role_known,
717 )),
718 crate::analysis::facts::RoleFact::SessionUser => Some((
719 self.local.session_role.clone(),
720 self.local.session_role_known,
721 )),
722 crate::analysis::facts::RoleFact::Unknown => None,
723 }
724 }
725
726 fn present_role(&self, name: &str) -> Option<&crate::model::role::RoleState> {
727 match self.local.roles.get(&ObjectId::new("", name)) {
728 Some(crate::model::role::RoleOverlay::Present(role)) => Some(role),
729 _ => None,
730 }
731 }
732
733 fn can_set_role_to(&self, target: &str) -> Option<bool> {
734 if !self.local.roles_known || !self.local.session_role_known {
735 return None;
736 }
737 if self.present_role(target).is_none() {
738 return Some(false);
739 }
740 if self.local.session_role == target {
741 return Some(true);
742 }
743 let session = self.present_role(&self.local.session_role)?;
744 if session.is_superuser {
745 return Some(true);
746 }
747
748 let mut pending = session.can_set_role_to.clone();
749 let mut visited = HashSet::new();
750 while let Some(role_id) = pending.pop() {
751 if !visited.insert(role_id.clone()) {
752 continue;
753 }
754 if role_id.name == target {
755 return Some(true);
756 }
757 if let Some(role) = self.present_role(&role_id.name) {
758 pending.extend(role.can_set_role_to.iter().cloned());
759 }
760 }
761 Some(false)
762 }
763
764 fn can_set_session_authorization_to(&self, target: &str) -> Option<bool> {
765 if !self.local.roles_known || !self.local.authenticated_role_known {
766 return None;
767 }
768 if self.present_role(target).is_none() {
769 return Some(false);
770 }
771 if self.local.authenticated_role == target {
772 return Some(true);
773 }
774 Some(
775 self.present_role(&self.local.authenticated_role)
776 .is_some_and(|role| role.is_superuser),
777 )
778 }
779
780 fn refresh_role_sensitive_search_path(&mut self) {
781 self.local.search_path = self
782 .local
783 .search_path_template
784 .iter()
785 .filter_map(|schema| {
786 if schema != "$user" {
787 return Some(schema.clone());
788 }
789 if self.local.current_role_known {
790 Some(self.local.current_role.clone())
791 } else {
792 self.local.confidence = Confidence::Tainted;
793 None
794 }
795 })
796 .collect();
797 }
798
799 fn restore_persistent_role_context(&mut self) {
800 self.local.current_role = self.local.persistent_current_role.clone();
801 self.local.current_role_known = self.local.persistent_current_role_known;
802 self.local.session_role = self.local.persistent_session_role.clone();
803 self.local.session_role_known = self.local.persistent_session_role_known;
804 self.refresh_role_sensitive_search_path();
805 }
806
807 fn apply_grant_to_relation(
808 &mut self,
809 id: &ObjectId,
810 privileges: &HashSet<Privilege>,
811 grantees: &[crate::analysis::facts::RoleFact],
812 ) {
813 self.snapshot_relation(id);
814 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
815 for grantee in grantees {
816 if let Some(role_id) = Self::resolve_role_name(
817 grantee,
818 &self.local.current_role,
819 &self.local.session_role,
820 ) {
821 rel.privileges.grant(role_id, privileges.clone());
822 }
823 }
824 }
825 }
826
827 fn apply_revoke_to_relation(
828 &mut self,
829 id: &ObjectId,
830 privileges: &HashSet<Privilege>,
831 revokees: &[crate::analysis::facts::RoleFact],
832 ) {
833 self.snapshot_relation(id);
834 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
835 for revokee in revokees {
836 if let Some(role_id) = Self::resolve_role_name(
837 revokee,
838 &self.local.current_role,
839 &self.local.session_role,
840 ) {
841 rel.privileges.revoke(&role_id, privileges);
842 }
843 }
844 }
845 }
846
847 pub fn apply(
848 &mut self,
849 mutation: &Mutation,
850 precomputed_cascade: Option<&CascadeResult>,
851 ) -> MutationResult {
852 if self.local.transaction_aborted
853 && !matches!(
854 mutation,
855 Mutation::CommitTransaction
856 | Mutation::CommitAndChain
857 | Mutation::RollbackTransaction
858 | Mutation::RollbackAndChain
859 | Mutation::RollbackToSavepoint(_)
860 )
861 {
862 return MutationResult::NotExecuted;
863 }
864
865 let result = self.apply_inner(mutation, precomputed_cascade);
866 if matches!(result, MutationResult::Conflict { .. }) && !self.local.transactions.is_empty()
867 {
868 self.local.transaction_aborted = true;
869 }
870 result
871 }
872
873 fn apply_inner(
874 &mut self,
875 mutation: &Mutation,
876 precomputed_cascade: Option<&CascadeResult>,
877 ) -> MutationResult {
878 match mutation {
879 Mutation::CreateSchema(_) => MutationResult::Applied,
880 Mutation::DropSchema(drop_schema) => {
881 if drop_schema.cascade {
882 let mut relations_to_drop = Vec::new();
883 for id in self.local.relations.keys() {
884 if drop_schema.names.contains(&id.schema) {
885 relations_to_drop.push(id.clone());
886 }
887 }
888 for id in relations_to_drop {
889 self.snapshot_relation(&id);
890 self.local.relations.insert(id, RelationOverlay::Dropped);
891 }
892
893 let constraints_to_drop: Vec<(ObjectId, String)> = self
894 .local
895 .constraints
896 .keys()
897 .filter(|(table_id, _)| drop_schema.names.contains(&table_id.schema))
898 .cloned()
899 .collect();
900 for (table_id, name) in constraints_to_drop {
901 self.snapshot_constraint(&table_id, &name);
902 self.local.constraints.remove(&(table_id, name));
903 }
904
905 let mut types_to_drop = Vec::new();
906 for id in self.local.types.keys() {
907 if drop_schema.names.contains(&id.schema) {
908 types_to_drop.push(id.clone());
909 }
910 }
911 for id in types_to_drop {
912 self.snapshot_type(&id);
913 self.local.types.insert(id, TypeOverlay::Dropped);
914 }
915
916 let mut seqs_to_drop = Vec::new();
917 for id in self.local.sequences.keys() {
918 if drop_schema.names.contains(&id.schema) {
919 seqs_to_drop.push(id.clone());
920 }
921 }
922 for id in seqs_to_drop {
923 self.snapshot_sequence(&id);
924 self.local.sequences.insert(id, SequenceOverlay::Dropped);
925 }
926
927 self.snapshot_graph_full();
928
929 let g = &mut self.local.graph;
930 g.edges.retain(|e| {
931 !drop_schema.names.contains(&e.dependent.schema)
932 && !drop_schema.names.contains(&e.referenced.schema)
933 && match &e.kind {
934 DependencyKind::TriggerOnTable { function_id, .. } => {
935 !drop_schema.names.contains(&function_id.schema)
936 }
937 _ => true,
938 }
939 });
940 } else {
941 let has_relation = self.local.relations.iter().any(|(id, ov)| {
943 drop_schema.names.contains(&id.schema)
944 && !matches!(ov, RelationOverlay::Dropped)
945 });
946 let has_type = self.local.types.iter().any(|(id, ov)| {
947 drop_schema.names.contains(&id.schema)
948 && !matches!(ov, TypeOverlay::Dropped)
949 });
950 let has_sequence = self.local.sequences.iter().any(|(id, ov)| {
951 drop_schema.names.contains(&id.schema)
952 && !matches!(ov, SequenceOverlay::Dropped)
953 });
954 let has_function = self.local.functions.iter().any(|(id, ov)| {
955 drop_schema.names.contains(&id.schema)
956 && !matches!(ov, crate::model::function::FunctionOverlay::Dropped)
957 });
958 let has_trigger = self.local.triggers.iter().any(|(id, ov)| {
959 drop_schema.names.contains(&id.schema)
960 && !matches!(ov, TriggerOverlay::Dropped)
961 });
962 if has_relation || has_type || has_sequence || has_function || has_trigger {
963 return MutationResult::Conflict {
964 reason: format!(
965 "schema(s) {:?} still contain objects; use CASCADE to drop them",
966 drop_schema.names
967 ),
968 };
969 }
970 }
971 MutationResult::Applied
972 }
973 Mutation::DropTable(drop_table) => {
974 if !self.relation_is_present(&drop_table.id) {
975 if drop_table.if_exists {
976 return MutationResult::Skipped;
977 } else {
978 self.local.confidence = Confidence::Tainted;
979 return MutationResult::Skipped;
980 }
981 }
982
983 let renames: Vec<DependencyEdge> = self
984 .local
985 .graph
986 .edges
987 .iter()
988 .filter(|e| matches!(e.kind, DependencyKind::RenameTo))
989 .cloned()
990 .collect();
991 let resolve = |id: &ObjectId| -> ObjectId {
992 let mut current = id;
993 let mut visited = HashSet::new();
994 loop {
995 if !visited.insert(current.clone()) {
996 return id.clone();
997 }
998 match renames.iter().find(|r| &r.dependent == current) {
999 Some(edge) => current = &edge.referenced,
1000 None => return current.clone(),
1001 }
1002 }
1003 };
1004
1005 let resolved_drop = resolve(&drop_table.id);
1006 let mut dropped_relations = HashSet::from([resolved_drop.clone()]);
1007
1008 if drop_table.cascade {
1009 let local_closure;
1010 let closure = match precomputed_cascade {
1011 Some(c) => c,
1012 None => {
1013 local_closure = self.get_cascade_closure(&drop_table.id);
1014 &local_closure
1015 }
1016 };
1017 dropped_relations = closure.dropped_relations.clone();
1018
1019 for dropped_rel_id in &closure.dropped_relations {
1020 self.snapshot_relation(dropped_rel_id);
1021 self.local
1022 .relations
1023 .insert(dropped_rel_id.clone(), RelationOverlay::Dropped);
1024 }
1025
1026 self.snapshot_graph_full();
1027 self.local.graph.edges.retain(|e| match &e.kind {
1028 DependencyKind::IndexOnRelation { .. } => {
1029 !closure.dropped_indexes.contains(&resolve(&e.dependent))
1030 }
1031 DependencyKind::ForeignKey {
1032 constraint_name, ..
1033 } => {
1034 let from_dropped =
1035 closure.dropped_relations.contains(&resolve(&e.dependent));
1036 let to_dropped =
1037 closure.dropped_relations.contains(&resolve(&e.referenced));
1038 let constraint_explicitly_dropped = if let Some(cname) = constraint_name
1039 {
1040 closure
1041 .dropped_constraints
1042 .contains(&(resolve(&e.dependent), cname.clone()))
1043 } else {
1044 false
1045 };
1046 !(from_dropped || to_dropped || constraint_explicitly_dropped)
1047 }
1048 DependencyKind::ViewDependency { .. } => {
1049 !closure.dropped_relations.contains(&resolve(&e.dependent))
1050 }
1051 DependencyKind::SequenceOwnedBy { .. } => {
1052 !closure.dropped_relations.contains(&resolve(&e.referenced))
1053 }
1054 _ => true,
1055 });
1056 } else {
1057 let has_view_deps = self.local.graph.edges.iter().any(|e| {
1058 matches!(e.kind, DependencyKind::ViewDependency { .. })
1059 && resolve(&e.referenced) == resolved_drop
1060 });
1061 let has_fk_deps = self.local.graph.edges.iter().any(|e| {
1062 matches!(e.kind, DependencyKind::ForeignKey { .. })
1063 && resolve(&e.referenced) == resolved_drop
1064 && resolve(&e.dependent) != resolved_drop
1065 });
1066 let has_partition_deps = self.local.graph.edges.iter().any(|e| {
1067 matches!(e.kind, DependencyKind::PartitionOf)
1068 && resolve(&e.referenced) == resolved_drop
1069 });
1070
1071 if has_view_deps || has_fk_deps || has_partition_deps {
1072 return MutationResult::Conflict {
1073 reason: format!(
1074 "relation '{}' still has dependent objects; use CASCADE",
1075 drop_table.id
1076 ),
1077 };
1078 }
1079
1080 self.snapshot_relation(&drop_table.id);
1081 self.local
1082 .relations
1083 .insert(drop_table.id.clone(), RelationOverlay::Dropped);
1084
1085 self.snapshot_graph_full();
1086 self.local.graph.edges.retain(|e| {
1087 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
1088 && resolve(&e.referenced) == resolved_drop)
1089 });
1090 }
1091
1092 let constraints_to_drop: Vec<(ObjectId, String)> = self
1093 .local
1094 .constraints
1095 .keys()
1096 .filter(|(table_id, _)| dropped_relations.contains(&resolve(table_id)))
1097 .cloned()
1098 .collect();
1099 for (table_id, name) in constraints_to_drop {
1100 self.snapshot_constraint(&table_id, &name);
1101 self.local.constraints.remove(&(table_id, name));
1102 }
1103
1104 let triggers_to_drop: Vec<ObjectId> = self
1105 .local
1106 .triggers
1107 .iter()
1108 .filter_map(|(id, overlay)| {
1109 let TriggerOverlay::Present(trigger) = overlay else {
1110 return None;
1111 };
1112 let graph_matches = self.local.graph.edges.iter().any(|edge| {
1113 matches!(edge.kind, DependencyKind::TriggerOnTable { .. })
1114 && edge.dependent == *id
1115 && dropped_relations.contains(&resolve(&edge.referenced))
1116 });
1117 (dropped_relations.contains(&resolve(&trigger.table_id)) || graph_matches)
1118 .then(|| id.clone())
1119 })
1120 .collect();
1121 for trigger_id in triggers_to_drop {
1122 self.snapshot_trigger(&trigger_id);
1123 self.local
1124 .triggers
1125 .insert(trigger_id, TriggerOverlay::Dropped);
1126 }
1127
1128 self.snapshot_graph_full();
1130 self.local.graph.edges.retain(|e| {
1131 !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
1132 && dropped_relations.contains(&resolve(&e.referenced)))
1133 });
1134
1135 self.snapshot_graph_full();
1136 self.local.graph.edges.retain(|e| {
1137 if let DependencyKind::PartitionOf = e.kind {
1138 resolve(&e.referenced) != resolved_drop
1139 && resolve(&e.dependent) != resolved_drop
1140 } else {
1141 true
1142 }
1143 });
1144
1145 MutationResult::Applied
1146 }
1147 Mutation::CreateTable(create) => {
1148 if create.if_not_exists && self.relation_namespace_is_taken(&create.id) {
1149 return MutationResult::Skipped;
1150 }
1151 if self.relation_namespace_is_taken(&create.id) {
1152 return MutationResult::Conflict {
1153 reason: format!("relation '{}' already exists", create.id),
1154 };
1155 }
1156
1157 self.snapshot_relation(&create.id);
1158
1159 self.snapshot_generation_counter();
1160 self.local.generation_counter += 1;
1161 let generation = self.local.generation_counter;
1162
1163 let resolved_persistence = match create.persistence {
1164 PersistenceMutation::Permanent => {
1165 crate::model::relation::Persistence::Permanent
1166 }
1167 PersistenceMutation::Temporary => {
1168 crate::model::relation::Persistence::Temporary
1169 }
1170 PersistenceMutation::Unlogged => crate::model::relation::Persistence::Unlogged,
1171 };
1172
1173 let mut rel_state = RelationState::new(
1174 create.id.clone(),
1175 ObjectId::new("", &self.local.current_role),
1176 generation,
1177 if create.as_select { None } else { Some(0) },
1178 RelationKind::Table,
1179 resolved_persistence,
1180 self.local.transactions.len(),
1181 );
1182
1183 rel_state.partition_type = create
1185 .partition_by
1186 .as_ref()
1187 .and_then(|pb| pb.split_whitespace().nth(2).map(|s| s.to_uppercase()))
1188 .or_else(|| {
1189 create.partition_of.as_ref().and_then(|parent_id| {
1190 self.local.relations.get(parent_id).and_then(|r| {
1191 if let RelationOverlay::Present(rel) = r {
1192 rel.partition_type.clone()
1193 } else {
1194 None
1195 }
1196 })
1197 })
1198 });
1199 rel_state.partition_by = create.partition_by.clone();
1200
1201 let pk_columns: HashSet<&str> = create
1202 .table_constraints
1203 .iter()
1204 .filter_map(|tc| {
1205 if let TableConstraintFact::PrimaryKey { columns, .. } = tc {
1206 Some(columns.iter().map(|s| s.as_str()))
1207 } else {
1208 None
1209 }
1210 })
1211 .flatten()
1212 .collect();
1213
1214 for col in &create.columns {
1215 let is_pk = col.is_primary_key || pk_columns.contains(col.name.as_str());
1216 rel_state.apply_column_action(&ColumnAction::Add {
1217 name: col.name.clone(),
1218 data_type: col.ty.clone(),
1219 not_null: col.not_null || is_pk,
1220 default: col.default.clone(),
1221 });
1222 }
1223
1224 self.local
1225 .relations
1226 .insert(create.id.clone(), RelationOverlay::Present(rel_state));
1227
1228 let primary_key_name = create
1229 .columns
1230 .iter()
1231 .find(|column| column.is_primary_key)
1232 .map(|column| column.primary_key_constraint_name.clone())
1233 .or_else(|| {
1234 create.table_constraints.iter().find_map(|constraint| {
1235 if let TableConstraintFact::PrimaryKey {
1236 constraint_name, ..
1237 } = constraint
1238 {
1239 Some(constraint_name.clone())
1240 } else {
1241 None
1242 }
1243 })
1244 });
1245 if let Some(explicit_name) = primary_key_name {
1246 let name = explicit_name.unwrap_or_else(|| {
1247 self.next_generated_constraint_name(
1248 &create.id,
1249 &create.id.name,
1250 None,
1251 "pkey",
1252 )
1253 });
1254 self.snapshot_constraint(&create.id, &name);
1255 self.local.constraints.insert(
1256 (create.id.clone(), name.clone()),
1257 ConstraintState {
1258 table_id: create.id.clone(),
1259 name,
1260 kind: ConstraintKind::PrimaryKey,
1261 validated: true,
1262 },
1263 );
1264 }
1265
1266 let unique_constraints = create
1267 .columns
1268 .iter()
1269 .filter(|column| column.is_unique)
1270 .map(|column| {
1271 (
1272 column.unique_constraint_name.as_ref(),
1273 vec![column.name.as_str()],
1274 )
1275 })
1276 .chain(create.table_constraints.iter().filter_map(|constraint| {
1277 if let TableConstraintFact::Unique {
1278 constraint_name,
1279 columns,
1280 } = constraint
1281 {
1282 Some((
1283 constraint_name.as_ref(),
1284 columns.iter().map(String::as_str).collect(),
1285 ))
1286 } else {
1287 None
1288 }
1289 }))
1290 .collect::<Vec<_>>();
1291 for (explicit_name, columns) in unique_constraints {
1292 let name = explicit_name.cloned().unwrap_or_else(|| {
1293 self.next_generated_constraint_name(
1294 &create.id,
1295 &create.id.name,
1296 Some(&columns.join("_")),
1297 "key",
1298 )
1299 });
1300 self.snapshot_constraint(&create.id, &name);
1301 self.local.constraints.insert(
1302 (create.id.clone(), name.clone()),
1303 ConstraintState {
1304 table_id: create.id.clone(),
1305 name,
1306 kind: ConstraintKind::Unique,
1307 validated: true,
1308 },
1309 );
1310 }
1311
1312 if let Some(parent_id) = &create.partition_of {
1313 self.snapshot_graph();
1314 self.local.graph.edges.push(DependencyEdge::new(
1315 create.id.clone(),
1316 parent_id.clone(),
1317 DependencyKind::PartitionOf,
1318 ));
1319 }
1320
1321 if !create.foreign_keys.is_empty() {
1322 self.snapshot_graph();
1323 }
1324
1325 for fk in &create.foreign_keys {
1326 self.local.graph.edges.push(DependencyEdge::new(
1327 create.id.clone(),
1328 fk.to_table.clone(),
1329 DependencyKind::ForeignKey {
1330 constraint_name: fk.constraint_name.clone(),
1331 from_columns: fk.from_columns.clone(),
1332 to_columns: fk.to_columns.clone(),
1333 from_generation: generation,
1334 },
1335 ));
1336 }
1337 MutationResult::Applied
1338 }
1339 Mutation::CreateView(create_view) => {
1340 if self.relation_namespace_is_taken(&create_view.id) {
1341 let is_replaceable_view = matches!(
1342 self.local.relations.get(&create_view.id),
1343 Some(RelationOverlay::Present(relation))
1344 if relation.kind == RelationKind::View
1345 );
1346 if !create_view.or_replace || !is_replaceable_view {
1347 return MutationResult::Conflict {
1348 reason: format!("relation '{}' already exists", create_view.id),
1349 };
1350 }
1351 }
1352 self.snapshot_relation(&create_view.id);
1353 self.snapshot_generation_counter();
1354 self.local.generation_counter += 1;
1355 let generation = self.local.generation_counter;
1356
1357 self.local.relations.insert(
1358 create_view.id.clone(),
1359 RelationOverlay::Present(RelationState::new(
1360 create_view.id.clone(),
1361 ObjectId::new("", &self.local.current_role),
1362 generation,
1363 None,
1364 RelationKind::View,
1365 Persistence::Permanent,
1366 self.local.transactions.len(),
1367 )),
1368 );
1369
1370 self.snapshot_graph();
1371 for dep in &create_view.depends_on {
1372 self.local.graph.edges.push(DependencyEdge::new(
1373 create_view.id.clone(),
1374 dep.clone(),
1375 DependencyKind::ViewDependency {
1376 view_generation: generation,
1377 },
1378 ));
1379 }
1380 MutationResult::Applied
1381 }
1382 Mutation::CreateMaterializedView(create_mv) => {
1383 if self.relation_namespace_is_taken(&create_mv.id) {
1384 return MutationResult::Conflict {
1385 reason: format!("relation '{}' already exists", create_mv.id),
1386 };
1387 }
1388 self.snapshot_relation(&create_mv.id);
1389 self.snapshot_generation_counter();
1390 self.local.generation_counter += 1;
1391 let generation = self.local.generation_counter;
1392
1393 self.local.relations.insert(
1394 create_mv.id.clone(),
1395 RelationOverlay::Present(RelationState::new(
1396 create_mv.id.clone(),
1397 ObjectId::new("", &self.local.current_role),
1398 generation,
1399 None,
1400 RelationKind::MaterializedView,
1401 Persistence::Permanent,
1402 self.local.transactions.len(),
1403 )),
1404 );
1405
1406 self.snapshot_graph();
1407 for dep in &create_mv.depends_on {
1408 self.local.graph.edges.push(DependencyEdge::new(
1409 create_mv.id.clone(),
1410 dep.clone(),
1411 DependencyKind::ViewDependency {
1412 view_generation: generation,
1413 },
1414 ));
1415 }
1416 MutationResult::Applied
1417 }
1418 Mutation::RefreshMaterializedView(_) => MutationResult::Applied,
1419 Mutation::CreateIndex(create_idx) => {
1420 let exists = self.index_is_present(&create_idx.id);
1421 if create_idx.if_not_exists && exists {
1422 return MutationResult::Skipped;
1423 }
1424 if self.relation_namespace_is_taken(&create_idx.id) {
1425 return MutationResult::Conflict {
1426 reason: format!("relation '{}' already exists", create_idx.id),
1427 };
1428 }
1429 self.snapshot_graph();
1430 self.local.graph.edges.push(DependencyEdge::new(
1431 create_idx.id.clone(),
1432 create_idx.table.clone(),
1433 DependencyKind::IndexOnRelation {
1434 using_method: create_idx.using_method.clone(),
1435 has_predicate: create_idx.has_predicate,
1436 is_concurrent: create_idx.concurrently,
1437 is_unique: create_idx.unique,
1438 eligibility_known: true,
1439 },
1440 ));
1441 MutationResult::Applied
1442 }
1443 Mutation::CreatePolicy(create_policy) => {
1444 self.snapshot_relation(&create_policy.table);
1445 if let Some(RelationOverlay::Present(rel)) =
1446 self.local.relations.get_mut(&create_policy.table)
1447 {
1448 if rel.policies.contains(&create_policy.name) {
1449 return MutationResult::Conflict {
1450 reason: format!(
1451 "policy '{}' already exists on relation '{}'",
1452 create_policy.name, create_policy.table
1453 ),
1454 };
1455 }
1456 rel.policies.insert(create_policy.name.clone());
1457 } else {
1458 return MutationResult::Conflict {
1459 reason: format!("relation '{}' does not exist", create_policy.table),
1460 };
1461 }
1462 MutationResult::Applied
1463 }
1464 Mutation::DropPolicy(drop_policy) => {
1465 self.snapshot_relation(&drop_policy.table);
1466 if let Some(RelationOverlay::Present(rel)) =
1467 self.local.relations.get_mut(&drop_policy.table)
1468 {
1469 if !rel.policies.contains(&drop_policy.name) {
1470 return if drop_policy.if_exists {
1471 MutationResult::Skipped
1472 } else {
1473 MutationResult::Conflict {
1474 reason: format!(
1475 "policy '{}' does not exist on relation '{}'",
1476 drop_policy.name, drop_policy.table
1477 ),
1478 }
1479 };
1480 }
1481 rel.policies.remove(&drop_policy.name);
1482 } else {
1483 return MutationResult::Conflict {
1484 reason: format!("relation '{}' does not exist", drop_policy.table),
1485 };
1486 }
1487 MutationResult::Applied
1488 }
1489 Mutation::CreateTrigger(create_trigger) => {
1490 let trigger_id = Self::trigger_key(&create_trigger.table, &create_trigger.name);
1491 if matches!(
1492 self.local.triggers.get(&trigger_id),
1493 Some(TriggerOverlay::Present(_))
1494 ) {
1495 return MutationResult::Conflict {
1496 reason: format!(
1497 "trigger '{}' already exists on relation '{}'",
1498 create_trigger.name, create_trigger.table
1499 ),
1500 };
1501 }
1502 self.snapshot_trigger(&trigger_id);
1503 self.local.triggers.insert(
1504 trigger_id.clone(),
1505 TriggerOverlay::Present(crate::model::trigger::TriggerState {
1506 name: create_trigger.name.clone(),
1507 id: trigger_id.clone(),
1508 table_id: create_trigger.table.clone(),
1509 enabled_mode: crate::model::trigger::TriggerEnableMode::Origin,
1510 generation: self.local.generation_counter,
1511 }),
1512 );
1513
1514 self.snapshot_relation(&create_trigger.table);
1515 if let Some(RelationOverlay::Present(rel)) =
1516 self.local.relations.get_mut(&create_trigger.table)
1517 {
1518 rel.triggers.insert(create_trigger.name.clone());
1519 }
1520
1521 self.snapshot_graph_full();
1522 self.local.graph.edges.push(DependencyEdge::new(
1523 trigger_id.clone(),
1524 create_trigger.table.clone(),
1525 DependencyKind::TriggerOnTable {
1526 trigger_id: trigger_id.clone(),
1527 function_id: create_trigger.function_id.clone(),
1528 },
1529 ));
1530
1531 MutationResult::Applied
1532 }
1533 Mutation::DropTrigger(drop_trigger) => {
1534 let trigger_id = Self::trigger_key(&drop_trigger.table, &drop_trigger.name);
1535 if !matches!(
1536 self.local.triggers.get(&trigger_id),
1537 Some(TriggerOverlay::Present(_))
1538 ) {
1539 return if drop_trigger.if_exists {
1540 MutationResult::Skipped
1541 } else {
1542 MutationResult::Conflict {
1543 reason: format!(
1544 "trigger '{}' does not exist on relation '{}'",
1545 drop_trigger.name, drop_trigger.table
1546 ),
1547 }
1548 };
1549 }
1550 self.snapshot_trigger(&trigger_id);
1551 self.local
1552 .triggers
1553 .insert(trigger_id.clone(), TriggerOverlay::Dropped);
1554
1555 self.snapshot_relation(&drop_trigger.table);
1556 if let Some(RelationOverlay::Present(rel)) =
1557 self.local.relations.get_mut(&drop_trigger.table)
1558 {
1559 rel.triggers.remove(&drop_trigger.name);
1560 }
1561
1562 self.snapshot_graph_full();
1563 self.local.graph.edges.retain(|e| {
1564 !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
1565 && e.dependent == trigger_id)
1566 });
1567
1568 MutationResult::Applied
1569 }
1570 Mutation::AlterTable(alter) => {
1571 if let AlterTableActionMutation::OwnerTo { new_owner } = &alter.action {
1572 let Some((owner, known)) = self.role_fact_identity(new_owner) else {
1573 self.snapshot_confidence();
1574 self.local.confidence = Confidence::Tainted;
1575 return MutationResult::Skipped;
1576 };
1577 if !known {
1578 self.snapshot_confidence();
1579 self.local.confidence = Confidence::Tainted;
1580 }
1581 self.snapshot_relation(&alter.id);
1582 return match self.local.relations.get_mut(&alter.id) {
1583 Some(RelationOverlay::Present(relation)) => {
1584 relation.owner = ObjectId::new("", owner);
1585 MutationResult::Applied
1586 }
1587 _ => MutationResult::Conflict {
1588 reason: format!("relation '{}' does not exist", alter.id),
1589 },
1590 };
1591 }
1592
1593 let trigger_mode = match &alter.action {
1594 AlterTableActionMutation::DisableTrigger { trigger_name } => Some((
1595 trigger_name.as_deref(),
1596 crate::model::trigger::TriggerEnableMode::Disabled,
1597 )),
1598 AlterTableActionMutation::EnableTrigger { trigger_name } => Some((
1599 trigger_name.as_deref(),
1600 crate::model::trigger::TriggerEnableMode::Origin,
1601 )),
1602 _ => None,
1603 };
1604 if let Some((trigger_name, enabled_mode)) = trigger_mode {
1605 let all = trigger_name.is_none_or(|name| name.eq_ignore_ascii_case("all"));
1606 let trigger_ids: Vec<ObjectId> = self
1607 .local
1608 .triggers
1609 .iter()
1610 .filter_map(|(id, overlay)| {
1611 let TriggerOverlay::Present(trigger) = overlay else {
1612 return None;
1613 };
1614 (trigger.table_id == alter.id
1615 && (all || trigger_name == Some(trigger.name.as_str())))
1616 .then(|| id.clone())
1617 })
1618 .collect();
1619 for trigger_id in trigger_ids {
1620 self.snapshot_trigger(&trigger_id);
1621 if let Some(TriggerOverlay::Present(trigger)) =
1622 self.local.triggers.get_mut(&trigger_id)
1623 {
1624 trigger.enabled_mode = enabled_mode;
1625 }
1626 }
1627 return MutationResult::Applied;
1628 }
1629
1630 if let AlterTableActionMutation::AddForeignKey {
1631 to_table,
1632 from_columns,
1633 to_columns,
1634 ..
1635 } = &alter.action
1636 {
1637 if let Some(RelationOverlay::Present(child)) =
1638 self.local.relations.get(&alter.id)
1639 {
1640 if let Some(column) =
1641 from_columns.iter().find(|column| !child.has_column(column))
1642 {
1643 return MutationResult::Conflict {
1644 reason: format!(
1645 "foreign key column '{}' does not exist on relation '{}'",
1646 column, alter.id
1647 ),
1648 };
1649 }
1650 }
1651
1652 let Some(RelationOverlay::Present(parent)) = self.local.relations.get(to_table)
1653 else {
1654 return MutationResult::Conflict {
1655 reason: format!(
1656 "foreign key references relation '{}' which does not exist",
1657 to_table
1658 ),
1659 };
1660 };
1661 if let Some(column) =
1662 to_columns.iter().find(|column| !parent.has_column(column))
1663 {
1664 return MutationResult::Conflict {
1665 reason: format!(
1666 "foreign key references column '{}.{}' which does not exist",
1667 to_table, column
1668 ),
1669 };
1670 }
1671 }
1672
1673 let using_index = match &alter.action {
1674 AlterTableActionMutation::AddUniqueConstraint { using_index, .. }
1675 | AlterTableActionMutation::AddPrimaryKeyConstraint { using_index, .. } => {
1676 using_index.as_ref()
1677 }
1678 _ => None,
1679 };
1680 if let Some(index) = using_index {
1681 let Some(edge) = self.local.graph.edges.iter().find(|edge| {
1682 matches!(edge.kind, DependencyKind::IndexOnRelation { .. })
1683 && edge.dependent == *index
1684 }) else {
1685 return MutationResult::Conflict {
1686 reason: format!(
1687 "constraint references index '{}' which does not exist",
1688 index
1689 ),
1690 };
1691 };
1692 if edge.referenced != alter.id {
1693 return MutationResult::Conflict {
1694 reason: format!(
1695 "constraint index '{}' belongs to relation '{}', not '{}'",
1696 index, edge.referenced, alter.id
1697 ),
1698 };
1699 }
1700 if let DependencyKind::IndexOnRelation {
1701 has_predicate,
1702 is_unique,
1703 eligibility_known,
1704 ..
1705 } = &edge.kind
1706 && *eligibility_known
1707 && (!is_unique || *has_predicate)
1708 {
1709 return MutationResult::Conflict {
1710 reason: format!(
1711 "constraint index '{}' must be unique and non-partial",
1712 index
1713 ),
1714 };
1715 }
1716 }
1717
1718 self.snapshot_relation(&alter.id);
1719 let rel_overlay = self.local.relations.get_mut(&alter.id);
1720 if let Some(RelationOverlay::Present(rel)) = rel_overlay {
1721 let generation = rel.generation;
1722 match &alter.action {
1723 AlterTableActionMutation::AddColumn {
1724 name,
1725 ty,
1726 if_not_exists,
1727 not_null,
1728 default,
1729 depends_on,
1730 } => {
1731 if let Some(existing_col) = rel.columns.iter().find(|c| c.name == *name)
1732 {
1733 if *if_not_exists {
1734 return MutationResult::Skipped;
1735 }
1736 return MutationResult::Conflict {
1737 reason: format!(
1738 "column '{}' already exists with type {}; this statement adds it again with type {}",
1739 name,
1740 existing_col.data_type.as_deref().unwrap_or("unknown"),
1741 ty.as_deref().unwrap_or("unknown")
1742 ),
1743 };
1744 }
1745 rel.apply_column_action(&ColumnAction::Add {
1746 name: name.clone(),
1747 data_type: ty.clone(),
1748 not_null: *not_null,
1749 default: default.clone(),
1750 });
1751
1752 if let Some((source_table, source_col)) = depends_on {
1753 self.snapshot_graph();
1754 self.local.graph.edges.push(DependencyEdge::new(
1755 alter.id.clone(),
1756 source_table.clone(),
1757 DependencyKind::ColumnGeneratedFrom {
1758 column: name.clone(),
1759 depends_on_column: source_col.clone(),
1760 },
1761 ));
1762 }
1763 }
1764 AlterTableActionMutation::DropColumn { name, if_exists } => {
1765 if !rel.has_column(name) {
1766 if *if_exists {
1767 return MutationResult::Skipped;
1769 }
1770 return MutationResult::Conflict {
1771 reason: format!(
1772 "column '{}' does not exist on relation '{}'",
1773 name, alter.id
1774 ),
1775 };
1776 }
1777 rel.apply_column_action(&ColumnAction::Drop { name: name.clone() });
1778 }
1779 AlterTableActionMutation::RenameColumn { from, to } => {
1780 rel.apply_column_action(&ColumnAction::Rename {
1781 from: from.clone(),
1782 to: to.clone(),
1783 });
1784 }
1785 AlterTableActionMutation::SetNotNull { column } => {
1786 rel.apply_column_action(&ColumnAction::SetNotNull {
1787 name: column.clone(),
1788 });
1789 }
1790 AlterTableActionMutation::DropNotNull { column } => {
1791 rel.apply_column_action(&ColumnAction::DropNotNull {
1792 name: column.clone(),
1793 });
1794 }
1795 AlterTableActionMutation::SetType { column, ty, .. } => {
1796 if !rel.has_column(column) {
1797 self.local.confidence = Confidence::Tainted;
1798 }
1799 rel.apply_column_action(&ColumnAction::SetType {
1800 name: column.clone(),
1801 data_type: ty.clone(),
1802 });
1803 }
1804 AlterTableActionMutation::SetDefault { column, default } => {
1805 if !rel.has_column(column) {
1806 self.local.confidence = Confidence::Tainted;
1807 }
1808 rel.apply_column_action(&ColumnAction::SetDefault {
1809 name: column.clone(),
1810 default: default.clone(),
1811 });
1812 }
1813 AlterTableActionMutation::AddForeignKey {
1814 constraint_name,
1815 to_table,
1816 from_columns,
1817 to_columns,
1818 not_valid,
1819 } => {
1820 let constraint_name = constraint_name.clone().unwrap_or_else(|| {
1821 format!("{}_{}_fkey", alter.id.name, from_columns.join("_"))
1822 });
1823 self.snapshot_constraint(&alter.id, &constraint_name);
1824 self.local.constraints.insert(
1825 (alter.id.clone(), constraint_name.clone()),
1826 ConstraintState {
1827 table_id: alter.id.clone(),
1828 name: constraint_name.clone(),
1829 kind: ConstraintKind::ForeignKey,
1830 validated: !not_valid,
1831 },
1832 );
1833 self.snapshot_graph();
1834 self.local.graph.edges.push(DependencyEdge::new(
1835 alter.id.clone(),
1836 to_table.clone(),
1837 DependencyKind::ForeignKey {
1838 constraint_name: Some(constraint_name),
1839 from_columns: from_columns.clone(),
1840 to_columns: to_columns.clone(),
1841 from_generation: generation,
1842 },
1843 ));
1844 }
1845 AlterTableActionMutation::DropConstraint { name } => {
1846 self.snapshot_constraint(&alter.id, name);
1847 self.local
1848 .constraints
1849 .remove(&(alter.id.clone(), name.clone()));
1850 self.snapshot_graph();
1851 self.local.graph.edges.retain(|e| {
1852 if let DependencyKind::ForeignKey {
1853 constraint_name, ..
1854 } = &e.kind
1855 {
1856 !(e.dependent == alter.id
1857 && constraint_name.as_ref() == Some(name))
1858 } else {
1859 true
1860 }
1861 });
1862 }
1863 AlterTableActionMutation::RenameConstraint { old_name, new_name } => {
1864 self.snapshot_constraint(&alter.id, old_name);
1865 self.snapshot_constraint(&alter.id, new_name);
1866 if let Some(mut constraint) = self
1867 .local
1868 .constraints
1869 .remove(&(alter.id.clone(), old_name.clone()))
1870 {
1871 constraint.name = new_name.clone();
1872 self.local
1873 .constraints
1874 .insert((alter.id.clone(), new_name.clone()), constraint);
1875 }
1876 self.snapshot_graph_full();
1877 for edge in &mut self.local.graph.edges {
1878 if edge.dependent == alter.id
1879 && let DependencyKind::ForeignKey {
1880 constraint_name, ..
1881 } = &mut edge.kind
1882 && constraint_name.as_deref() == Some(old_name)
1883 {
1884 *constraint_name = Some(new_name.clone());
1885 }
1886 }
1887 }
1888 AlterTableActionMutation::AddCheckConstraint {
1889 constraint_name,
1890 not_valid,
1891 } => {
1892 let constraint_name = constraint_name
1893 .clone()
1894 .unwrap_or_else(|| format!("{}_check", alter.id.name));
1895 self.snapshot_constraint(&alter.id, &constraint_name);
1896 self.local.constraints.insert(
1897 (alter.id.clone(), constraint_name.clone()),
1898 ConstraintState {
1899 table_id: alter.id.clone(),
1900 name: constraint_name,
1901 kind: ConstraintKind::Check,
1902 validated: !not_valid,
1903 },
1904 );
1905 }
1906 AlterTableActionMutation::AddUniqueConstraint {
1907 constraint_name,
1908 using_index,
1909 } => {
1910 let constraint_name = constraint_name
1911 .clone()
1912 .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
1913 .unwrap_or_else(|| format!("{}_key", alter.id.name));
1914 self.snapshot_constraint(&alter.id, &constraint_name);
1915 self.local.constraints.insert(
1916 (alter.id.clone(), constraint_name.clone()),
1917 ConstraintState {
1918 table_id: alter.id.clone(),
1919 name: constraint_name,
1920 kind: ConstraintKind::Unique,
1921 validated: true,
1922 },
1923 );
1924 }
1925 AlterTableActionMutation::AddPrimaryKeyConstraint {
1926 constraint_name,
1927 using_index,
1928 } => {
1929 let constraint_name = constraint_name
1930 .clone()
1931 .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
1932 .unwrap_or_else(|| format!("{}_pkey", alter.id.name));
1933 self.snapshot_constraint(&alter.id, &constraint_name);
1934 self.local.constraints.insert(
1935 (alter.id.clone(), constraint_name.clone()),
1936 ConstraintState {
1937 table_id: alter.id.clone(),
1938 name: constraint_name,
1939 kind: ConstraintKind::PrimaryKey,
1940 validated: true,
1941 },
1942 );
1943 }
1944 AlterTableActionMutation::AddExcludeConstraint { constraint_name } => {
1945 let constraint_name = constraint_name
1946 .clone()
1947 .unwrap_or_else(|| format!("{}_excl", alter.id.name));
1948 self.snapshot_constraint(&alter.id, &constraint_name);
1949 self.local.constraints.insert(
1950 (alter.id.clone(), constraint_name.clone()),
1951 ConstraintState {
1952 table_id: alter.id.clone(),
1953 name: constraint_name,
1954 kind: ConstraintKind::Exclusion,
1955 validated: true,
1956 },
1957 );
1958 }
1959 AlterTableActionMutation::ValidateConstraint { constraint_name } => {
1960 self.snapshot_constraint(&alter.id, constraint_name);
1961 if let Some(constraint) = self
1962 .local
1963 .constraints
1964 .get_mut(&(alter.id.clone(), constraint_name.clone()))
1965 {
1966 constraint.validated = true;
1967 }
1968 }
1969 AlterTableActionMutation::AttachPartition { child } => {
1970 if self.local.graph.check_partition_cycle(&alter.id, child) {
1972 self.snapshot_confidence();
1973 self.local.confidence = Confidence::Tainted;
1974 } else {
1975 self.snapshot_graph();
1976 self.local.graph.edges.push(DependencyEdge::new(
1977 child.clone(),
1978 alter.id.clone(),
1979 DependencyKind::PartitionOf,
1980 ));
1981 }
1982 }
1983 AlterTableActionMutation::DetachPartition { child } => {
1984 self.snapshot_graph();
1985 self.local.graph.edges.retain(|e| {
1986 !(matches!(e.kind, DependencyKind::PartitionOf)
1987 && e.dependent == *child
1988 && e.referenced == alter.id)
1989 });
1990 }
1991 _ => {}
1992 }
1993 }
1994 MutationResult::Applied
1995 }
1996 Mutation::CreateType(create_type) => {
1997 if self.relation_namespace_is_taken(&create_type.id) {
1998 return MutationResult::Conflict {
1999 reason: format!("type '{}' already exists", create_type.id),
2000 };
2001 }
2002 self.snapshot_type(&create_type.id);
2003 self.snapshot_generation_counter();
2004 self.local.generation_counter += 1;
2005 let generation = self.local.generation_counter;
2006
2007 self.local.types.insert(
2008 create_type.id.clone(),
2009 TypeOverlay::Present(TypeState {
2010 id: create_type.id.clone(),
2011 generation,
2012 kind: create_type.kind.clone(),
2013 }),
2014 );
2015 MutationResult::Applied
2016 }
2017 Mutation::AlterType(alter_type) => {
2018 self.snapshot_type(&alter_type.id);
2019 if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter_type.id) {
2020 match &alter_type.action {
2021 AlterTypeActionMutation::AddValue {
2022 new_value,
2023 neighbor,
2024 before,
2025 } => {
2026 if let TypeKind::Enum { variants } = &mut t.kind {
2027 if variants.contains(new_value) {
2028 return MutationResult::Skipped;
2029 }
2030 let insertion_index = neighbor
2031 .as_ref()
2032 .and_then(|neighbor| {
2033 variants.iter().position(|value| value == neighbor)
2034 })
2035 .map(|index| if *before { index } else { index + 1 })
2036 .unwrap_or(variants.len());
2037 variants.insert(insertion_index, new_value.clone());
2038 }
2039 }
2040 AlterTypeActionMutation::RenameValue {
2041 old_value,
2042 new_value,
2043 } => {
2044 let TypeKind::Enum { variants } = &mut t.kind else {
2045 return MutationResult::Conflict {
2046 reason: format!("type '{}' is not an enum", alter_type.id),
2047 };
2048 };
2049 let Some(old_index) =
2050 variants.iter().position(|value| value == old_value)
2051 else {
2052 return MutationResult::Conflict {
2053 reason: format!(
2054 "'{}' is not an existing label of enum '{}'",
2055 old_value, alter_type.id
2056 ),
2057 };
2058 };
2059 if variants.iter().any(|value| value == new_value) {
2060 return MutationResult::Conflict {
2061 reason: format!(
2062 "enum label '{}' already exists on type '{}'",
2063 new_value, alter_type.id
2064 ),
2065 };
2066 }
2067 variants[old_index] = new_value.clone();
2068 }
2069 }
2070 } else if matches!(
2071 alter_type.action,
2072 AlterTypeActionMutation::RenameValue { .. }
2073 ) {
2074 return MutationResult::Conflict {
2075 reason: format!("type '{}' does not exist", alter_type.id),
2076 };
2077 }
2078 MutationResult::Applied
2079 }
2080 Mutation::CreateDomain(create_domain) => {
2081 if self.relation_namespace_is_taken(&create_domain.id) {
2082 return MutationResult::Conflict {
2083 reason: format!("type '{}' already exists", create_domain.id),
2084 };
2085 }
2086 self.snapshot_type(&create_domain.id);
2087 self.snapshot_generation_counter();
2088 self.local.generation_counter += 1;
2089 let generation = self.local.generation_counter;
2090
2091 self.local.types.insert(
2092 create_domain.id.clone(),
2093 TypeOverlay::Present(TypeState {
2094 id: create_domain.id.clone(),
2095 generation,
2096 kind: TypeKind::Domain {
2097 base_type: create_domain.base_type.clone(),
2098 },
2099 }),
2100 );
2101 MutationResult::Applied
2102 }
2103 Mutation::AlterDomain(_) => MutationResult::Applied,
2104 Mutation::DropDomain(drop_domain) => {
2105 for id in &drop_domain.ids {
2106 self.snapshot_type(id);
2107 self.local.types.insert(id.clone(), TypeOverlay::Dropped);
2108 }
2109 MutationResult::Applied
2110 }
2111 Mutation::DropType(drop_type) => {
2112 for id in &drop_type.ids {
2113 self.snapshot_type(id);
2114 self.local.types.insert(id.clone(), TypeOverlay::Dropped);
2115 }
2116 MutationResult::Applied
2117 }
2118 Mutation::CreateSequence(create_seq) => {
2119 if create_seq.if_not_exists && self.relation_namespace_is_taken(&create_seq.id) {
2120 return MutationResult::Skipped;
2121 }
2122 if self.relation_namespace_is_taken(&create_seq.id) {
2123 return MutationResult::Conflict {
2124 reason: format!("relation '{}' already exists", create_seq.id),
2125 };
2126 }
2127 self.snapshot_sequence(&create_seq.id);
2128 self.snapshot_generation_counter();
2129 self.local.generation_counter += 1;
2130 let generation = self.local.generation_counter;
2131
2132 self.local.sequences.insert(
2133 create_seq.id.clone(),
2134 SequenceOverlay::Present(SequenceState {
2135 id: create_seq.id.clone(),
2136 generation,
2137 }),
2138 );
2139
2140 if let Some((table_id, col)) = &create_seq.owned_by {
2141 self.snapshot_graph();
2142 self.local.graph.edges.push(DependencyEdge::new(
2143 create_seq.id.clone(),
2144 table_id.clone(),
2145 DependencyKind::SequenceOwnedBy {
2146 column: col.clone(),
2147 },
2148 ));
2149 }
2150 MutationResult::Applied
2151 }
2152 Mutation::AlterSequence(alter_seq) => {
2153 self.snapshot_sequence(&alter_seq.id);
2154 self.snapshot_graph();
2155 self.local.graph.edges.retain(|e| {
2156 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
2157 && e.dependent == alter_seq.id)
2158 });
2159 if let Some((table_id, col)) = &alter_seq.owned_by {
2160 self.local.graph.edges.push(DependencyEdge::new(
2161 alter_seq.id.clone(),
2162 table_id.clone(),
2163 DependencyKind::SequenceOwnedBy {
2164 column: col.clone(),
2165 },
2166 ));
2167 }
2168 MutationResult::Applied
2169 }
2170 Mutation::DropSequence(drop_seq) => {
2171 if !drop_seq.if_exists
2172 && let Some(id) = drop_seq.ids.iter().find(|id| !self.sequence_is_present(id))
2173 {
2174 return MutationResult::Conflict {
2175 reason: format!("sequence '{}' does not exist", id),
2176 };
2177 }
2178 let present: Vec<ObjectId> = drop_seq
2179 .ids
2180 .iter()
2181 .filter(|id| self.sequence_is_present(id))
2182 .cloned()
2183 .collect();
2184 if present.is_empty() {
2185 return MutationResult::Skipped;
2186 }
2187 for id in &present {
2188 self.snapshot_sequence(id);
2189 self.local
2190 .sequences
2191 .insert(id.clone(), SequenceOverlay::Dropped);
2192 }
2193 self.snapshot_graph_full();
2194 self.local.graph.edges.retain(|e| {
2195 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
2196 && present.contains(&e.dependent))
2197 });
2198 MutationResult::Applied
2199 }
2200 Mutation::Rename(rename) => {
2201 self.snapshot_relation(&rename.old_id);
2202 self.snapshot_relation(&rename.new_id);
2203 if let Some(RelationOverlay::Present(mut state)) =
2204 self.local.relations.remove(&rename.old_id)
2205 {
2206 state.id = rename.new_id.clone();
2207 self.local
2208 .relations
2209 .insert(rename.new_id.clone(), RelationOverlay::Present(state));
2210 }
2211 let constraints_to_move: Vec<(String, ConstraintState)> = self
2212 .local
2213 .constraints
2214 .iter()
2215 .filter(|((table_id, _), _)| table_id == &rename.old_id)
2216 .map(|((_, name), constraint)| (name.clone(), constraint.clone()))
2217 .collect();
2218 for (name, mut constraint) in constraints_to_move {
2219 self.snapshot_constraint(&rename.old_id, &name);
2220 self.snapshot_constraint(&rename.new_id, &name);
2221 self.local
2222 .constraints
2223 .remove(&(rename.old_id.clone(), name.clone()));
2224 constraint.table_id = rename.new_id.clone();
2225 self.local
2226 .constraints
2227 .insert((rename.new_id.clone(), name), constraint);
2228 }
2229 self.snapshot_graph();
2230 self.local.graph.edges.push(DependencyEdge::new(
2231 rename.old_id.clone(),
2232 rename.new_id.clone(),
2233 DependencyKind::RenameTo,
2234 ));
2235
2236 self.snapshot_graph_full();
2238
2239 self.local
2240 .graph
2241 .propagate_rename(&rename.old_id, &rename.new_id);
2242
2243 MutationResult::Applied
2244 }
2245 Mutation::DropView(drop_view) => {
2246 for id in &drop_view.ids {
2247 self.snapshot_relation(id);
2248 self.local
2249 .relations
2250 .insert(id.clone(), RelationOverlay::Dropped);
2251 }
2252 self.snapshot_graph_full();
2253 self.local.graph.edges.retain(|e| {
2254 !(matches!(e.kind, DependencyKind::ViewDependency { .. })
2255 && drop_view.ids.contains(&e.dependent))
2256 });
2257 MutationResult::Applied
2258 }
2259 Mutation::DropMaterializedView(drop_mv) => {
2260 for id in &drop_mv.ids {
2261 self.snapshot_relation(id);
2262 self.local
2263 .relations
2264 .insert(id.clone(), RelationOverlay::Dropped);
2265 }
2266 self.snapshot_graph_full();
2267 self.local.graph.edges.retain(|e| {
2268 !((matches!(e.kind, DependencyKind::ViewDependency { .. })
2269 && drop_mv.ids.contains(&e.dependent))
2270 || (matches!(e.kind, DependencyKind::IndexOnRelation { .. })
2271 && drop_mv.ids.contains(&e.referenced)))
2272 });
2273 MutationResult::Applied
2274 }
2275 Mutation::DropIndex(drop_idx) => {
2276 self.snapshot_graph();
2277 self.local.graph.edges.retain(|e| {
2278 !(matches!(e.kind, DependencyKind::IndexOnRelation { .. })
2279 && e.dependent == drop_idx.id)
2280 });
2281 MutationResult::Applied
2282 }
2283 Mutation::ChangeRelationOwner { id, new_owner } => {
2284 let Some((owner, known)) = self.role_fact_identity(new_owner) else {
2285 self.snapshot_confidence();
2286 self.local.confidence = Confidence::Tainted;
2287 return MutationResult::Skipped;
2288 };
2289 if !known {
2290 self.snapshot_confidence();
2291 self.local.confidence = Confidence::Tainted;
2292 }
2293 self.snapshot_relation(id);
2294 if let Some(RelationOverlay::Present(relation)) = self.local.relations.get_mut(id) {
2295 relation.owner = ObjectId::new("", owner);
2296 MutationResult::Applied
2297 } else {
2298 MutationResult::Conflict {
2299 reason: format!("relation '{}' does not exist", id),
2300 }
2301 }
2302 }
2303 Mutation::SearchPath(sp) => {
2304 self.snapshot_search_path();
2305 self.snapshot_confidence();
2306 match &sp.target {
2307 SearchPathTarget::Default => {
2308 self.local.search_path_template =
2309 self.local.default_search_path_template.clone();
2310 self.refresh_role_sensitive_search_path();
2311 }
2312 SearchPathTarget::Schemas(schemas) => {
2313 self.local.search_path_template = schemas.clone();
2314 self.refresh_role_sensitive_search_path();
2315 }
2316 }
2317 MutationResult::Applied
2318 }
2319 Mutation::SwitchRole {
2320 role,
2321 local,
2322 is_session_auth,
2323 } => {
2324 if *local && self.local.transactions.is_empty() {
2325 return MutationResult::Skipped;
2327 }
2328
2329 let target = if let Some(role) = role {
2330 let Some(identity) = self.role_fact_identity(role) else {
2331 self.snapshot_confidence();
2332 self.local.confidence = Confidence::Tainted;
2333 return MutationResult::Skipped;
2334 };
2335 Some(identity)
2336 } else if *is_session_auth {
2337 Some((
2338 self.local.authenticated_role.clone(),
2339 self.local.authenticated_role_known,
2340 ))
2341 } else {
2342 Some((
2343 self.local.session_role.clone(),
2344 self.local.session_role_known,
2345 ))
2346 };
2347 let (target_name, target_known) = target.expect("role reset always has a target");
2348 let persistent_role_reset_target = if role.is_none() && !*is_session_auth {
2349 Some((
2350 self.local.persistent_session_role.clone(),
2351 self.local.persistent_session_role_known,
2352 ))
2353 } else {
2354 None
2355 };
2356
2357 let authorized = if role.is_none() {
2358 Some(true)
2359 } else if *is_session_auth {
2360 self.can_set_session_authorization_to(&target_name)
2361 } else {
2362 self.can_set_role_to(&target_name)
2363 };
2364 match authorized {
2365 Some(false) => {
2366 return MutationResult::Conflict {
2367 reason: if self.present_role(&target_name).is_none() {
2368 format!("role '{}' does not exist", target_name)
2369 } else {
2370 format!("permission denied to set role '{}'", target_name)
2371 },
2372 };
2373 }
2374 None => {
2375 self.snapshot_confidence();
2376 self.local.confidence = Confidence::Tainted;
2377 }
2378 Some(true) => {}
2379 }
2380
2381 self.snapshot_role_context();
2382 self.snapshot_search_path();
2383 self.snapshot_confidence();
2384 if *is_session_auth {
2385 self.local.session_role = target_name.clone();
2386 self.local.session_role_known = target_known;
2387 self.local.current_role = target_name.clone();
2388 self.local.current_role_known = target_known;
2389 if !local {
2390 self.local.persistent_session_role = target_name.clone();
2391 self.local.persistent_session_role_known = target_known;
2392 self.local.persistent_current_role = target_name;
2393 self.local.persistent_current_role_known = target_known;
2394 }
2395 } else {
2396 self.local.current_role = target_name.clone();
2397 self.local.current_role_known = target_known;
2398 if !local {
2399 let (persistent_name, persistent_known) =
2400 persistent_role_reset_target.unwrap_or((target_name, target_known));
2401 self.local.persistent_current_role = persistent_name;
2402 self.local.persistent_current_role_known = persistent_known;
2403 }
2404 }
2405 self.refresh_role_sensitive_search_path();
2406 MutationResult::Applied
2407 }
2408 Mutation::BeginTransaction => {
2409 if self.local.transactions.is_empty() {
2410 self.local.transactions.push(TransactionFrame::root());
2411 MutationResult::Applied
2412 } else {
2413 MutationResult::Skipped
2416 }
2417 }
2418 Mutation::CommitTransaction => {
2419 if self.local.transaction_aborted {
2420 while let Some(frame) = self.local.transactions.pop() {
2421 self.rollback_frame(frame);
2422 }
2423 } else {
2424 while self.local.transactions.pop().is_some() {}
2425 self.restore_persistent_role_context();
2426 }
2427 self.local.transaction_aborted = false;
2428 MutationResult::Applied
2429 }
2430 Mutation::CommitAndChain => {
2431 if self.local.transactions.is_empty() {
2432 self.local.confidence = Confidence::Tainted;
2433 return MutationResult::Conflict {
2434 reason: "COMMIT AND CHAIN can only be used in transaction blocks"
2435 .to_string(),
2436 };
2437 }
2438 if self.local.transaction_aborted {
2439 while let Some(frame) = self.local.transactions.pop() {
2440 self.rollback_frame(frame);
2441 }
2442 } else {
2443 while self.local.transactions.pop().is_some() {}
2444 self.restore_persistent_role_context();
2445 }
2446 self.local.transaction_aborted = false;
2447 self.local.transactions.push(TransactionFrame::root());
2448 MutationResult::Applied
2449 }
2450 Mutation::RollbackTransaction => {
2451 while let Some(frame) = self.local.transactions.pop() {
2452 self.rollback_frame(frame);
2453 }
2454 self.local.transaction_aborted = false;
2455 MutationResult::Applied
2456 }
2457 Mutation::RollbackAndChain => {
2458 if self.local.transactions.is_empty() {
2459 self.local.confidence = Confidence::Tainted;
2460 return MutationResult::Conflict {
2461 reason: "ROLLBACK AND CHAIN can only be used in transaction blocks"
2462 .to_string(),
2463 };
2464 }
2465 while let Some(frame) = self.local.transactions.pop() {
2466 self.rollback_frame(frame);
2467 }
2468 self.local.transaction_aborted = false;
2469 self.local.transactions.push(TransactionFrame::root());
2470 MutationResult::Applied
2471 }
2472 Mutation::RollbackToSavepoint(rts) => {
2473 let Some(position) = self
2474 .local
2475 .transactions
2476 .iter()
2477 .rposition(|frame| frame.is_named_savepoint(&rts.name))
2478 else {
2479 self.local.confidence = Confidence::Tainted;
2480 if !self.local.transactions.is_empty() {
2481 self.local.transaction_aborted = true;
2482 }
2483 return MutationResult::Conflict {
2484 reason: format!("savepoint '{}' does not exist", rts.name),
2485 };
2486 };
2487 let rolled_back = self.local.transactions.split_off(position + 1);
2488 for frame in rolled_back.into_iter().rev() {
2492 self.rollback_frame(frame);
2493 }
2494 let undo_log = std::mem::take(&mut self.local.transactions[position].undo_log);
2495 self.rollback_undo_log(undo_log);
2496 self.local.transaction_aborted = false;
2497 MutationResult::Applied
2498 }
2499 Mutation::Savepoint(sp) => {
2500 if self.local.transactions.is_empty() {
2501 self.local.confidence = Confidence::Tainted;
2502 return MutationResult::Conflict {
2503 reason: "SAVEPOINT can only be used in transaction blocks".to_string(),
2504 };
2505 }
2506 self.local
2507 .transactions
2508 .push(TransactionFrame::savepoint(sp.name.clone()));
2509 MutationResult::Applied
2510 }
2511 Mutation::ReleaseSavepoint(rsp) => {
2512 let Some(position) = self
2513 .local
2514 .transactions
2515 .iter()
2516 .rposition(|frame| frame.is_named_savepoint(&rsp.name))
2517 else {
2518 self.local.confidence = Confidence::Tainted;
2519 if !self.local.transactions.is_empty() {
2520 self.local.transaction_aborted = true;
2521 }
2522 return MutationResult::Conflict {
2523 reason: format!("savepoint '{}' does not exist", rsp.name),
2524 };
2525 };
2526 if position == 0 {
2527 self.local.confidence = Confidence::Tainted;
2528 return MutationResult::Conflict {
2529 reason: format!("savepoint '{}' is not inside a transaction", rsp.name),
2530 };
2531 }
2532
2533 let released = self.local.transactions.split_off(position);
2534 let outer = self
2535 .local
2536 .transactions
2537 .last_mut()
2538 .expect("a released savepoint always has an outer transaction frame");
2539 for frame in released {
2540 outer.undo_log.extend(frame.undo_log);
2541 }
2542 MutationResult::Applied
2543 }
2544 Mutation::Opaque(_) => {
2545 self.snapshot_confidence();
2546 self.local.confidence = Confidence::Tainted;
2547 MutationResult::Applied
2548 }
2549 Mutation::CreateFunction(f) => {
2550 if matches!(
2551 self.local.functions.get(&f.id),
2552 Some(crate::model::function::FunctionOverlay::Present(_))
2553 ) && !f.or_replace
2554 {
2555 return MutationResult::Conflict {
2556 reason: format!("routine '{}' already exists", f.id),
2557 };
2558 }
2559 self.snapshot_function(&f.id);
2560 self.snapshot_generation_counter();
2561 self.local.generation_counter += 1;
2562 let _generation = self.local.generation_counter;
2563
2564 let volatility = f
2565 .options
2566 .iter()
2567 .find_map(|opt| {
2568 if let crate::analysis::facts::FuncOptionFact::Volatility(v) = opt {
2569 Some(match v {
2570 crate::analysis::facts::VolatilityKind::Volatile => {
2571 crate::model::function::Volatility::Volatile
2572 }
2573 crate::analysis::facts::VolatilityKind::Stable => {
2574 crate::model::function::Volatility::Stable
2575 }
2576 crate::analysis::facts::VolatilityKind::Immutable => {
2577 crate::model::function::Volatility::Immutable
2578 }
2579 })
2580 } else {
2581 None
2582 }
2583 })
2584 .unwrap_or(crate::model::function::Volatility::Volatile);
2585
2586 let security = f
2587 .options
2588 .iter()
2589 .find_map(|opt| {
2590 if let crate::analysis::facts::FuncOptionFact::Security(s) = opt {
2591 Some(match s {
2592 crate::analysis::facts::SecurityKind::Invoker => {
2593 crate::model::function::SecurityMode::Invoker
2594 }
2595 crate::analysis::facts::SecurityKind::Definer => {
2596 crate::model::function::SecurityMode::Definer
2597 }
2598 })
2599 } else {
2600 None
2601 }
2602 })
2603 .unwrap_or(crate::model::function::SecurityMode::Invoker);
2604
2605 let language = f
2606 .options
2607 .iter()
2608 .find_map(|opt| {
2609 if let crate::analysis::facts::FuncOptionFact::Language(l) = opt {
2610 Some(l.clone())
2611 } else {
2612 None
2613 }
2614 })
2615 .unwrap_or_else(|| "sql".to_string());
2616
2617 self.local.functions.insert(
2618 f.id.clone(),
2619 crate::model::function::FunctionOverlay::Present(
2620 crate::model::function::FunctionState {
2621 id: f.id.clone(),
2622 arg_types: f.params.iter().map(|p| p.ty.clone()).collect(),
2623 return_type: f
2624 .return_type
2625 .as_ref()
2626 .map(|rt| format!("{:?}", rt))
2627 .unwrap_or_default(),
2628 volatility,
2629 language,
2630 security,
2631 },
2632 ),
2633 );
2634 MutationResult::Applied
2635 }
2636 Mutation::AlterFunction(f) => {
2637 use crate::analysis::facts::{AlterFunctionAction, FuncOptionFact};
2638 use crate::model::function::{FunctionOverlay, SecurityMode, Volatility};
2639
2640 match &f.action {
2641 AlterFunctionAction::OptionsChange(options) => {
2642 self.snapshot_function(&f.id);
2643 if let Some(FunctionOverlay::Present(function)) =
2644 self.local.functions.get_mut(&f.id)
2645 {
2646 for option in options {
2647 match option {
2648 FuncOptionFact::Volatility(volatility) => {
2649 function.volatility = match volatility {
2650 crate::analysis::facts::VolatilityKind::Volatile => {
2651 Volatility::Volatile
2652 }
2653 crate::analysis::facts::VolatilityKind::Stable => {
2654 Volatility::Stable
2655 }
2656 crate::analysis::facts::VolatilityKind::Immutable => {
2657 Volatility::Immutable
2658 }
2659 };
2660 }
2661 FuncOptionFact::Security(security) => {
2662 function.security = match security {
2663 crate::analysis::facts::SecurityKind::Invoker => {
2664 SecurityMode::Invoker
2665 }
2666 crate::analysis::facts::SecurityKind::Definer => {
2667 SecurityMode::Definer
2668 }
2669 };
2670 }
2671 FuncOptionFact::Language(language) => {
2672 function.language = language.clone();
2673 }
2674 _ => {}
2675 }
2676 }
2677 }
2678 }
2679 AlterFunctionAction::Rename { to, .. } => {
2680 let signature =
2681 f.id.name
2682 .find('(')
2683 .map(|index| &f.id.name[index..])
2684 .unwrap_or("");
2685 let new_id = ObjectId::new(f.id.schema.clone(), format!("{to}{signature}"));
2686 self.move_function(&f.id, &new_id);
2687 }
2688 AlterFunctionAction::SchemaChange { new_schema } => {
2689 let new_id = ObjectId::new(new_schema.clone(), f.id.name.clone());
2690 self.move_function(&f.id, &new_id);
2691 }
2692 AlterFunctionAction::OwnerChange(_)
2693 | AlterFunctionAction::DependsOnExtension { .. }
2694 | AlterFunctionAction::NoDependsOnExtension { .. } => {
2695 self.snapshot_function(&f.id);
2696 }
2697 }
2698 MutationResult::Applied
2699 }
2700 Mutation::DropFunction(f) => {
2701 let mut any_applied = false;
2702 for sig in &f.signatures {
2703 let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
2704 let schema = self.resolve_function_schema(&sig.name, &sig_str);
2705 let id = ObjectId::new(schema, sig_str);
2706 if !matches!(
2707 self.local.functions.get(&id),
2708 Some(crate::model::function::FunctionOverlay::Present(_))
2709 ) {
2710 if !f.if_exists {
2711 return MutationResult::Conflict {
2712 reason: format!("function '{}' does not exist", id),
2713 };
2714 }
2715 } else {
2716 let dependent_triggers: Vec<(ObjectId, ObjectId)> = self
2717 .local
2718 .graph
2719 .edges
2720 .iter()
2721 .filter_map(|edge| {
2722 let DependencyKind::TriggerOnTable { function_id, .. } = &edge.kind
2723 else {
2724 return None;
2725 };
2726 (function_id == &id)
2727 .then(|| (edge.dependent.clone(), edge.referenced.clone()))
2728 })
2729 .collect();
2730 if !dependent_triggers.is_empty() && !f.cascade {
2731 return MutationResult::Conflict {
2732 reason: format!(
2733 "function '{}' still has dependent triggers; use CASCADE",
2734 id
2735 ),
2736 };
2737 }
2738
2739 any_applied = true;
2740 self.snapshot_function(&id);
2741 self.local
2742 .functions
2743 .insert(id.clone(), crate::model::function::FunctionOverlay::Dropped);
2744
2745 if f.cascade {
2746 for (trigger_id, table_id) in &dependent_triggers {
2747 let trigger_name =
2748 self.local.triggers.get(trigger_id).and_then(|overlay| {
2749 match overlay {
2750 TriggerOverlay::Present(trigger) => {
2751 Some(trigger.name.clone())
2752 }
2753 TriggerOverlay::Dropped => None,
2754 }
2755 });
2756 self.snapshot_trigger(trigger_id);
2757 self.local
2758 .triggers
2759 .insert(trigger_id.clone(), TriggerOverlay::Dropped);
2760 self.snapshot_relation(table_id);
2761 if let Some(RelationOverlay::Present(relation)) =
2762 self.local.relations.get_mut(table_id)
2763 {
2764 if let Some(trigger_name) = trigger_name {
2765 relation.triggers.remove(&trigger_name);
2766 }
2767 }
2768 }
2769 if !dependent_triggers.is_empty() {
2770 self.snapshot_graph_full();
2771 self.local.graph.edges.retain(|edge| {
2772 !dependent_triggers
2773 .iter()
2774 .any(|(trigger_id, _)| edge.dependent == *trigger_id)
2775 });
2776 }
2777 }
2778 }
2779 }
2780 if any_applied {
2781 MutationResult::Applied
2782 } else {
2783 MutationResult::Skipped
2784 }
2785 }
2786 Mutation::CreateProcedure(p) => {
2787 if matches!(
2788 self.local.functions.get(&p.id),
2789 Some(crate::model::function::FunctionOverlay::Present(_))
2790 ) && !p.or_replace
2791 {
2792 return MutationResult::Conflict {
2793 reason: format!("routine '{}' already exists", p.id),
2794 };
2795 }
2796 self.snapshot_function(&p.id);
2797 self.snapshot_generation_counter();
2798 self.local.generation_counter += 1;
2799 let _generation = self.local.generation_counter;
2800
2801 self.local.functions.insert(
2802 p.id.clone(),
2803 crate::model::function::FunctionOverlay::Present(
2804 crate::model::function::FunctionState {
2805 id: p.id.clone(),
2806 arg_types: p.params.iter().map(|p| p.ty.clone()).collect(),
2807 return_type: "void".to_string(),
2808 volatility: crate::model::function::Volatility::Volatile,
2809 language: "sql".to_string(),
2810 security: crate::model::function::SecurityMode::Invoker,
2811 },
2812 ),
2813 );
2814 MutationResult::Applied
2815 }
2816 Mutation::AlterProcedure(p) => {
2817 self.snapshot_function(&p.id);
2818 MutationResult::Applied
2820 }
2821 Mutation::DropProcedure(p) => {
2822 let mut any_applied = false;
2823 for sig in &p.signatures {
2824 let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
2825 let schema = self.resolve_function_schema(&sig.name, &sig_str);
2826 let id = ObjectId::new(schema, sig_str);
2827 if !matches!(
2828 self.local.functions.get(&id),
2829 Some(crate::model::function::FunctionOverlay::Present(_))
2830 ) {
2831 if !p.if_exists {
2832 return MutationResult::Conflict {
2833 reason: format!("procedure '{}' does not exist", id),
2834 };
2835 }
2836 } else {
2837 any_applied = true;
2838 self.snapshot_function(&id);
2839 self.local
2840 .functions
2841 .insert(id, crate::model::function::FunctionOverlay::Dropped);
2842 }
2843 }
2844 if any_applied {
2845 MutationResult::Applied
2846 } else {
2847 MutationResult::Skipped
2848 }
2849 }
2850 Mutation::CreatePublication(p) => {
2851 self.snapshot_publication(&p.name);
2852 self.snapshot_generation_counter();
2853 self.local.generation_counter += 1;
2854 let generation = self.local.generation_counter;
2855
2856 self.local.publications.insert(
2857 p.name.clone(),
2858 crate::model::replication::PublicationOverlay::Present(
2859 crate::model::replication::PublicationState {
2860 name: p.name.clone(),
2861 scope: p.scope.clone(),
2862 params: p.params.clone(),
2863 generation,
2864 },
2865 ),
2866 );
2867
2868 if let crate::analysis::facts::PublicationScope::Explicit(objects) = &p.scope {
2869 self.snapshot_graph_full();
2870 for obj in objects {
2871 if let crate::analysis::facts::PublicationObjectFact::Table {
2872 name, ..
2873 } = obj
2874 {
2875 let table_id = self.resolve_relation_id(name);
2876 self.local.graph.edges.push(DependencyEdge::new(
2877 table_id,
2878 ObjectId::new("public", &p.name),
2879 DependencyKind::PublicationIncludes {
2880 publication_name: p.name.clone(),
2881 },
2882 ));
2883 }
2884 }
2885 }
2886 MutationResult::Applied
2887 }
2888 Mutation::AlterPublication(p) => {
2889 self.snapshot_publication(&p.name);
2890 if !self.local.publications.contains_key(&p.name) {
2891 self.local.confidence = Confidence::Tainted;
2892 return MutationResult::Skipped;
2893 }
2894 self.snapshot_generation_counter();
2895 self.local.generation_counter += 1;
2896 let new_gen = self.local.generation_counter;
2897
2898 if let Some(crate::model::replication::PublicationOverlay::Present(publ)) =
2899 self.local.publications.get_mut(&p.name)
2900 {
2901 publ.generation = new_gen;
2902 }
2903 MutationResult::Applied
2904 }
2905 Mutation::DropPublication(p) => {
2906 for name in &p.names {
2907 self.snapshot_publication(name);
2908 if !p.if_exists && !self.local.publications.contains_key(name) {
2909 self.local.confidence = Confidence::Tainted;
2910 return MutationResult::Skipped;
2911 }
2912 self.local.publications.insert(
2913 name.clone(),
2914 crate::model::replication::PublicationOverlay::Dropped,
2915 );
2916 }
2917 self.snapshot_graph_full();
2918 self.local.graph.edges.retain(|e| {
2919 !(matches!(e.kind, DependencyKind::PublicationIncludes { .. })
2920 && p.names.contains(&e.referenced.name))
2921 });
2922 MutationResult::Applied
2923 }
2924 Mutation::CreateSubscription(s) => {
2925 let name = s.name.clone().unwrap_or_else(|| "unnamed_sub".into());
2926 self.snapshot_subscription(&name);
2927 self.snapshot_generation_counter();
2928 self.local.generation_counter += 1;
2929 let generation = self.local.generation_counter;
2930
2931 self.local.subscriptions.insert(
2932 name.clone(),
2933 crate::model::replication::SubscriptionOverlay::Present(
2934 crate::model::replication::SubscriptionState {
2935 name,
2936 connection: s.connection.clone(),
2937 publications: s.publications.clone(),
2938 params: s.params.clone(),
2939 generation,
2940 },
2941 ),
2942 );
2943 MutationResult::Applied
2944 }
2945 Mutation::AlterSubscription(s) => {
2946 self.snapshot_subscription(&s.name);
2947 if !self.local.subscriptions.contains_key(&s.name) {
2948 self.local.confidence = Confidence::Tainted;
2949 return MutationResult::Skipped;
2950 }
2951 self.snapshot_generation_counter();
2952 self.local.generation_counter += 1;
2953 let new_gen = self.local.generation_counter;
2954
2955 if let Some(crate::model::replication::SubscriptionOverlay::Present(sub)) =
2956 self.local.subscriptions.get_mut(&s.name)
2957 {
2958 sub.generation = new_gen;
2959 }
2960 MutationResult::Applied
2961 }
2962 Mutation::DropSubscription(s) => {
2963 self.snapshot_subscription(&s.name);
2964 if !s.if_exists && !self.local.subscriptions.contains_key(&s.name) {
2965 self.local.confidence = Confidence::Tainted;
2966 return MutationResult::Skipped;
2967 }
2968 self.local.subscriptions.insert(
2969 s.name.clone(),
2970 crate::model::replication::SubscriptionOverlay::Dropped,
2971 );
2972 MutationResult::Applied
2973 }
2974 Mutation::CreateRole(r) => {
2975 let role_id = ObjectId::new("", &r.name);
2976 if matches!(
2977 self.local.roles.get(&role_id),
2978 Some(crate::model::role::RoleOverlay::Present(_))
2979 ) {
2980 return MutationResult::Conflict {
2981 reason: format!("role '{}' already exists", r.name),
2982 };
2983 }
2984 self.snapshot_role(&role_id);
2985 self.snapshot_generation_counter();
2986 self.local.generation_counter += 1;
2987 let _generation = self.local.generation_counter;
2988
2989 self.local.roles.insert(
2990 role_id.clone(),
2991 crate::model::role::RoleOverlay::Present(crate::model::role::RoleState {
2992 id: role_id,
2993 can_login: true,
2994 is_superuser: false,
2995 member_of: Vec::new(),
2996 can_set_role_to: Vec::new(),
2997 granted_privileges: Vec::new(),
2998 }),
2999 );
3000 MutationResult::Applied
3001 }
3002 Mutation::AlterRole(r) => {
3003 if let Some(role_id) = Self::resolve_role_name(
3004 &r.name,
3005 &self.local.current_role,
3006 &self.local.session_role,
3007 ) {
3008 self.snapshot_role(&role_id);
3009 if !self.local.roles.contains_key(&role_id) {
3010 self.local.confidence = Confidence::Tainted;
3011 return MutationResult::Skipped;
3012 }
3013 self.snapshot_generation_counter();
3014 self.local.generation_counter += 1;
3015 let _new_gen = self.local.generation_counter;
3016
3017 if let Some(crate::model::role::RoleOverlay::Present(_role)) =
3018 self.local.roles.get_mut(&role_id)
3019 {
3020 }
3022 MutationResult::Applied
3023 } else {
3024 MutationResult::Skipped
3025 }
3026 }
3027 Mutation::DropRole(r) => {
3028 for name in &r.names {
3029 if let Some(role_id) = Self::resolve_role_name(
3030 &crate::analysis::facts::RoleFact::Named {
3031 name: name.clone(),
3032 via_legacy_group_syntax: false,
3033 },
3034 &self.local.current_role,
3035 &self.local.session_role,
3036 ) {
3037 self.snapshot_role(&role_id);
3038 if !r.if_exists
3039 && !matches!(
3040 self.local.roles.get(&role_id),
3041 Some(crate::model::role::RoleOverlay::Present(_))
3042 )
3043 {
3044 return MutationResult::Conflict {
3045 reason: format!("role '{}' does not exist", name),
3046 };
3047 }
3048 self.local
3049 .roles
3050 .insert(role_id, crate::model::role::RoleOverlay::Dropped);
3051 }
3052 }
3053 MutationResult::Applied
3054 }
3055 Mutation::Grant(grant) => {
3056 let privileges = Self::resolve_grant_privileges(&grant.privileges);
3057 let grantees = &grant.grantees;
3058 match &grant.target {
3059 crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
3060 for id in ids {
3061 self.apply_grant_to_relation(id, &privileges, grantees);
3062 }
3063 }
3064 crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
3065 let target_ids: Vec<ObjectId> = self
3066 .local
3067 .relations
3068 .keys()
3069 .filter(|id| schemas.contains(&id.schema))
3070 .cloned()
3071 .collect();
3072 for id in &target_ids {
3073 self.apply_grant_to_relation(id, &privileges, grantees);
3074 }
3075 }
3076 }
3077 MutationResult::Applied
3078 }
3079 Mutation::Revoke(revoke) => {
3080 let privileges = Self::resolve_grant_privileges(&revoke.privileges);
3081 let revokees = &revoke.revokees;
3082 match &revoke.target {
3083 crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
3084 for id in ids {
3085 self.apply_revoke_to_relation(id, &privileges, revokees);
3086 }
3087 }
3088 crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
3089 let target_ids: Vec<ObjectId> = self
3090 .local
3091 .relations
3092 .keys()
3093 .filter(|id| schemas.contains(&id.schema))
3094 .cloned()
3095 .collect();
3096 for id in &target_ids {
3097 self.apply_revoke_to_relation(id, &privileges, revokees);
3098 }
3099 }
3100 }
3101 MutationResult::Applied
3102 }
3103 Mutation::CreateDatabase(_) => MutationResult::Applied,
3104 Mutation::AlterDatabase(_) => MutationResult::Applied,
3105 Mutation::DropDatabase(_) => MutationResult::Applied,
3106 Mutation::Vacuum { .. } => MutationResult::Applied,
3107 }
3108 }
3109
3110 fn snapshot_relation(&mut self, id: &ObjectId) {
3111 if let Some(frame) = self.local.transactions.last_mut() {
3112 let previous = self.local.relations.get(id).cloned();
3113 frame.undo_log.push(StateChange::RelationSnapshot {
3114 id: id.clone(),
3115 previous: Box::new(previous),
3116 });
3117 }
3118 }
3119
3120 fn snapshot_type(&mut self, id: &ObjectId) {
3121 if let Some(frame) = self.local.transactions.last_mut() {
3122 let previous = self.local.types.get(id).cloned();
3123 frame.undo_log.push(StateChange::TypeSnapshot {
3124 id: id.clone(),
3125 previous,
3126 });
3127 }
3128 }
3129
3130 fn snapshot_sequence(&mut self, id: &ObjectId) {
3131 if let Some(frame) = self.local.transactions.last_mut() {
3132 let previous = self.local.sequences.get(id).cloned();
3133 frame.undo_log.push(StateChange::SequenceSnapshot {
3134 id: id.clone(),
3135 previous,
3136 });
3137 }
3138 }
3139
3140 fn move_function(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
3141 self.snapshot_function(old_id);
3142 self.snapshot_function(new_id);
3143 if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
3144 self.local.functions.remove(old_id)
3145 {
3146 function.id = new_id.clone();
3147 self.local.functions.insert(
3148 new_id.clone(),
3149 crate::model::function::FunctionOverlay::Present(function),
3150 );
3151 }
3152
3153 self.snapshot_graph_full();
3154 self.local.graph.propagate_rename(old_id, new_id);
3155 self.local.graph.edges.push(DependencyEdge::new(
3156 old_id.clone(),
3157 new_id.clone(),
3158 DependencyKind::RenameTo,
3159 ));
3160 }
3161
3162 fn snapshot_function(&mut self, id: &ObjectId) {
3163 if let Some(frame) = self.local.transactions.last_mut() {
3164 let previous = self.local.functions.get(id).cloned();
3165 frame.undo_log.push(StateChange::FunctionSnapshot {
3166 id: id.clone(),
3167 previous,
3168 });
3169 }
3170 }
3171
3172 fn snapshot_publication(&mut self, name: &str) {
3173 if let Some(frame) = self.local.transactions.last_mut() {
3174 let previous = self.local.publications.get(name).cloned();
3175 frame.undo_log.push(StateChange::PublicationSnapshot {
3176 id: ObjectId::new("", name),
3177 previous,
3178 });
3179 }
3180 }
3181
3182 fn snapshot_subscription(&mut self, name: &str) {
3183 if let Some(frame) = self.local.transactions.last_mut() {
3184 let previous = self.local.subscriptions.get(name).cloned();
3185 frame.undo_log.push(StateChange::SubscriptionSnapshot {
3186 id: ObjectId::new("", name),
3187 previous,
3188 });
3189 }
3190 }
3191
3192 fn snapshot_role(&mut self, id: &ObjectId) {
3193 if let Some(frame) = self.local.transactions.last_mut() {
3194 let previous = self.local.roles.get(id).cloned();
3195 frame.undo_log.push(StateChange::RoleSnapshot {
3196 id: id.clone(),
3197 previous,
3198 });
3199 }
3200 }
3201
3202 fn snapshot_trigger(&mut self, id: &ObjectId) {
3203 if let Some(frame) = self.local.transactions.last_mut() {
3204 let previous = self.local.triggers.get(id).cloned();
3205 frame.undo_log.push(StateChange::TriggerSnapshot {
3206 id: id.clone(),
3207 previous,
3208 });
3209 }
3210 }
3211
3212 fn snapshot_constraint(&mut self, table_id: &ObjectId, name: &str) {
3213 if let Some(frame) = self.local.transactions.last_mut() {
3214 let key = (table_id.clone(), name.to_string());
3215 let previous = self.local.constraints.get(&key).cloned();
3216 frame.undo_log.push(StateChange::ConstraintSnapshot {
3217 table_id: table_id.clone(),
3218 name: name.to_string(),
3219 previous,
3220 });
3221 }
3222 }
3223
3224 fn snapshot_role_context(&mut self) {
3225 if let Some(frame) = self.local.transactions.last_mut() {
3226 frame.undo_log.push(StateChange::RoleContextSnapshot {
3227 current_role: self.local.current_role.clone(),
3228 current_role_known: self.local.current_role_known,
3229 persistent_current_role: self.local.persistent_current_role.clone(),
3230 persistent_current_role_known: self.local.persistent_current_role_known,
3231 session_role: self.local.session_role.clone(),
3232 session_role_known: self.local.session_role_known,
3233 persistent_session_role: self.local.persistent_session_role.clone(),
3234 persistent_session_role_known: self.local.persistent_session_role_known,
3235 });
3236 }
3237 }
3238
3239 fn snapshot_search_path(&mut self) {
3240 if let Some(frame) = self.local.transactions.last_mut() {
3241 frame.undo_log.push(StateChange::SearchPathSnapshot {
3242 previous: self.local.search_path.clone(),
3243 previous_template: self.local.search_path_template.clone(),
3244 });
3245 }
3246 }
3247
3248 fn snapshot_generation_counter(&mut self) {
3249 if let Some(frame) = self.local.transactions.last_mut() {
3250 frame.undo_log.push(StateChange::GenerationCounterSnapshot {
3251 previous: self.local.generation_counter,
3252 });
3253 }
3254 }
3255
3256 #[allow(dead_code)]
3257 fn snapshot_pending_validation(&mut self) {
3258 if let Some(frame) = self.local.transactions.last_mut() {
3259 frame.undo_log.push(StateChange::PendingValidationSnapshot {
3260 previous: self.local.pending_validation.clone(),
3261 });
3262 }
3263 }
3264
3265 fn snapshot_confidence(&mut self) {
3266 if let Some(frame) = self.local.transactions.last_mut() {
3267 frame.undo_log.push(StateChange::ConfidenceSnapshot {
3268 previous: self.local.confidence.clone(),
3269 });
3270 }
3271 }
3272
3273 fn snapshot_graph(&mut self) {
3274 if let Some(frame) = self.local.transactions.last_mut() {
3275 frame.undo_log.push(StateChange::GraphLengthMarker {
3276 len: self.local.graph.edges.len(),
3277 });
3278 }
3279 }
3280
3281 fn snapshot_graph_full(&mut self) {
3282 if let Some(frame) = self.local.transactions.last_mut() {
3283 frame.undo_log.push(StateChange::GraphSnapshot {
3284 previous: self.local.graph.edges.clone(),
3285 });
3286 }
3287 }
3288
3289 fn rollback_frame(&mut self, mut frame: TransactionFrame) {
3290 self.rollback_undo_log(std::mem::take(&mut frame.undo_log));
3291 }
3292
3293 fn rollback_undo_log(&mut self, mut undo_log: Vec<StateChange>) {
3294 while let Some(change) = undo_log.pop() {
3295 match change {
3296 StateChange::RelationSnapshot { id, previous } => {
3297 if let Some(prev) = *previous {
3298 self.local.relations.insert(id, prev);
3299 } else {
3300 self.local.relations.remove(&id);
3301 }
3302 }
3303 StateChange::TypeSnapshot { id, previous } => {
3304 if let Some(prev) = previous {
3305 self.local.types.insert(id, prev);
3306 } else {
3307 self.local.types.remove(&id);
3308 }
3309 }
3310 StateChange::SequenceSnapshot { id, previous } => {
3311 if let Some(prev) = previous {
3312 self.local.sequences.insert(id, prev);
3313 } else {
3314 self.local.sequences.remove(&id);
3315 }
3316 }
3317 StateChange::FunctionSnapshot { id, previous } => {
3318 if let Some(prev) = previous {
3319 self.local.functions.insert(id, prev);
3320 } else {
3321 self.local.functions.remove(&id);
3322 }
3323 }
3324 StateChange::PublicationSnapshot { id, previous } => {
3325 if let Some(prev) = previous {
3326 self.local.publications.insert(id.name, prev);
3327 } else {
3328 self.local.publications.remove(&id.name);
3329 }
3330 }
3331 StateChange::SubscriptionSnapshot { id, previous } => {
3332 if let Some(prev) = previous {
3333 self.local.subscriptions.insert(id.name, prev);
3334 } else {
3335 self.local.subscriptions.remove(&id.name);
3336 }
3337 }
3338 StateChange::RoleSnapshot { id, previous } => {
3339 if let Some(prev) = previous {
3340 self.local.roles.insert(id, prev);
3341 } else {
3342 self.local.roles.remove(&id);
3343 }
3344 }
3345 StateChange::TriggerSnapshot { id, previous } => {
3346 if let Some(prev) = previous {
3347 self.local.triggers.insert(id, prev);
3348 } else {
3349 self.local.triggers.remove(&id);
3350 }
3351 }
3352 StateChange::ConstraintSnapshot {
3353 table_id,
3354 name,
3355 previous,
3356 } => {
3357 let key = (table_id, name);
3358 if let Some(previous) = previous {
3359 self.local.constraints.insert(key, previous);
3360 } else {
3361 self.local.constraints.remove(&key);
3362 }
3363 }
3364 StateChange::GraphLengthMarker { len } => {
3365 self.local.graph.edges.truncate(len);
3366 }
3367 StateChange::GraphSnapshot { previous } => {
3368 self.local.graph.edges = previous;
3369 }
3370 StateChange::RoleContextSnapshot {
3371 current_role,
3372 current_role_known,
3373 persistent_current_role,
3374 persistent_current_role_known,
3375 session_role,
3376 session_role_known,
3377 persistent_session_role,
3378 persistent_session_role_known,
3379 } => {
3380 self.local.current_role = current_role;
3381 self.local.current_role_known = current_role_known;
3382 self.local.persistent_current_role = persistent_current_role;
3383 self.local.persistent_current_role_known = persistent_current_role_known;
3384 self.local.session_role = session_role;
3385 self.local.session_role_known = session_role_known;
3386 self.local.persistent_session_role = persistent_session_role;
3387 self.local.persistent_session_role_known = persistent_session_role_known;
3388 }
3389 StateChange::SearchPathSnapshot {
3390 previous,
3391 previous_template,
3392 } => {
3393 self.local.search_path = previous;
3394 self.local.search_path_template = previous_template;
3395 }
3396 StateChange::GenerationCounterSnapshot { previous } => {
3397 self.local.generation_counter = previous;
3398 }
3399 StateChange::PendingValidationSnapshot { previous } => {
3400 self.local.pending_validation = previous;
3401 }
3402 StateChange::ConfidenceSnapshot { previous } => {
3403 self.local.confidence = previous;
3404 }
3405 }
3406 }
3407 }
3408}