1use crate::analysis::graph::{DependencyEdge, DependencyGraph, DependencyKind};
2use crate::analysis::mutations::Mutation;
3use crate::analysis::settings::ScopedSetting;
4use crate::analysis::transaction::{NamespaceSnapshot, StateChange, TransactionFrame};
5use crate::ast::identifiers::ObjectId;
6use crate::db::cache::DbCache;
7use crate::model::constraint::ConstraintState;
8use crate::model::function::FunctionOverlay;
9pub use crate::model::relation::RelationOverlay;
10use crate::model::relation::{Persistence, Privilege, RelationKind};
11use crate::model::schema::SchemaOverlay;
12use crate::model::sequence::SequenceOverlay;
13use crate::model::trigger::TriggerOverlay;
14use crate::model::types::{TypeKind, TypeOverlay, TypeState};
15use std::collections::{HashMap, HashSet};
16use std::hash::Hash;
17
18mod apply_misc;
19mod apply_policy_trigger;
20mod apply_relation;
21mod apply_replication;
22mod apply_role;
23mod apply_routine;
24mod apply_schema;
25mod apply_sequence;
26mod apply_settings;
27mod apply_transaction;
28mod apply_type;
29mod apply_view_index;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum Confidence {
33 Exact,
34 Tainted,
35}
36
37#[derive(Debug, PartialEq, Eq)]
38pub enum MutationResult {
39 Applied,
40 Skipped,
41 NotExecuted,
44 Conflict {
45 reason: String,
46 },
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50enum ObjectLookup {
51 Present,
52 WrongKind,
53 Tombstone,
54 AuthoritativelyAbsent,
55 Unknown,
56}
57
58fn sync_present_map<K, V, O, F>(target: &mut HashMap<K, V>, source: &HashMap<K, O>, present: F)
59where
60 K: Clone + Eq + Hash,
61 V: Clone + PartialEq,
62 F: for<'a> Fn(&'a O) -> Option<&'a V> + Copy,
63{
64 target.retain(|key, value| {
65 let Some(current) = source.get(key).and_then(present) else {
66 return false;
67 };
68 if value != current {
69 value.clone_from(current);
70 }
71 true
72 });
73 target.reserve(source.len().saturating_sub(target.len()));
74 for (key, overlay) in source {
75 if !target.contains_key(key)
76 && let Some(value) = present(overlay)
77 {
78 target.insert(key.clone(), value.clone());
79 }
80 }
81}
82
83#[derive(Debug, Default, Clone)]
84pub struct CascadeResult {
85 pub dropped_relations: HashSet<ObjectId>,
86 pub dropped_indexes: HashSet<ObjectId>,
87 pub dropped_constraints: HashSet<(ObjectId, String)>,
88}
89
90#[derive(Clone)]
91pub struct LocalState {
92 pub schemas: HashMap<String, SchemaOverlay>,
93 pub relations: HashMap<ObjectId, RelationOverlay>,
94 pub types: HashMap<ObjectId, TypeOverlay>,
95 pub functions: HashMap<ObjectId, crate::model::function::FunctionOverlay>,
96 pub sequences: HashMap<ObjectId, SequenceOverlay>,
97 pub publications: HashMap<String, crate::model::replication::PublicationOverlay>,
98 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionOverlay>,
99 pub roles: HashMap<ObjectId, crate::model::role::RoleOverlay>,
100 pub triggers: HashMap<ObjectId, TriggerOverlay>,
101 pub constraints: HashMap<(ObjectId, String), ConstraintState>,
102 pub graph: DependencyGraph,
103 pub search_path: Vec<String>,
104 pub default_search_path: Vec<String>,
105 pub search_path_template: Vec<String>,
106 pub session_search_path_template: Vec<String>,
107 pub default_search_path_template: Vec<String>,
108 pub lock_timeout: ScopedSetting<Option<u64>>,
109 pub statement_timeout: ScopedSetting<Option<u64>>,
110 pub current_role: String,
113 pub current_role_known: bool,
116 pub persistent_current_role: String,
119 pub persistent_current_role_known: bool,
120 pub session_role: String,
124 pub session_role_known: bool,
127 pub persistent_session_role: String,
129 pub persistent_session_role_known: bool,
130 pub authenticated_role: String,
132 pub authenticated_role_known: bool,
133 pub roles_known: bool,
135 pub confidence: Confidence,
136 pub transactions: Vec<TransactionFrame>,
137 pub transaction_aborted: bool,
138 pub pending_validation: HashSet<(ObjectId, String)>,
139 pub generation_counter: u64,
140}
141
142#[derive(Clone, Debug, Default, PartialEq)]
143pub struct PreState {
144 pub relations: HashMap<ObjectId, crate::model::relation::RelationState>,
145 pub functions: HashMap<ObjectId, crate::model::function::FunctionState>,
146 pub roles: HashMap<ObjectId, crate::model::role::RoleState>,
147 pub publications: HashMap<String, crate::model::replication::PublicationState>,
148 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionState>,
149 pub sequences: HashMap<ObjectId, crate::model::sequence::SequenceState>,
150 pub types: HashMap<ObjectId, crate::model::types::TypeState>,
151 pub indexes: Vec<crate::analysis::graph::DependencyEdge>,
152 pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
153}
154
155#[cfg(test)]
156mod pre_state_tests {
157 use super::*;
158
159 #[test]
160 fn incremental_capture_matches_a_fresh_public_pre_state() {
161 let mut state = AnalysisState::new(DbCache::new());
162 let table_id = ObjectId::new("public", "capture_table");
163 state.local.relations.insert(
164 table_id.clone(),
165 RelationOverlay::Present(crate::model::relation::RelationState::new(
166 table_id.clone(),
167 ObjectId::new("", "postgres"),
168 1,
169 Some(1),
170 RelationKind::Table,
171 Persistence::Permanent,
172 0,
173 )),
174 );
175
176 let mut reused = PreState::default();
177 state.capture_pre_state_into(&mut reused);
178 assert_eq!(reused, state.capture_pre_state());
179
180 let Some(RelationOverlay::Present(relation)) = state.local.relations.get_mut(&table_id)
181 else {
182 panic!("test relation must remain present");
183 };
184 relation.estimated_rows = Some(2);
185 let index_id = ObjectId::new("public", "capture_table_idx");
186 state.local.graph.add_edge(DependencyEdge::new(
187 index_id,
188 table_id.clone(),
189 DependencyKind::IndexOnRelation {
190 using_method: Some("btree".into()),
191 has_predicate: false,
192 is_concurrent: false,
193 is_unique: false,
194 eligibility_known: true,
195 },
196 ));
197 state.capture_pre_state_into(&mut reused);
198 assert_eq!(reused, state.capture_pre_state());
199 assert_eq!(reused.relations[&table_id].estimated_rows, Some(2));
200 assert_eq!(reused.indexes.len(), 1);
201
202 state
203 .local
204 .relations
205 .insert(table_id.clone(), RelationOverlay::Dropped);
206 state.local.graph.replace_edges(Vec::new());
207 state.capture_pre_state_into(&mut reused);
208 assert_eq!(reused, state.capture_pre_state());
209 assert!(!reused.relations.contains_key(&table_id));
210 assert!(reused.indexes.is_empty());
211 }
212}
213
214#[derive(Clone)]
215pub struct AnalysisState {
216 pub pg_version_num: Option<u32>,
217 pub baseline_available: bool,
221 pub baseline_schemas: Option<HashSet<String>>,
225 pub baseline_relations: HashSet<ObjectId>,
226 pub baseline_indexes: HashSet<ObjectId>,
227 pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
228 pub baseline_fk_dependencies: HashSet<ObjectId>,
229 pub baseline_sequences: HashSet<ObjectId>,
230 pub local: LocalState,
231}
232
233impl AnalysisState {
234 fn trigger_key(table_id: &ObjectId, name: &str) -> ObjectId {
235 ObjectId::new(&table_id.schema, format!("{}\0{name}", table_id.name))
239 }
240
241 fn publication_object_key(
242 &self,
243 object: &crate::analysis::facts::PublicationObjectFact,
244 ) -> String {
245 match object {
246 crate::analysis::facts::PublicationObjectFact::Table { name, .. } => {
247 format!("table\0{}", self.resolve_relation_id(name))
248 }
249 crate::analysis::facts::PublicationObjectFact::SchemaTables { schema, .. } => {
250 format!("schema\0{schema}")
251 }
252 crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand => {
253 format!(
254 "schema\0{}",
255 self.local
256 .search_path
257 .first()
258 .map(String::as_str)
259 .unwrap_or("public")
260 )
261 }
262 crate::analysis::facts::PublicationObjectFact::Unknown => "unknown".to_string(),
263 }
264 }
265
266 fn replace_publication_edges(
267 &mut self,
268 publication_name: &str,
269 scope: &crate::analysis::facts::PublicationScope,
270 ) {
271 self.snapshot_graph_full();
272 self.local.graph.retain_edges(|edge| {
273 !matches!(
274 &edge.kind,
275 DependencyKind::PublicationIncludes { publication_name: name }
276 if name == publication_name
277 )
278 });
279 if let crate::analysis::facts::PublicationScope::Explicit(objects) = scope {
280 for object in objects {
281 if let crate::analysis::facts::PublicationObjectFact::Table { name, .. } = object {
282 self.local.graph.add_edge(DependencyEdge::new(
283 self.resolve_relation_id(name),
284 ObjectId::new("public", publication_name),
285 DependencyKind::PublicationIncludes {
286 publication_name: publication_name.to_string(),
287 },
288 ));
289 }
290 }
291 }
292 }
293
294 fn validate_publication_scope(
295 &mut self,
296 scope: &crate::analysis::facts::PublicationScope,
297 ) -> Result<(), String> {
298 let crate::analysis::facts::PublicationScope::Explicit(objects) = scope else {
299 return Ok(());
300 };
301 let mut object_keys = HashSet::new();
302 for object in objects {
303 if !object_keys.insert(self.publication_object_key(object)) {
304 return Err("publication contains the same object more than once".to_string());
305 }
306 match object {
307 crate::analysis::facts::PublicationObjectFact::Table { name, columns, .. } => {
308 let id = self.resolve_relation_id(name);
309 match self.local.relations.get(&id) {
310 Some(RelationOverlay::Present(relation))
311 if relation.kind == RelationKind::Table
312 && relation.persistence == Persistence::Permanent =>
313 {
314 if let Some(columns) = columns {
315 let mut seen = HashSet::new();
316 for column in columns {
317 if !seen.insert(column) {
318 return Err(format!(
319 "publication lists column '{}' more than once for '{}'",
320 column, id
321 ));
322 }
323 if !relation.has_column(column) {
324 return Err(format!(
325 "publication column '{}.{}' does not exist",
326 id, column
327 ));
328 }
329 }
330 }
331 }
332 Some(RelationOverlay::Present(_)) => {
333 return Err(format!(
334 "publication target '{}' is not a permanent table",
335 id
336 ));
337 }
338 Some(RelationOverlay::Dropped) => {
339 return Err(format!("publication table '{}' does not exist", id));
340 }
341 None if self.baseline_available && self.baseline_covers_object(&id) => {
342 return Err(format!("publication table '{}' does not exist", id));
343 }
344 None => {
345 self.snapshot_confidence();
346 self.local.confidence = Confidence::Tainted;
347 }
348 }
349 }
350 crate::analysis::facts::PublicationObjectFact::SchemaTables { schema, .. } => {
351 if !self.schema_is_present(schema) {
352 if self.schema_absence_is_authoritative(schema) {
353 return Err(format!("publication schema '{}' does not exist", schema));
354 }
355 self.snapshot_confidence();
356 self.local.confidence = Confidence::Tainted;
357 }
358 }
359 crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand
360 | crate::analysis::facts::PublicationObjectFact::Unknown => {
361 self.snapshot_confidence();
362 self.local.confidence = Confidence::Tainted;
363 }
364 }
365 }
366 Ok(())
367 }
368
369 fn publication_scope_needs_inheritance_knowledge(
370 &self,
371 scope: &crate::analysis::facts::PublicationScope,
372 ) -> bool {
373 match scope {
374 crate::analysis::facts::PublicationScope::AllTables { .. } => true,
377 crate::analysis::facts::PublicationScope::Explicit(objects) => {
378 objects.iter().any(|object| match object {
379 crate::analysis::facts::PublicationObjectFact::Table {
380 name,
381 only,
382 include_partitions,
383 ..
384 } if !only || *include_partitions => {
385 let id = self.resolve_relation_id(name);
386 !matches!(
387 self.local.relations.get(&id),
388 Some(RelationOverlay::Present(relation)) if relation.generation > 0
389 ) || self.local.graph.edges().iter().any(|edge| {
390 matches!(edge.kind, DependencyKind::PartitionOf)
391 && self.local.graph.resolve_rename(&edge.referenced)
392 == self.local.graph.resolve_rename(&id)
393 })
394 }
395 crate::analysis::facts::PublicationObjectFact::SchemaTables { .. }
398 | crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand => true,
399 crate::analysis::facts::PublicationObjectFact::Unknown => false,
400 _ => false,
401 })
402 }
403 }
404 }
405
406 fn taint_inheritance_sensitive_publication_scope(
407 &mut self,
408 scope: &crate::analysis::facts::PublicationScope,
409 ) {
410 if self.publication_scope_needs_inheritance_knowledge(scope) {
411 self.snapshot_confidence();
412 self.local.confidence = Confidence::Tainted;
413 }
414 }
415
416 fn subscription_option<'a>(
417 params: Option<&'a [crate::analysis::facts::AttributeFact]>,
418 name: &str,
419 ) -> Option<&'a str> {
420 params?
421 .iter()
422 .rev()
423 .find(|param| param.name.eq_ignore_ascii_case(name))
424 .map(|param| param.value.as_str())
425 }
426
427 fn postgres_boolean(value: &str) -> Option<bool> {
428 let value = value.trim().to_ascii_lowercase();
429 match value.as_str() {
430 "1" => return Some(true),
431 "0" => return Some(false),
432 "" => return None,
433 _ => {}
434 }
435
436 let mut matched = None;
437 for (spelling, parsed) in [
438 ("true", true),
439 ("yes", true),
440 ("on", true),
441 ("false", false),
442 ("no", false),
443 ("off", false),
444 ] {
445 if spelling.starts_with(&value) {
446 if matched.is_some() {
447 return None;
448 }
449 matched = Some(parsed);
450 }
451 }
452 matched
453 }
454
455 fn subscription_boolean_option(
456 params: Option<&[crate::analysis::facts::AttributeFact]>,
457 name: &str,
458 ) -> Option<bool> {
459 Self::subscription_option(params, name).and_then(Self::postgres_boolean)
460 }
461
462 fn validate_subscription_boolean_options(
463 params: Option<&[crate::analysis::facts::AttributeFact]>,
464 names: &[&str],
465 ) -> Result<(), String> {
466 let Some(params) = params else {
467 return Ok(());
468 };
469 for option in params {
470 if names
471 .iter()
472 .any(|name| option.name.eq_ignore_ascii_case(name))
473 && Self::postgres_boolean(&option.value).is_none()
474 {
475 return Err(format!(
476 "subscription option '{}' requires a PostgreSQL boolean value",
477 option.name
478 ));
479 }
480 }
481 Ok(())
482 }
483
484 fn set_subscription_option(
485 subscription: &mut crate::model::replication::SubscriptionState,
486 option: &crate::analysis::facts::AttributeFact,
487 ) {
488 let params = subscription.params.get_or_insert_with(Vec::new);
489 params.retain(|existing| !existing.name.eq_ignore_ascii_case(&option.name));
490 params.push(option.clone());
491 }
492
493 pub fn new(cache: DbCache) -> Self {
494 Self::with_baseline(cache, true)
495 }
496
497 pub fn with_baseline(cache: DbCache, baseline_available: bool) -> Self {
498 let source_lock_timeout =
499 baseline_available.then_some(cache.metadata.source_lock_timeout_ms);
500 let source_statement_timeout =
501 baseline_available.then_some(cache.metadata.source_statement_timeout_ms);
502 let default_search_path = cache.search_path.clone();
503 let default_search_path_template = if cache.metadata.schemas.is_none() {
504 cache
505 .metadata
506 .source_search_path
507 .clone()
508 .unwrap_or_else(|| default_search_path.clone())
509 } else {
510 default_search_path.clone()
511 };
512 let current_role_known = cache.metadata.source_role.is_some();
513 let current_role = cache
514 .metadata
515 .source_role
516 .clone()
517 .unwrap_or_else(|| "postgres".to_string());
518 let session_role_known = cache.metadata.source_session_role.is_some();
519 let session_role = cache
520 .metadata
521 .source_session_role
522 .clone()
523 .unwrap_or_else(|| current_role.clone());
524 let authenticated_role = session_role.clone();
525 let authenticated_role_known = session_role_known;
526 let persistent_current_role = current_role.clone();
527 let persistent_current_role_known = current_role_known;
528 let persistent_session_role = session_role.clone();
529 let persistent_session_role_known = session_role_known;
530 let roles_known = cache.metadata.source_session_role.is_some();
531 let baseline_schemas: Option<HashSet<String>> = cache
532 .metadata
533 .schemas
534 .as_ref()
535 .map(|schemas| schemas.iter().cloned().collect());
536 let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
537 let mut baseline_relations = HashSet::new();
538 let mut baseline_indexes = HashSet::new();
539 let mut baseline_foreign_keys = HashSet::new();
540 let mut baseline_fk_dependencies = HashSet::new();
541 let mut triggers = HashMap::new();
542 let mut constraints = HashMap::new();
543 let mut types = HashMap::new();
544 let mut graph = DependencyGraph::new();
545
546 let mut schemas: HashMap<String, SchemaOverlay> = cache
547 .schemas
548 .iter()
549 .map(|(name, schema)| (name.clone(), SchemaOverlay::Present(schema.clone())))
550 .collect();
551 let inferred_schema_owner = ObjectId::new(
557 "",
558 cache.metadata.source_role.as_deref().unwrap_or("postgres"),
559 );
560 for name in cache
561 .relations
562 .keys()
563 .map(|id| &id.schema)
564 .chain(cache.types.keys().map(|id| &id.schema))
565 .chain(cache.functions.keys().map(|id| &id.schema))
566 .chain(cache.sequences.keys().map(|id| &id.schema))
567 {
568 schemas.entry(name.clone()).or_insert_with(|| {
569 SchemaOverlay::Present(crate::model::schema::SchemaState {
570 name: name.clone(),
571 owner: inferred_schema_owner.clone(),
572 generation: 0,
573 })
574 });
575 }
576 if cache.schemas.is_empty() && cache.metadata.schemas.is_none() {
577 for name in &cache.search_path {
578 schemas.entry(name.clone()).or_insert_with(|| {
579 SchemaOverlay::Present(crate::model::schema::SchemaState {
580 name: name.clone(),
581 owner: inferred_schema_owner.clone(),
582 generation: 0,
583 })
584 });
585 }
586 }
587
588 let sequences = cache
589 .sequences
590 .iter()
591 .map(|(id, sequence)| (id.clone(), SequenceOverlay::Present(sequence.clone())))
592 .collect();
593 let baseline_sequences = cache.sequences.keys().cloned().collect();
594 for sequence in cache.sequences.values() {
595 if let Some((table, column)) = &sequence.owned_by {
596 graph.add_edge(DependencyEdge::new(
597 sequence.id.clone(),
598 table.clone(),
599 DependencyKind::SequenceOwnedBy {
600 column: column.clone(),
601 },
602 ));
603 }
604 }
605
606 for (id, rel_state) in cache.baseline_relations() {
607 if rel_state.is_fk_dependency {
608 baseline_fk_dependencies.insert(id.clone());
609 }
610 relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
611 baseline_relations.insert(id.clone());
612 }
613
614 for (id, type_state) in &cache.types {
615 types.insert(id.clone(), TypeOverlay::Present(type_state.clone()));
616 }
617 let type_catalog = types.clone();
618 for overlay in relations.values_mut() {
619 if let RelationOverlay::Present(relation) = overlay {
620 for column in &mut relation.columns {
621 column.type_id = column.data_type.as_deref().and_then(|raw| {
622 Self::resolve_type_reference_from_catalog(
623 raw,
624 &type_catalog,
625 &default_search_path,
626 )
627 });
628 }
629 }
630 }
631 for overlay in types.values_mut() {
632 if let TypeOverlay::Present(TypeState {
633 kind:
634 TypeKind::Domain {
635 base_type,
636 base_type_id,
637 },
638 ..
639 }) = overlay
640 {
641 *base_type_id = Self::resolve_type_reference_from_catalog(
642 base_type,
643 &type_catalog,
644 &default_search_path,
645 );
646 }
647 }
648 for fk in cache.foreign_keys {
649 baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
650 graph.add_edge(DependencyEdge::new(
651 fk.from_table,
652 fk.to_table,
653 DependencyKind::ForeignKey {
654 constraint_name: Some(fk.constraint_name),
655 from_columns: Vec::new(),
656 to_columns: Vec::new(),
657 from_generation: 0,
658 },
659 ));
660 }
661
662 for idx in cache.indexes {
663 baseline_indexes.insert(idx.index_id.clone());
665 graph.add_edge(DependencyEdge::new(
666 idx.index_id,
667 idx.table_id,
668 DependencyKind::IndexOnRelation {
669 using_method: None,
670 has_predicate: false,
671 is_concurrent: false,
672 is_unique: false,
673 eligibility_known: false,
674 },
675 ));
676 }
677
678 for dependency in cache.dependencies {
679 if dependency.deptype != "view" {
680 continue;
681 }
682 let (Some(obj_schema), Some(obj_name), Some(ref_schema), Some(ref_name)) = (
683 dependency.obj_schema,
684 dependency.obj_name,
685 dependency.ref_schema,
686 dependency.ref_name,
687 ) else {
688 continue;
689 };
690 let dependent = ObjectId::new(obj_schema, obj_name);
691 let referenced = ObjectId::new(ref_schema, ref_name);
692 if dependent == referenced {
697 continue;
698 }
699 let is_view = relations.get(&dependent).is_some_and(|relation| {
700 matches!(
701 relation,
702 RelationOverlay::Present(state)
703 if matches!(
704 state.kind,
705 crate::model::relation::RelationKind::View
706 | crate::model::relation::RelationKind::MaterializedView
707 )
708 )
709 });
710 let dependent_schema_is_omitted = baseline_schemas
711 .as_ref()
712 .is_some_and(|schemas| !schemas.contains(&dependent.schema));
713 if is_view || dependent_schema_is_omitted {
714 graph.add_edge(DependencyEdge::new(
721 dependent,
722 referenced,
723 DependencyKind::ViewDependency { view_generation: 0 },
724 ));
725 }
726 }
727
728 for constraint in cache.constraints {
729 constraints.insert(
730 (constraint.table_id.clone(), constraint.name.clone()),
731 constraint,
732 );
733 }
734
735 for t in cache.triggers {
736 let trigger_key = Self::trigger_key(&t.table_id, &t.trigger_id.name);
737 triggers.insert(
738 trigger_key.clone(),
739 TriggerOverlay::Present(crate::model::trigger::TriggerState {
740 name: t.trigger_id.name.clone(),
741 id: trigger_key.clone(),
742 table_id: t.table_id.clone(),
743 enabled_mode: t.enabled_mode,
744 generation: 0,
745 }),
746 );
747 graph.add_edge(DependencyEdge::new(
748 trigger_key.clone(),
749 t.table_id,
750 DependencyKind::TriggerOnTable {
751 trigger_id: trigger_key,
752 function_id: t.function_id,
753 },
754 ));
755 }
756
757 let mut functions: HashMap<ObjectId, crate::model::function::FunctionOverlay> =
758 HashMap::new();
759 for (id, func_state) in &cache.functions {
760 functions.insert(
761 id.clone(),
762 crate::model::function::FunctionOverlay::Present(func_state.clone()),
763 );
764 }
765 for overlay in functions.values_mut() {
766 if let crate::model::function::FunctionOverlay::Present(function) = overlay {
767 function.arg_type_ids = function
768 .arg_types
769 .iter()
770 .map(|raw| {
771 Self::resolve_type_reference_from_catalog(
772 raw,
773 &type_catalog,
774 &default_search_path,
775 )
776 })
777 .collect();
778 function.return_type_id = Self::resolve_type_reference_from_catalog(
779 &function.return_type,
780 &type_catalog,
781 &default_search_path,
782 );
783 }
784 }
785
786 let publications = cache
787 .publications
788 .into_iter()
789 .map(|(name, publication)| {
790 if let crate::analysis::facts::PublicationScope::Explicit(objects) =
791 &publication.scope
792 {
793 for object in objects {
794 if let crate::analysis::facts::PublicationObjectFact::Table {
795 name: relation,
796 ..
797 } = object
798 {
799 let table_id = ObjectId::new(
800 relation
801 .schema
802 .as_ref()
803 .map(|schema| schema.resolve())
804 .unwrap_or_else(|| "public".to_string()),
805 relation.name.resolve(),
806 );
807 graph.add_edge(DependencyEdge::new(
808 table_id,
809 ObjectId::new("public", &name),
810 DependencyKind::PublicationIncludes {
811 publication_name: name.clone(),
812 },
813 ));
814 }
815 }
816 }
817 (
818 name,
819 crate::model::replication::PublicationOverlay::Present(publication),
820 )
821 })
822 .collect();
823 let subscriptions = cache
824 .subscriptions
825 .into_iter()
826 .map(|(name, subscription)| {
827 (
828 name,
829 crate::model::replication::SubscriptionOverlay::Present(subscription),
830 )
831 })
832 .collect();
833
834 let mut state = Self {
835 pg_version_num: cache.pg_version_num,
836 baseline_available,
837 baseline_schemas,
838 baseline_relations,
839 baseline_indexes,
840 baseline_foreign_keys,
841 baseline_fk_dependencies,
842 baseline_sequences,
843 local: LocalState {
844 schemas,
845 relations,
846 types,
847 functions,
848 sequences,
849 publications,
850 subscriptions,
851 roles: cache
852 .roles
853 .into_iter()
854 .map(|(id, role)| (id, crate::model::role::RoleOverlay::Present(role)))
855 .collect(),
856 triggers,
857 constraints,
858 graph,
859 search_path: default_search_path.clone(),
860 default_search_path,
861 search_path_template: default_search_path_template.clone(),
862 session_search_path_template: default_search_path_template.clone(),
863 default_search_path_template,
864 lock_timeout: ScopedSetting::new(source_lock_timeout),
865 statement_timeout: ScopedSetting::new(source_statement_timeout),
866 current_role,
867 current_role_known,
868 persistent_current_role,
869 persistent_current_role_known,
870 session_role,
871 session_role_known,
872 persistent_session_role,
873 persistent_session_role_known,
874 authenticated_role,
875 authenticated_role_known,
876 roles_known,
877 confidence: Confidence::Exact,
878 transactions: Vec::new(),
879 transaction_aborted: false,
880 pending_validation: HashSet::new(),
881 generation_counter: 0,
882 },
883 };
884 state.refresh_role_sensitive_search_path();
885 state.local.default_search_path = state.local.search_path.clone();
886 state
887 }
888
889 pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
890 self.local.relations.get(id)
891 }
892
893 pub fn resolve_function_schema(
894 &self,
895 name: &crate::ast::identifiers::QualifiedName,
896 sig_str: &str,
897 ) -> String {
898 if let Some(schema) = &name.schema {
899 return schema.resolve();
900 }
901 for schema in &self.local.search_path {
902 let candidate = ObjectId::new(schema.clone(), sig_str.to_string());
903 if self.routine_is_present(&candidate) {
904 return schema.clone();
905 }
906 }
907 self.local
908 .search_path
909 .first()
910 .cloned()
911 .unwrap_or_else(|| "public".to_string())
912 }
913
914 pub fn resolve_relation_id(&self, name: &crate::ast::identifiers::QualifiedName) -> ObjectId {
915 if let Some(schema) = &name.schema {
916 return ObjectId::new(schema.resolve(), name.name.resolve());
917 }
918 let resolved_name = name.name.resolve();
919 for schema in &self.local.search_path {
920 let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
921 if self.relation_namespace_object_is_present(&candidate) {
922 candidate.inferred_schema = true;
923 return candidate;
924 }
925 }
926 let schema = self
927 .local
928 .search_path
929 .first()
930 .cloned()
931 .unwrap_or_else(|| "public".to_string());
932 let mut id = ObjectId::new(schema, resolved_name);
933 id.inferred_schema = true;
934 id
935 }
936
937 pub fn relation_is_present(&self, id: &ObjectId) -> bool {
938 matches!(
939 self.local.relations.get(id),
940 Some(RelationOverlay::Present(_))
941 )
942 }
943
944 pub fn baseline_covers_object(&self, id: &ObjectId) -> bool {
948 self.baseline_schemas
949 .as_ref()
950 .is_none_or(|schemas| schemas.contains(&id.schema))
951 }
952
953 fn relation_lookup(
954 &self,
955 id: &ObjectId,
956 expected: impl FnOnce(&RelationKind) -> bool,
957 ) -> ObjectLookup {
958 match self.local.relations.get(id) {
959 Some(RelationOverlay::Present(relation)) if expected(&relation.kind) => {
960 ObjectLookup::Present
961 }
962 Some(RelationOverlay::Present(_)) => ObjectLookup::WrongKind,
963 Some(RelationOverlay::Dropped) => ObjectLookup::Tombstone,
964 None if self.sequence_is_present(id) || self.index_is_present(id) => {
965 ObjectLookup::WrongKind
966 }
967 None if self.baseline_available && self.baseline_covers_object(id) => {
968 ObjectLookup::AuthoritativelyAbsent
969 }
970 None => ObjectLookup::Unknown,
971 }
972 }
973
974 pub(super) fn ensure_relation_target<F>(
978 &mut self,
979 id: &ObjectId,
980 expected: F,
981 missing_reason: String,
982 wrong_kind_reason: String,
983 ) -> Result<(), MutationResult>
984 where
985 F: FnOnce(&RelationKind) -> bool,
986 {
987 match self.relation_lookup(id, expected) {
988 ObjectLookup::Present => Ok(()),
989 ObjectLookup::WrongKind => Err(MutationResult::Conflict {
990 reason: wrong_kind_reason,
991 }),
992 ObjectLookup::AuthoritativelyAbsent | ObjectLookup::Tombstone => {
993 Err(MutationResult::Conflict {
994 reason: missing_reason,
995 })
996 }
997 ObjectLookup::Unknown => {
998 self.snapshot_confidence();
999 self.local.confidence = Confidence::Tainted;
1000 Err(MutationResult::Skipped)
1001 }
1002 }
1003 }
1004
1005 fn type_lookup(&self, id: &ObjectId, expected: impl FnOnce(&TypeKind) -> bool) -> ObjectLookup {
1006 match self.local.types.get(id) {
1007 Some(TypeOverlay::Present(state)) if expected(&state.kind) => ObjectLookup::Present,
1008 Some(TypeOverlay::Present(_)) => ObjectLookup::WrongKind,
1009 Some(TypeOverlay::Dropped) => ObjectLookup::Tombstone,
1010 None if self.baseline_available && self.baseline_covers_object(id) => {
1011 ObjectLookup::AuthoritativelyAbsent
1012 }
1013 None => ObjectLookup::Unknown,
1014 }
1015 }
1016
1017 pub(super) fn ensure_routine_target(
1018 &mut self,
1019 id: &ObjectId,
1020 expected: crate::model::function::RoutineKind,
1021 missing_reason: String,
1022 wrong_kind_reason: String,
1023 ) -> Result<(), MutationResult> {
1024 match self.local.functions.get(id) {
1025 Some(FunctionOverlay::Present(function)) if function.routine_kind == expected => Ok(()),
1026 Some(FunctionOverlay::Present(_)) => Err(MutationResult::Conflict {
1027 reason: wrong_kind_reason,
1028 }),
1029 Some(FunctionOverlay::Dropped) => Err(MutationResult::Conflict {
1030 reason: missing_reason,
1031 }),
1032 None if self.baseline_available && self.baseline_covers_object(id) => {
1033 Err(MutationResult::Conflict {
1034 reason: missing_reason,
1035 })
1036 }
1037 None => {
1038 self.snapshot_confidence();
1039 self.local.confidence = Confidence::Tainted;
1040 Err(MutationResult::Skipped)
1041 }
1042 }
1043 }
1044
1045 pub(super) fn ensure_schema_target(&mut self, schema: &str) -> Result<(), MutationResult> {
1051 match self.schema_lookup(schema) {
1052 ObjectLookup::Present => Ok(()),
1053 ObjectLookup::Tombstone | ObjectLookup::AuthoritativelyAbsent => {
1054 Err(MutationResult::Conflict {
1055 reason: format!("schema '{}' does not exist", schema),
1056 })
1057 }
1058 ObjectLookup::Unknown => {
1059 self.snapshot_confidence();
1060 self.local.confidence = Confidence::Tainted;
1061 Err(MutationResult::Skipped)
1062 }
1063 ObjectLookup::WrongKind => {
1064 unreachable!("schemas do not share an overlay with other object kinds")
1065 }
1066 }
1067 }
1068
1069 fn snapshot_baseline_foreign_keys(&mut self) {
1070 if let Some(frame) = self.local.transactions.last_mut() {
1071 frame
1072 .undo_log
1073 .push(StateChange::BaselineForeignKeysSnapshot {
1074 previous: self.baseline_foreign_keys.clone(),
1075 });
1076 }
1077 }
1078
1079 pub(super) fn remove_dropped_constraints(
1082 &mut self,
1083 dropped_relations: &HashSet<ObjectId>,
1084 dropped_constraints: &HashSet<(ObjectId, String)>,
1085 ) {
1086 let resolution_graph = self.local.graph.clone();
1087 let should_remove = |table_id: &ObjectId, name: &str| {
1088 let resolved_table = resolution_graph.resolve_rename(table_id);
1089 dropped_relations.contains(resolved_table)
1090 || dropped_constraints.contains(&(resolved_table.clone(), name.to_string()))
1091 };
1092
1093 let constraint_keys: Vec<(ObjectId, String)> = self
1094 .local
1095 .constraints
1096 .keys()
1097 .filter(|(table_id, name)| should_remove(table_id, name))
1098 .cloned()
1099 .collect();
1100 for (table_id, name) in constraint_keys {
1101 self.snapshot_constraint(&table_id, &name);
1102 self.local.constraints.remove(&(table_id, name));
1103 }
1104
1105 let pending_changed = self
1106 .local
1107 .pending_validation
1108 .iter()
1109 .any(|(table_id, name)| should_remove(table_id, name));
1110 if pending_changed {
1111 self.snapshot_pending_validation();
1112 self.local
1113 .pending_validation
1114 .retain(|(table_id, name)| !should_remove(table_id, name));
1115 }
1116
1117 if self
1118 .baseline_foreign_keys
1119 .iter()
1120 .any(|(table_id, name)| should_remove(table_id, name))
1121 {
1122 self.snapshot_baseline_foreign_keys();
1123 self.baseline_foreign_keys
1124 .retain(|(table_id, name)| !should_remove(table_id, name));
1125 }
1126 }
1127
1128 pub fn baseline_scope_omits_displayed_object<'a>(
1129 &self,
1130 object_name: &'a str,
1131 ) -> Option<&'a str> {
1132 let schemas = self.baseline_schemas.as_ref()?;
1133 let (schema, _) = object_name.split_once('.')?;
1134 (!schemas.contains(schema)).then_some(schema)
1135 }
1136
1137 pub(crate) fn sequence_is_present(&self, id: &ObjectId) -> bool {
1138 matches!(
1139 self.local.sequences.get(id),
1140 Some(SequenceOverlay::Present(_))
1141 )
1142 }
1143
1144 pub(crate) fn type_is_present(&self, id: &ObjectId) -> bool {
1145 matches!(self.local.types.get(id), Some(TypeOverlay::Present(_)))
1146 }
1147
1148 pub(crate) fn routine_is_present(&self, id: &ObjectId) -> bool {
1149 matches!(
1150 self.local.functions.get(id),
1151 Some(FunctionOverlay::Present(_))
1152 )
1153 }
1154
1155 fn resolve_type_reference_from_catalog(
1156 raw: &str,
1157 types: &HashMap<ObjectId, TypeOverlay>,
1158 search_path: &[String],
1159 ) -> Option<ObjectId> {
1160 let (schema, name) = Self::parse_type_reference(raw)?;
1161 if let Some(schema) = schema {
1162 let candidate = ObjectId::new(schema, name);
1163 return matches!(types.get(&candidate), Some(TypeOverlay::Present(_)))
1164 .then_some(candidate);
1165 }
1166 search_path.iter().find_map(|schema| {
1167 let candidate = ObjectId::new(schema, &name);
1168 matches!(types.get(&candidate), Some(TypeOverlay::Present(_))).then_some(candidate)
1169 })
1170 }
1171
1172 fn parse_type_reference(raw: &str) -> Option<(Option<String>, String)> {
1176 let mut token = raw.trim();
1177 while let Some(without_array) = token.strip_suffix("[]") {
1178 token = without_array.trim_end();
1179 }
1180
1181 let mut parts = Vec::new();
1182 let mut current = String::new();
1183 let mut quoted = false;
1184 let mut part_is_quoted = false;
1185 let mut chars = token.chars().peekable();
1186 while let Some(character) = chars.next() {
1187 match character {
1188 '"' if quoted && chars.peek() == Some(&'"') => {
1189 current.push('"');
1190 chars.next();
1191 }
1192 '"' => {
1193 quoted = !quoted;
1194 part_is_quoted = true;
1195 }
1196 '.' if !quoted => {
1197 parts.push(Self::resolve_type_identifier(¤t, part_is_quoted)?);
1198 current.clear();
1199 part_is_quoted = false;
1200 }
1201 character if !quoted && character.is_whitespace() => {}
1202 character => current.push(character),
1203 }
1204 }
1205 if quoted {
1206 return None;
1207 }
1208 parts.push(Self::resolve_type_identifier(¤t, part_is_quoted)?);
1209 match parts.as_slice() {
1210 [name] => Some((None, name.clone())),
1211 [schema, name] => Some((Some(schema.clone()), name.clone())),
1212 _ => None,
1213 }
1214 }
1215
1216 fn resolve_type_identifier(identifier: &str, quoted: bool) -> Option<String> {
1217 (!identifier.is_empty()).then(|| {
1218 if quoted {
1219 identifier.to_string()
1220 } else {
1221 identifier.to_lowercase()
1222 }
1223 })
1224 }
1225
1226 fn resolve_type_reference(&self, raw: &str) -> Option<ObjectId> {
1227 Self::resolve_type_reference_from_catalog(raw, &self.local.types, &self.local.search_path)
1228 }
1229
1230 fn type_reference_name(id: &ObjectId, qualified: bool) -> String {
1231 let quote = |identifier: &str| {
1232 let unquoted = identifier
1233 .chars()
1234 .enumerate()
1235 .all(|(index, character)| match index {
1236 0 => character.is_ascii_lowercase() || character == '_',
1237 _ => {
1238 character.is_ascii_lowercase()
1239 || character.is_ascii_digit()
1240 || character == '_'
1241 || character == '$'
1242 }
1243 });
1244 if unquoted {
1245 identifier.to_string()
1246 } else {
1247 format!("\"{}\"", identifier.replace('"', "\"\""))
1248 }
1249 };
1250
1251 if qualified {
1252 format!("{}.{}", quote(&id.schema), quote(&id.name))
1253 } else {
1254 quote(&id.name)
1255 }
1256 }
1257
1258 fn remapped_type_display(raw: &str, new_id: &ObjectId, schema_changed: bool) -> String {
1259 let suffix = raw.find('[').map(|index| &raw[index..]).unwrap_or("");
1260 format!(
1261 "{}{}",
1262 Self::type_reference_name(new_id, schema_changed),
1263 suffix
1264 )
1265 }
1266
1267 pub(crate) fn index_is_present(&self, id: &ObjectId) -> bool {
1268 self.local.graph.edges().iter().any(|edge| {
1269 matches!(edge.kind, DependencyKind::IndexOnRelation { .. }) && edge.dependent == *id
1270 })
1271 }
1272
1273 pub(crate) fn relation_namespace_object_is_present(&self, id: &ObjectId) -> bool {
1274 self.relation_is_present(id) || self.sequence_is_present(id) || self.index_is_present(id)
1275 }
1276
1277 pub(super) fn next_generated_constraint_name_avoiding(
1282 &self,
1283 table: &ObjectId,
1284 name1: &str,
1285 name2: Option<&str>,
1286 label: &str,
1287 reserved: &HashSet<String>,
1288 ) -> String {
1289 (0..)
1290 .map(|suffix| {
1291 let label = if suffix == 0 {
1292 label.to_string()
1293 } else {
1294 format!("{label}{suffix}")
1295 };
1296 Self::postgres_object_name(name1, name2, &label)
1297 })
1298 .find(|candidate| {
1299 !reserved.contains(candidate)
1300 && !self
1301 .local
1302 .constraints
1303 .contains_key(&(table.clone(), candidate.clone()))
1304 })
1305 .expect("constraint suffix space is unbounded")
1306 }
1307
1308 fn postgres_object_name(name1: &str, name2: Option<&str>, label: &str) -> String {
1309 const MAX_IDENTIFIER_BYTES: usize = 63;
1310
1311 fn truncate(value: &str, max_bytes: usize) -> &str {
1312 let mut end = max_bytes.min(value.len());
1313 while !value.is_char_boundary(end) {
1314 end -= 1;
1315 }
1316 &value[..end]
1317 }
1318
1319 let separators = usize::from(name2.is_some()) + 1;
1320 let available = MAX_IDENTIFIER_BYTES.saturating_sub(label.len() + separators);
1321 let mut name1_bytes = name1.len();
1322 let mut name2_bytes = name2.map_or(0, str::len);
1323 while name1_bytes + name2_bytes > available {
1324 if name1_bytes > name2_bytes {
1325 name1_bytes -= 1;
1326 } else {
1327 name2_bytes -= 1;
1328 }
1329 }
1330
1331 let name1 = truncate(name1, name1_bytes);
1332 match name2 {
1333 Some(name2) => format!("{name1}_{}_{}", truncate(name2, name2_bytes), label),
1334 None => format!("{name1}_{label}"),
1335 }
1336 }
1337
1338 fn relation_namespace_is_taken(&self, id: &ObjectId) -> bool {
1339 self.relation_namespace_object_is_present(id) || self.type_is_present(id)
1340 }
1341
1342 fn next_implicit_sequence_id(
1343 &self,
1344 table: &ObjectId,
1345 column: &str,
1346 reserved: &HashSet<ObjectId>,
1347 ) -> ObjectId {
1348 (0..)
1349 .map(|suffix| {
1350 let label = if suffix == 0 {
1351 "seq".to_string()
1352 } else {
1353 format!("seq{suffix}")
1354 };
1355 ObjectId::new(
1356 &table.schema,
1357 Self::postgres_object_name(&table.name, Some(column), &label),
1358 )
1359 })
1360 .find(|candidate| {
1361 !reserved.contains(candidate) && !self.relation_namespace_is_taken(candidate)
1362 })
1363 .expect("implicit sequence suffix space is unbounded")
1364 }
1365
1366 fn sequence_nextval_default(id: &ObjectId) -> crate::analysis::expr_ir::ExprIr {
1367 crate::analysis::expr_ir::ExprIr::FunctionCall {
1368 name: "nextval".to_string(),
1369 args: vec![crate::analysis::expr_ir::ExprIr::Literal(format!(
1370 "{}.{}",
1371 id.schema, id.name
1372 ))],
1373 }
1374 }
1375
1376 pub fn column_was_added_in_transaction(&self, table_id: &ObjectId, column: &str) -> bool {
1377 if self.local.transactions.is_empty() {
1378 return false;
1379 }
1380
1381 for frame in &self.local.transactions {
1383 for change in &frame.undo_log {
1384 if let StateChange::RelationSnapshot { id, previous } = change
1385 && id == table_id
1386 {
1387 match previous.as_ref() {
1388 None | Some(RelationOverlay::Dropped) => {
1389 return true;
1390 }
1391 Some(RelationOverlay::Present(r)) => {
1392 let col_existed = r.columns.iter().any(|c| c.name == column);
1393 return !col_existed;
1394 }
1395 }
1396 }
1397 }
1398 }
1399 false
1400 }
1401
1402 pub fn capture_pre_state(&self) -> PreState {
1403 let mut pre_state = PreState::default();
1404 self.capture_pre_state_into(&mut pre_state);
1405 pre_state
1406 }
1407
1408 pub(crate) fn capture_pre_state_into(&self, pre_state: &mut PreState) {
1409 let PreState {
1410 relations,
1411 functions,
1412 roles,
1413 publications,
1414 subscriptions,
1415 sequences,
1416 types,
1417 indexes,
1418 baseline_foreign_keys,
1419 } = pre_state;
1420
1421 sync_present_map(relations, &self.local.relations, |overlay| match overlay {
1422 RelationOverlay::Present(state) => Some(state),
1423 RelationOverlay::Dropped => None,
1424 });
1425 sync_present_map(functions, &self.local.functions, |overlay| match overlay {
1426 crate::model::function::FunctionOverlay::Present(state) => Some(state),
1427 crate::model::function::FunctionOverlay::Dropped => None,
1428 });
1429 sync_present_map(roles, &self.local.roles, |overlay| match overlay {
1430 crate::model::role::RoleOverlay::Present(state) => Some(state),
1431 crate::model::role::RoleOverlay::Dropped => None,
1432 });
1433 sync_present_map(
1434 publications,
1435 &self.local.publications,
1436 |overlay| match overlay {
1437 crate::model::replication::PublicationOverlay::Present(state) => Some(state),
1438 crate::model::replication::PublicationOverlay::Dropped => None,
1439 },
1440 );
1441 sync_present_map(
1442 subscriptions,
1443 &self.local.subscriptions,
1444 |overlay| match overlay {
1445 crate::model::replication::SubscriptionOverlay::Present(state) => Some(state),
1446 crate::model::replication::SubscriptionOverlay::Dropped => None,
1447 },
1448 );
1449 sync_present_map(sequences, &self.local.sequences, |overlay| match overlay {
1450 SequenceOverlay::Present(state) => Some(state),
1451 SequenceOverlay::Dropped => None,
1452 });
1453 sync_present_map(types, &self.local.types, |overlay| match overlay {
1454 TypeOverlay::Present(state) => Some(state),
1455 TypeOverlay::Dropped => None,
1456 });
1457
1458 let mut index = 0;
1459 for edge in self
1460 .local
1461 .graph
1462 .edges()
1463 .iter()
1464 .filter(|edge| matches!(edge.kind, DependencyKind::IndexOnRelation { .. }))
1465 {
1466 if let Some(existing) = indexes.get_mut(index) {
1467 if existing != edge {
1468 existing.clone_from(edge);
1469 }
1470 } else {
1471 indexes.push(edge.clone());
1472 }
1473 index += 1;
1474 }
1475 indexes.truncate(index);
1476 baseline_foreign_keys.clone_from(&self.baseline_foreign_keys);
1477 }
1478
1479 pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
1480 let mut result = CascadeResult::default();
1481 let mut visited = HashSet::new();
1482 self.walk_cascade(target_oid, &mut visited, &mut result);
1483 result
1484 }
1485
1486 fn walk_cascade(
1487 &self,
1488 current: &ObjectId,
1489 visited: &mut HashSet<ObjectId>,
1490 result: &mut CascadeResult,
1491 ) {
1492 let resolved_current = self.local.graph.resolve_rename(current).clone();
1493
1494 if !visited.insert(resolved_current.clone()) {
1495 return;
1496 }
1497
1498 result.dropped_relations.insert(resolved_current.clone());
1499
1500 if self.local.graph.cascade_index_is_worthwhile() {
1501 for edge in self.local.graph.cascade_edges(&resolved_current) {
1502 self.walk_cascade_edge(edge, &resolved_current, visited, result);
1503 }
1504 } else {
1505 for edge in self.local.graph.edges() {
1506 self.walk_cascade_edge(edge, &resolved_current, visited, result);
1507 }
1508 }
1509 }
1510
1511 fn walk_cascade_edge(
1512 &self,
1513 edge: &DependencyEdge,
1514 resolved_current: &ObjectId,
1515 visited: &mut HashSet<ObjectId>,
1516 result: &mut CascadeResult,
1517 ) {
1518 match &edge.kind {
1519 DependencyKind::ViewDependency { .. } => {
1520 if self.local.graph.resolve_rename(&edge.referenced) == resolved_current {
1521 let resolved_view_id = self.local.graph.resolve_rename(&edge.dependent).clone();
1522 if !visited.contains(&resolved_view_id) {
1523 self.walk_cascade(&resolved_view_id, visited, result);
1524 }
1525 }
1526 }
1527 DependencyKind::IndexOnRelation { .. } => {
1528 if self.local.graph.resolve_rename(&edge.referenced) == resolved_current {
1529 result
1530 .dropped_indexes
1531 .insert(self.local.graph.resolve_rename(&edge.dependent).clone());
1532 }
1533 }
1534 DependencyKind::ForeignKey {
1535 constraint_name, ..
1536 } => {
1537 if self.local.graph.resolve_rename(&edge.referenced) == resolved_current
1538 && let Some(cname) = constraint_name
1539 {
1540 result.dropped_constraints.insert((
1541 self.local.graph.resolve_rename(&edge.dependent).clone(),
1542 cname.clone(),
1543 ));
1544 }
1545 }
1546 DependencyKind::PartitionOf
1547 if self.local.graph.resolve_rename(&edge.referenced) == resolved_current =>
1548 {
1549 let resolved_child = self.local.graph.resolve_rename(&edge.dependent).clone();
1550 if !visited.contains(&resolved_child) {
1551 self.walk_cascade(&resolved_child, visited, result);
1552 }
1553 }
1554 _ => {}
1555 }
1556 }
1557
1558 fn resolve_grant_privileges(
1559 &self,
1560 spec: &crate::analysis::facts::PrivilegeSpec,
1561 ) -> HashSet<Privilege> {
1562 let supports_maintain = self
1563 .pg_version_num
1564 .is_some_and(|version| version >= 170_000);
1565 match spec {
1566 crate::analysis::facts::PrivilegeSpec::All => {
1567 let mut privileges = [
1568 Privilege::Select,
1569 Privilege::Insert,
1570 Privilege::Update,
1571 Privilege::Delete,
1572 Privilege::Truncate,
1573 Privilege::References,
1574 Privilege::Trigger,
1575 ]
1576 .into_iter()
1577 .collect::<HashSet<_>>();
1578 if supports_maintain {
1579 privileges.insert(Privilege::Maintain);
1580 }
1581 privileges
1582 }
1583 crate::analysis::facts::PrivilegeSpec::List(list) => list
1584 .iter()
1585 .filter_map(|p| match p {
1586 crate::analysis::facts::PrivilegeFact::Select => Some(Privilege::Select),
1587 crate::analysis::facts::PrivilegeFact::Insert => Some(Privilege::Insert),
1588 crate::analysis::facts::PrivilegeFact::Update => Some(Privilege::Update),
1589 crate::analysis::facts::PrivilegeFact::Delete => Some(Privilege::Delete),
1590 crate::analysis::facts::PrivilegeFact::Truncate => Some(Privilege::Truncate),
1591 crate::analysis::facts::PrivilegeFact::References => {
1592 Some(Privilege::References)
1593 }
1594 crate::analysis::facts::PrivilegeFact::Trigger => Some(Privilege::Trigger),
1595 crate::analysis::facts::PrivilegeFact::Maintain if supports_maintain => {
1596 Some(Privilege::Maintain)
1597 }
1598 _ => None,
1599 })
1600 .collect(),
1601 }
1602 }
1603
1604 fn resolve_role_name(
1605 role: &crate::analysis::facts::RoleFact,
1606 current_role: &str,
1607 session_role: &str,
1608 ) -> Option<ObjectId> {
1609 let name = match role {
1610 crate::analysis::facts::RoleFact::Named { name, .. } => Some(name.clone()),
1611 crate::analysis::facts::RoleFact::CurrentUser
1612 | crate::analysis::facts::RoleFact::CurrentRole => Some(current_role.to_string()),
1613 crate::analysis::facts::RoleFact::SessionUser => Some(session_role.to_string()),
1614 crate::analysis::facts::RoleFact::Unknown => None,
1615 }?;
1616 Some(ObjectId::new("", name))
1617 }
1618
1619 fn role_fact_identity(
1620 &self,
1621 role: &crate::analysis::facts::RoleFact,
1622 ) -> Option<(String, bool)> {
1623 match role {
1624 crate::analysis::facts::RoleFact::Named { name, .. } => Some((name.clone(), true)),
1625 crate::analysis::facts::RoleFact::CurrentUser
1626 | crate::analysis::facts::RoleFact::CurrentRole => Some((
1627 self.local.current_role.clone(),
1628 self.local.current_role_known,
1629 )),
1630 crate::analysis::facts::RoleFact::SessionUser => Some((
1631 self.local.session_role.clone(),
1632 self.local.session_role_known,
1633 )),
1634 crate::analysis::facts::RoleFact::Unknown => None,
1635 }
1636 }
1637
1638 fn present_role(&self, name: &str) -> Option<&crate::model::role::RoleState> {
1639 match self.local.roles.get(&ObjectId::new("", name)) {
1640 Some(crate::model::role::RoleOverlay::Present(role)) => Some(role),
1641 _ => None,
1642 }
1643 }
1644
1645 fn can_set_role_to(&self, target: &str) -> Option<bool> {
1646 if !self.local.roles_known || !self.local.session_role_known {
1647 return None;
1648 }
1649 if self.present_role(target).is_none() {
1650 return Some(false);
1651 }
1652 if self.local.session_role == target {
1653 return Some(true);
1654 }
1655 let session = self.present_role(&self.local.session_role)?;
1656 if session.is_superuser {
1657 return Some(true);
1658 }
1659
1660 let mut pending = session.can_set_role_to.clone();
1661 let mut visited = HashSet::new();
1662 while let Some(role_id) = pending.pop() {
1663 if !visited.insert(role_id.clone()) {
1664 continue;
1665 }
1666 if role_id.name == target {
1667 return Some(true);
1668 }
1669 if let Some(role) = self.present_role(&role_id.name) {
1670 pending.extend(role.can_set_role_to.iter().cloned());
1671 }
1672 }
1673 Some(false)
1674 }
1675
1676 fn can_set_session_authorization_to(&self, target: &str) -> Option<bool> {
1677 if !self.local.roles_known || !self.local.authenticated_role_known {
1678 return None;
1679 }
1680 if self.present_role(target).is_none() {
1681 return Some(false);
1682 }
1683 if self.local.authenticated_role == target {
1684 return Some(true);
1685 }
1686 Some(
1687 self.present_role(&self.local.authenticated_role)
1688 .is_some_and(|role| role.is_superuser),
1689 )
1690 }
1691
1692 fn schema_is_present(&self, name: &str) -> bool {
1693 matches!(
1694 self.local.schemas.get(name),
1695 Some(SchemaOverlay::Present(_))
1696 )
1697 }
1698
1699 fn schema_lookup(&self, name: &str) -> ObjectLookup {
1700 match self.local.schemas.get(name) {
1701 Some(SchemaOverlay::Present(_)) => ObjectLookup::Present,
1702 Some(SchemaOverlay::Dropped) => ObjectLookup::Tombstone,
1703 None if self.schema_absence_is_authoritative(name) => {
1704 ObjectLookup::AuthoritativelyAbsent
1705 }
1706 None => ObjectLookup::Unknown,
1707 }
1708 }
1709
1710 fn schema_absence_is_authoritative(&self, name: &str) -> bool {
1711 if matches!(self.local.schemas.get(name), Some(SchemaOverlay::Dropped)) {
1712 return true;
1713 }
1714 self.baseline_available
1715 && self
1716 .baseline_schemas
1717 .as_ref()
1718 .is_none_or(|schemas| schemas.contains(name))
1719 }
1720
1721 fn refresh_role_sensitive_search_path(&mut self) {
1722 let template = self.local.search_path_template.clone();
1723 let mut effective = Vec::new();
1724 for entry in template {
1725 let schema = if entry == "$user" {
1726 if self.local.current_role_known {
1727 self.local.current_role.clone()
1728 } else {
1729 self.local.confidence = Confidence::Tainted;
1730 continue;
1731 }
1732 } else {
1733 entry
1734 };
1735 if self.schema_is_present(&schema) {
1736 if !effective.contains(&schema) {
1737 effective.push(schema);
1738 }
1739 } else if !self.schema_absence_is_authoritative(&schema) {
1740 self.local.confidence = Confidence::Tainted;
1741 if !effective.contains(&schema) {
1742 effective.push(schema);
1743 }
1744 }
1745 }
1746 self.local.search_path = effective;
1747 }
1748
1749 fn remap_schema_id(id: &mut ObjectId, old_name: &str, new_name: &str) {
1750 if id.schema == old_name {
1751 id.schema = new_name.to_string();
1752 }
1753 }
1754
1755 fn rename_schema_namespace(&mut self, old_name: &str, new_name: &str) {
1756 self.snapshot_namespace();
1757
1758 let mut aliases = Vec::new();
1759 let mut relations = HashMap::new();
1760 for (mut id, mut overlay) in std::mem::take(&mut self.local.relations) {
1761 let old_id = id.clone();
1762 Self::remap_schema_id(&mut id, old_name, new_name);
1763 if let RelationOverlay::Present(state) = &mut overlay {
1764 Self::remap_schema_id(&mut state.id, old_name, new_name);
1765 }
1766 if id != old_id {
1767 aliases.push((old_id, id.clone()));
1768 }
1769 relations.insert(id, overlay);
1770 }
1771 self.local.relations = relations;
1772
1773 let mut types = HashMap::new();
1774 for (mut id, mut overlay) in std::mem::take(&mut self.local.types) {
1775 let old_id = id.clone();
1776 Self::remap_schema_id(&mut id, old_name, new_name);
1777 if let TypeOverlay::Present(state) = &mut overlay {
1778 Self::remap_schema_id(&mut state.id, old_name, new_name);
1779 }
1780 if id != old_id {
1781 aliases.push((old_id, id.clone()));
1782 }
1783 types.insert(id, overlay);
1784 }
1785 self.local.types = types;
1786
1787 let mut functions = HashMap::new();
1788 for (mut id, mut overlay) in std::mem::take(&mut self.local.functions) {
1789 let old_id = id.clone();
1790 Self::remap_schema_id(&mut id, old_name, new_name);
1791 if let crate::model::function::FunctionOverlay::Present(state) = &mut overlay {
1792 Self::remap_schema_id(&mut state.id, old_name, new_name);
1793 }
1794 if id != old_id {
1795 aliases.push((old_id, id.clone()));
1796 }
1797 functions.insert(id, overlay);
1798 }
1799 self.local.functions = functions;
1800
1801 let mut sequences = HashMap::new();
1802 for (mut id, mut overlay) in std::mem::take(&mut self.local.sequences) {
1803 let old_id = id.clone();
1804 Self::remap_schema_id(&mut id, old_name, new_name);
1805 if let SequenceOverlay::Present(state) = &mut overlay {
1806 Self::remap_schema_id(&mut state.id, old_name, new_name);
1807 if let Some((table, _)) = &mut state.owned_by {
1808 Self::remap_schema_id(table, old_name, new_name);
1809 }
1810 }
1811 if id != old_id {
1812 aliases.push((old_id, id.clone()));
1813 }
1814 sequences.insert(id, overlay);
1815 }
1816 self.local.sequences = sequences;
1817
1818 let mut triggers = HashMap::new();
1819 for (mut id, mut overlay) in std::mem::take(&mut self.local.triggers) {
1820 let old_id = id.clone();
1821 Self::remap_schema_id(&mut id, old_name, new_name);
1822 if let TriggerOverlay::Present(state) = &mut overlay {
1823 Self::remap_schema_id(&mut state.id, old_name, new_name);
1824 Self::remap_schema_id(&mut state.table_id, old_name, new_name);
1825 }
1826 if id != old_id {
1827 aliases.push((old_id, id.clone()));
1828 }
1829 triggers.insert(id, overlay);
1830 }
1831 self.local.triggers = triggers;
1832
1833 for overlay in self.local.publications.values_mut() {
1834 let crate::model::replication::PublicationOverlay::Present(publication) = overlay
1835 else {
1836 continue;
1837 };
1838 let crate::analysis::facts::PublicationScope::Explicit(objects) =
1839 &mut publication.scope
1840 else {
1841 continue;
1842 };
1843 for object in objects {
1844 match object {
1845 crate::analysis::facts::PublicationObjectFact::Table { name, .. } => {
1846 if name
1847 .schema
1848 .as_ref()
1849 .is_some_and(|schema| schema.resolve() == old_name)
1850 {
1851 name.schema = Some(crate::ast::identifiers::Ident::new(new_name, true));
1852 }
1853 }
1854 crate::analysis::facts::PublicationObjectFact::SchemaTables {
1855 schema, ..
1856 } if schema == old_name => *schema = new_name.to_string(),
1857 _ => {}
1858 }
1859 }
1860 }
1861
1862 self.local.constraints = std::mem::take(&mut self.local.constraints)
1863 .into_iter()
1864 .map(|((mut table, name), mut constraint)| {
1865 Self::remap_schema_id(&mut table, old_name, new_name);
1866 Self::remap_schema_id(&mut constraint.table_id, old_name, new_name);
1867 ((table, name), constraint)
1868 })
1869 .collect();
1870 self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
1871 .into_iter()
1872 .map(|(mut table, name)| {
1873 Self::remap_schema_id(&mut table, old_name, new_name);
1874 (table, name)
1875 })
1876 .collect();
1877
1878 self.local.graph.mutate_edges(|edges| {
1879 for edge in edges {
1880 match &mut edge.kind {
1881 DependencyKind::PublicationIncludes { .. } => {
1884 Self::remap_schema_id(&mut edge.dependent, old_name, new_name);
1885 }
1886 DependencyKind::TriggerOnTable {
1887 trigger_id,
1888 function_id,
1889 } => {
1890 Self::remap_schema_id(&mut edge.dependent, old_name, new_name);
1891 Self::remap_schema_id(&mut edge.referenced, old_name, new_name);
1892 Self::remap_schema_id(trigger_id, old_name, new_name);
1893 Self::remap_schema_id(function_id, old_name, new_name);
1894 }
1895 _ => {
1896 Self::remap_schema_id(&mut edge.dependent, old_name, new_name);
1897 Self::remap_schema_id(&mut edge.referenced, old_name, new_name);
1898 }
1899 }
1900 }
1901 });
1902 for (old_id, new_id) in aliases {
1903 self.local.graph.add_edge(DependencyEdge::new(
1904 old_id,
1905 new_id,
1906 DependencyKind::RenameTo,
1907 ));
1908 }
1909
1910 let remap_set = |set: &mut HashSet<ObjectId>| {
1911 *set = std::mem::take(set)
1912 .into_iter()
1913 .map(|mut id| {
1914 Self::remap_schema_id(&mut id, old_name, new_name);
1915 id
1916 })
1917 .collect();
1918 };
1919 remap_set(&mut self.baseline_relations);
1920 remap_set(&mut self.baseline_indexes);
1921 remap_set(&mut self.baseline_fk_dependencies);
1922 remap_set(&mut self.baseline_sequences);
1923 self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
1924 .into_iter()
1925 .map(|(mut table, name)| {
1926 Self::remap_schema_id(&mut table, old_name, new_name);
1927 (table, name)
1928 })
1929 .collect();
1930 if let Some(schemas) = &mut self.baseline_schemas
1931 && schemas.remove(old_name)
1932 {
1933 schemas.insert(new_name.to_string());
1934 }
1935
1936 if let Some(SchemaOverlay::Present(mut schema)) = self.local.schemas.remove(old_name) {
1937 schema.name = new_name.to_string();
1938 self.local
1939 .schemas
1940 .insert(new_name.to_string(), SchemaOverlay::Present(schema));
1941 }
1942 self.refresh_role_sensitive_search_path();
1943 }
1944
1945 fn restore_persistent_role_context(&mut self) {
1946 self.local.current_role = self.local.persistent_current_role.clone();
1947 self.local.current_role_known = self.local.persistent_current_role_known;
1948 self.local.session_role = self.local.persistent_session_role.clone();
1949 self.local.session_role_known = self.local.persistent_session_role_known;
1950 self.local.search_path_template = self.local.session_search_path_template.clone();
1951 self.local.lock_timeout.reset_effective_to_session();
1952 self.local.statement_timeout.reset_effective_to_session();
1953 self.refresh_role_sensitive_search_path();
1954 }
1955
1956 fn apply_grant_to_relation(
1957 &mut self,
1958 id: &ObjectId,
1959 privileges: &HashSet<Privilege>,
1960 grantees: &[ObjectId],
1961 ) {
1962 self.snapshot_relation(id);
1963 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1964 for grantee in grantees {
1965 rel.privileges.grant(grantee.clone(), privileges.clone());
1966 }
1967 }
1968 }
1969
1970 fn apply_revoke_to_relation(
1971 &mut self,
1972 id: &ObjectId,
1973 privileges: &HashSet<Privilege>,
1974 revokees: &[ObjectId],
1975 ) {
1976 self.snapshot_relation(id);
1977 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1978 for revokee in revokees {
1979 rel.privileges.revoke(revokee, privileges);
1980 }
1981 }
1982 }
1983
1984 pub fn apply(
1985 &mut self,
1986 mutation: &Mutation,
1987 precomputed_cascade: Option<&CascadeResult>,
1988 ) -> MutationResult {
1989 if self.local.transaction_aborted
1990 && !matches!(
1991 mutation,
1992 Mutation::CommitTransaction
1993 | Mutation::CommitAndChain
1994 | Mutation::RollbackTransaction
1995 | Mutation::RollbackAndChain
1996 | Mutation::RollbackToSavepoint(_)
1997 )
1998 {
1999 return MutationResult::NotExecuted;
2000 }
2001
2002 let result = self.apply_inner(mutation, precomputed_cascade);
2003 if matches!(result, MutationResult::Conflict { .. }) && !self.local.transactions.is_empty()
2004 {
2005 self.local.transaction_aborted = true;
2006 }
2007 result
2008 }
2009
2010 fn apply_inner(
2011 &mut self,
2012 mutation: &Mutation,
2013 precomputed_cascade: Option<&CascadeResult>,
2014 ) -> MutationResult {
2015 match mutation {
2016 Mutation::CreateSchema(create_schema) => self.apply_create_schema(create_schema),
2017 Mutation::AlterSchema(alter_schema) => self.apply_alter_schema(alter_schema),
2018 Mutation::DropSchema(drop_schema) => self.apply_drop_schema(drop_schema),
2019 Mutation::DropTable(drop) => self.apply_drop_table(drop, precomputed_cascade),
2020 Mutation::CreateTable(create) => self.apply_create_table(create),
2021 Mutation::CreateView(create) => self.apply_create_view(create),
2022 Mutation::CreateMaterializedView(create) => self.apply_create_materialized_view(create),
2023 Mutation::RefreshMaterializedView(refresh) => {
2024 self.apply_refresh_materialized_view(refresh)
2025 }
2026 Mutation::CreateIndex(create) => self.apply_create_index(create),
2027 Mutation::CreatePolicy(create_policy) => self.apply_create_policy(create_policy),
2028 Mutation::DropPolicy(drop_policy) => self.apply_drop_policy(drop_policy),
2029 Mutation::CreateTrigger(create_trigger) => self.apply_create_trigger(create_trigger),
2030 Mutation::DropTrigger(drop_trigger) => self.apply_drop_trigger(drop_trigger),
2031 Mutation::RenameTrigger(rename_trigger) => self.apply_rename_trigger(rename_trigger),
2032 Mutation::AlterTable(alter) => self.apply_alter_table(alter),
2033 Mutation::CreateType(create) => self.apply_create_type(create),
2034 Mutation::RenameType(rename) => self.apply_rename_type(rename),
2035 Mutation::AlterType(alter) => self.apply_alter_type(alter),
2036 Mutation::CreateDomain(create) => self.apply_create_domain(create),
2037 Mutation::AlterDomain(alter) => self.apply_alter_domain(alter),
2038 Mutation::DropDomain(drop) => self.apply_drop_domain(drop),
2039 Mutation::DropType(drop) => self.apply_drop_type(drop),
2040 Mutation::CreateSequence(create) => self.apply_create_sequence(create),
2041 Mutation::AlterSequence(alter) => self.apply_alter_sequence(alter),
2042 Mutation::DropSequence(drop) => self.apply_drop_sequence(drop),
2043 Mutation::Rename(rename) => self.apply_rename_relation(rename),
2044 Mutation::DropView(drop) => self.apply_drop_view(drop),
2045 Mutation::DropMaterializedView(drop) => self.apply_drop_materialized_view(drop),
2046 Mutation::DropIndex(drop) => self.apply_drop_index(drop),
2047 Mutation::ChangeRelationOwner { id, new_owner } => {
2048 self.apply_change_relation_owner(id, new_owner)
2049 }
2050 Mutation::SearchPath(search_path) => self.apply_search_path(search_path),
2051 Mutation::TimeoutSetting(timeout) => self.apply_timeout_setting(timeout),
2052 Mutation::ResetSettings(target) => self.apply_reset_settings(target),
2053 Mutation::CheckTimeouts => self.apply_check_timeouts(),
2054 Mutation::SwitchRole {
2055 role,
2056 local,
2057 is_session_auth,
2058 } => self.apply_switch_role(role, *local, *is_session_auth),
2059 Mutation::BeginTransaction => self.apply_begin_transaction(),
2060 Mutation::CommitTransaction => self.apply_commit_transaction(false),
2061 Mutation::CommitAndChain => self.apply_commit_transaction(true),
2062 Mutation::RollbackTransaction => self.apply_rollback_transaction(false),
2063 Mutation::RollbackAndChain => self.apply_rollback_transaction(true),
2064 Mutation::RollbackToSavepoint(rollback) => self.apply_rollback_to_savepoint(rollback),
2065 Mutation::Savepoint(savepoint) => self.apply_savepoint(savepoint),
2066 Mutation::ReleaseSavepoint(release) => self.apply_release_savepoint(release),
2067 Mutation::Opaque(opaque) => self.apply_opaque(opaque),
2068 Mutation::CreateFunction(function) => self.apply_create_function(function),
2069 Mutation::AlterFunction(function) => self.apply_alter_function(function),
2070 Mutation::DropFunction(function) => self.apply_drop_function(function),
2071 Mutation::CreateProcedure(procedure) => self.apply_create_procedure(procedure),
2072 Mutation::AlterProcedure(procedure) => self.apply_alter_procedure(procedure),
2073 Mutation::DropProcedure(procedure) => self.apply_drop_procedure(procedure),
2074 Mutation::CreateAggregate(aggregate) => self.apply_create_aggregate(aggregate),
2075 Mutation::AlterAggregate(aggregate) => self.apply_alter_aggregate(aggregate),
2076 Mutation::DropAggregate(aggregate) => self.apply_drop_aggregate(aggregate),
2077 Mutation::CreatePublication(publication) => self.apply_create_publication(publication),
2078 Mutation::AlterPublication(publication) => self.apply_alter_publication(publication),
2079 Mutation::DropPublication(publication) => self.apply_drop_publication(publication),
2080 Mutation::CreateSubscription(subscription) => {
2081 self.apply_create_subscription(subscription)
2082 }
2083 Mutation::AlterSubscription(subscription) => {
2084 self.apply_alter_subscription(subscription)
2085 }
2086 Mutation::DropSubscription(subscription) => self.apply_drop_subscription(subscription),
2087 Mutation::CreateRole(role) => self.apply_create_role(role),
2088 Mutation::AlterRole(role) => self.apply_alter_role(role),
2089 Mutation::DropRole(role) => self.apply_drop_role(role),
2090 Mutation::Grant(grant) => self.apply_grant(grant),
2091 Mutation::Revoke(revoke) => self.apply_revoke(revoke),
2092 Mutation::CreateDatabase(create_database) => {
2093 self.apply_create_database(create_database)
2094 }
2095 Mutation::AlterDatabase(alter_database) => self.apply_alter_database(alter_database),
2096 Mutation::DropDatabase(drop_database) => self.apply_drop_database(drop_database),
2097 Mutation::Vacuum { table_id, is_full } => self.apply_vacuum(table_id, *is_full),
2098 }
2099 }
2100
2101 fn snapshot_relation(&mut self, id: &ObjectId) {
2102 if let Some(frame) = self.local.transactions.last_mut() {
2103 let previous = self.local.relations.get(id).cloned();
2104 frame.undo_log.push(StateChange::RelationSnapshot {
2105 id: id.clone(),
2106 previous: Box::new(previous),
2107 });
2108 }
2109 }
2110
2111 fn snapshot_schema(&mut self, name: &str) {
2112 if let Some(frame) = self.local.transactions.last_mut() {
2113 frame.undo_log.push(StateChange::SchemaSnapshot {
2114 name: name.to_string(),
2115 previous: self.local.schemas.get(name).cloned(),
2116 });
2117 }
2118 }
2119
2120 fn snapshot_namespace(&mut self) {
2121 if let Some(frame) = self.local.transactions.last_mut() {
2122 frame.undo_log.push(StateChange::NamespaceSnapshot(Box::new(
2123 NamespaceSnapshot {
2124 schemas: self.local.schemas.clone(),
2125 relations: self.local.relations.clone(),
2126 types: self.local.types.clone(),
2127 functions: self.local.functions.clone(),
2128 sequences: self.local.sequences.clone(),
2129 publications: self.local.publications.clone(),
2130 triggers: self.local.triggers.clone(),
2131 constraints: self.local.constraints.clone(),
2132 graph: self.local.graph.edges().to_vec(),
2133 pending_validation: self.local.pending_validation.clone(),
2134 baseline_relations: self.baseline_relations.clone(),
2135 baseline_indexes: self.baseline_indexes.clone(),
2136 baseline_foreign_keys: self.baseline_foreign_keys.clone(),
2137 baseline_fk_dependencies: self.baseline_fk_dependencies.clone(),
2138 baseline_sequences: self.baseline_sequences.clone(),
2139 },
2140 )));
2141 }
2142 }
2143
2144 fn snapshot_type(&mut self, id: &ObjectId) {
2145 if let Some(frame) = self.local.transactions.last_mut() {
2146 let previous = self.local.types.get(id).cloned();
2147 frame.undo_log.push(StateChange::TypeSnapshot {
2148 id: id.clone(),
2149 previous,
2150 });
2151 }
2152 }
2153
2154 fn snapshot_sequence(&mut self, id: &ObjectId) {
2155 if let Some(frame) = self.local.transactions.last_mut() {
2156 let previous = self.local.sequences.get(id).cloned();
2157 frame.undo_log.push(StateChange::SequenceSnapshot {
2158 id: id.clone(),
2159 previous,
2160 });
2161 }
2162 }
2163
2164 fn move_function(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
2165 self.snapshot_function(old_id);
2166 self.snapshot_function(new_id);
2167 if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
2168 self.local.functions.remove(old_id)
2169 {
2170 function.id = new_id.clone();
2171 self.local.functions.insert(
2172 new_id.clone(),
2173 crate::model::function::FunctionOverlay::Present(function),
2174 );
2175 }
2176
2177 self.snapshot_graph_full();
2178 self.local.graph.propagate_function_rename(old_id, new_id);
2179 self.local.graph.add_edge(DependencyEdge::new(
2180 old_id.clone(),
2181 new_id.clone(),
2182 DependencyKind::RenameTo,
2183 ));
2184 }
2185
2186 pub(super) fn validate_function_move(
2187 &mut self,
2188 old_id: &ObjectId,
2189 new_id: &ObjectId,
2190 ) -> Result<(), MutationResult> {
2191 if old_id == new_id {
2192 return Ok(());
2193 }
2194 self.ensure_schema_target(&new_id.schema)?;
2195 match self.local.functions.get(new_id) {
2196 Some(crate::model::function::FunctionOverlay::Present(_)) => {
2197 Err(MutationResult::Conflict {
2198 reason: format!("routine '{}' already exists", new_id),
2199 })
2200 }
2201 Some(crate::model::function::FunctionOverlay::Dropped) => Ok(()),
2204 None => Ok(()),
2205 }
2206 }
2207
2208 fn snapshot_function(&mut self, id: &ObjectId) {
2209 if let Some(frame) = self.local.transactions.last_mut() {
2210 let previous = self.local.functions.get(id).cloned();
2211 frame.undo_log.push(StateChange::FunctionSnapshot {
2212 id: id.clone(),
2213 previous,
2214 });
2215 }
2216 }
2217
2218 fn snapshot_publication(&mut self, name: &str) {
2219 if let Some(frame) = self.local.transactions.last_mut() {
2220 let previous = self.local.publications.get(name).cloned();
2221 frame.undo_log.push(StateChange::PublicationSnapshot {
2222 id: ObjectId::new("", name),
2223 previous,
2224 });
2225 }
2226 }
2227
2228 fn snapshot_subscription(&mut self, name: &str) {
2229 if let Some(frame) = self.local.transactions.last_mut() {
2230 let previous = self.local.subscriptions.get(name).cloned();
2231 frame.undo_log.push(StateChange::SubscriptionSnapshot {
2232 id: ObjectId::new("", name),
2233 previous,
2234 });
2235 }
2236 }
2237
2238 fn snapshot_role(&mut self, id: &ObjectId) {
2239 if let Some(frame) = self.local.transactions.last_mut() {
2240 let previous = self.local.roles.get(id).cloned();
2241 frame.undo_log.push(StateChange::RoleSnapshot {
2242 id: id.clone(),
2243 previous,
2244 });
2245 }
2246 }
2247
2248 fn snapshot_trigger(&mut self, id: &ObjectId) {
2249 if let Some(frame) = self.local.transactions.last_mut() {
2250 let previous = self.local.triggers.get(id).cloned();
2251 frame.undo_log.push(StateChange::TriggerSnapshot {
2252 id: id.clone(),
2253 previous,
2254 });
2255 }
2256 }
2257
2258 fn snapshot_constraint(&mut self, table_id: &ObjectId, name: &str) {
2259 if let Some(frame) = self.local.transactions.last_mut() {
2260 let key = (table_id.clone(), name.to_string());
2261 let previous = self.local.constraints.get(&key).cloned();
2262 frame.undo_log.push(StateChange::ConstraintSnapshot {
2263 table_id: table_id.clone(),
2264 name: name.to_string(),
2265 previous,
2266 });
2267 }
2268 }
2269
2270 fn snapshot_role_context(&mut self) {
2271 if let Some(frame) = self.local.transactions.last_mut() {
2272 frame.undo_log.push(StateChange::RoleContextSnapshot {
2273 current_role: self.local.current_role.clone(),
2274 current_role_known: self.local.current_role_known,
2275 persistent_current_role: self.local.persistent_current_role.clone(),
2276 persistent_current_role_known: self.local.persistent_current_role_known,
2277 session_role: self.local.session_role.clone(),
2278 session_role_known: self.local.session_role_known,
2279 persistent_session_role: self.local.persistent_session_role.clone(),
2280 persistent_session_role_known: self.local.persistent_session_role_known,
2281 });
2282 }
2283 }
2284
2285 fn snapshot_search_path(&mut self) {
2286 if let Some(frame) = self.local.transactions.last_mut() {
2287 frame.undo_log.push(StateChange::SearchPathSnapshot {
2288 previous: self.local.search_path.clone(),
2289 previous_template: self.local.search_path_template.clone(),
2290 previous_session_template: self.local.session_search_path_template.clone(),
2291 });
2292 }
2293 }
2294
2295 fn snapshot_timeout_settings(&mut self) {
2296 if let Some(frame) = self.local.transactions.last_mut() {
2297 frame.undo_log.push(StateChange::TimeoutSettingsSnapshot {
2298 lock_timeout: self.local.lock_timeout.clone(),
2299 statement_timeout: self.local.statement_timeout.clone(),
2300 });
2301 }
2302 }
2303
2304 fn snapshot_generation_counter(&mut self) {
2305 if let Some(frame) = self.local.transactions.last_mut() {
2306 frame.undo_log.push(StateChange::GenerationCounterSnapshot {
2307 previous: self.local.generation_counter,
2308 });
2309 }
2310 }
2311
2312 #[allow(dead_code)]
2313 fn snapshot_pending_validation(&mut self) {
2314 if let Some(frame) = self.local.transactions.last_mut() {
2315 frame.undo_log.push(StateChange::PendingValidationSnapshot {
2316 previous: self.local.pending_validation.clone(),
2317 });
2318 }
2319 }
2320
2321 fn snapshot_confidence(&mut self) {
2322 if let Some(frame) = self.local.transactions.last_mut() {
2323 frame.undo_log.push(StateChange::ConfidenceSnapshot {
2324 previous: self.local.confidence.clone(),
2325 });
2326 }
2327 }
2328
2329 fn snapshot_graph(&mut self) {
2330 if let Some(frame) = self.local.transactions.last_mut() {
2331 frame.undo_log.push(StateChange::GraphLengthMarker {
2332 len: self.local.graph.edge_count(),
2333 });
2334 }
2335 }
2336
2337 fn snapshot_graph_full(&mut self) {
2338 if let Some(frame) = self.local.transactions.last_mut() {
2339 frame.undo_log.push(StateChange::GraphSnapshot {
2340 previous: self.local.graph.edges().to_vec(),
2341 });
2342 }
2343 }
2344
2345 fn rollback_frame(&mut self, mut frame: TransactionFrame) {
2346 self.rollback_undo_log(std::mem::take(&mut frame.undo_log));
2347 }
2348
2349 fn rollback_undo_log(&mut self, mut undo_log: Vec<StateChange>) {
2350 while let Some(change) = undo_log.pop() {
2351 match change {
2352 StateChange::SchemaSnapshot { name, previous } => match previous {
2353 Some(overlay) => {
2354 self.local.schemas.insert(name, overlay);
2355 }
2356 None => {
2357 self.local.schemas.remove(&name);
2358 }
2359 },
2360 StateChange::NamespaceSnapshot(snapshot) => {
2361 self.local.schemas = snapshot.schemas;
2362 self.local.relations = snapshot.relations;
2363 self.local.types = snapshot.types;
2364 self.local.functions = snapshot.functions;
2365 self.local.sequences = snapshot.sequences;
2366 self.local.publications = snapshot.publications;
2367 self.local.triggers = snapshot.triggers;
2368 self.local.constraints = snapshot.constraints;
2369 self.local.graph.replace_edges(snapshot.graph);
2370 self.local.pending_validation = snapshot.pending_validation;
2371 self.baseline_relations = snapshot.baseline_relations;
2372 self.baseline_indexes = snapshot.baseline_indexes;
2373 self.baseline_foreign_keys = snapshot.baseline_foreign_keys;
2374 self.baseline_fk_dependencies = snapshot.baseline_fk_dependencies;
2375 self.baseline_sequences = snapshot.baseline_sequences;
2376 }
2377 StateChange::RelationSnapshot { id, previous } => {
2378 if let Some(prev) = *previous {
2379 self.local.relations.insert(id, prev);
2380 } else {
2381 self.local.relations.remove(&id);
2382 }
2383 }
2384 StateChange::TypeSnapshot { id, previous } => {
2385 if let Some(prev) = previous {
2386 self.local.types.insert(id, prev);
2387 } else {
2388 self.local.types.remove(&id);
2389 }
2390 }
2391 StateChange::SequenceSnapshot { id, previous } => {
2392 if let Some(prev) = previous {
2393 self.local.sequences.insert(id, prev);
2394 } else {
2395 self.local.sequences.remove(&id);
2396 }
2397 }
2398 StateChange::FunctionSnapshot { id, previous } => {
2399 if let Some(prev) = previous {
2400 self.local.functions.insert(id, prev);
2401 } else {
2402 self.local.functions.remove(&id);
2403 }
2404 }
2405 StateChange::PublicationSnapshot { id, previous } => {
2406 if let Some(prev) = previous {
2407 self.local.publications.insert(id.name, prev);
2408 } else {
2409 self.local.publications.remove(&id.name);
2410 }
2411 }
2412 StateChange::SubscriptionSnapshot { id, previous } => {
2413 if let Some(prev) = previous {
2414 self.local.subscriptions.insert(id.name, prev);
2415 } else {
2416 self.local.subscriptions.remove(&id.name);
2417 }
2418 }
2419 StateChange::RoleSnapshot { id, previous } => {
2420 if let Some(prev) = previous {
2421 self.local.roles.insert(id, prev);
2422 } else {
2423 self.local.roles.remove(&id);
2424 }
2425 }
2426 StateChange::TriggerSnapshot { id, previous } => {
2427 if let Some(prev) = previous {
2428 self.local.triggers.insert(id, prev);
2429 } else {
2430 self.local.triggers.remove(&id);
2431 }
2432 }
2433 StateChange::ConstraintSnapshot {
2434 table_id,
2435 name,
2436 previous,
2437 } => {
2438 let key = (table_id, name);
2439 if let Some(previous) = previous {
2440 self.local.constraints.insert(key, previous);
2441 } else {
2442 self.local.constraints.remove(&key);
2443 }
2444 }
2445 StateChange::BaselineForeignKeysSnapshot { previous } => {
2446 self.baseline_foreign_keys = previous;
2447 }
2448 StateChange::GraphLengthMarker { len } => {
2449 self.local.graph.truncate(len);
2450 }
2451 StateChange::GraphSnapshot { previous } => {
2452 self.local.graph.replace_edges(previous);
2453 }
2454 StateChange::RoleContextSnapshot {
2455 current_role,
2456 current_role_known,
2457 persistent_current_role,
2458 persistent_current_role_known,
2459 session_role,
2460 session_role_known,
2461 persistent_session_role,
2462 persistent_session_role_known,
2463 } => {
2464 self.local.current_role = current_role;
2465 self.local.current_role_known = current_role_known;
2466 self.local.persistent_current_role = persistent_current_role;
2467 self.local.persistent_current_role_known = persistent_current_role_known;
2468 self.local.session_role = session_role;
2469 self.local.session_role_known = session_role_known;
2470 self.local.persistent_session_role = persistent_session_role;
2471 self.local.persistent_session_role_known = persistent_session_role_known;
2472 }
2473 StateChange::SearchPathSnapshot {
2474 previous,
2475 previous_template,
2476 previous_session_template,
2477 } => {
2478 self.local.search_path = previous;
2479 self.local.search_path_template = previous_template;
2480 self.local.session_search_path_template = previous_session_template;
2481 }
2482 StateChange::TimeoutSettingsSnapshot {
2483 lock_timeout,
2484 statement_timeout,
2485 } => {
2486 self.local.lock_timeout = lock_timeout;
2487 self.local.statement_timeout = statement_timeout;
2488 }
2489 StateChange::GenerationCounterSnapshot { previous } => {
2490 self.local.generation_counter = previous;
2491 }
2492 StateChange::PendingValidationSnapshot { previous } => {
2493 self.local.pending_validation = previous;
2494 }
2495 StateChange::ConfidenceSnapshot { previous } => {
2496 self.local.confidence = previous;
2497 }
2498 }
2499 }
2500 }
2501
2502 pub(crate) fn transaction_undo_checkpoint(&self) -> Option<(usize, usize)> {
2503 self.local
2504 .transactions
2505 .last()
2506 .map(|frame| (self.local.transactions.len(), frame.undo_log.len()))
2507 }
2508
2509 pub(crate) fn rollback_to_transaction_undo_checkpoint(
2510 &mut self,
2511 transaction_depth: usize,
2512 undo_len: usize,
2513 ) -> Result<(), &'static str> {
2514 if self.local.transactions.len() != transaction_depth {
2515 return Err("statement changed transaction depth while using an undo checkpoint");
2516 }
2517 let Some(frame) = self.local.transactions.last_mut() else {
2518 return Err("statement undo checkpoint lost its transaction frame");
2519 };
2520 if frame.undo_log.len() < undo_len {
2521 return Err("statement shortened the transaction undo log unexpectedly");
2522 }
2523 let statement_undo = frame.undo_log.split_off(undo_len);
2524 self.rollback_undo_log(statement_undo);
2525 Ok(())
2526 }
2527}