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