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