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