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::{NamespaceSnapshot, 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::schema::SchemaOverlay;
14use crate::model::sequence::{SequenceKind, SequenceOverlay, SequenceState};
15use crate::model::trigger::TriggerOverlay;
16use crate::model::types::{TypeKind, TypeOverlay, TypeState};
17use std::collections::{HashMap, HashSet};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Confidence {
21 Exact,
22 Tainted,
23}
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum MutationResult {
27 Applied,
28 Skipped,
29 NotExecuted,
32 Conflict {
33 reason: String,
34 },
35}
36
37#[derive(Debug, Default, Clone)]
38pub struct CascadeResult {
39 pub dropped_relations: HashSet<ObjectId>,
40 pub dropped_indexes: HashSet<ObjectId>,
41 pub dropped_constraints: HashSet<(ObjectId, String)>,
42}
43
44#[derive(Clone)]
45pub struct LocalState {
46 pub schemas: HashMap<String, SchemaOverlay>,
47 pub relations: HashMap<ObjectId, RelationOverlay>,
48 pub types: HashMap<ObjectId, TypeOverlay>,
49 pub functions: HashMap<ObjectId, crate::model::function::FunctionOverlay>,
50 pub sequences: HashMap<ObjectId, SequenceOverlay>,
51 pub publications: HashMap<String, crate::model::replication::PublicationOverlay>,
52 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionOverlay>,
53 pub roles: HashMap<ObjectId, crate::model::role::RoleOverlay>,
54 pub triggers: HashMap<ObjectId, TriggerOverlay>,
55 pub constraints: HashMap<(ObjectId, String), ConstraintState>,
56 pub graph: DependencyGraph,
57 pub search_path: Vec<String>,
58 pub default_search_path: Vec<String>,
59 pub search_path_template: Vec<String>,
60 pub default_search_path_template: Vec<String>,
61 pub current_role: String,
64 pub current_role_known: bool,
67 pub persistent_current_role: String,
70 pub persistent_current_role_known: bool,
71 pub session_role: String,
75 pub session_role_known: bool,
78 pub persistent_session_role: String,
80 pub persistent_session_role_known: bool,
81 pub authenticated_role: String,
83 pub authenticated_role_known: bool,
84 pub roles_known: bool,
86 pub confidence: Confidence,
87 pub transactions: Vec<TransactionFrame>,
88 pub transaction_aborted: bool,
89 pub pending_validation: HashSet<(ObjectId, String)>,
90 pub generation_counter: u64,
91}
92
93#[derive(Clone, Debug)]
94pub struct PreState {
95 pub relations: HashMap<ObjectId, crate::model::relation::RelationState>,
96 pub functions: HashMap<ObjectId, crate::model::function::FunctionState>,
97 pub roles: HashMap<ObjectId, crate::model::role::RoleState>,
98 pub publications: HashMap<String, crate::model::replication::PublicationState>,
99 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionState>,
100 pub sequences: HashMap<ObjectId, crate::model::sequence::SequenceState>,
101 pub types: HashMap<ObjectId, crate::model::types::TypeState>,
102 pub indexes: Vec<crate::analysis::graph::DependencyEdge>,
103}
104
105#[derive(Clone)]
106pub struct AnalysisState {
107 pub pg_version_num: Option<u32>,
108 pub baseline_available: bool,
112 pub baseline_schemas: Option<HashSet<String>>,
116 pub baseline_relations: HashSet<ObjectId>,
117 pub baseline_indexes: HashSet<ObjectId>,
118 pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
119 pub baseline_fk_dependencies: HashSet<ObjectId>,
120 pub baseline_sequences: HashSet<ObjectId>,
121 pub local: LocalState,
122}
123
124impl AnalysisState {
125 fn trigger_key(table_id: &ObjectId, name: &str) -> ObjectId {
126 ObjectId::new(&table_id.schema, format!("{}\0{name}", table_id.name))
130 }
131
132 pub fn new(cache: DbCache) -> Self {
133 Self::with_baseline(cache, true)
134 }
135
136 pub fn with_baseline(cache: DbCache, baseline_available: bool) -> Self {
137 let default_search_path = cache.search_path.clone();
138 let default_search_path_template = if cache.metadata.schemas.is_none() {
139 cache
140 .metadata
141 .source_search_path
142 .clone()
143 .unwrap_or_else(|| default_search_path.clone())
144 } else {
145 default_search_path.clone()
146 };
147 let current_role_known = cache.metadata.source_role.is_some();
148 let current_role = cache
149 .metadata
150 .source_role
151 .clone()
152 .unwrap_or_else(|| "postgres".to_string());
153 let session_role_known = cache.metadata.source_session_role.is_some();
154 let session_role = cache
155 .metadata
156 .source_session_role
157 .clone()
158 .unwrap_or_else(|| current_role.clone());
159 let authenticated_role = session_role.clone();
160 let authenticated_role_known = session_role_known;
161 let persistent_current_role = current_role.clone();
162 let persistent_current_role_known = current_role_known;
163 let persistent_session_role = session_role.clone();
164 let persistent_session_role_known = session_role_known;
165 let roles_known = cache.metadata.source_session_role.is_some();
166 let baseline_schemas = cache
167 .metadata
168 .schemas
169 .as_ref()
170 .map(|schemas| schemas.iter().cloned().collect());
171 let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
172 let mut baseline_relations = HashSet::new();
173 let mut baseline_indexes = HashSet::new();
174 let mut baseline_foreign_keys = HashSet::new();
175 let mut baseline_fk_dependencies = HashSet::new();
176 let mut triggers = HashMap::new();
177 let mut constraints = HashMap::new();
178 let mut types = HashMap::new();
179 let mut graph = DependencyGraph::new();
180
181 let mut schemas: HashMap<String, SchemaOverlay> = cache
182 .schemas
183 .iter()
184 .map(|(name, schema)| (name.clone(), SchemaOverlay::Present(schema.clone())))
185 .collect();
186 let inferred_schema_owner = ObjectId::new(
192 "",
193 cache.metadata.source_role.as_deref().unwrap_or("postgres"),
194 );
195 for name in cache
196 .relations
197 .keys()
198 .map(|id| &id.schema)
199 .chain(cache.types.keys().map(|id| &id.schema))
200 .chain(cache.functions.keys().map(|id| &id.schema))
201 .chain(cache.sequences.keys().map(|id| &id.schema))
202 {
203 schemas.entry(name.clone()).or_insert_with(|| {
204 SchemaOverlay::Present(crate::model::schema::SchemaState {
205 name: name.clone(),
206 owner: inferred_schema_owner.clone(),
207 generation: 0,
208 })
209 });
210 }
211 if cache.schemas.is_empty() && cache.metadata.schemas.is_none() {
212 for name in &cache.search_path {
213 schemas.entry(name.clone()).or_insert_with(|| {
214 SchemaOverlay::Present(crate::model::schema::SchemaState {
215 name: name.clone(),
216 owner: inferred_schema_owner.clone(),
217 generation: 0,
218 })
219 });
220 }
221 }
222
223 let sequences = cache
224 .sequences
225 .iter()
226 .map(|(id, sequence)| (id.clone(), SequenceOverlay::Present(sequence.clone())))
227 .collect();
228 let baseline_sequences = cache.sequences.keys().cloned().collect();
229 for sequence in cache.sequences.values() {
230 if let Some((table, column)) = &sequence.owned_by {
231 graph.edges.push(DependencyEdge::new(
232 sequence.id.clone(),
233 table.clone(),
234 DependencyKind::SequenceOwnedBy {
235 column: column.clone(),
236 },
237 ));
238 }
239 }
240
241 for (id, rel_state) in cache.baseline_relations() {
242 if rel_state.is_fk_dependency {
243 baseline_fk_dependencies.insert(id.clone());
244 }
245 relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
246 baseline_relations.insert(id.clone());
247 }
248
249 for (id, type_state) in &cache.types {
250 types.insert(id.clone(), TypeOverlay::Present(type_state.clone()));
251 }
252
253 for fk in cache.foreign_keys {
254 baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
255 graph.edges.push(DependencyEdge::new(
256 fk.from_table,
257 fk.to_table,
258 DependencyKind::ForeignKey {
259 constraint_name: Some(fk.constraint_name),
260 from_columns: Vec::new(),
261 to_columns: Vec::new(),
262 from_generation: 0,
263 },
264 ));
265 }
266
267 for idx in cache.indexes {
268 baseline_indexes.insert(idx.index_id.clone());
270 graph.edges.push(DependencyEdge::new(
271 idx.index_id,
272 idx.table_id,
273 DependencyKind::IndexOnRelation {
274 using_method: None,
275 has_predicate: false,
276 is_concurrent: false,
277 is_unique: false,
278 eligibility_known: false,
279 },
280 ));
281 }
282
283 for dependency in cache.dependencies {
284 if dependency.deptype != "view" {
285 continue;
286 }
287 let (Some(obj_schema), Some(obj_name), Some(ref_schema), Some(ref_name)) = (
288 dependency.obj_schema,
289 dependency.obj_name,
290 dependency.ref_schema,
291 dependency.ref_name,
292 ) else {
293 continue;
294 };
295 let dependent = ObjectId::new(obj_schema, obj_name);
296 let referenced = ObjectId::new(ref_schema, ref_name);
297 if dependent == referenced {
302 continue;
303 }
304 let is_view = relations.get(&dependent).is_some_and(|relation| {
305 matches!(
306 relation,
307 RelationOverlay::Present(state)
308 if matches!(
309 state.kind,
310 crate::model::relation::RelationKind::View
311 | crate::model::relation::RelationKind::MaterializedView
312 )
313 )
314 });
315 if is_view && relations.contains_key(&referenced) {
316 graph.edges.push(DependencyEdge::new(
317 dependent,
318 referenced,
319 DependencyKind::ViewDependency { view_generation: 0 },
320 ));
321 }
322 }
323
324 for constraint in cache.constraints {
325 constraints.insert(
326 (constraint.table_id.clone(), constraint.name.clone()),
327 constraint,
328 );
329 }
330
331 for t in cache.triggers {
332 let trigger_key = Self::trigger_key(&t.table_id, &t.trigger_id.name);
333 triggers.insert(
334 trigger_key.clone(),
335 TriggerOverlay::Present(crate::model::trigger::TriggerState {
336 name: t.trigger_id.name.clone(),
337 id: trigger_key.clone(),
338 table_id: t.table_id.clone(),
339 enabled_mode: t.enabled_mode,
340 generation: 0,
341 }),
342 );
343 graph.edges.push(DependencyEdge::new(
344 trigger_key.clone(),
345 t.table_id,
346 DependencyKind::TriggerOnTable {
347 trigger_id: trigger_key,
348 function_id: t.function_id,
349 },
350 ));
351 }
352
353 let mut functions: HashMap<ObjectId, crate::model::function::FunctionOverlay> =
354 HashMap::new();
355 for (id, func_state) in &cache.functions {
356 functions.insert(
357 id.clone(),
358 crate::model::function::FunctionOverlay::Present(func_state.clone()),
359 );
360 }
361
362 let mut state = Self {
363 pg_version_num: cache.pg_version_num,
364 baseline_available,
365 baseline_schemas,
366 baseline_relations,
367 baseline_indexes,
368 baseline_foreign_keys,
369 baseline_fk_dependencies,
370 baseline_sequences,
371 local: LocalState {
372 schemas,
373 relations,
374 types,
375 functions,
376 sequences,
377 publications: HashMap::new(),
378 subscriptions: HashMap::new(),
379 roles: cache
380 .roles
381 .into_iter()
382 .map(|(id, role)| (id, crate::model::role::RoleOverlay::Present(role)))
383 .collect(),
384 triggers,
385 constraints,
386 graph,
387 search_path: default_search_path.clone(),
388 default_search_path,
389 search_path_template: default_search_path_template.clone(),
390 default_search_path_template,
391 current_role,
392 current_role_known,
393 persistent_current_role,
394 persistent_current_role_known,
395 session_role,
396 session_role_known,
397 persistent_session_role,
398 persistent_session_role_known,
399 authenticated_role,
400 authenticated_role_known,
401 roles_known,
402 confidence: Confidence::Exact,
403 transactions: Vec::new(),
404 transaction_aborted: false,
405 pending_validation: HashSet::new(),
406 generation_counter: 0,
407 },
408 };
409 state.refresh_role_sensitive_search_path();
410 state.local.default_search_path = state.local.search_path.clone();
411 state
412 }
413
414 pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
415 self.local.relations.get(id)
416 }
417
418 pub fn resolve_function_schema(
419 &self,
420 name: &crate::ast::identifiers::QualifiedName,
421 sig_str: &str,
422 ) -> String {
423 if let Some(schema) = &name.schema {
424 return schema.resolve();
425 }
426 for schema in &self.local.search_path {
427 let candidate = ObjectId::new(schema.clone(), sig_str.to_string());
428 if self.local.functions.contains_key(&candidate) {
429 return schema.clone();
430 }
431 }
432 self.local
433 .search_path
434 .first()
435 .cloned()
436 .unwrap_or_else(|| "public".to_string())
437 }
438
439 pub fn resolve_relation_id(&self, name: &crate::ast::identifiers::QualifiedName) -> ObjectId {
440 if let Some(schema) = &name.schema {
441 return ObjectId::new(schema.resolve(), name.name.resolve());
442 }
443 let resolved_name = name.name.resolve();
444 for schema in &self.local.search_path {
445 let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
446 if self.local.relations.contains_key(&candidate) {
447 candidate.inferred_schema = true;
448 return candidate;
449 }
450 }
451 let schema = self
452 .local
453 .search_path
454 .first()
455 .cloned()
456 .unwrap_or_else(|| "public".to_string());
457 let mut id = ObjectId::new(schema, resolved_name);
458 id.inferred_schema = true;
459 id
460 }
461
462 pub fn relation_is_present(&self, id: &ObjectId) -> bool {
463 matches!(
464 self.local.relations.get(id),
465 Some(RelationOverlay::Present(_))
466 )
467 }
468
469 pub fn baseline_covers_object(&self, id: &ObjectId) -> bool {
473 self.baseline_schemas
474 .as_ref()
475 .is_none_or(|schemas| schemas.contains(&id.schema))
476 }
477
478 pub fn baseline_scope_omits_displayed_object<'a>(
479 &self,
480 object_name: &'a str,
481 ) -> Option<&'a str> {
482 let schemas = self.baseline_schemas.as_ref()?;
483 let (schema, _) = object_name.split_once('.')?;
484 (!schemas.contains(schema)).then_some(schema)
485 }
486
487 fn sequence_is_present(&self, id: &ObjectId) -> bool {
488 matches!(
489 self.local.sequences.get(id),
490 Some(SequenceOverlay::Present(_))
491 )
492 }
493
494 fn type_is_present(&self, id: &ObjectId) -> bool {
495 matches!(self.local.types.get(id), Some(TypeOverlay::Present(_)))
496 }
497
498 fn index_is_present(&self, id: &ObjectId) -> bool {
499 self.local.graph.edges.iter().any(|edge| {
500 matches!(edge.kind, DependencyKind::IndexOnRelation { .. }) && edge.dependent == *id
501 })
502 }
503
504 fn next_generated_constraint_name(
505 &self,
506 table: &ObjectId,
507 name1: &str,
508 name2: Option<&str>,
509 label: &str,
510 ) -> String {
511 (0..)
512 .map(|suffix| {
513 let label = if suffix == 0 {
514 label.to_string()
515 } else {
516 format!("{label}{suffix}")
517 };
518 Self::postgres_object_name(name1, name2, &label)
519 })
520 .find(|candidate| {
521 !self
522 .local
523 .constraints
524 .contains_key(&(table.clone(), candidate.clone()))
525 })
526 .expect("constraint suffix space is unbounded")
527 }
528
529 fn postgres_object_name(name1: &str, name2: Option<&str>, label: &str) -> String {
530 const MAX_IDENTIFIER_BYTES: usize = 63;
531
532 fn truncate(value: &str, max_bytes: usize) -> &str {
533 let mut end = max_bytes.min(value.len());
534 while !value.is_char_boundary(end) {
535 end -= 1;
536 }
537 &value[..end]
538 }
539
540 let separators = usize::from(name2.is_some()) + 1;
541 let available = MAX_IDENTIFIER_BYTES.saturating_sub(label.len() + separators);
542 let mut name1_bytes = name1.len();
543 let mut name2_bytes = name2.map_or(0, str::len);
544 while name1_bytes + name2_bytes > available {
545 if name1_bytes > name2_bytes {
546 name1_bytes -= 1;
547 } else {
548 name2_bytes -= 1;
549 }
550 }
551
552 let name1 = truncate(name1, name1_bytes);
553 match name2 {
554 Some(name2) => format!("{name1}_{}_{}", truncate(name2, name2_bytes), label),
555 None => format!("{name1}_{label}"),
556 }
557 }
558
559 fn relation_namespace_is_taken(&self, id: &ObjectId) -> bool {
560 self.relation_is_present(id)
561 || self.sequence_is_present(id)
562 || self.index_is_present(id)
563 || self.type_is_present(id)
564 }
565
566 fn next_implicit_sequence_id(
567 &self,
568 table: &ObjectId,
569 column: &str,
570 reserved: &HashSet<ObjectId>,
571 ) -> ObjectId {
572 (0..)
573 .map(|suffix| {
574 let label = if suffix == 0 {
575 "seq".to_string()
576 } else {
577 format!("seq{suffix}")
578 };
579 ObjectId::new(
580 &table.schema,
581 Self::postgres_object_name(&table.name, Some(column), &label),
582 )
583 })
584 .find(|candidate| {
585 !reserved.contains(candidate) && !self.relation_namespace_is_taken(candidate)
586 })
587 .expect("implicit sequence suffix space is unbounded")
588 }
589
590 fn sequence_nextval_default(id: &ObjectId) -> crate::analysis::expr_ir::ExprIr {
591 crate::analysis::expr_ir::ExprIr::FunctionCall {
592 name: "nextval".to_string(),
593 args: vec![crate::analysis::expr_ir::ExprIr::Literal(format!(
594 "{}.{}",
595 id.schema, id.name
596 ))],
597 }
598 }
599
600 pub fn column_was_added_in_transaction(&self, table_id: &ObjectId, column: &str) -> bool {
601 if self.local.transactions.is_empty() {
602 return false;
603 }
604
605 for frame in &self.local.transactions {
607 for change in &frame.undo_log {
608 if let StateChange::RelationSnapshot { id, previous } = change
609 && id == table_id
610 {
611 match previous.as_ref() {
612 None | Some(RelationOverlay::Dropped) => {
613 return true;
614 }
615 Some(RelationOverlay::Present(r)) => {
616 let col_existed = r.columns.iter().any(|c| c.name == column);
617 return !col_existed;
618 }
619 }
620 }
621 }
622 }
623 false
624 }
625
626 pub fn capture_pre_state(&self) -> PreState {
627 let mut relations = HashMap::new();
628 for (id, overlay) in &self.local.relations {
629 if let RelationOverlay::Present(s) = overlay {
630 relations.insert(id.clone(), s.clone());
631 }
632 }
633
634 let mut functions = HashMap::new();
635 for (id, overlay) in &self.local.functions {
636 if let crate::model::function::FunctionOverlay::Present(s) = overlay {
637 functions.insert(id.clone(), s.clone());
638 }
639 }
640
641 let mut roles = HashMap::new();
642 for (name, overlay) in &self.local.roles {
643 if let crate::model::role::RoleOverlay::Present(s) = overlay {
644 roles.insert(name.clone(), s.clone());
645 }
646 }
647
648 let mut publications = HashMap::new();
649 for (name, overlay) in &self.local.publications {
650 if let crate::model::replication::PublicationOverlay::Present(s) = overlay {
651 publications.insert(name.clone(), s.clone());
652 }
653 }
654
655 let mut subscriptions = HashMap::new();
656 for (name, overlay) in &self.local.subscriptions {
657 if let crate::model::replication::SubscriptionOverlay::Present(s) = overlay {
658 subscriptions.insert(name.clone(), s.clone());
659 }
660 }
661
662 let mut sequences = HashMap::new();
663 for (id, overlay) in &self.local.sequences {
664 if let SequenceOverlay::Present(s) = overlay {
665 sequences.insert(id.clone(), s.clone());
666 }
667 }
668
669 let mut types = HashMap::new();
670 for (id, overlay) in &self.local.types {
671 if let TypeOverlay::Present(s) = overlay {
672 types.insert(id.clone(), s.clone());
673 }
674 }
675
676 let indexes = self
677 .local
678 .graph
679 .edges
680 .iter()
681 .filter(|e| matches!(e.kind, DependencyKind::IndexOnRelation { .. }))
682 .cloned()
683 .collect();
684
685 PreState {
686 relations,
687 functions,
688 roles,
689 publications,
690 subscriptions,
691 sequences,
692 types,
693 indexes,
694 }
695 }
696
697 pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
698 let mut result = CascadeResult::default();
699 let mut visited = HashSet::new();
700 self.walk_cascade(target_oid, &mut visited, &mut result);
701 result
702 }
703
704 fn walk_cascade(
705 &self,
706 current: &ObjectId,
707 visited: &mut HashSet<ObjectId>,
708 result: &mut CascadeResult,
709 ) {
710 let resolved_current = self.local.graph.resolve_rename(current).clone();
711
712 if !visited.insert(resolved_current.clone()) {
713 return;
714 }
715
716 result.dropped_relations.insert(resolved_current.clone());
717
718 for edge in &self.local.graph.edges {
719 match &edge.kind {
720 DependencyKind::ViewDependency { .. } => {
721 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
722 let resolved_view_id =
723 self.local.graph.resolve_rename(&edge.dependent).clone();
724 if !visited.contains(&resolved_view_id) {
725 self.walk_cascade(&resolved_view_id, visited, result);
726 }
727 }
728 }
729 DependencyKind::IndexOnRelation { .. } => {
730 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
731 result
732 .dropped_indexes
733 .insert(self.local.graph.resolve_rename(&edge.dependent).clone());
734 }
735 }
736 DependencyKind::ForeignKey {
737 constraint_name, ..
738 } => {
739 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current
740 && let Some(cname) = constraint_name
741 {
742 result.dropped_constraints.insert((
743 self.local.graph.resolve_rename(&edge.dependent).clone(),
744 cname.clone(),
745 ));
746 }
747 }
748 DependencyKind::PartitionOf
749 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current =>
750 {
751 let resolved_child = self.local.graph.resolve_rename(&edge.dependent).clone();
752 if !visited.contains(&resolved_child) {
753 self.walk_cascade(&resolved_child, visited, result);
754 }
755 }
756 _ => {}
757 }
758 }
759 }
760
761 fn resolve_grant_privileges(
762 spec: &crate::analysis::facts::PrivilegeSpec,
763 ) -> HashSet<Privilege> {
764 match spec {
765 crate::analysis::facts::PrivilegeSpec::All => vec![
766 Privilege::Select,
767 Privilege::Insert,
768 Privilege::Update,
769 Privilege::Delete,
770 Privilege::Truncate,
771 Privilege::References,
772 Privilege::Trigger,
773 ]
774 .into_iter()
775 .collect(),
776 crate::analysis::facts::PrivilegeSpec::List(list) => list
777 .iter()
778 .filter_map(|p| match p {
779 crate::analysis::facts::PrivilegeFact::Select => Some(Privilege::Select),
780 crate::analysis::facts::PrivilegeFact::Insert => Some(Privilege::Insert),
781 crate::analysis::facts::PrivilegeFact::Update => Some(Privilege::Update),
782 crate::analysis::facts::PrivilegeFact::Delete => Some(Privilege::Delete),
783 crate::analysis::facts::PrivilegeFact::Truncate => Some(Privilege::Truncate),
784 crate::analysis::facts::PrivilegeFact::References => {
785 Some(Privilege::References)
786 }
787 crate::analysis::facts::PrivilegeFact::Trigger => Some(Privilege::Trigger),
788 _ => None,
789 })
790 .collect(),
791 }
792 }
793
794 fn resolve_role_name(
795 role: &crate::analysis::facts::RoleFact,
796 current_role: &str,
797 session_role: &str,
798 ) -> Option<ObjectId> {
799 let name = match role {
800 crate::analysis::facts::RoleFact::Named { name, .. } => Some(name.clone()),
801 crate::analysis::facts::RoleFact::CurrentUser
802 | crate::analysis::facts::RoleFact::CurrentRole => Some(current_role.to_string()),
803 crate::analysis::facts::RoleFact::SessionUser => Some(session_role.to_string()),
804 crate::analysis::facts::RoleFact::Unknown => None,
805 }?;
806 Some(ObjectId::new("", name))
807 }
808
809 fn role_fact_identity(
810 &self,
811 role: &crate::analysis::facts::RoleFact,
812 ) -> Option<(String, bool)> {
813 match role {
814 crate::analysis::facts::RoleFact::Named { name, .. } => Some((name.clone(), true)),
815 crate::analysis::facts::RoleFact::CurrentUser
816 | crate::analysis::facts::RoleFact::CurrentRole => Some((
817 self.local.current_role.clone(),
818 self.local.current_role_known,
819 )),
820 crate::analysis::facts::RoleFact::SessionUser => Some((
821 self.local.session_role.clone(),
822 self.local.session_role_known,
823 )),
824 crate::analysis::facts::RoleFact::Unknown => None,
825 }
826 }
827
828 fn present_role(&self, name: &str) -> Option<&crate::model::role::RoleState> {
829 match self.local.roles.get(&ObjectId::new("", name)) {
830 Some(crate::model::role::RoleOverlay::Present(role)) => Some(role),
831 _ => None,
832 }
833 }
834
835 fn can_set_role_to(&self, target: &str) -> Option<bool> {
836 if !self.local.roles_known || !self.local.session_role_known {
837 return None;
838 }
839 if self.present_role(target).is_none() {
840 return Some(false);
841 }
842 if self.local.session_role == target {
843 return Some(true);
844 }
845 let session = self.present_role(&self.local.session_role)?;
846 if session.is_superuser {
847 return Some(true);
848 }
849
850 let mut pending = session.can_set_role_to.clone();
851 let mut visited = HashSet::new();
852 while let Some(role_id) = pending.pop() {
853 if !visited.insert(role_id.clone()) {
854 continue;
855 }
856 if role_id.name == target {
857 return Some(true);
858 }
859 if let Some(role) = self.present_role(&role_id.name) {
860 pending.extend(role.can_set_role_to.iter().cloned());
861 }
862 }
863 Some(false)
864 }
865
866 fn can_set_session_authorization_to(&self, target: &str) -> Option<bool> {
867 if !self.local.roles_known || !self.local.authenticated_role_known {
868 return None;
869 }
870 if self.present_role(target).is_none() {
871 return Some(false);
872 }
873 if self.local.authenticated_role == target {
874 return Some(true);
875 }
876 Some(
877 self.present_role(&self.local.authenticated_role)
878 .is_some_and(|role| role.is_superuser),
879 )
880 }
881
882 fn schema_is_present(&self, name: &str) -> bool {
883 matches!(
884 self.local.schemas.get(name),
885 Some(SchemaOverlay::Present(_))
886 )
887 }
888
889 fn schema_absence_is_authoritative(&self, name: &str) -> bool {
890 if matches!(self.local.schemas.get(name), Some(SchemaOverlay::Dropped)) {
891 return true;
892 }
893 self.baseline_available
894 && self
895 .baseline_schemas
896 .as_ref()
897 .is_none_or(|schemas| schemas.contains(name))
898 }
899
900 fn refresh_role_sensitive_search_path(&mut self) {
901 let template = self.local.search_path_template.clone();
902 let mut effective = Vec::new();
903 for entry in template {
904 let schema = if entry == "$user" {
905 if self.local.current_role_known {
906 self.local.current_role.clone()
907 } else {
908 self.local.confidence = Confidence::Tainted;
909 continue;
910 }
911 } else {
912 entry
913 };
914 if self.schema_is_present(&schema) {
915 if !effective.contains(&schema) {
916 effective.push(schema);
917 }
918 } else if !self.schema_absence_is_authoritative(&schema) {
919 self.local.confidence = Confidence::Tainted;
920 if !effective.contains(&schema) {
921 effective.push(schema);
922 }
923 }
924 }
925 self.local.search_path = effective;
926 }
927
928 fn remap_schema_id(id: &mut ObjectId, old_name: &str, new_name: &str) {
929 if id.schema == old_name {
930 id.schema = new_name.to_string();
931 }
932 }
933
934 fn rename_schema_namespace(&mut self, old_name: &str, new_name: &str) {
935 self.snapshot_namespace();
936
937 let mut aliases = Vec::new();
938 let mut relations = HashMap::new();
939 for (mut id, mut overlay) in std::mem::take(&mut self.local.relations) {
940 let old_id = id.clone();
941 Self::remap_schema_id(&mut id, old_name, new_name);
942 if let RelationOverlay::Present(state) = &mut overlay {
943 Self::remap_schema_id(&mut state.id, old_name, new_name);
944 }
945 if id != old_id {
946 aliases.push((old_id, id.clone()));
947 }
948 relations.insert(id, overlay);
949 }
950 self.local.relations = relations;
951
952 let mut types = HashMap::new();
953 for (mut id, mut overlay) in std::mem::take(&mut self.local.types) {
954 let old_id = id.clone();
955 Self::remap_schema_id(&mut id, old_name, new_name);
956 if let TypeOverlay::Present(state) = &mut overlay {
957 Self::remap_schema_id(&mut state.id, old_name, new_name);
958 }
959 if id != old_id {
960 aliases.push((old_id, id.clone()));
961 }
962 types.insert(id, overlay);
963 }
964 self.local.types = types;
965
966 let mut functions = HashMap::new();
967 for (mut id, mut overlay) in std::mem::take(&mut self.local.functions) {
968 let old_id = id.clone();
969 Self::remap_schema_id(&mut id, old_name, new_name);
970 if let crate::model::function::FunctionOverlay::Present(state) = &mut overlay {
971 Self::remap_schema_id(&mut state.id, old_name, new_name);
972 }
973 if id != old_id {
974 aliases.push((old_id, id.clone()));
975 }
976 functions.insert(id, overlay);
977 }
978 self.local.functions = functions;
979
980 let mut sequences = HashMap::new();
981 for (mut id, mut overlay) in std::mem::take(&mut self.local.sequences) {
982 let old_id = id.clone();
983 Self::remap_schema_id(&mut id, old_name, new_name);
984 if let SequenceOverlay::Present(state) = &mut overlay {
985 Self::remap_schema_id(&mut state.id, old_name, new_name);
986 if let Some((table, _)) = &mut state.owned_by {
987 Self::remap_schema_id(table, old_name, new_name);
988 }
989 }
990 if id != old_id {
991 aliases.push((old_id, id.clone()));
992 }
993 sequences.insert(id, overlay);
994 }
995 self.local.sequences = sequences;
996
997 let mut triggers = HashMap::new();
998 for (mut id, mut overlay) in std::mem::take(&mut self.local.triggers) {
999 let old_id = id.clone();
1000 Self::remap_schema_id(&mut id, old_name, new_name);
1001 if let TriggerOverlay::Present(state) = &mut overlay {
1002 Self::remap_schema_id(&mut state.id, old_name, new_name);
1003 Self::remap_schema_id(&mut state.table_id, old_name, new_name);
1004 }
1005 if id != old_id {
1006 aliases.push((old_id, id.clone()));
1007 }
1008 triggers.insert(id, overlay);
1009 }
1010 self.local.triggers = triggers;
1011
1012 for overlay in self.local.publications.values_mut() {
1013 let crate::model::replication::PublicationOverlay::Present(publication) = overlay
1014 else {
1015 continue;
1016 };
1017 let crate::analysis::facts::PublicationScope::Explicit(objects) =
1018 &mut publication.scope
1019 else {
1020 continue;
1021 };
1022 for object in objects {
1023 match object {
1024 crate::analysis::facts::PublicationObjectFact::Table { name, .. } => {
1025 if name
1026 .schema
1027 .as_ref()
1028 .is_some_and(|schema| schema.resolve() == old_name)
1029 {
1030 name.schema = Some(crate::ast::identifiers::Ident::new(new_name, true));
1031 }
1032 }
1033 crate::analysis::facts::PublicationObjectFact::SchemaTables {
1034 schema, ..
1035 } if schema == old_name => *schema = new_name.to_string(),
1036 _ => {}
1037 }
1038 }
1039 }
1040
1041 self.local.constraints = std::mem::take(&mut self.local.constraints)
1042 .into_iter()
1043 .map(|((mut table, name), mut constraint)| {
1044 Self::remap_schema_id(&mut table, old_name, new_name);
1045 Self::remap_schema_id(&mut constraint.table_id, old_name, new_name);
1046 ((table, name), constraint)
1047 })
1048 .collect();
1049 self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
1050 .into_iter()
1051 .map(|(mut table, name)| {
1052 Self::remap_schema_id(&mut table, old_name, new_name);
1053 (table, name)
1054 })
1055 .collect();
1056
1057 for edge in &mut self.local.graph.edges {
1058 Self::remap_schema_id(&mut edge.dependent, old_name, new_name);
1059 Self::remap_schema_id(&mut edge.referenced, old_name, new_name);
1060 if let DependencyKind::TriggerOnTable {
1061 trigger_id,
1062 function_id,
1063 } = &mut edge.kind
1064 {
1065 Self::remap_schema_id(trigger_id, old_name, new_name);
1066 Self::remap_schema_id(function_id, old_name, new_name);
1067 }
1068 }
1069 for (old_id, new_id) in aliases {
1070 self.local.graph.edges.push(DependencyEdge::new(
1071 old_id,
1072 new_id,
1073 DependencyKind::RenameTo,
1074 ));
1075 }
1076
1077 let remap_set = |set: &mut HashSet<ObjectId>| {
1078 *set = std::mem::take(set)
1079 .into_iter()
1080 .map(|mut id| {
1081 Self::remap_schema_id(&mut id, old_name, new_name);
1082 id
1083 })
1084 .collect();
1085 };
1086 remap_set(&mut self.baseline_relations);
1087 remap_set(&mut self.baseline_indexes);
1088 remap_set(&mut self.baseline_fk_dependencies);
1089 remap_set(&mut self.baseline_sequences);
1090 self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
1091 .into_iter()
1092 .map(|(mut table, name)| {
1093 Self::remap_schema_id(&mut table, old_name, new_name);
1094 (table, name)
1095 })
1096 .collect();
1097
1098 if let Some(SchemaOverlay::Present(mut schema)) = self.local.schemas.remove(old_name) {
1099 schema.name = new_name.to_string();
1100 self.local
1101 .schemas
1102 .insert(new_name.to_string(), SchemaOverlay::Present(schema));
1103 }
1104 self.refresh_role_sensitive_search_path();
1105 }
1106
1107 fn restore_persistent_role_context(&mut self) {
1108 self.local.current_role = self.local.persistent_current_role.clone();
1109 self.local.current_role_known = self.local.persistent_current_role_known;
1110 self.local.session_role = self.local.persistent_session_role.clone();
1111 self.local.session_role_known = self.local.persistent_session_role_known;
1112 self.refresh_role_sensitive_search_path();
1113 }
1114
1115 fn apply_grant_to_relation(
1116 &mut self,
1117 id: &ObjectId,
1118 privileges: &HashSet<Privilege>,
1119 grantees: &[crate::analysis::facts::RoleFact],
1120 ) {
1121 self.snapshot_relation(id);
1122 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1123 for grantee in grantees {
1124 if let Some(role_id) = Self::resolve_role_name(
1125 grantee,
1126 &self.local.current_role,
1127 &self.local.session_role,
1128 ) {
1129 rel.privileges.grant(role_id, privileges.clone());
1130 }
1131 }
1132 }
1133 }
1134
1135 fn apply_revoke_to_relation(
1136 &mut self,
1137 id: &ObjectId,
1138 privileges: &HashSet<Privilege>,
1139 revokees: &[crate::analysis::facts::RoleFact],
1140 ) {
1141 self.snapshot_relation(id);
1142 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1143 for revokee in revokees {
1144 if let Some(role_id) = Self::resolve_role_name(
1145 revokee,
1146 &self.local.current_role,
1147 &self.local.session_role,
1148 ) {
1149 rel.privileges.revoke(&role_id, privileges);
1150 }
1151 }
1152 }
1153 }
1154
1155 pub fn apply(
1156 &mut self,
1157 mutation: &Mutation,
1158 precomputed_cascade: Option<&CascadeResult>,
1159 ) -> MutationResult {
1160 if self.local.transaction_aborted
1161 && !matches!(
1162 mutation,
1163 Mutation::CommitTransaction
1164 | Mutation::CommitAndChain
1165 | Mutation::RollbackTransaction
1166 | Mutation::RollbackAndChain
1167 | Mutation::RollbackToSavepoint(_)
1168 )
1169 {
1170 return MutationResult::NotExecuted;
1171 }
1172
1173 let result = self.apply_inner(mutation, precomputed_cascade);
1174 if matches!(result, MutationResult::Conflict { .. }) && !self.local.transactions.is_empty()
1175 {
1176 self.local.transaction_aborted = true;
1177 }
1178 result
1179 }
1180
1181 fn apply_inner(
1182 &mut self,
1183 mutation: &Mutation,
1184 precomputed_cascade: Option<&CascadeResult>,
1185 ) -> MutationResult {
1186 match mutation {
1187 Mutation::CreateSchema(create_schema) => {
1188 if self.schema_is_present(&create_schema.name) {
1189 return if create_schema.if_not_exists {
1190 MutationResult::Skipped
1191 } else {
1192 MutationResult::Conflict {
1193 reason: format!("schema '{}' already exists", create_schema.name),
1194 }
1195 };
1196 }
1197 let (owner_name, owner_known) = match &create_schema.authorization {
1198 Some(role) => match self.role_fact_identity(role) {
1199 Some(identity) => identity,
1200 None => {
1201 self.snapshot_confidence();
1202 self.local.confidence = Confidence::Tainted;
1203 (self.local.current_role.clone(), false)
1204 }
1205 },
1206 None => (
1207 self.local.current_role.clone(),
1208 self.local.current_role_known,
1209 ),
1210 };
1211 if owner_known && self.local.roles_known && self.present_role(&owner_name).is_none()
1212 {
1213 return MutationResult::Conflict {
1214 reason: format!("role '{}' does not exist", owner_name),
1215 };
1216 }
1217 if !owner_known || !self.local.roles_known {
1218 self.snapshot_confidence();
1219 self.local.confidence = Confidence::Tainted;
1220 }
1221 self.snapshot_generation_counter();
1222 self.local.generation_counter += 1;
1223 let generation = self.local.generation_counter;
1224 self.snapshot_schema(&create_schema.name);
1225 self.local.schemas.insert(
1226 create_schema.name.clone(),
1227 SchemaOverlay::Present(crate::model::schema::SchemaState {
1228 name: create_schema.name.clone(),
1229 owner: ObjectId::new("", owner_name),
1230 generation,
1231 }),
1232 );
1233 self.snapshot_search_path();
1234 self.refresh_role_sensitive_search_path();
1235 MutationResult::Applied
1236 }
1237 Mutation::AlterSchema(alter_schema) => match alter_schema {
1238 crate::analysis::mutations::AlterSchemaMutation::OwnerTo { name, new_owner } => {
1239 if !self.schema_is_present(name) {
1240 if self.schema_absence_is_authoritative(name) {
1241 return MutationResult::Conflict {
1242 reason: format!("schema '{}' does not exist", name),
1243 };
1244 }
1245 self.snapshot_confidence();
1246 self.local.confidence = Confidence::Tainted;
1247 return MutationResult::Skipped;
1248 }
1249 let Some((owner_name, owner_known)) = self.role_fact_identity(new_owner) else {
1250 self.snapshot_confidence();
1251 self.local.confidence = Confidence::Tainted;
1252 return MutationResult::Skipped;
1253 };
1254 if owner_known
1255 && self.local.roles_known
1256 && self.present_role(&owner_name).is_none()
1257 {
1258 return MutationResult::Conflict {
1259 reason: format!("role '{}' does not exist", owner_name),
1260 };
1261 }
1262 if !owner_known || !self.local.roles_known {
1263 self.snapshot_confidence();
1264 self.local.confidence = Confidence::Tainted;
1265 }
1266 self.snapshot_schema(name);
1267 if let Some(SchemaOverlay::Present(schema)) = self.local.schemas.get_mut(name) {
1268 schema.owner = ObjectId::new("", owner_name);
1269 }
1270 MutationResult::Applied
1271 }
1272 crate::analysis::mutations::AlterSchemaMutation::Rename { old_name, new_name } => {
1273 if !self.schema_is_present(old_name) {
1274 if !self.schema_absence_is_authoritative(old_name) {
1275 self.snapshot_confidence();
1276 self.local.confidence = Confidence::Tainted;
1277 return MutationResult::Skipped;
1278 }
1279 return MutationResult::Conflict {
1280 reason: format!("schema '{}' does not exist", old_name),
1281 };
1282 }
1283 if self.schema_is_present(new_name) {
1284 return MutationResult::Conflict {
1285 reason: format!("schema '{}' already exists", new_name),
1286 };
1287 }
1288 if !self.schema_absence_is_authoritative(new_name) {
1289 self.snapshot_confidence();
1290 self.local.confidence = Confidence::Tainted;
1291 }
1292 self.snapshot_search_path();
1293 self.rename_schema_namespace(old_name, new_name);
1294 MutationResult::Applied
1295 }
1296 },
1297 Mutation::DropSchema(drop_schema) => {
1298 for name in &drop_schema.names {
1299 if !self.schema_is_present(name) && self.schema_absence_is_authoritative(name) {
1300 if !drop_schema.if_exists {
1301 return MutationResult::Conflict {
1302 reason: format!("schema '{}' does not exist", name),
1303 };
1304 }
1305 } else if !self.schema_is_present(name) {
1306 self.snapshot_confidence();
1307 self.local.confidence = Confidence::Tainted;
1308 }
1309 }
1310 let present_names: Vec<String> = drop_schema
1311 .names
1312 .iter()
1313 .filter(|name| self.schema_is_present(name))
1314 .cloned()
1315 .collect();
1316 if present_names.is_empty() {
1317 return MutationResult::Skipped;
1318 }
1319 if drop_schema.cascade {
1320 self.snapshot_namespace();
1321 let mut relations_to_drop = Vec::new();
1322 for id in self.local.relations.keys() {
1323 if drop_schema.names.contains(&id.schema) {
1324 relations_to_drop.push(id.clone());
1325 }
1326 }
1327 for id in relations_to_drop {
1328 self.snapshot_relation(&id);
1329 self.local.relations.insert(id, RelationOverlay::Dropped);
1330 }
1331
1332 let constraints_to_drop: Vec<(ObjectId, String)> = self
1333 .local
1334 .constraints
1335 .keys()
1336 .filter(|(table_id, _)| drop_schema.names.contains(&table_id.schema))
1337 .cloned()
1338 .collect();
1339 for (table_id, name) in constraints_to_drop {
1340 self.snapshot_constraint(&table_id, &name);
1341 self.local.constraints.remove(&(table_id, name));
1342 }
1343
1344 let mut types_to_drop = Vec::new();
1345 for id in self.local.types.keys() {
1346 if drop_schema.names.contains(&id.schema) {
1347 types_to_drop.push(id.clone());
1348 }
1349 }
1350 for id in types_to_drop {
1351 self.snapshot_type(&id);
1352 self.local.types.insert(id, TypeOverlay::Dropped);
1353 }
1354
1355 let mut seqs_to_drop = Vec::new();
1356 for id in self.local.sequences.keys() {
1357 if drop_schema.names.contains(&id.schema) {
1358 seqs_to_drop.push(id.clone());
1359 }
1360 }
1361 for id in seqs_to_drop {
1362 self.snapshot_sequence(&id);
1363 self.local.sequences.insert(id, SequenceOverlay::Dropped);
1364 }
1365
1366 let functions_to_drop: Vec<ObjectId> = self
1367 .local
1368 .functions
1369 .keys()
1370 .filter(|id| drop_schema.names.contains(&id.schema))
1371 .cloned()
1372 .collect();
1373 for id in functions_to_drop {
1374 self.snapshot_function(&id);
1375 self.local
1376 .functions
1377 .insert(id, crate::model::function::FunctionOverlay::Dropped);
1378 }
1379
1380 let triggers_to_drop: Vec<ObjectId> = self
1381 .local
1382 .triggers
1383 .keys()
1384 .filter(|id| drop_schema.names.contains(&id.schema))
1385 .cloned()
1386 .collect();
1387 for id in triggers_to_drop {
1388 self.snapshot_trigger(&id);
1389 self.local.triggers.insert(id, TriggerOverlay::Dropped);
1390 }
1391
1392 self.local
1393 .pending_validation
1394 .retain(|(table, _)| !drop_schema.names.contains(&table.schema));
1395 for overlay in self.local.publications.values_mut() {
1396 let crate::model::replication::PublicationOverlay::Present(publication) =
1397 overlay
1398 else {
1399 continue;
1400 };
1401 let crate::analysis::facts::PublicationScope::Explicit(objects) =
1402 &mut publication.scope
1403 else {
1404 continue;
1405 };
1406 objects.retain(|object| match object {
1407 crate::analysis::facts::PublicationObjectFact::Table {
1408 name, ..
1409 } => name.schema.as_ref().is_none_or(|schema| {
1410 !drop_schema.names.contains(&schema.resolve())
1411 }),
1412 crate::analysis::facts::PublicationObjectFact::SchemaTables {
1413 schema,
1414 ..
1415 } => !drop_schema.names.contains(schema),
1416 _ => true,
1417 });
1418 }
1419
1420 self.snapshot_graph_full();
1421
1422 let g = &mut self.local.graph;
1423 g.edges.retain(|e| {
1424 !drop_schema.names.contains(&e.dependent.schema)
1425 && !drop_schema.names.contains(&e.referenced.schema)
1426 && match &e.kind {
1427 DependencyKind::TriggerOnTable { function_id, .. } => {
1428 !drop_schema.names.contains(&function_id.schema)
1429 }
1430 _ => true,
1431 }
1432 });
1433 } else {
1434 let has_relation = self.local.relations.iter().any(|(id, ov)| {
1436 drop_schema.names.contains(&id.schema)
1437 && !matches!(ov, RelationOverlay::Dropped)
1438 });
1439 let has_type = self.local.types.iter().any(|(id, ov)| {
1440 drop_schema.names.contains(&id.schema)
1441 && !matches!(ov, TypeOverlay::Dropped)
1442 });
1443 let has_sequence = self.local.sequences.iter().any(|(id, ov)| {
1444 drop_schema.names.contains(&id.schema)
1445 && !matches!(ov, SequenceOverlay::Dropped)
1446 });
1447 let has_function = self.local.functions.iter().any(|(id, ov)| {
1448 drop_schema.names.contains(&id.schema)
1449 && !matches!(ov, crate::model::function::FunctionOverlay::Dropped)
1450 });
1451 let has_trigger = self.local.triggers.iter().any(|(id, ov)| {
1452 drop_schema.names.contains(&id.schema)
1453 && !matches!(ov, TriggerOverlay::Dropped)
1454 });
1455 if has_relation || has_type || has_sequence || has_function || has_trigger {
1456 return MutationResult::Conflict {
1457 reason: format!(
1458 "schema(s) {:?} still contain objects; use CASCADE to drop them",
1459 drop_schema.names
1460 ),
1461 };
1462 }
1463 }
1464 for name in present_names {
1465 self.snapshot_schema(&name);
1466 self.local.schemas.insert(name, SchemaOverlay::Dropped);
1467 }
1468 self.snapshot_search_path();
1469 self.refresh_role_sensitive_search_path();
1470 MutationResult::Applied
1471 }
1472 Mutation::DropTable(drop_table) => {
1473 if !self.relation_is_present(&drop_table.id) {
1474 if drop_table.if_exists {
1475 return MutationResult::Skipped;
1476 } else {
1477 self.local.confidence = Confidence::Tainted;
1478 return MutationResult::Skipped;
1479 }
1480 }
1481
1482 let renames: Vec<DependencyEdge> = self
1483 .local
1484 .graph
1485 .edges
1486 .iter()
1487 .filter(|e| matches!(e.kind, DependencyKind::RenameTo))
1488 .cloned()
1489 .collect();
1490 let resolve = |id: &ObjectId| -> ObjectId {
1491 let mut current = id;
1492 let mut visited = HashSet::new();
1493 loop {
1494 if !visited.insert(current.clone()) {
1495 return id.clone();
1496 }
1497 match renames.iter().find(|r| &r.dependent == current) {
1498 Some(edge) => current = &edge.referenced,
1499 None => return current.clone(),
1500 }
1501 }
1502 };
1503
1504 let resolved_drop = resolve(&drop_table.id);
1505 let mut dropped_relations = HashSet::from([resolved_drop.clone()]);
1506
1507 if drop_table.cascade {
1508 let local_closure;
1509 let closure = match precomputed_cascade {
1510 Some(c) => c,
1511 None => {
1512 local_closure = self.get_cascade_closure(&drop_table.id);
1513 &local_closure
1514 }
1515 };
1516 dropped_relations = closure.dropped_relations.clone();
1517
1518 for dropped_rel_id in &closure.dropped_relations {
1519 self.snapshot_relation(dropped_rel_id);
1520 self.local
1521 .relations
1522 .insert(dropped_rel_id.clone(), RelationOverlay::Dropped);
1523 }
1524
1525 self.snapshot_graph_full();
1526 self.local.graph.edges.retain(|e| match &e.kind {
1527 DependencyKind::IndexOnRelation { .. } => {
1528 !closure.dropped_indexes.contains(&resolve(&e.dependent))
1529 }
1530 DependencyKind::ForeignKey {
1531 constraint_name, ..
1532 } => {
1533 let from_dropped =
1534 closure.dropped_relations.contains(&resolve(&e.dependent));
1535 let to_dropped =
1536 closure.dropped_relations.contains(&resolve(&e.referenced));
1537 let constraint_explicitly_dropped = if let Some(cname) = constraint_name
1538 {
1539 closure
1540 .dropped_constraints
1541 .contains(&(resolve(&e.dependent), cname.clone()))
1542 } else {
1543 false
1544 };
1545 !(from_dropped || to_dropped || constraint_explicitly_dropped)
1546 }
1547 DependencyKind::ViewDependency { .. } => {
1548 !closure.dropped_relations.contains(&resolve(&e.dependent))
1549 }
1550 DependencyKind::SequenceOwnedBy { .. } => {
1551 !closure.dropped_relations.contains(&resolve(&e.referenced))
1552 }
1553 _ => true,
1554 });
1555 } else {
1556 let has_view_deps = self.local.graph.edges.iter().any(|e| {
1557 matches!(e.kind, DependencyKind::ViewDependency { .. })
1558 && resolve(&e.referenced) == resolved_drop
1559 });
1560 let has_fk_deps = self.local.graph.edges.iter().any(|e| {
1561 matches!(e.kind, DependencyKind::ForeignKey { .. })
1562 && resolve(&e.referenced) == resolved_drop
1563 && resolve(&e.dependent) != resolved_drop
1564 });
1565 let has_partition_deps = self.local.graph.edges.iter().any(|e| {
1566 matches!(e.kind, DependencyKind::PartitionOf)
1567 && resolve(&e.referenced) == resolved_drop
1568 });
1569
1570 if has_view_deps || has_fk_deps || has_partition_deps {
1571 return MutationResult::Conflict {
1572 reason: format!(
1573 "relation '{}' still has dependent objects; use CASCADE",
1574 drop_table.id
1575 ),
1576 };
1577 }
1578
1579 self.snapshot_relation(&drop_table.id);
1580 self.local
1581 .relations
1582 .insert(drop_table.id.clone(), RelationOverlay::Dropped);
1583
1584 self.snapshot_graph_full();
1585 self.local.graph.edges.retain(|e| {
1586 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
1587 && resolve(&e.referenced) == resolved_drop)
1588 });
1589 }
1590
1591 let owned_sequences_to_drop: Vec<ObjectId> = self
1592 .local
1593 .sequences
1594 .iter()
1595 .filter_map(|(id, overlay)| match overlay {
1596 SequenceOverlay::Present(sequence)
1597 if sequence.owned_by.as_ref().is_some_and(|(table, _)| {
1598 dropped_relations.contains(&resolve(table))
1599 }) =>
1600 {
1601 Some(id.clone())
1602 }
1603 _ => None,
1604 })
1605 .collect();
1606 for sequence_id in owned_sequences_to_drop {
1607 self.snapshot_sequence(&sequence_id);
1608 self.local
1609 .sequences
1610 .insert(sequence_id, SequenceOverlay::Dropped);
1611 }
1612
1613 let constraints_to_drop: Vec<(ObjectId, String)> = self
1614 .local
1615 .constraints
1616 .keys()
1617 .filter(|(table_id, _)| dropped_relations.contains(&resolve(table_id)))
1618 .cloned()
1619 .collect();
1620 for (table_id, name) in constraints_to_drop {
1621 self.snapshot_constraint(&table_id, &name);
1622 self.local.constraints.remove(&(table_id, name));
1623 }
1624
1625 let triggers_to_drop: Vec<ObjectId> = self
1626 .local
1627 .triggers
1628 .iter()
1629 .filter_map(|(id, overlay)| {
1630 let TriggerOverlay::Present(trigger) = overlay else {
1631 return None;
1632 };
1633 let graph_matches = self.local.graph.edges.iter().any(|edge| {
1634 matches!(edge.kind, DependencyKind::TriggerOnTable { .. })
1635 && edge.dependent == *id
1636 && dropped_relations.contains(&resolve(&edge.referenced))
1637 });
1638 (dropped_relations.contains(&resolve(&trigger.table_id)) || graph_matches)
1639 .then(|| id.clone())
1640 })
1641 .collect();
1642 for trigger_id in triggers_to_drop {
1643 self.snapshot_trigger(&trigger_id);
1644 self.local
1645 .triggers
1646 .insert(trigger_id, TriggerOverlay::Dropped);
1647 }
1648
1649 self.snapshot_graph_full();
1651 self.local.graph.edges.retain(|e| {
1652 !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
1653 && dropped_relations.contains(&resolve(&e.referenced)))
1654 });
1655
1656 self.snapshot_graph_full();
1657 self.local.graph.edges.retain(|e| {
1658 if let DependencyKind::PartitionOf = e.kind {
1659 resolve(&e.referenced) != resolved_drop
1660 && resolve(&e.dependent) != resolved_drop
1661 } else {
1662 true
1663 }
1664 });
1665
1666 MutationResult::Applied
1667 }
1668 Mutation::CreateTable(create) => {
1669 if create.if_not_exists && self.relation_namespace_is_taken(&create.id) {
1670 return MutationResult::Skipped;
1671 }
1672 if self.relation_namespace_is_taken(&create.id) {
1673 return MutationResult::Conflict {
1674 reason: format!("relation '{}' already exists", create.id),
1675 };
1676 }
1677
1678 let mut reserved_sequences = HashSet::new();
1682 let mut implicit_sequences = Vec::new();
1683 for column in &create.columns {
1684 let kind = match column.generation {
1685 crate::analysis::facts::ColumnGeneration::Serial => {
1686 Some(SequenceKind::SerialLike)
1687 }
1688 crate::analysis::facts::ColumnGeneration::Identity => {
1689 Some(SequenceKind::Identity)
1690 }
1691 crate::analysis::facts::ColumnGeneration::Ordinary => None,
1692 };
1693 if let Some(kind) = kind {
1694 let sequence_id = self.next_implicit_sequence_id(
1695 &create.id,
1696 &column.name,
1697 &reserved_sequences,
1698 );
1699 reserved_sequences.insert(sequence_id.clone());
1700 implicit_sequences.push((sequence_id, column.name.clone(), kind));
1701 }
1702 }
1703
1704 self.snapshot_relation(&create.id);
1705
1706 self.snapshot_generation_counter();
1707 self.local.generation_counter += 1;
1708 let generation = self.local.generation_counter;
1709
1710 let resolved_persistence = match create.persistence {
1711 PersistenceMutation::Permanent => {
1712 crate::model::relation::Persistence::Permanent
1713 }
1714 PersistenceMutation::Temporary => {
1715 crate::model::relation::Persistence::Temporary
1716 }
1717 PersistenceMutation::Unlogged => crate::model::relation::Persistence::Unlogged,
1718 };
1719
1720 let mut rel_state = RelationState::new(
1721 create.id.clone(),
1722 ObjectId::new("", &self.local.current_role),
1723 generation,
1724 if create.as_select { None } else { Some(0) },
1725 RelationKind::Table,
1726 resolved_persistence,
1727 self.local.transactions.len(),
1728 );
1729
1730 rel_state.partition_type = create
1732 .partition_by
1733 .as_ref()
1734 .and_then(|pb| pb.split_whitespace().nth(2).map(|s| s.to_uppercase()))
1735 .or_else(|| {
1736 create.partition_of.as_ref().and_then(|parent_id| {
1737 self.local.relations.get(parent_id).and_then(|r| {
1738 if let RelationOverlay::Present(rel) = r {
1739 rel.partition_type.clone()
1740 } else {
1741 None
1742 }
1743 })
1744 })
1745 });
1746 rel_state.partition_by = create.partition_by.clone();
1747
1748 let pk_columns: HashSet<&str> = create
1749 .table_constraints
1750 .iter()
1751 .filter_map(|tc| {
1752 if let TableConstraintFact::PrimaryKey { columns, .. } = tc {
1753 Some(columns.iter().map(|s| s.as_str()))
1754 } else {
1755 None
1756 }
1757 })
1758 .flatten()
1759 .collect();
1760
1761 for col in &create.columns {
1762 let is_pk = col.is_primary_key || pk_columns.contains(col.name.as_str());
1763 rel_state.apply_column_action(&ColumnAction::Add {
1764 name: col.name.clone(),
1765 data_type: col.ty.clone(),
1766 not_null: col.not_null || is_pk,
1767 default: col.default.clone(),
1768 });
1769 }
1770
1771 for (sequence_id, column_name, _) in &implicit_sequences {
1772 if let Some(column) = rel_state
1773 .columns
1774 .iter_mut()
1775 .find(|column| column.name == *column_name)
1776 {
1777 column.default = Some(Self::sequence_nextval_default(sequence_id));
1778 column.default_expr_text = Some(format!(
1779 "nextval('{}.{}'::regclass)",
1780 sequence_id.schema, sequence_id.name
1781 ));
1782 column.is_nullable = false;
1783 }
1784 }
1785
1786 self.local
1787 .relations
1788 .insert(create.id.clone(), RelationOverlay::Present(rel_state));
1789
1790 for (sequence_id, column_name, kind) in implicit_sequences {
1791 self.snapshot_sequence(&sequence_id);
1792 self.snapshot_generation_counter();
1793 self.local.generation_counter += 1;
1794 self.local.sequences.insert(
1795 sequence_id.clone(),
1796 SequenceOverlay::Present(SequenceState {
1797 id: sequence_id.clone(),
1798 owner: ObjectId::new("", &self.local.current_role),
1799 owned_by: Some((create.id.clone(), column_name.clone())),
1800 kind,
1801 generation: self.local.generation_counter,
1802 }),
1803 );
1804 self.snapshot_graph();
1805 self.local.graph.edges.push(DependencyEdge::new(
1806 sequence_id,
1807 create.id.clone(),
1808 DependencyKind::SequenceOwnedBy {
1809 column: column_name,
1810 },
1811 ));
1812 }
1813
1814 let primary_key_name = create
1815 .columns
1816 .iter()
1817 .find(|column| column.is_primary_key)
1818 .map(|column| column.primary_key_constraint_name.clone())
1819 .or_else(|| {
1820 create.table_constraints.iter().find_map(|constraint| {
1821 if let TableConstraintFact::PrimaryKey {
1822 constraint_name, ..
1823 } = constraint
1824 {
1825 Some(constraint_name.clone())
1826 } else {
1827 None
1828 }
1829 })
1830 });
1831 if let Some(explicit_name) = primary_key_name {
1832 let name = explicit_name.unwrap_or_else(|| {
1833 self.next_generated_constraint_name(
1834 &create.id,
1835 &create.id.name,
1836 None,
1837 "pkey",
1838 )
1839 });
1840 self.snapshot_constraint(&create.id, &name);
1841 self.local.constraints.insert(
1842 (create.id.clone(), name.clone()),
1843 ConstraintState {
1844 table_id: create.id.clone(),
1845 name,
1846 kind: ConstraintKind::PrimaryKey,
1847 validated: true,
1848 },
1849 );
1850 }
1851
1852 let unique_constraints = create
1853 .columns
1854 .iter()
1855 .filter(|column| column.is_unique)
1856 .map(|column| {
1857 (
1858 column.unique_constraint_name.as_ref(),
1859 vec![column.name.as_str()],
1860 )
1861 })
1862 .chain(create.table_constraints.iter().filter_map(|constraint| {
1863 if let TableConstraintFact::Unique {
1864 constraint_name,
1865 columns,
1866 } = constraint
1867 {
1868 Some((
1869 constraint_name.as_ref(),
1870 columns.iter().map(String::as_str).collect(),
1871 ))
1872 } else {
1873 None
1874 }
1875 }))
1876 .collect::<Vec<_>>();
1877 for (explicit_name, columns) in unique_constraints {
1878 let name = explicit_name.cloned().unwrap_or_else(|| {
1879 self.next_generated_constraint_name(
1880 &create.id,
1881 &create.id.name,
1882 Some(&columns.join("_")),
1883 "key",
1884 )
1885 });
1886 self.snapshot_constraint(&create.id, &name);
1887 self.local.constraints.insert(
1888 (create.id.clone(), name.clone()),
1889 ConstraintState {
1890 table_id: create.id.clone(),
1891 name,
1892 kind: ConstraintKind::Unique,
1893 validated: true,
1894 },
1895 );
1896 }
1897
1898 if let Some(parent_id) = &create.partition_of {
1899 self.snapshot_graph();
1900 self.local.graph.edges.push(DependencyEdge::new(
1901 create.id.clone(),
1902 parent_id.clone(),
1903 DependencyKind::PartitionOf,
1904 ));
1905 }
1906
1907 if !create.foreign_keys.is_empty() {
1908 self.snapshot_graph();
1909 }
1910
1911 for fk in &create.foreign_keys {
1912 self.local.graph.edges.push(DependencyEdge::new(
1913 create.id.clone(),
1914 fk.to_table.clone(),
1915 DependencyKind::ForeignKey {
1916 constraint_name: fk.constraint_name.clone(),
1917 from_columns: fk.from_columns.clone(),
1918 to_columns: fk.to_columns.clone(),
1919 from_generation: generation,
1920 },
1921 ));
1922 }
1923 MutationResult::Applied
1924 }
1925 Mutation::CreateView(create_view) => {
1926 if self.relation_namespace_is_taken(&create_view.id) {
1927 let is_replaceable_view = matches!(
1928 self.local.relations.get(&create_view.id),
1929 Some(RelationOverlay::Present(relation))
1930 if relation.kind == RelationKind::View
1931 );
1932 if !create_view.or_replace || !is_replaceable_view {
1933 return MutationResult::Conflict {
1934 reason: format!("relation '{}' already exists", create_view.id),
1935 };
1936 }
1937 }
1938 self.snapshot_relation(&create_view.id);
1939 self.snapshot_generation_counter();
1940 self.local.generation_counter += 1;
1941 let generation = self.local.generation_counter;
1942
1943 self.local.relations.insert(
1944 create_view.id.clone(),
1945 RelationOverlay::Present(RelationState::new(
1946 create_view.id.clone(),
1947 ObjectId::new("", &self.local.current_role),
1948 generation,
1949 None,
1950 RelationKind::View,
1951 Persistence::Permanent,
1952 self.local.transactions.len(),
1953 )),
1954 );
1955
1956 self.snapshot_graph();
1957 for dep in &create_view.depends_on {
1958 self.local.graph.edges.push(DependencyEdge::new(
1959 create_view.id.clone(),
1960 dep.clone(),
1961 DependencyKind::ViewDependency {
1962 view_generation: generation,
1963 },
1964 ));
1965 }
1966 MutationResult::Applied
1967 }
1968 Mutation::CreateMaterializedView(create_mv) => {
1969 if self.relation_namespace_is_taken(&create_mv.id) {
1970 return MutationResult::Conflict {
1971 reason: format!("relation '{}' already exists", create_mv.id),
1972 };
1973 }
1974 self.snapshot_relation(&create_mv.id);
1975 self.snapshot_generation_counter();
1976 self.local.generation_counter += 1;
1977 let generation = self.local.generation_counter;
1978
1979 self.local.relations.insert(
1980 create_mv.id.clone(),
1981 RelationOverlay::Present(RelationState::new(
1982 create_mv.id.clone(),
1983 ObjectId::new("", &self.local.current_role),
1984 generation,
1985 None,
1986 RelationKind::MaterializedView,
1987 Persistence::Permanent,
1988 self.local.transactions.len(),
1989 )),
1990 );
1991
1992 self.snapshot_graph();
1993 for dep in &create_mv.depends_on {
1994 self.local.graph.edges.push(DependencyEdge::new(
1995 create_mv.id.clone(),
1996 dep.clone(),
1997 DependencyKind::ViewDependency {
1998 view_generation: generation,
1999 },
2000 ));
2001 }
2002 MutationResult::Applied
2003 }
2004 Mutation::RefreshMaterializedView(_) => MutationResult::Applied,
2005 Mutation::CreateIndex(create_idx) => {
2006 let exists = self.index_is_present(&create_idx.id);
2007 if create_idx.if_not_exists && exists {
2008 return MutationResult::Skipped;
2009 }
2010 if self.relation_namespace_is_taken(&create_idx.id) {
2011 return MutationResult::Conflict {
2012 reason: format!("relation '{}' already exists", create_idx.id),
2013 };
2014 }
2015 self.snapshot_graph();
2016 self.local.graph.edges.push(DependencyEdge::new(
2017 create_idx.id.clone(),
2018 create_idx.table.clone(),
2019 DependencyKind::IndexOnRelation {
2020 using_method: create_idx.using_method.clone(),
2021 has_predicate: create_idx.has_predicate,
2022 is_concurrent: create_idx.concurrently,
2023 is_unique: create_idx.unique,
2024 eligibility_known: true,
2025 },
2026 ));
2027 MutationResult::Applied
2028 }
2029 Mutation::CreatePolicy(create_policy) => {
2030 self.snapshot_relation(&create_policy.table);
2031 if let Some(RelationOverlay::Present(rel)) =
2032 self.local.relations.get_mut(&create_policy.table)
2033 {
2034 if rel.policies.contains(&create_policy.name) {
2035 return MutationResult::Conflict {
2036 reason: format!(
2037 "policy '{}' already exists on relation '{}'",
2038 create_policy.name, create_policy.table
2039 ),
2040 };
2041 }
2042 rel.policies.insert(create_policy.name.clone());
2043 } else {
2044 return MutationResult::Conflict {
2045 reason: format!("relation '{}' does not exist", create_policy.table),
2046 };
2047 }
2048 MutationResult::Applied
2049 }
2050 Mutation::DropPolicy(drop_policy) => {
2051 self.snapshot_relation(&drop_policy.table);
2052 if let Some(RelationOverlay::Present(rel)) =
2053 self.local.relations.get_mut(&drop_policy.table)
2054 {
2055 if !rel.policies.contains(&drop_policy.name) {
2056 return if drop_policy.if_exists {
2057 MutationResult::Skipped
2058 } else {
2059 MutationResult::Conflict {
2060 reason: format!(
2061 "policy '{}' does not exist on relation '{}'",
2062 drop_policy.name, drop_policy.table
2063 ),
2064 }
2065 };
2066 }
2067 rel.policies.remove(&drop_policy.name);
2068 } else {
2069 return MutationResult::Conflict {
2070 reason: format!("relation '{}' does not exist", drop_policy.table),
2071 };
2072 }
2073 MutationResult::Applied
2074 }
2075 Mutation::CreateTrigger(create_trigger) => {
2076 let trigger_id = Self::trigger_key(&create_trigger.table, &create_trigger.name);
2077 if matches!(
2078 self.local.triggers.get(&trigger_id),
2079 Some(TriggerOverlay::Present(_))
2080 ) {
2081 return MutationResult::Conflict {
2082 reason: format!(
2083 "trigger '{}' already exists on relation '{}'",
2084 create_trigger.name, create_trigger.table
2085 ),
2086 };
2087 }
2088 self.snapshot_trigger(&trigger_id);
2089 self.local.triggers.insert(
2090 trigger_id.clone(),
2091 TriggerOverlay::Present(crate::model::trigger::TriggerState {
2092 name: create_trigger.name.clone(),
2093 id: trigger_id.clone(),
2094 table_id: create_trigger.table.clone(),
2095 enabled_mode: crate::model::trigger::TriggerEnableMode::Origin,
2096 generation: self.local.generation_counter,
2097 }),
2098 );
2099
2100 self.snapshot_relation(&create_trigger.table);
2101 if let Some(RelationOverlay::Present(rel)) =
2102 self.local.relations.get_mut(&create_trigger.table)
2103 {
2104 rel.triggers.insert(create_trigger.name.clone());
2105 }
2106
2107 self.snapshot_graph_full();
2108 self.local.graph.edges.push(DependencyEdge::new(
2109 trigger_id.clone(),
2110 create_trigger.table.clone(),
2111 DependencyKind::TriggerOnTable {
2112 trigger_id: trigger_id.clone(),
2113 function_id: create_trigger.function_id.clone(),
2114 },
2115 ));
2116
2117 MutationResult::Applied
2118 }
2119 Mutation::DropTrigger(drop_trigger) => {
2120 let trigger_id = Self::trigger_key(&drop_trigger.table, &drop_trigger.name);
2121 if !matches!(
2122 self.local.triggers.get(&trigger_id),
2123 Some(TriggerOverlay::Present(_))
2124 ) {
2125 return if drop_trigger.if_exists {
2126 MutationResult::Skipped
2127 } else {
2128 MutationResult::Conflict {
2129 reason: format!(
2130 "trigger '{}' does not exist on relation '{}'",
2131 drop_trigger.name, drop_trigger.table
2132 ),
2133 }
2134 };
2135 }
2136 self.snapshot_trigger(&trigger_id);
2137 self.local
2138 .triggers
2139 .insert(trigger_id.clone(), TriggerOverlay::Dropped);
2140
2141 self.snapshot_relation(&drop_trigger.table);
2142 if let Some(RelationOverlay::Present(rel)) =
2143 self.local.relations.get_mut(&drop_trigger.table)
2144 {
2145 rel.triggers.remove(&drop_trigger.name);
2146 }
2147
2148 self.snapshot_graph_full();
2149 self.local.graph.edges.retain(|e| {
2150 !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
2151 && e.dependent == trigger_id)
2152 });
2153
2154 MutationResult::Applied
2155 }
2156 Mutation::AlterTable(alter) => {
2157 if let AlterTableActionMutation::OwnerTo { new_owner } = &alter.action {
2158 let Some((owner, known)) = self.role_fact_identity(new_owner) else {
2159 self.snapshot_confidence();
2160 self.local.confidence = Confidence::Tainted;
2161 return MutationResult::Skipped;
2162 };
2163 if !known {
2164 self.snapshot_confidence();
2165 self.local.confidence = Confidence::Tainted;
2166 }
2167 self.snapshot_relation(&alter.id);
2168 return match self.local.relations.get_mut(&alter.id) {
2169 Some(RelationOverlay::Present(relation)) => {
2170 relation.owner = ObjectId::new("", owner);
2171 MutationResult::Applied
2172 }
2173 _ => MutationResult::Conflict {
2174 reason: format!("relation '{}' does not exist", alter.id),
2175 },
2176 };
2177 }
2178
2179 let trigger_mode = match &alter.action {
2180 AlterTableActionMutation::DisableTrigger { trigger_name } => Some((
2181 trigger_name.as_deref(),
2182 crate::model::trigger::TriggerEnableMode::Disabled,
2183 )),
2184 AlterTableActionMutation::EnableTrigger { trigger_name } => Some((
2185 trigger_name.as_deref(),
2186 crate::model::trigger::TriggerEnableMode::Origin,
2187 )),
2188 _ => None,
2189 };
2190 if let Some((trigger_name, enabled_mode)) = trigger_mode {
2191 let all = trigger_name.is_none_or(|name| name.eq_ignore_ascii_case("all"));
2192 let trigger_ids: Vec<ObjectId> = self
2193 .local
2194 .triggers
2195 .iter()
2196 .filter_map(|(id, overlay)| {
2197 let TriggerOverlay::Present(trigger) = overlay else {
2198 return None;
2199 };
2200 (trigger.table_id == alter.id
2201 && (all || trigger_name == Some(trigger.name.as_str())))
2202 .then(|| id.clone())
2203 })
2204 .collect();
2205 for trigger_id in trigger_ids {
2206 self.snapshot_trigger(&trigger_id);
2207 if let Some(TriggerOverlay::Present(trigger)) =
2208 self.local.triggers.get_mut(&trigger_id)
2209 {
2210 trigger.enabled_mode = enabled_mode;
2211 }
2212 }
2213 return MutationResult::Applied;
2214 }
2215
2216 if let AlterTableActionMutation::AddForeignKey {
2217 to_table,
2218 from_columns,
2219 to_columns,
2220 ..
2221 } = &alter.action
2222 {
2223 if let Some(RelationOverlay::Present(child)) =
2224 self.local.relations.get(&alter.id)
2225 {
2226 if let Some(column) =
2227 from_columns.iter().find(|column| !child.has_column(column))
2228 {
2229 return MutationResult::Conflict {
2230 reason: format!(
2231 "foreign key column '{}' does not exist on relation '{}'",
2232 column, alter.id
2233 ),
2234 };
2235 }
2236 }
2237
2238 let Some(RelationOverlay::Present(parent)) = self.local.relations.get(to_table)
2239 else {
2240 return MutationResult::Conflict {
2241 reason: format!(
2242 "foreign key references relation '{}' which does not exist",
2243 to_table
2244 ),
2245 };
2246 };
2247 if let Some(column) =
2248 to_columns.iter().find(|column| !parent.has_column(column))
2249 {
2250 return MutationResult::Conflict {
2251 reason: format!(
2252 "foreign key references column '{}.{}' which does not exist",
2253 to_table, column
2254 ),
2255 };
2256 }
2257 }
2258
2259 let implicit_add = match &alter.action {
2260 AlterTableActionMutation::AddColumn {
2261 name, generation, ..
2262 } => match generation {
2263 crate::analysis::facts::ColumnGeneration::Serial => Some((
2264 self.next_implicit_sequence_id(&alter.id, name, &HashSet::new()),
2265 name.clone(),
2266 SequenceKind::SerialLike,
2267 )),
2268 crate::analysis::facts::ColumnGeneration::Identity => Some((
2269 self.next_implicit_sequence_id(&alter.id, name, &HashSet::new()),
2270 name.clone(),
2271 SequenceKind::Identity,
2272 )),
2273 crate::analysis::facts::ColumnGeneration::Ordinary => None,
2274 },
2275 _ => None,
2276 };
2277 let owned_sequences_for_column: Vec<ObjectId> = match &alter.action {
2278 AlterTableActionMutation::DropColumn { name, .. }
2279 | AlterTableActionMutation::RenameColumn { from: name, .. } => self
2280 .local
2281 .sequences
2282 .iter()
2283 .filter_map(|(id, overlay)| match overlay {
2284 SequenceOverlay::Present(sequence)
2285 if sequence.owned_by.as_ref()
2286 == Some(&(alter.id.clone(), name.clone())) =>
2287 {
2288 Some(id.clone())
2289 }
2290 _ => None,
2291 })
2292 .collect(),
2293 _ => Vec::new(),
2294 };
2295
2296 let using_index = match &alter.action {
2297 AlterTableActionMutation::AddUniqueConstraint { using_index, .. }
2298 | AlterTableActionMutation::AddPrimaryKeyConstraint { using_index, .. } => {
2299 using_index.as_ref()
2300 }
2301 _ => None,
2302 };
2303 if let Some(index) = using_index {
2304 let Some(edge) = self.local.graph.edges.iter().find(|edge| {
2305 matches!(edge.kind, DependencyKind::IndexOnRelation { .. })
2306 && edge.dependent == *index
2307 }) else {
2308 return MutationResult::Conflict {
2309 reason: format!(
2310 "constraint references index '{}' which does not exist",
2311 index
2312 ),
2313 };
2314 };
2315 if edge.referenced != alter.id {
2316 return MutationResult::Conflict {
2317 reason: format!(
2318 "constraint index '{}' belongs to relation '{}', not '{}'",
2319 index, edge.referenced, alter.id
2320 ),
2321 };
2322 }
2323 if let DependencyKind::IndexOnRelation {
2324 has_predicate,
2325 is_unique,
2326 eligibility_known,
2327 ..
2328 } = &edge.kind
2329 && *eligibility_known
2330 && (!is_unique || *has_predicate)
2331 {
2332 return MutationResult::Conflict {
2333 reason: format!(
2334 "constraint index '{}' must be unique and non-partial",
2335 index
2336 ),
2337 };
2338 }
2339 }
2340
2341 self.snapshot_relation(&alter.id);
2342 let rel_overlay = self.local.relations.get_mut(&alter.id);
2343 if let Some(RelationOverlay::Present(rel)) = rel_overlay {
2344 let generation = rel.generation;
2345 match &alter.action {
2346 AlterTableActionMutation::AddColumn {
2347 name,
2348 ty,
2349 if_not_exists,
2350 not_null,
2351 default,
2352 depends_on,
2353 generation: _,
2354 } => {
2355 if let Some(existing_col) = rel.columns.iter().find(|c| c.name == *name)
2356 {
2357 if *if_not_exists {
2358 return MutationResult::Skipped;
2359 }
2360 return MutationResult::Conflict {
2361 reason: format!(
2362 "column '{}' already exists with type {}; this statement adds it again with type {}",
2363 name,
2364 existing_col.data_type.as_deref().unwrap_or("unknown"),
2365 ty.as_deref().unwrap_or("unknown")
2366 ),
2367 };
2368 }
2369 rel.apply_column_action(&ColumnAction::Add {
2370 name: name.clone(),
2371 data_type: ty.clone(),
2372 not_null: *not_null,
2373 default: default.clone(),
2374 });
2375
2376 if let Some((sequence_id, column_name, _)) = &implicit_add
2377 && column_name == name
2378 && let Some(column) =
2379 rel.columns.iter_mut().find(|column| column.name == *name)
2380 {
2381 column.default = Some(Self::sequence_nextval_default(sequence_id));
2382 column.default_expr_text = Some(format!(
2383 "nextval('{}.{}'::regclass)",
2384 sequence_id.schema, sequence_id.name
2385 ));
2386 column.is_nullable = false;
2387 }
2388
2389 if let Some((source_table, source_col)) = depends_on {
2390 self.snapshot_graph();
2391 self.local.graph.edges.push(DependencyEdge::new(
2392 alter.id.clone(),
2393 source_table.clone(),
2394 DependencyKind::ColumnGeneratedFrom {
2395 column: name.clone(),
2396 depends_on_column: source_col.clone(),
2397 },
2398 ));
2399 }
2400 }
2401 AlterTableActionMutation::DropColumn { name, if_exists } => {
2402 if !rel.has_column(name) {
2403 if *if_exists {
2404 return MutationResult::Skipped;
2406 }
2407 return MutationResult::Conflict {
2408 reason: format!(
2409 "column '{}' does not exist on relation '{}'",
2410 name, alter.id
2411 ),
2412 };
2413 }
2414 rel.apply_column_action(&ColumnAction::Drop { name: name.clone() });
2415 }
2416 AlterTableActionMutation::RenameColumn { from, to } => {
2417 rel.apply_column_action(&ColumnAction::Rename {
2418 from: from.clone(),
2419 to: to.clone(),
2420 });
2421 }
2422 AlterTableActionMutation::SetNotNull { column } => {
2423 rel.apply_column_action(&ColumnAction::SetNotNull {
2424 name: column.clone(),
2425 });
2426 }
2427 AlterTableActionMutation::DropNotNull { column } => {
2428 rel.apply_column_action(&ColumnAction::DropNotNull {
2429 name: column.clone(),
2430 });
2431 }
2432 AlterTableActionMutation::SetType { column, ty, .. } => {
2433 if !rel.has_column(column) {
2434 self.local.confidence = Confidence::Tainted;
2435 }
2436 rel.apply_column_action(&ColumnAction::SetType {
2437 name: column.clone(),
2438 data_type: ty.clone(),
2439 });
2440 }
2441 AlterTableActionMutation::SetDefault { column, default } => {
2442 if !rel.has_column(column) {
2443 self.local.confidence = Confidence::Tainted;
2444 }
2445 rel.apply_column_action(&ColumnAction::SetDefault {
2446 name: column.clone(),
2447 default: default.clone(),
2448 });
2449 }
2450 AlterTableActionMutation::AddForeignKey {
2451 constraint_name,
2452 to_table,
2453 from_columns,
2454 to_columns,
2455 not_valid,
2456 } => {
2457 let constraint_name = constraint_name.clone().unwrap_or_else(|| {
2458 format!("{}_{}_fkey", alter.id.name, from_columns.join("_"))
2459 });
2460 self.snapshot_constraint(&alter.id, &constraint_name);
2461 self.local.constraints.insert(
2462 (alter.id.clone(), constraint_name.clone()),
2463 ConstraintState {
2464 table_id: alter.id.clone(),
2465 name: constraint_name.clone(),
2466 kind: ConstraintKind::ForeignKey,
2467 validated: !not_valid,
2468 },
2469 );
2470 self.snapshot_graph();
2471 self.local.graph.edges.push(DependencyEdge::new(
2472 alter.id.clone(),
2473 to_table.clone(),
2474 DependencyKind::ForeignKey {
2475 constraint_name: Some(constraint_name),
2476 from_columns: from_columns.clone(),
2477 to_columns: to_columns.clone(),
2478 from_generation: generation,
2479 },
2480 ));
2481 }
2482 AlterTableActionMutation::DropConstraint { name } => {
2483 self.snapshot_constraint(&alter.id, name);
2484 self.local
2485 .constraints
2486 .remove(&(alter.id.clone(), name.clone()));
2487 self.snapshot_graph();
2488 self.local.graph.edges.retain(|e| {
2489 if let DependencyKind::ForeignKey {
2490 constraint_name, ..
2491 } = &e.kind
2492 {
2493 !(e.dependent == alter.id
2494 && constraint_name.as_ref() == Some(name))
2495 } else {
2496 true
2497 }
2498 });
2499 }
2500 AlterTableActionMutation::RenameConstraint { old_name, new_name } => {
2501 self.snapshot_constraint(&alter.id, old_name);
2502 self.snapshot_constraint(&alter.id, new_name);
2503 if let Some(mut constraint) = self
2504 .local
2505 .constraints
2506 .remove(&(alter.id.clone(), old_name.clone()))
2507 {
2508 constraint.name = new_name.clone();
2509 self.local
2510 .constraints
2511 .insert((alter.id.clone(), new_name.clone()), constraint);
2512 }
2513 self.snapshot_graph_full();
2514 for edge in &mut self.local.graph.edges {
2515 if edge.dependent == alter.id
2516 && let DependencyKind::ForeignKey {
2517 constraint_name, ..
2518 } = &mut edge.kind
2519 && constraint_name.as_deref() == Some(old_name)
2520 {
2521 *constraint_name = Some(new_name.clone());
2522 }
2523 }
2524 }
2525 AlterTableActionMutation::AddCheckConstraint {
2526 constraint_name,
2527 not_valid,
2528 } => {
2529 let constraint_name = constraint_name
2530 .clone()
2531 .unwrap_or_else(|| format!("{}_check", alter.id.name));
2532 self.snapshot_constraint(&alter.id, &constraint_name);
2533 self.local.constraints.insert(
2534 (alter.id.clone(), constraint_name.clone()),
2535 ConstraintState {
2536 table_id: alter.id.clone(),
2537 name: constraint_name,
2538 kind: ConstraintKind::Check,
2539 validated: !not_valid,
2540 },
2541 );
2542 }
2543 AlterTableActionMutation::AddUniqueConstraint {
2544 constraint_name,
2545 using_index,
2546 } => {
2547 let constraint_name = constraint_name
2548 .clone()
2549 .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
2550 .unwrap_or_else(|| format!("{}_key", alter.id.name));
2551 self.snapshot_constraint(&alter.id, &constraint_name);
2552 self.local.constraints.insert(
2553 (alter.id.clone(), constraint_name.clone()),
2554 ConstraintState {
2555 table_id: alter.id.clone(),
2556 name: constraint_name,
2557 kind: ConstraintKind::Unique,
2558 validated: true,
2559 },
2560 );
2561 }
2562 AlterTableActionMutation::AddPrimaryKeyConstraint {
2563 constraint_name,
2564 using_index,
2565 } => {
2566 let constraint_name = constraint_name
2567 .clone()
2568 .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
2569 .unwrap_or_else(|| format!("{}_pkey", alter.id.name));
2570 self.snapshot_constraint(&alter.id, &constraint_name);
2571 self.local.constraints.insert(
2572 (alter.id.clone(), constraint_name.clone()),
2573 ConstraintState {
2574 table_id: alter.id.clone(),
2575 name: constraint_name,
2576 kind: ConstraintKind::PrimaryKey,
2577 validated: true,
2578 },
2579 );
2580 }
2581 AlterTableActionMutation::AddExcludeConstraint { constraint_name } => {
2582 let constraint_name = constraint_name
2583 .clone()
2584 .unwrap_or_else(|| format!("{}_excl", alter.id.name));
2585 self.snapshot_constraint(&alter.id, &constraint_name);
2586 self.local.constraints.insert(
2587 (alter.id.clone(), constraint_name.clone()),
2588 ConstraintState {
2589 table_id: alter.id.clone(),
2590 name: constraint_name,
2591 kind: ConstraintKind::Exclusion,
2592 validated: true,
2593 },
2594 );
2595 }
2596 AlterTableActionMutation::ValidateConstraint { constraint_name } => {
2597 self.snapshot_constraint(&alter.id, constraint_name);
2598 if let Some(constraint) = self
2599 .local
2600 .constraints
2601 .get_mut(&(alter.id.clone(), constraint_name.clone()))
2602 {
2603 constraint.validated = true;
2604 }
2605 }
2606 AlterTableActionMutation::AttachPartition { child, .. } => {
2607 if self.local.graph.check_partition_cycle(&alter.id, child) {
2609 self.snapshot_confidence();
2610 self.local.confidence = Confidence::Tainted;
2611 } else {
2612 self.snapshot_graph();
2613 self.local.graph.edges.push(DependencyEdge::new(
2614 child.clone(),
2615 alter.id.clone(),
2616 DependencyKind::PartitionOf,
2617 ));
2618 }
2619 }
2620 AlterTableActionMutation::DetachPartition { child } => {
2621 self.snapshot_graph();
2622 self.local.graph.edges.retain(|e| {
2623 !(matches!(e.kind, DependencyKind::PartitionOf)
2624 && e.dependent == *child
2625 && e.referenced == alter.id)
2626 });
2627 }
2628 _ => {}
2629 }
2630 }
2631 if let Some((sequence_id, column_name, kind)) = implicit_add {
2632 self.snapshot_sequence(&sequence_id);
2633 self.snapshot_generation_counter();
2634 self.local.generation_counter += 1;
2635 self.local.sequences.insert(
2636 sequence_id.clone(),
2637 SequenceOverlay::Present(SequenceState {
2638 id: sequence_id.clone(),
2639 owner: self
2640 .local
2641 .relations
2642 .get(&alter.id)
2643 .and_then(|overlay| match overlay {
2644 RelationOverlay::Present(table) => Some(table.owner.clone()),
2645 RelationOverlay::Dropped => None,
2646 })
2647 .unwrap_or_else(|| ObjectId::new("", &self.local.current_role)),
2648 owned_by: Some((alter.id.clone(), column_name.clone())),
2649 kind,
2650 generation: self.local.generation_counter,
2651 }),
2652 );
2653 self.snapshot_graph();
2654 self.local.graph.edges.push(DependencyEdge::new(
2655 sequence_id,
2656 alter.id.clone(),
2657 DependencyKind::SequenceOwnedBy {
2658 column: column_name,
2659 },
2660 ));
2661 }
2662 match &alter.action {
2663 AlterTableActionMutation::DropColumn { .. } => {
2664 for sequence_id in owned_sequences_for_column {
2665 self.snapshot_sequence(&sequence_id);
2666 self.local
2667 .sequences
2668 .insert(sequence_id.clone(), SequenceOverlay::Dropped);
2669 self.snapshot_graph_full();
2670 self.local.graph.edges.retain(|edge| {
2671 !(matches!(edge.kind, DependencyKind::SequenceOwnedBy { .. })
2672 && edge.dependent == sequence_id)
2673 });
2674 }
2675 }
2676 AlterTableActionMutation::RenameColumn { to, .. } => {
2677 for sequence_id in owned_sequences_for_column {
2678 self.snapshot_sequence(&sequence_id);
2679 if let Some(SequenceOverlay::Present(sequence)) =
2680 self.local.sequences.get_mut(&sequence_id)
2681 && let Some((_, column)) = &mut sequence.owned_by
2682 {
2683 *column = to.clone();
2684 }
2685 self.snapshot_graph_full();
2686 for edge in &mut self.local.graph.edges {
2687 if edge.dependent == sequence_id
2688 && let DependencyKind::SequenceOwnedBy { column } =
2689 &mut edge.kind
2690 {
2691 *column = to.clone();
2692 }
2693 }
2694 }
2695 }
2696 _ => {}
2697 }
2698 MutationResult::Applied
2699 }
2700 Mutation::CreateType(create_type) => {
2701 if self.relation_namespace_is_taken(&create_type.id) {
2702 return MutationResult::Conflict {
2703 reason: format!("type '{}' already exists", create_type.id),
2704 };
2705 }
2706 self.snapshot_type(&create_type.id);
2707 self.snapshot_generation_counter();
2708 self.local.generation_counter += 1;
2709 let generation = self.local.generation_counter;
2710
2711 self.local.types.insert(
2712 create_type.id.clone(),
2713 TypeOverlay::Present(TypeState {
2714 id: create_type.id.clone(),
2715 generation,
2716 kind: create_type.kind.clone(),
2717 }),
2718 );
2719 MutationResult::Applied
2720 }
2721 Mutation::AlterType(alter_type) => {
2722 self.snapshot_type(&alter_type.id);
2723 if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter_type.id) {
2724 match &alter_type.action {
2725 AlterTypeActionMutation::AddValue {
2726 new_value,
2727 neighbor,
2728 before,
2729 } => {
2730 if let TypeKind::Enum { variants } = &mut t.kind {
2731 if variants.contains(new_value) {
2732 return MutationResult::Skipped;
2733 }
2734 let insertion_index = neighbor
2735 .as_ref()
2736 .and_then(|neighbor| {
2737 variants.iter().position(|value| value == neighbor)
2738 })
2739 .map(|index| if *before { index } else { index + 1 })
2740 .unwrap_or(variants.len());
2741 variants.insert(insertion_index, new_value.clone());
2742 }
2743 }
2744 AlterTypeActionMutation::RenameValue {
2745 old_value,
2746 new_value,
2747 } => {
2748 let TypeKind::Enum { variants } = &mut t.kind else {
2749 return MutationResult::Conflict {
2750 reason: format!("type '{}' is not an enum", alter_type.id),
2751 };
2752 };
2753 let Some(old_index) =
2754 variants.iter().position(|value| value == old_value)
2755 else {
2756 return MutationResult::Conflict {
2757 reason: format!(
2758 "'{}' is not an existing label of enum '{}'",
2759 old_value, alter_type.id
2760 ),
2761 };
2762 };
2763 if variants.iter().any(|value| value == new_value) {
2764 return MutationResult::Conflict {
2765 reason: format!(
2766 "enum label '{}' already exists on type '{}'",
2767 new_value, alter_type.id
2768 ),
2769 };
2770 }
2771 variants[old_index] = new_value.clone();
2772 }
2773 }
2774 } else if matches!(
2775 alter_type.action,
2776 AlterTypeActionMutation::RenameValue { .. }
2777 ) {
2778 return MutationResult::Conflict {
2779 reason: format!("type '{}' does not exist", alter_type.id),
2780 };
2781 }
2782 MutationResult::Applied
2783 }
2784 Mutation::CreateDomain(create_domain) => {
2785 if self.relation_namespace_is_taken(&create_domain.id) {
2786 return MutationResult::Conflict {
2787 reason: format!("type '{}' already exists", create_domain.id),
2788 };
2789 }
2790 self.snapshot_type(&create_domain.id);
2791 self.snapshot_generation_counter();
2792 self.local.generation_counter += 1;
2793 let generation = self.local.generation_counter;
2794
2795 self.local.types.insert(
2796 create_domain.id.clone(),
2797 TypeOverlay::Present(TypeState {
2798 id: create_domain.id.clone(),
2799 generation,
2800 kind: TypeKind::Domain {
2801 base_type: create_domain.base_type.clone(),
2802 },
2803 }),
2804 );
2805 MutationResult::Applied
2806 }
2807 Mutation::AlterDomain(_) => MutationResult::Applied,
2808 Mutation::DropDomain(drop_domain) => {
2809 for id in &drop_domain.ids {
2810 self.snapshot_type(id);
2811 self.local.types.insert(id.clone(), TypeOverlay::Dropped);
2812 }
2813 MutationResult::Applied
2814 }
2815 Mutation::DropType(drop_type) => {
2816 for id in &drop_type.ids {
2817 self.snapshot_type(id);
2818 self.local.types.insert(id.clone(), TypeOverlay::Dropped);
2819 }
2820 MutationResult::Applied
2821 }
2822 Mutation::CreateSequence(create_seq) => {
2823 if create_seq.if_not_exists && self.relation_namespace_is_taken(&create_seq.id) {
2824 return MutationResult::Skipped;
2825 }
2826 if self.relation_namespace_is_taken(&create_seq.id) {
2827 return MutationResult::Conflict {
2828 reason: format!("relation '{}' already exists", create_seq.id),
2829 };
2830 }
2831 if let Some((table_id, column)) = &create_seq.owned_by {
2832 if table_id.schema != create_seq.id.schema {
2833 return MutationResult::Conflict {
2834 reason: "sequence must be in the same schema as its owning table"
2835 .to_string(),
2836 };
2837 }
2838 match self.local.relations.get(table_id) {
2839 Some(RelationOverlay::Present(table)) => {
2840 if !table.has_column(column) {
2841 return MutationResult::Conflict {
2842 reason: format!(
2843 "column '{}.{}' does not exist",
2844 table_id, column
2845 ),
2846 };
2847 }
2848 if self.local.current_role_known
2849 && table.owner.name != self.local.current_role
2850 {
2851 return MutationResult::Conflict {
2852 reason: "sequence and table must have the same owner"
2853 .to_string(),
2854 };
2855 }
2856 }
2857 _ if self.baseline_covers_object(table_id) && self.baseline_available => {
2858 return MutationResult::Conflict {
2859 reason: format!("relation '{}' does not exist", table_id),
2860 };
2861 }
2862 _ => {
2863 self.snapshot_confidence();
2864 self.local.confidence = Confidence::Tainted;
2865 }
2866 }
2867 }
2868 self.snapshot_sequence(&create_seq.id);
2869 self.snapshot_generation_counter();
2870 self.local.generation_counter += 1;
2871 let generation = self.local.generation_counter;
2872
2873 self.local.sequences.insert(
2874 create_seq.id.clone(),
2875 SequenceOverlay::Present(SequenceState {
2876 id: create_seq.id.clone(),
2877 owner: ObjectId::new("", self.local.current_role.clone()),
2878 owned_by: create_seq.owned_by.clone(),
2879 kind: if create_seq.owned_by.is_some() {
2880 SequenceKind::Owned
2881 } else {
2882 SequenceKind::Standalone
2883 },
2884 generation,
2885 }),
2886 );
2887
2888 if let Some((table_id, col)) = &create_seq.owned_by {
2889 self.snapshot_graph();
2890 self.local.graph.edges.push(DependencyEdge::new(
2891 create_seq.id.clone(),
2892 table_id.clone(),
2893 DependencyKind::SequenceOwnedBy {
2894 column: col.clone(),
2895 },
2896 ));
2897 }
2898 MutationResult::Applied
2899 }
2900 Mutation::AlterSequence(alter_seq) => {
2901 if !self.sequence_is_present(&alter_seq.id) {
2902 if alter_seq.if_exists {
2903 return MutationResult::Skipped;
2904 }
2905 if self.baseline_covers_object(&alter_seq.id) && self.baseline_available {
2906 return MutationResult::Conflict {
2907 reason: format!("sequence '{}' does not exist", alter_seq.id),
2908 };
2909 }
2910 self.snapshot_confidence();
2911 self.local.confidence = Confidence::Tainted;
2912 return MutationResult::Skipped;
2913 }
2914 let current = match self.local.sequences.get(&alter_seq.id) {
2915 Some(SequenceOverlay::Present(sequence)) => sequence.clone(),
2916 _ => unreachable!("presence checked above"),
2917 };
2918 match &alter_seq.action {
2919 crate::analysis::mutations::AlterSequenceActionMutation::OwnedBy(owned_by) => {
2920 if current.kind == SequenceKind::Identity {
2921 return MutationResult::Conflict {
2922 reason: "cannot change ownership of an identity sequence"
2923 .to_string(),
2924 };
2925 }
2926 if let Some((table_id, column)) = owned_by {
2927 if table_id.schema != alter_seq.id.schema {
2928 return MutationResult::Conflict {
2929 reason:
2930 "sequence must be in the same schema as its owning table"
2931 .to_string(),
2932 };
2933 }
2934 let Some(RelationOverlay::Present(table)) =
2935 self.local.relations.get(table_id)
2936 else {
2937 return MutationResult::Conflict {
2938 reason: format!("relation '{}' does not exist", table_id),
2939 };
2940 };
2941 if !table.has_column(column) {
2942 return MutationResult::Conflict {
2943 reason: format!(
2944 "column '{}.{}' does not exist",
2945 table_id, column
2946 ),
2947 };
2948 }
2949 if table.owner != current.owner {
2950 return MutationResult::Conflict {
2951 reason: "sequence and table must have the same owner"
2952 .to_string(),
2953 };
2954 }
2955 }
2956 self.snapshot_sequence(&alter_seq.id);
2957 self.snapshot_graph();
2958 self.local.graph.edges.retain(|edge| {
2959 !(matches!(edge.kind, DependencyKind::SequenceOwnedBy { .. })
2960 && edge.dependent == alter_seq.id)
2961 });
2962 if let Some(SequenceOverlay::Present(sequence)) =
2963 self.local.sequences.get_mut(&alter_seq.id)
2964 {
2965 sequence.owned_by = owned_by.clone();
2966 sequence.kind = if owned_by.is_some() {
2967 SequenceKind::Owned
2968 } else {
2969 SequenceKind::Standalone
2970 };
2971 }
2972 if let Some((table_id, column)) = owned_by {
2973 self.local.graph.edges.push(DependencyEdge::new(
2974 alter_seq.id.clone(),
2975 table_id.clone(),
2976 DependencyKind::SequenceOwnedBy {
2977 column: column.clone(),
2978 },
2979 ));
2980 }
2981 MutationResult::Applied
2982 }
2983 crate::analysis::mutations::AlterSequenceActionMutation::OwnerTo(owner) => {
2984 if current.kind == SequenceKind::Identity {
2985 return MutationResult::Conflict {
2986 reason: "cannot alter an identity sequence independently"
2987 .to_string(),
2988 };
2989 }
2990 let Some((owner_name, known)) = self.role_fact_identity(owner) else {
2991 self.snapshot_confidence();
2992 self.local.confidence = Confidence::Tainted;
2993 return MutationResult::Skipped;
2994 };
2995 if known
2996 && self.local.roles_known
2997 && self.present_role(&owner_name).is_none()
2998 {
2999 return MutationResult::Conflict {
3000 reason: format!("role '{}' does not exist", owner_name),
3001 };
3002 }
3003 if let Some((table_id, _)) = ¤t.owned_by
3004 && let Some(RelationOverlay::Present(table)) =
3005 self.local.relations.get(table_id)
3006 && table.owner.name != owner_name
3007 {
3008 return MutationResult::Conflict {
3009 reason: "sequence and table must have the same owner".to_string(),
3010 };
3011 }
3012 self.snapshot_sequence(&alter_seq.id);
3013 if let Some(SequenceOverlay::Present(sequence)) =
3014 self.local.sequences.get_mut(&alter_seq.id)
3015 {
3016 sequence.owner = ObjectId::new("", owner_name);
3017 }
3018 MutationResult::Applied
3019 }
3020 crate::analysis::mutations::AlterSequenceActionMutation::RenameTo(new_id)
3021 | crate::analysis::mutations::AlterSequenceActionMutation::SetSchema(new_id) => {
3022 if current.kind == SequenceKind::Identity {
3023 return MutationResult::Conflict {
3024 reason: "cannot alter an identity sequence independently"
3025 .to_string(),
3026 };
3027 }
3028 if self.relation_namespace_is_taken(new_id) {
3029 return MutationResult::Conflict {
3030 reason: format!("relation '{}' already exists", new_id),
3031 };
3032 }
3033 if let Some((table_id, _)) = ¤t.owned_by
3034 && table_id.schema != new_id.schema
3035 {
3036 return MutationResult::Conflict {
3037 reason: "sequence must be in the same schema as its owning table"
3038 .to_string(),
3039 };
3040 }
3041 self.snapshot_namespace();
3042 let mut moved = current;
3043 moved.id = new_id.clone();
3044 self.local.sequences.remove(&alter_seq.id);
3045 self.local
3046 .sequences
3047 .insert(new_id.clone(), SequenceOverlay::Present(moved));
3048 self.local.graph.propagate_rename(&alter_seq.id, new_id);
3049 self.local.graph.edges.push(DependencyEdge::new(
3050 alter_seq.id.clone(),
3051 new_id.clone(),
3052 DependencyKind::RenameTo,
3053 ));
3054 if self.baseline_sequences.remove(&alter_seq.id) {
3055 self.baseline_sequences.insert(new_id.clone());
3056 }
3057 MutationResult::Applied
3058 }
3059 crate::analysis::mutations::AlterSequenceActionMutation::Other => {
3060 MutationResult::Applied
3061 }
3062 }
3063 }
3064 Mutation::DropSequence(drop_seq) => {
3065 if !drop_seq.if_exists {
3066 let missing: Vec<ObjectId> = drop_seq
3067 .ids
3068 .iter()
3069 .filter(|id| !self.sequence_is_present(id))
3070 .cloned()
3071 .collect();
3072 for id in &missing {
3073 if self.baseline_covers_object(id) && self.baseline_available {
3074 return MutationResult::Conflict {
3075 reason: format!("sequence '{}' does not exist", id),
3076 };
3077 }
3078 self.snapshot_confidence();
3079 self.local.confidence = Confidence::Tainted;
3080 }
3081 }
3082 let present: Vec<ObjectId> = drop_seq
3083 .ids
3084 .iter()
3085 .filter(|id| self.sequence_is_present(id))
3086 .cloned()
3087 .collect();
3088 if present.is_empty() {
3089 return MutationResult::Skipped;
3090 }
3091 for id in &present {
3092 let Some(SequenceOverlay::Present(sequence)) = self.local.sequences.get(id)
3093 else {
3094 continue;
3095 };
3096 if sequence.kind == SequenceKind::Identity {
3097 return MutationResult::Conflict {
3098 reason: format!("cannot drop identity sequence '{}' independently", id),
3099 };
3100 }
3101 if sequence.kind == SequenceKind::SerialLike && !drop_seq.cascade {
3102 return MutationResult::Conflict {
3103 reason: format!("sequence '{}' still has dependent defaults", id),
3104 };
3105 }
3106 }
3107 if drop_seq.cascade {
3108 let serial_owners: Vec<(ObjectId, String)> = present
3109 .iter()
3110 .filter_map(|id| match self.local.sequences.get(id) {
3111 Some(SequenceOverlay::Present(sequence))
3112 if sequence.kind == SequenceKind::SerialLike =>
3113 {
3114 sequence.owned_by.clone()
3115 }
3116 _ => None,
3117 })
3118 .collect();
3119 for (table_id, column) in serial_owners {
3120 self.snapshot_relation(&table_id);
3121 if let Some(RelationOverlay::Present(table)) =
3122 self.local.relations.get_mut(&table_id)
3123 && let Some(column) =
3124 table.columns.iter_mut().find(|item| item.name == column)
3125 {
3126 column.default = None;
3127 column.default_expr_text = None;
3128 }
3129 }
3130 }
3131 for id in &present {
3132 self.snapshot_sequence(id);
3133 self.local
3134 .sequences
3135 .insert(id.clone(), SequenceOverlay::Dropped);
3136 }
3137 self.snapshot_graph_full();
3138 self.local.graph.edges.retain(|e| {
3139 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
3140 && present.contains(&e.dependent))
3141 });
3142 MutationResult::Applied
3143 }
3144 Mutation::Rename(rename) => {
3145 let renames_relation = self.relation_is_present(&rename.old_id);
3146 let renames_index = self.index_is_present(&rename.old_id);
3147 if !renames_relation && !renames_index {
3148 if self.baseline_covers_object(&rename.old_id) {
3149 return MutationResult::Conflict {
3150 reason: format!("relation '{}' does not exist", rename.old_id),
3151 };
3152 }
3153 self.snapshot_confidence();
3154 self.local.confidence = Confidence::Tainted;
3155 return MutationResult::Skipped;
3156 }
3157 if rename.old_id != rename.new_id
3158 && self.relation_namespace_is_taken(&rename.new_id)
3159 {
3160 return MutationResult::Conflict {
3161 reason: format!("relation '{}' already exists", rename.new_id),
3162 };
3163 }
3164 if rename.old_id.schema != rename.new_id.schema
3165 && !self.schema_is_present(&rename.new_id.schema)
3166 {
3167 if self.schema_absence_is_authoritative(&rename.new_id.schema) {
3168 return MutationResult::Conflict {
3169 reason: format!("schema '{}' does not exist", rename.new_id.schema),
3170 };
3171 }
3172 self.snapshot_confidence();
3173 self.local.confidence = Confidence::Tainted;
3174 return MutationResult::Skipped;
3175 }
3176
3177 self.snapshot_namespace();
3178 if let Some(RelationOverlay::Present(mut state)) =
3179 self.local.relations.remove(&rename.old_id)
3180 {
3181 state.id = rename.new_id.clone();
3182 self.local
3183 .relations
3184 .insert(rename.new_id.clone(), RelationOverlay::Present(state));
3185 }
3186 let owned_sequence_ids: Vec<ObjectId> = self
3187 .local
3188 .sequences
3189 .iter()
3190 .filter_map(|(id, overlay)| match overlay {
3191 SequenceOverlay::Present(sequence)
3192 if sequence
3193 .owned_by
3194 .as_ref()
3195 .is_some_and(|(table, _)| table == &rename.old_id) =>
3196 {
3197 Some(id.clone())
3198 }
3199 _ => None,
3200 })
3201 .collect();
3202 for sequence_id in owned_sequence_ids {
3203 self.snapshot_sequence(&sequence_id);
3204 if let Some(SequenceOverlay::Present(sequence)) =
3205 self.local.sequences.get_mut(&sequence_id)
3206 && let Some((table, _)) = &mut sequence.owned_by
3207 {
3208 *table = rename.new_id.clone();
3209 }
3210 }
3211 let triggers_to_move: Vec<(ObjectId, crate::model::trigger::TriggerState)> = self
3212 .local
3213 .triggers
3214 .iter()
3215 .filter_map(|(id, overlay)| match overlay {
3216 TriggerOverlay::Present(trigger) if trigger.table_id == rename.old_id => {
3217 Some((id.clone(), trigger.clone()))
3218 }
3219 _ => None,
3220 })
3221 .collect();
3222 for (old_trigger_id, mut trigger) in triggers_to_move {
3223 let new_trigger_id = Self::trigger_key(&rename.new_id, &trigger.name);
3224 self.local.triggers.remove(&old_trigger_id);
3225 trigger.id = new_trigger_id.clone();
3226 trigger.table_id = rename.new_id.clone();
3227 self.local
3228 .triggers
3229 .insert(new_trigger_id.clone(), TriggerOverlay::Present(trigger));
3230 self.local
3231 .graph
3232 .propagate_rename(&old_trigger_id, &new_trigger_id);
3233 self.local.graph.edges.push(DependencyEdge::new(
3234 old_trigger_id,
3235 new_trigger_id,
3236 DependencyKind::RenameTo,
3237 ));
3238 }
3239 let constraints_to_move: Vec<(String, ConstraintState)> = self
3240 .local
3241 .constraints
3242 .iter()
3243 .filter(|((table_id, _), _)| table_id == &rename.old_id)
3244 .map(|((_, name), constraint)| (name.clone(), constraint.clone()))
3245 .collect();
3246 for (name, mut constraint) in constraints_to_move {
3247 self.snapshot_constraint(&rename.old_id, &name);
3248 self.snapshot_constraint(&rename.new_id, &name);
3249 self.local
3250 .constraints
3251 .remove(&(rename.old_id.clone(), name.clone()));
3252 constraint.table_id = rename.new_id.clone();
3253 self.local
3254 .constraints
3255 .insert((rename.new_id.clone(), name), constraint);
3256 }
3257 self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
3258 .into_iter()
3259 .map(|(table, name)| {
3260 if table == rename.old_id {
3261 (rename.new_id.clone(), name)
3262 } else {
3263 (table, name)
3264 }
3265 })
3266 .collect();
3267 self.local.graph.edges.push(DependencyEdge::new(
3268 rename.old_id.clone(),
3269 rename.new_id.clone(),
3270 DependencyKind::RenameTo,
3271 ));
3272 self.local
3273 .graph
3274 .propagate_rename(&rename.old_id, &rename.new_id);
3275
3276 if renames_relation {
3277 if self.baseline_relations.remove(&rename.old_id) {
3278 self.baseline_relations.insert(rename.new_id.clone());
3279 }
3280 if self.baseline_fk_dependencies.remove(&rename.old_id) {
3281 self.baseline_fk_dependencies.insert(rename.new_id.clone());
3282 }
3283 self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
3284 .into_iter()
3285 .map(|(table, name)| {
3286 if table == rename.old_id {
3287 (rename.new_id.clone(), name)
3288 } else {
3289 (table, name)
3290 }
3291 })
3292 .collect();
3293 }
3294 if renames_index && self.baseline_indexes.remove(&rename.old_id) {
3295 self.baseline_indexes.insert(rename.new_id.clone());
3296 }
3297
3298 MutationResult::Applied
3299 }
3300 Mutation::DropView(drop_view) => {
3301 for id in &drop_view.ids {
3302 self.snapshot_relation(id);
3303 self.local
3304 .relations
3305 .insert(id.clone(), RelationOverlay::Dropped);
3306 }
3307 self.snapshot_graph_full();
3308 self.local.graph.edges.retain(|e| {
3309 !(matches!(e.kind, DependencyKind::ViewDependency { .. })
3310 && drop_view.ids.contains(&e.dependent))
3311 });
3312 MutationResult::Applied
3313 }
3314 Mutation::DropMaterializedView(drop_mv) => {
3315 for id in &drop_mv.ids {
3316 self.snapshot_relation(id);
3317 self.local
3318 .relations
3319 .insert(id.clone(), RelationOverlay::Dropped);
3320 }
3321 self.snapshot_graph_full();
3322 self.local.graph.edges.retain(|e| {
3323 !((matches!(e.kind, DependencyKind::ViewDependency { .. })
3324 && drop_mv.ids.contains(&e.dependent))
3325 || (matches!(e.kind, DependencyKind::IndexOnRelation { .. })
3326 && drop_mv.ids.contains(&e.referenced)))
3327 });
3328 MutationResult::Applied
3329 }
3330 Mutation::DropIndex(drop_idx) => {
3331 self.snapshot_graph();
3332 self.local.graph.edges.retain(|e| {
3333 !(matches!(e.kind, DependencyKind::IndexOnRelation { .. })
3334 && e.dependent == drop_idx.id)
3335 });
3336 MutationResult::Applied
3337 }
3338 Mutation::ChangeRelationOwner { id, new_owner } => {
3339 let Some((owner, known)) = self.role_fact_identity(new_owner) else {
3340 self.snapshot_confidence();
3341 self.local.confidence = Confidence::Tainted;
3342 return MutationResult::Skipped;
3343 };
3344 if !known {
3345 self.snapshot_confidence();
3346 self.local.confidence = Confidence::Tainted;
3347 }
3348 self.snapshot_relation(id);
3349 if let Some(RelationOverlay::Present(relation)) = self.local.relations.get_mut(id) {
3350 relation.owner = ObjectId::new("", owner);
3351 MutationResult::Applied
3352 } else {
3353 MutationResult::Conflict {
3354 reason: format!("relation '{}' does not exist", id),
3355 }
3356 }
3357 }
3358 Mutation::SearchPath(sp) => {
3359 self.snapshot_search_path();
3360 self.snapshot_confidence();
3361 match &sp.target {
3362 SearchPathTarget::Default => {
3363 self.local.search_path_template =
3364 self.local.default_search_path_template.clone();
3365 self.refresh_role_sensitive_search_path();
3366 }
3367 SearchPathTarget::Schemas(schemas) => {
3368 self.local.search_path_template = schemas.clone();
3369 self.refresh_role_sensitive_search_path();
3370 }
3371 }
3372 MutationResult::Applied
3373 }
3374 Mutation::SwitchRole {
3375 role,
3376 local,
3377 is_session_auth,
3378 } => {
3379 if *local && self.local.transactions.is_empty() {
3380 return MutationResult::Skipped;
3382 }
3383
3384 let target = if let Some(role) = role {
3385 let Some(identity) = self.role_fact_identity(role) else {
3386 self.snapshot_confidence();
3387 self.local.confidence = Confidence::Tainted;
3388 return MutationResult::Skipped;
3389 };
3390 Some(identity)
3391 } else if *is_session_auth {
3392 Some((
3393 self.local.authenticated_role.clone(),
3394 self.local.authenticated_role_known,
3395 ))
3396 } else {
3397 Some((
3398 self.local.session_role.clone(),
3399 self.local.session_role_known,
3400 ))
3401 };
3402 let (target_name, target_known) = target.expect("role reset always has a target");
3403 let persistent_role_reset_target = if role.is_none() && !*is_session_auth {
3404 Some((
3405 self.local.persistent_session_role.clone(),
3406 self.local.persistent_session_role_known,
3407 ))
3408 } else {
3409 None
3410 };
3411
3412 let authorized = if role.is_none() {
3413 Some(true)
3414 } else if *is_session_auth {
3415 self.can_set_session_authorization_to(&target_name)
3416 } else {
3417 self.can_set_role_to(&target_name)
3418 };
3419 match authorized {
3420 Some(false) => {
3421 return MutationResult::Conflict {
3422 reason: if self.present_role(&target_name).is_none() {
3423 format!("role '{}' does not exist", target_name)
3424 } else {
3425 format!("permission denied to set role '{}'", target_name)
3426 },
3427 };
3428 }
3429 None => {
3430 self.snapshot_confidence();
3431 self.local.confidence = Confidence::Tainted;
3432 }
3433 Some(true) => {}
3434 }
3435
3436 self.snapshot_role_context();
3437 self.snapshot_search_path();
3438 self.snapshot_confidence();
3439 if *is_session_auth {
3440 self.local.session_role = target_name.clone();
3441 self.local.session_role_known = target_known;
3442 self.local.current_role = target_name.clone();
3443 self.local.current_role_known = target_known;
3444 if !local {
3445 self.local.persistent_session_role = target_name.clone();
3446 self.local.persistent_session_role_known = target_known;
3447 self.local.persistent_current_role = target_name;
3448 self.local.persistent_current_role_known = target_known;
3449 }
3450 } else {
3451 self.local.current_role = target_name.clone();
3452 self.local.current_role_known = target_known;
3453 if !local {
3454 let (persistent_name, persistent_known) =
3455 persistent_role_reset_target.unwrap_or((target_name, target_known));
3456 self.local.persistent_current_role = persistent_name;
3457 self.local.persistent_current_role_known = persistent_known;
3458 }
3459 }
3460 self.refresh_role_sensitive_search_path();
3461 MutationResult::Applied
3462 }
3463 Mutation::BeginTransaction => {
3464 if self.local.transactions.is_empty() {
3465 self.local.transactions.push(TransactionFrame::root());
3466 MutationResult::Applied
3467 } else {
3468 MutationResult::Skipped
3471 }
3472 }
3473 Mutation::CommitTransaction => {
3474 if self.local.transaction_aborted {
3475 while let Some(frame) = self.local.transactions.pop() {
3476 self.rollback_frame(frame);
3477 }
3478 } else {
3479 while self.local.transactions.pop().is_some() {}
3480 self.restore_persistent_role_context();
3481 }
3482 self.local.transaction_aborted = false;
3483 MutationResult::Applied
3484 }
3485 Mutation::CommitAndChain => {
3486 if self.local.transactions.is_empty() {
3487 self.local.confidence = Confidence::Tainted;
3488 return MutationResult::Conflict {
3489 reason: "COMMIT AND CHAIN can only be used in transaction blocks"
3490 .to_string(),
3491 };
3492 }
3493 if self.local.transaction_aborted {
3494 while let Some(frame) = self.local.transactions.pop() {
3495 self.rollback_frame(frame);
3496 }
3497 } else {
3498 while self.local.transactions.pop().is_some() {}
3499 self.restore_persistent_role_context();
3500 }
3501 self.local.transaction_aborted = false;
3502 self.local.transactions.push(TransactionFrame::root());
3503 MutationResult::Applied
3504 }
3505 Mutation::RollbackTransaction => {
3506 while let Some(frame) = self.local.transactions.pop() {
3507 self.rollback_frame(frame);
3508 }
3509 self.local.transaction_aborted = false;
3510 MutationResult::Applied
3511 }
3512 Mutation::RollbackAndChain => {
3513 if self.local.transactions.is_empty() {
3514 self.local.confidence = Confidence::Tainted;
3515 return MutationResult::Conflict {
3516 reason: "ROLLBACK AND CHAIN can only be used in transaction blocks"
3517 .to_string(),
3518 };
3519 }
3520 while let Some(frame) = self.local.transactions.pop() {
3521 self.rollback_frame(frame);
3522 }
3523 self.local.transaction_aborted = false;
3524 self.local.transactions.push(TransactionFrame::root());
3525 MutationResult::Applied
3526 }
3527 Mutation::RollbackToSavepoint(rts) => {
3528 let Some(position) = self
3529 .local
3530 .transactions
3531 .iter()
3532 .rposition(|frame| frame.is_named_savepoint(&rts.name))
3533 else {
3534 self.local.confidence = Confidence::Tainted;
3535 if !self.local.transactions.is_empty() {
3536 self.local.transaction_aborted = true;
3537 }
3538 return MutationResult::Conflict {
3539 reason: format!("savepoint '{}' does not exist", rts.name),
3540 };
3541 };
3542 let rolled_back = self.local.transactions.split_off(position + 1);
3543 for frame in rolled_back.into_iter().rev() {
3547 self.rollback_frame(frame);
3548 }
3549 let undo_log = std::mem::take(&mut self.local.transactions[position].undo_log);
3550 self.rollback_undo_log(undo_log);
3551 self.local.transaction_aborted = false;
3552 MutationResult::Applied
3553 }
3554 Mutation::Savepoint(sp) => {
3555 if self.local.transactions.is_empty() {
3556 self.local.confidence = Confidence::Tainted;
3557 return MutationResult::Conflict {
3558 reason: "SAVEPOINT can only be used in transaction blocks".to_string(),
3559 };
3560 }
3561 self.local
3562 .transactions
3563 .push(TransactionFrame::savepoint(sp.name.clone()));
3564 MutationResult::Applied
3565 }
3566 Mutation::ReleaseSavepoint(rsp) => {
3567 let Some(position) = self
3568 .local
3569 .transactions
3570 .iter()
3571 .rposition(|frame| frame.is_named_savepoint(&rsp.name))
3572 else {
3573 self.local.confidence = Confidence::Tainted;
3574 if !self.local.transactions.is_empty() {
3575 self.local.transaction_aborted = true;
3576 }
3577 return MutationResult::Conflict {
3578 reason: format!("savepoint '{}' does not exist", rsp.name),
3579 };
3580 };
3581 if position == 0 {
3582 self.local.confidence = Confidence::Tainted;
3583 return MutationResult::Conflict {
3584 reason: format!("savepoint '{}' is not inside a transaction", rsp.name),
3585 };
3586 }
3587
3588 let released = self.local.transactions.split_off(position);
3589 let outer = self
3590 .local
3591 .transactions
3592 .last_mut()
3593 .expect("a released savepoint always has an outer transaction frame");
3594 for frame in released {
3595 outer.undo_log.extend(frame.undo_log);
3596 }
3597 MutationResult::Applied
3598 }
3599 Mutation::Opaque(_) => {
3600 self.snapshot_confidence();
3601 self.local.confidence = Confidence::Tainted;
3602 MutationResult::Applied
3603 }
3604 Mutation::CreateFunction(f) => {
3605 if matches!(
3606 self.local.functions.get(&f.id),
3607 Some(crate::model::function::FunctionOverlay::Present(_))
3608 ) && !f.or_replace
3609 {
3610 return MutationResult::Conflict {
3611 reason: format!("routine '{}' already exists", f.id),
3612 };
3613 }
3614 self.snapshot_function(&f.id);
3615 self.snapshot_generation_counter();
3616 self.local.generation_counter += 1;
3617 let _generation = self.local.generation_counter;
3618
3619 let volatility = f
3620 .options
3621 .iter()
3622 .find_map(|opt| {
3623 if let crate::analysis::facts::FuncOptionFact::Volatility(v) = opt {
3624 Some(match v {
3625 crate::analysis::facts::VolatilityKind::Volatile => {
3626 crate::model::function::Volatility::Volatile
3627 }
3628 crate::analysis::facts::VolatilityKind::Stable => {
3629 crate::model::function::Volatility::Stable
3630 }
3631 crate::analysis::facts::VolatilityKind::Immutable => {
3632 crate::model::function::Volatility::Immutable
3633 }
3634 })
3635 } else {
3636 None
3637 }
3638 })
3639 .unwrap_or(crate::model::function::Volatility::Volatile);
3640
3641 let security = f
3642 .options
3643 .iter()
3644 .find_map(|opt| {
3645 if let crate::analysis::facts::FuncOptionFact::Security(s) = opt {
3646 Some(match s {
3647 crate::analysis::facts::SecurityKind::Invoker => {
3648 crate::model::function::SecurityMode::Invoker
3649 }
3650 crate::analysis::facts::SecurityKind::Definer => {
3651 crate::model::function::SecurityMode::Definer
3652 }
3653 })
3654 } else {
3655 None
3656 }
3657 })
3658 .unwrap_or(crate::model::function::SecurityMode::Invoker);
3659
3660 let language = f
3661 .options
3662 .iter()
3663 .find_map(|opt| {
3664 if let crate::analysis::facts::FuncOptionFact::Language(l) = opt {
3665 Some(l.clone())
3666 } else {
3667 None
3668 }
3669 })
3670 .unwrap_or_else(|| "sql".to_string());
3671
3672 self.local.functions.insert(
3673 f.id.clone(),
3674 crate::model::function::FunctionOverlay::Present(
3675 crate::model::function::FunctionState {
3676 id: f.id.clone(),
3677 arg_types: f.params.iter().map(|p| p.ty.clone()).collect(),
3678 return_type: f
3679 .return_type
3680 .as_ref()
3681 .map(|rt| format!("{:?}", rt))
3682 .unwrap_or_default(),
3683 volatility,
3684 language,
3685 security,
3686 },
3687 ),
3688 );
3689 MutationResult::Applied
3690 }
3691 Mutation::AlterFunction(f) => {
3692 use crate::analysis::facts::{AlterFunctionAction, FuncOptionFact};
3693 use crate::model::function::{FunctionOverlay, SecurityMode, Volatility};
3694
3695 match &f.action {
3696 AlterFunctionAction::OptionsChange(options) => {
3697 self.snapshot_function(&f.id);
3698 if let Some(FunctionOverlay::Present(function)) =
3699 self.local.functions.get_mut(&f.id)
3700 {
3701 for option in options {
3702 match option {
3703 FuncOptionFact::Volatility(volatility) => {
3704 function.volatility = match volatility {
3705 crate::analysis::facts::VolatilityKind::Volatile => {
3706 Volatility::Volatile
3707 }
3708 crate::analysis::facts::VolatilityKind::Stable => {
3709 Volatility::Stable
3710 }
3711 crate::analysis::facts::VolatilityKind::Immutable => {
3712 Volatility::Immutable
3713 }
3714 };
3715 }
3716 FuncOptionFact::Security(security) => {
3717 function.security = match security {
3718 crate::analysis::facts::SecurityKind::Invoker => {
3719 SecurityMode::Invoker
3720 }
3721 crate::analysis::facts::SecurityKind::Definer => {
3722 SecurityMode::Definer
3723 }
3724 };
3725 }
3726 FuncOptionFact::Language(language) => {
3727 function.language = language.clone();
3728 }
3729 _ => {}
3730 }
3731 }
3732 }
3733 }
3734 AlterFunctionAction::Rename { to, .. } => {
3735 let signature =
3736 f.id.name
3737 .find('(')
3738 .map(|index| &f.id.name[index..])
3739 .unwrap_or("");
3740 let new_id = ObjectId::new(f.id.schema.clone(), format!("{to}{signature}"));
3741 self.move_function(&f.id, &new_id);
3742 }
3743 AlterFunctionAction::SchemaChange { new_schema } => {
3744 let new_id = ObjectId::new(new_schema.clone(), f.id.name.clone());
3745 self.move_function(&f.id, &new_id);
3746 }
3747 AlterFunctionAction::OwnerChange(_)
3748 | AlterFunctionAction::DependsOnExtension { .. }
3749 | AlterFunctionAction::NoDependsOnExtension { .. } => {
3750 self.snapshot_function(&f.id);
3751 }
3752 }
3753 MutationResult::Applied
3754 }
3755 Mutation::DropFunction(f) => {
3756 let mut any_applied = false;
3757 for sig in &f.signatures {
3758 let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
3759 let schema = self.resolve_function_schema(&sig.name, &sig_str);
3760 let id = ObjectId::new(schema, sig_str);
3761 if !matches!(
3762 self.local.functions.get(&id),
3763 Some(crate::model::function::FunctionOverlay::Present(_))
3764 ) {
3765 if !f.if_exists {
3766 return MutationResult::Conflict {
3767 reason: format!("function '{}' does not exist", id),
3768 };
3769 }
3770 } else {
3771 let dependent_triggers: Vec<(ObjectId, ObjectId)> = self
3772 .local
3773 .graph
3774 .edges
3775 .iter()
3776 .filter_map(|edge| {
3777 let DependencyKind::TriggerOnTable { function_id, .. } = &edge.kind
3778 else {
3779 return None;
3780 };
3781 (function_id == &id)
3782 .then(|| (edge.dependent.clone(), edge.referenced.clone()))
3783 })
3784 .collect();
3785 if !dependent_triggers.is_empty() && !f.cascade {
3786 return MutationResult::Conflict {
3787 reason: format!(
3788 "function '{}' still has dependent triggers; use CASCADE",
3789 id
3790 ),
3791 };
3792 }
3793
3794 any_applied = true;
3795 self.snapshot_function(&id);
3796 self.local
3797 .functions
3798 .insert(id.clone(), crate::model::function::FunctionOverlay::Dropped);
3799
3800 if f.cascade {
3801 for (trigger_id, table_id) in &dependent_triggers {
3802 let trigger_name =
3803 self.local.triggers.get(trigger_id).and_then(|overlay| {
3804 match overlay {
3805 TriggerOverlay::Present(trigger) => {
3806 Some(trigger.name.clone())
3807 }
3808 TriggerOverlay::Dropped => None,
3809 }
3810 });
3811 self.snapshot_trigger(trigger_id);
3812 self.local
3813 .triggers
3814 .insert(trigger_id.clone(), TriggerOverlay::Dropped);
3815 self.snapshot_relation(table_id);
3816 if let Some(RelationOverlay::Present(relation)) =
3817 self.local.relations.get_mut(table_id)
3818 {
3819 if let Some(trigger_name) = trigger_name {
3820 relation.triggers.remove(&trigger_name);
3821 }
3822 }
3823 }
3824 if !dependent_triggers.is_empty() {
3825 self.snapshot_graph_full();
3826 self.local.graph.edges.retain(|edge| {
3827 !dependent_triggers
3828 .iter()
3829 .any(|(trigger_id, _)| edge.dependent == *trigger_id)
3830 });
3831 }
3832 }
3833 }
3834 }
3835 if any_applied {
3836 MutationResult::Applied
3837 } else {
3838 MutationResult::Skipped
3839 }
3840 }
3841 Mutation::CreateProcedure(p) => {
3842 if matches!(
3843 self.local.functions.get(&p.id),
3844 Some(crate::model::function::FunctionOverlay::Present(_))
3845 ) && !p.or_replace
3846 {
3847 return MutationResult::Conflict {
3848 reason: format!("routine '{}' already exists", p.id),
3849 };
3850 }
3851 self.snapshot_function(&p.id);
3852 self.snapshot_generation_counter();
3853 self.local.generation_counter += 1;
3854 let _generation = self.local.generation_counter;
3855
3856 self.local.functions.insert(
3857 p.id.clone(),
3858 crate::model::function::FunctionOverlay::Present(
3859 crate::model::function::FunctionState {
3860 id: p.id.clone(),
3861 arg_types: p.params.iter().map(|p| p.ty.clone()).collect(),
3862 return_type: "void".to_string(),
3863 volatility: crate::model::function::Volatility::Volatile,
3864 language: "sql".to_string(),
3865 security: crate::model::function::SecurityMode::Invoker,
3866 },
3867 ),
3868 );
3869 MutationResult::Applied
3870 }
3871 Mutation::AlterProcedure(p) => {
3872 self.snapshot_function(&p.id);
3873 MutationResult::Applied
3875 }
3876 Mutation::DropProcedure(p) => {
3877 let mut any_applied = false;
3878 for sig in &p.signatures {
3879 let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
3880 let schema = self.resolve_function_schema(&sig.name, &sig_str);
3881 let id = ObjectId::new(schema, sig_str);
3882 if !matches!(
3883 self.local.functions.get(&id),
3884 Some(crate::model::function::FunctionOverlay::Present(_))
3885 ) {
3886 if !p.if_exists {
3887 return MutationResult::Conflict {
3888 reason: format!("procedure '{}' does not exist", id),
3889 };
3890 }
3891 } else {
3892 any_applied = true;
3893 self.snapshot_function(&id);
3894 self.local
3895 .functions
3896 .insert(id, crate::model::function::FunctionOverlay::Dropped);
3897 }
3898 }
3899 if any_applied {
3900 MutationResult::Applied
3901 } else {
3902 MutationResult::Skipped
3903 }
3904 }
3905 Mutation::CreatePublication(p) => {
3906 self.snapshot_publication(&p.name);
3907 self.snapshot_generation_counter();
3908 self.local.generation_counter += 1;
3909 let generation = self.local.generation_counter;
3910
3911 self.local.publications.insert(
3912 p.name.clone(),
3913 crate::model::replication::PublicationOverlay::Present(
3914 crate::model::replication::PublicationState {
3915 name: p.name.clone(),
3916 scope: p.scope.clone(),
3917 params: p.params.clone(),
3918 generation,
3919 },
3920 ),
3921 );
3922
3923 if let crate::analysis::facts::PublicationScope::Explicit(objects) = &p.scope {
3924 self.snapshot_graph_full();
3925 for obj in objects {
3926 if let crate::analysis::facts::PublicationObjectFact::Table {
3927 name, ..
3928 } = obj
3929 {
3930 let table_id = self.resolve_relation_id(name);
3931 self.local.graph.edges.push(DependencyEdge::new(
3932 table_id,
3933 ObjectId::new("public", &p.name),
3934 DependencyKind::PublicationIncludes {
3935 publication_name: p.name.clone(),
3936 },
3937 ));
3938 }
3939 }
3940 }
3941 MutationResult::Applied
3942 }
3943 Mutation::AlterPublication(p) => {
3944 self.snapshot_publication(&p.name);
3945 if !self.local.publications.contains_key(&p.name) {
3946 self.local.confidence = Confidence::Tainted;
3947 return MutationResult::Skipped;
3948 }
3949 self.snapshot_generation_counter();
3950 self.local.generation_counter += 1;
3951 let new_gen = self.local.generation_counter;
3952
3953 if let Some(crate::model::replication::PublicationOverlay::Present(publ)) =
3954 self.local.publications.get_mut(&p.name)
3955 {
3956 publ.generation = new_gen;
3957 }
3958 MutationResult::Applied
3959 }
3960 Mutation::DropPublication(p) => {
3961 for name in &p.names {
3962 self.snapshot_publication(name);
3963 if !p.if_exists && !self.local.publications.contains_key(name) {
3964 self.local.confidence = Confidence::Tainted;
3965 return MutationResult::Skipped;
3966 }
3967 self.local.publications.insert(
3968 name.clone(),
3969 crate::model::replication::PublicationOverlay::Dropped,
3970 );
3971 }
3972 self.snapshot_graph_full();
3973 self.local.graph.edges.retain(|e| {
3974 !(matches!(e.kind, DependencyKind::PublicationIncludes { .. })
3975 && p.names.contains(&e.referenced.name))
3976 });
3977 MutationResult::Applied
3978 }
3979 Mutation::CreateSubscription(s) => {
3980 let name = s.name.clone().unwrap_or_else(|| "unnamed_sub".into());
3981 self.snapshot_subscription(&name);
3982 self.snapshot_generation_counter();
3983 self.local.generation_counter += 1;
3984 let generation = self.local.generation_counter;
3985
3986 self.local.subscriptions.insert(
3987 name.clone(),
3988 crate::model::replication::SubscriptionOverlay::Present(
3989 crate::model::replication::SubscriptionState {
3990 name,
3991 connection: s.connection.clone(),
3992 publications: s.publications.clone(),
3993 params: s.params.clone(),
3994 generation,
3995 },
3996 ),
3997 );
3998 MutationResult::Applied
3999 }
4000 Mutation::AlterSubscription(s) => {
4001 self.snapshot_subscription(&s.name);
4002 if !self.local.subscriptions.contains_key(&s.name) {
4003 self.local.confidence = Confidence::Tainted;
4004 return MutationResult::Skipped;
4005 }
4006 self.snapshot_generation_counter();
4007 self.local.generation_counter += 1;
4008 let new_gen = self.local.generation_counter;
4009
4010 if let Some(crate::model::replication::SubscriptionOverlay::Present(sub)) =
4011 self.local.subscriptions.get_mut(&s.name)
4012 {
4013 sub.generation = new_gen;
4014 }
4015 MutationResult::Applied
4016 }
4017 Mutation::DropSubscription(s) => {
4018 self.snapshot_subscription(&s.name);
4019 if !s.if_exists && !self.local.subscriptions.contains_key(&s.name) {
4020 self.local.confidence = Confidence::Tainted;
4021 return MutationResult::Skipped;
4022 }
4023 self.local.subscriptions.insert(
4024 s.name.clone(),
4025 crate::model::replication::SubscriptionOverlay::Dropped,
4026 );
4027 MutationResult::Applied
4028 }
4029 Mutation::CreateRole(r) => {
4030 let role_id = ObjectId::new("", &r.name);
4031 if matches!(
4032 self.local.roles.get(&role_id),
4033 Some(crate::model::role::RoleOverlay::Present(_))
4034 ) {
4035 return MutationResult::Conflict {
4036 reason: format!("role '{}' already exists", r.name),
4037 };
4038 }
4039 self.snapshot_role(&role_id);
4040 self.snapshot_generation_counter();
4041 self.local.generation_counter += 1;
4042 let _generation = self.local.generation_counter;
4043
4044 self.local.roles.insert(
4045 role_id.clone(),
4046 crate::model::role::RoleOverlay::Present(crate::model::role::RoleState {
4047 id: role_id,
4048 can_login: true,
4049 is_superuser: false,
4050 member_of: Vec::new(),
4051 can_set_role_to: Vec::new(),
4052 granted_privileges: Vec::new(),
4053 }),
4054 );
4055 MutationResult::Applied
4056 }
4057 Mutation::AlterRole(r) => {
4058 if let Some(role_id) = Self::resolve_role_name(
4059 &r.name,
4060 &self.local.current_role,
4061 &self.local.session_role,
4062 ) {
4063 self.snapshot_role(&role_id);
4064 if !self.local.roles.contains_key(&role_id) {
4065 self.local.confidence = Confidence::Tainted;
4066 return MutationResult::Skipped;
4067 }
4068 self.snapshot_generation_counter();
4069 self.local.generation_counter += 1;
4070 let _new_gen = self.local.generation_counter;
4071
4072 if let Some(crate::model::role::RoleOverlay::Present(_role)) =
4073 self.local.roles.get_mut(&role_id)
4074 {
4075 }
4077 MutationResult::Applied
4078 } else {
4079 MutationResult::Skipped
4080 }
4081 }
4082 Mutation::DropRole(r) => {
4083 for name in &r.names {
4084 if let Some(role_id) = Self::resolve_role_name(
4085 &crate::analysis::facts::RoleFact::Named {
4086 name: name.clone(),
4087 via_legacy_group_syntax: false,
4088 },
4089 &self.local.current_role,
4090 &self.local.session_role,
4091 ) {
4092 self.snapshot_role(&role_id);
4093 if !r.if_exists
4094 && !matches!(
4095 self.local.roles.get(&role_id),
4096 Some(crate::model::role::RoleOverlay::Present(_))
4097 )
4098 {
4099 return MutationResult::Conflict {
4100 reason: format!("role '{}' does not exist", name),
4101 };
4102 }
4103 self.local
4104 .roles
4105 .insert(role_id, crate::model::role::RoleOverlay::Dropped);
4106 }
4107 }
4108 MutationResult::Applied
4109 }
4110 Mutation::Grant(grant) => {
4111 let privileges = Self::resolve_grant_privileges(&grant.privileges);
4112 let grantees = &grant.grantees;
4113 match &grant.target {
4114 crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
4115 for id in ids {
4116 self.apply_grant_to_relation(id, &privileges, grantees);
4117 }
4118 }
4119 crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
4120 let target_ids: Vec<ObjectId> = self
4121 .local
4122 .relations
4123 .keys()
4124 .filter(|id| schemas.contains(&id.schema))
4125 .cloned()
4126 .collect();
4127 for id in &target_ids {
4128 self.apply_grant_to_relation(id, &privileges, grantees);
4129 }
4130 }
4131 }
4132 MutationResult::Applied
4133 }
4134 Mutation::Revoke(revoke) => {
4135 let privileges = Self::resolve_grant_privileges(&revoke.privileges);
4136 let revokees = &revoke.revokees;
4137 match &revoke.target {
4138 crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
4139 for id in ids {
4140 self.apply_revoke_to_relation(id, &privileges, revokees);
4141 }
4142 }
4143 crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
4144 let target_ids: Vec<ObjectId> = self
4145 .local
4146 .relations
4147 .keys()
4148 .filter(|id| schemas.contains(&id.schema))
4149 .cloned()
4150 .collect();
4151 for id in &target_ids {
4152 self.apply_revoke_to_relation(id, &privileges, revokees);
4153 }
4154 }
4155 }
4156 MutationResult::Applied
4157 }
4158 Mutation::CreateDatabase(_) => MutationResult::Applied,
4159 Mutation::AlterDatabase(_) => MutationResult::Applied,
4160 Mutation::DropDatabase(_) => MutationResult::Applied,
4161 Mutation::Vacuum { .. } => MutationResult::Applied,
4162 }
4163 }
4164
4165 fn snapshot_relation(&mut self, id: &ObjectId) {
4166 if let Some(frame) = self.local.transactions.last_mut() {
4167 let previous = self.local.relations.get(id).cloned();
4168 frame.undo_log.push(StateChange::RelationSnapshot {
4169 id: id.clone(),
4170 previous: Box::new(previous),
4171 });
4172 }
4173 }
4174
4175 fn snapshot_schema(&mut self, name: &str) {
4176 if let Some(frame) = self.local.transactions.last_mut() {
4177 frame.undo_log.push(StateChange::SchemaSnapshot {
4178 name: name.to_string(),
4179 previous: self.local.schemas.get(name).cloned(),
4180 });
4181 }
4182 }
4183
4184 fn snapshot_namespace(&mut self) {
4185 if let Some(frame) = self.local.transactions.last_mut() {
4186 frame.undo_log.push(StateChange::NamespaceSnapshot(Box::new(
4187 NamespaceSnapshot {
4188 schemas: self.local.schemas.clone(),
4189 relations: self.local.relations.clone(),
4190 types: self.local.types.clone(),
4191 functions: self.local.functions.clone(),
4192 sequences: self.local.sequences.clone(),
4193 publications: self.local.publications.clone(),
4194 triggers: self.local.triggers.clone(),
4195 constraints: self.local.constraints.clone(),
4196 graph: self.local.graph.edges.clone(),
4197 pending_validation: self.local.pending_validation.clone(),
4198 baseline_relations: self.baseline_relations.clone(),
4199 baseline_indexes: self.baseline_indexes.clone(),
4200 baseline_foreign_keys: self.baseline_foreign_keys.clone(),
4201 baseline_fk_dependencies: self.baseline_fk_dependencies.clone(),
4202 baseline_sequences: self.baseline_sequences.clone(),
4203 },
4204 )));
4205 }
4206 }
4207
4208 fn snapshot_type(&mut self, id: &ObjectId) {
4209 if let Some(frame) = self.local.transactions.last_mut() {
4210 let previous = self.local.types.get(id).cloned();
4211 frame.undo_log.push(StateChange::TypeSnapshot {
4212 id: id.clone(),
4213 previous,
4214 });
4215 }
4216 }
4217
4218 fn snapshot_sequence(&mut self, id: &ObjectId) {
4219 if let Some(frame) = self.local.transactions.last_mut() {
4220 let previous = self.local.sequences.get(id).cloned();
4221 frame.undo_log.push(StateChange::SequenceSnapshot {
4222 id: id.clone(),
4223 previous,
4224 });
4225 }
4226 }
4227
4228 fn move_function(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
4229 self.snapshot_function(old_id);
4230 self.snapshot_function(new_id);
4231 if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
4232 self.local.functions.remove(old_id)
4233 {
4234 function.id = new_id.clone();
4235 self.local.functions.insert(
4236 new_id.clone(),
4237 crate::model::function::FunctionOverlay::Present(function),
4238 );
4239 }
4240
4241 self.snapshot_graph_full();
4242 self.local.graph.propagate_rename(old_id, new_id);
4243 self.local.graph.edges.push(DependencyEdge::new(
4244 old_id.clone(),
4245 new_id.clone(),
4246 DependencyKind::RenameTo,
4247 ));
4248 }
4249
4250 fn snapshot_function(&mut self, id: &ObjectId) {
4251 if let Some(frame) = self.local.transactions.last_mut() {
4252 let previous = self.local.functions.get(id).cloned();
4253 frame.undo_log.push(StateChange::FunctionSnapshot {
4254 id: id.clone(),
4255 previous,
4256 });
4257 }
4258 }
4259
4260 fn snapshot_publication(&mut self, name: &str) {
4261 if let Some(frame) = self.local.transactions.last_mut() {
4262 let previous = self.local.publications.get(name).cloned();
4263 frame.undo_log.push(StateChange::PublicationSnapshot {
4264 id: ObjectId::new("", name),
4265 previous,
4266 });
4267 }
4268 }
4269
4270 fn snapshot_subscription(&mut self, name: &str) {
4271 if let Some(frame) = self.local.transactions.last_mut() {
4272 let previous = self.local.subscriptions.get(name).cloned();
4273 frame.undo_log.push(StateChange::SubscriptionSnapshot {
4274 id: ObjectId::new("", name),
4275 previous,
4276 });
4277 }
4278 }
4279
4280 fn snapshot_role(&mut self, id: &ObjectId) {
4281 if let Some(frame) = self.local.transactions.last_mut() {
4282 let previous = self.local.roles.get(id).cloned();
4283 frame.undo_log.push(StateChange::RoleSnapshot {
4284 id: id.clone(),
4285 previous,
4286 });
4287 }
4288 }
4289
4290 fn snapshot_trigger(&mut self, id: &ObjectId) {
4291 if let Some(frame) = self.local.transactions.last_mut() {
4292 let previous = self.local.triggers.get(id).cloned();
4293 frame.undo_log.push(StateChange::TriggerSnapshot {
4294 id: id.clone(),
4295 previous,
4296 });
4297 }
4298 }
4299
4300 fn snapshot_constraint(&mut self, table_id: &ObjectId, name: &str) {
4301 if let Some(frame) = self.local.transactions.last_mut() {
4302 let key = (table_id.clone(), name.to_string());
4303 let previous = self.local.constraints.get(&key).cloned();
4304 frame.undo_log.push(StateChange::ConstraintSnapshot {
4305 table_id: table_id.clone(),
4306 name: name.to_string(),
4307 previous,
4308 });
4309 }
4310 }
4311
4312 fn snapshot_role_context(&mut self) {
4313 if let Some(frame) = self.local.transactions.last_mut() {
4314 frame.undo_log.push(StateChange::RoleContextSnapshot {
4315 current_role: self.local.current_role.clone(),
4316 current_role_known: self.local.current_role_known,
4317 persistent_current_role: self.local.persistent_current_role.clone(),
4318 persistent_current_role_known: self.local.persistent_current_role_known,
4319 session_role: self.local.session_role.clone(),
4320 session_role_known: self.local.session_role_known,
4321 persistent_session_role: self.local.persistent_session_role.clone(),
4322 persistent_session_role_known: self.local.persistent_session_role_known,
4323 });
4324 }
4325 }
4326
4327 fn snapshot_search_path(&mut self) {
4328 if let Some(frame) = self.local.transactions.last_mut() {
4329 frame.undo_log.push(StateChange::SearchPathSnapshot {
4330 previous: self.local.search_path.clone(),
4331 previous_template: self.local.search_path_template.clone(),
4332 });
4333 }
4334 }
4335
4336 fn snapshot_generation_counter(&mut self) {
4337 if let Some(frame) = self.local.transactions.last_mut() {
4338 frame.undo_log.push(StateChange::GenerationCounterSnapshot {
4339 previous: self.local.generation_counter,
4340 });
4341 }
4342 }
4343
4344 #[allow(dead_code)]
4345 fn snapshot_pending_validation(&mut self) {
4346 if let Some(frame) = self.local.transactions.last_mut() {
4347 frame.undo_log.push(StateChange::PendingValidationSnapshot {
4348 previous: self.local.pending_validation.clone(),
4349 });
4350 }
4351 }
4352
4353 fn snapshot_confidence(&mut self) {
4354 if let Some(frame) = self.local.transactions.last_mut() {
4355 frame.undo_log.push(StateChange::ConfidenceSnapshot {
4356 previous: self.local.confidence.clone(),
4357 });
4358 }
4359 }
4360
4361 fn snapshot_graph(&mut self) {
4362 if let Some(frame) = self.local.transactions.last_mut() {
4363 frame.undo_log.push(StateChange::GraphLengthMarker {
4364 len: self.local.graph.edges.len(),
4365 });
4366 }
4367 }
4368
4369 fn snapshot_graph_full(&mut self) {
4370 if let Some(frame) = self.local.transactions.last_mut() {
4371 frame.undo_log.push(StateChange::GraphSnapshot {
4372 previous: self.local.graph.edges.clone(),
4373 });
4374 }
4375 }
4376
4377 fn rollback_frame(&mut self, mut frame: TransactionFrame) {
4378 self.rollback_undo_log(std::mem::take(&mut frame.undo_log));
4379 }
4380
4381 fn rollback_undo_log(&mut self, mut undo_log: Vec<StateChange>) {
4382 while let Some(change) = undo_log.pop() {
4383 match change {
4384 StateChange::SchemaSnapshot { name, previous } => match previous {
4385 Some(overlay) => {
4386 self.local.schemas.insert(name, overlay);
4387 }
4388 None => {
4389 self.local.schemas.remove(&name);
4390 }
4391 },
4392 StateChange::NamespaceSnapshot(snapshot) => {
4393 self.local.schemas = snapshot.schemas;
4394 self.local.relations = snapshot.relations;
4395 self.local.types = snapshot.types;
4396 self.local.functions = snapshot.functions;
4397 self.local.sequences = snapshot.sequences;
4398 self.local.publications = snapshot.publications;
4399 self.local.triggers = snapshot.triggers;
4400 self.local.constraints = snapshot.constraints;
4401 self.local.graph.edges = snapshot.graph;
4402 self.local.pending_validation = snapshot.pending_validation;
4403 self.baseline_relations = snapshot.baseline_relations;
4404 self.baseline_indexes = snapshot.baseline_indexes;
4405 self.baseline_foreign_keys = snapshot.baseline_foreign_keys;
4406 self.baseline_fk_dependencies = snapshot.baseline_fk_dependencies;
4407 self.baseline_sequences = snapshot.baseline_sequences;
4408 }
4409 StateChange::RelationSnapshot { id, previous } => {
4410 if let Some(prev) = *previous {
4411 self.local.relations.insert(id, prev);
4412 } else {
4413 self.local.relations.remove(&id);
4414 }
4415 }
4416 StateChange::TypeSnapshot { id, previous } => {
4417 if let Some(prev) = previous {
4418 self.local.types.insert(id, prev);
4419 } else {
4420 self.local.types.remove(&id);
4421 }
4422 }
4423 StateChange::SequenceSnapshot { id, previous } => {
4424 if let Some(prev) = previous {
4425 self.local.sequences.insert(id, prev);
4426 } else {
4427 self.local.sequences.remove(&id);
4428 }
4429 }
4430 StateChange::FunctionSnapshot { id, previous } => {
4431 if let Some(prev) = previous {
4432 self.local.functions.insert(id, prev);
4433 } else {
4434 self.local.functions.remove(&id);
4435 }
4436 }
4437 StateChange::PublicationSnapshot { id, previous } => {
4438 if let Some(prev) = previous {
4439 self.local.publications.insert(id.name, prev);
4440 } else {
4441 self.local.publications.remove(&id.name);
4442 }
4443 }
4444 StateChange::SubscriptionSnapshot { id, previous } => {
4445 if let Some(prev) = previous {
4446 self.local.subscriptions.insert(id.name, prev);
4447 } else {
4448 self.local.subscriptions.remove(&id.name);
4449 }
4450 }
4451 StateChange::RoleSnapshot { id, previous } => {
4452 if let Some(prev) = previous {
4453 self.local.roles.insert(id, prev);
4454 } else {
4455 self.local.roles.remove(&id);
4456 }
4457 }
4458 StateChange::TriggerSnapshot { id, previous } => {
4459 if let Some(prev) = previous {
4460 self.local.triggers.insert(id, prev);
4461 } else {
4462 self.local.triggers.remove(&id);
4463 }
4464 }
4465 StateChange::ConstraintSnapshot {
4466 table_id,
4467 name,
4468 previous,
4469 } => {
4470 let key = (table_id, name);
4471 if let Some(previous) = previous {
4472 self.local.constraints.insert(key, previous);
4473 } else {
4474 self.local.constraints.remove(&key);
4475 }
4476 }
4477 StateChange::GraphLengthMarker { len } => {
4478 self.local.graph.edges.truncate(len);
4479 }
4480 StateChange::GraphSnapshot { previous } => {
4481 self.local.graph.edges = previous;
4482 }
4483 StateChange::RoleContextSnapshot {
4484 current_role,
4485 current_role_known,
4486 persistent_current_role,
4487 persistent_current_role_known,
4488 session_role,
4489 session_role_known,
4490 persistent_session_role,
4491 persistent_session_role_known,
4492 } => {
4493 self.local.current_role = current_role;
4494 self.local.current_role_known = current_role_known;
4495 self.local.persistent_current_role = persistent_current_role;
4496 self.local.persistent_current_role_known = persistent_current_role_known;
4497 self.local.session_role = session_role;
4498 self.local.session_role_known = session_role_known;
4499 self.local.persistent_session_role = persistent_session_role;
4500 self.local.persistent_session_role_known = persistent_session_role_known;
4501 }
4502 StateChange::SearchPathSnapshot {
4503 previous,
4504 previous_template,
4505 } => {
4506 self.local.search_path = previous;
4507 self.local.search_path_template = previous_template;
4508 }
4509 StateChange::GenerationCounterSnapshot { previous } => {
4510 self.local.generation_counter = previous;
4511 }
4512 StateChange::PendingValidationSnapshot { previous } => {
4513 self.local.pending_validation = previous;
4514 }
4515 StateChange::ConfidenceSnapshot { previous } => {
4516 self.local.confidence = previous;
4517 }
4518 }
4519 }
4520 }
4521}