1use crate::analysis::facts::{
2 ResetSettingTarget, SearchPathTarget, TableConstraintFact, TimeoutSetting, TimeoutSettingValue,
3};
4use crate::analysis::graph::{DependencyEdge, DependencyGraph, DependencyKind};
5use crate::analysis::mutations::{
6 AlterTableActionMutation, AlterTypeActionMutation, Mutation, PersistenceMutation,
7};
8use crate::analysis::settings::ScopedSetting;
9use crate::analysis::transaction::{NamespaceSnapshot, StateChange, TransactionFrame};
10use crate::ast::identifiers::ObjectId;
11use crate::db::cache::DbCache;
12use crate::model::constraint::{ConstraintKind, ConstraintState};
13pub use crate::model::relation::RelationOverlay;
14use crate::model::relation::{ColumnAction, Persistence, Privilege, RelationKind, RelationState};
15use crate::model::schema::SchemaOverlay;
16use crate::model::sequence::{SequenceKind, SequenceOverlay, SequenceState};
17use crate::model::trigger::TriggerOverlay;
18use crate::model::types::{TypeKind, TypeOverlay, TypeState};
19use std::collections::{HashMap, HashSet};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum Confidence {
23 Exact,
24 Tainted,
25}
26
27#[derive(Debug, PartialEq, Eq)]
28pub enum MutationResult {
29 Applied,
30 Skipped,
31 NotExecuted,
34 Conflict {
35 reason: String,
36 },
37}
38
39#[derive(Debug, Default, Clone)]
40pub struct CascadeResult {
41 pub dropped_relations: HashSet<ObjectId>,
42 pub dropped_indexes: HashSet<ObjectId>,
43 pub dropped_constraints: HashSet<(ObjectId, String)>,
44}
45
46#[derive(Clone)]
47pub struct LocalState {
48 pub schemas: HashMap<String, SchemaOverlay>,
49 pub relations: HashMap<ObjectId, RelationOverlay>,
50 pub types: HashMap<ObjectId, TypeOverlay>,
51 pub functions: HashMap<ObjectId, crate::model::function::FunctionOverlay>,
52 pub sequences: HashMap<ObjectId, SequenceOverlay>,
53 pub publications: HashMap<String, crate::model::replication::PublicationOverlay>,
54 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionOverlay>,
55 pub roles: HashMap<ObjectId, crate::model::role::RoleOverlay>,
56 pub triggers: HashMap<ObjectId, TriggerOverlay>,
57 pub constraints: HashMap<(ObjectId, String), ConstraintState>,
58 pub graph: DependencyGraph,
59 pub search_path: Vec<String>,
60 pub default_search_path: Vec<String>,
61 pub search_path_template: Vec<String>,
62 pub session_search_path_template: Vec<String>,
63 pub default_search_path_template: Vec<String>,
64 pub lock_timeout: ScopedSetting<Option<u64>>,
65 pub statement_timeout: ScopedSetting<Option<u64>>,
66 pub current_role: String,
69 pub current_role_known: bool,
72 pub persistent_current_role: String,
75 pub persistent_current_role_known: bool,
76 pub session_role: String,
80 pub session_role_known: bool,
83 pub persistent_session_role: String,
85 pub persistent_session_role_known: bool,
86 pub authenticated_role: String,
88 pub authenticated_role_known: bool,
89 pub roles_known: bool,
91 pub confidence: Confidence,
92 pub transactions: Vec<TransactionFrame>,
93 pub transaction_aborted: bool,
94 pub pending_validation: HashSet<(ObjectId, String)>,
95 pub generation_counter: u64,
96}
97
98#[derive(Clone, Debug)]
99pub struct PreState {
100 pub relations: HashMap<ObjectId, crate::model::relation::RelationState>,
101 pub functions: HashMap<ObjectId, crate::model::function::FunctionState>,
102 pub roles: HashMap<ObjectId, crate::model::role::RoleState>,
103 pub publications: HashMap<String, crate::model::replication::PublicationState>,
104 pub subscriptions: HashMap<String, crate::model::replication::SubscriptionState>,
105 pub sequences: HashMap<ObjectId, crate::model::sequence::SequenceState>,
106 pub types: HashMap<ObjectId, crate::model::types::TypeState>,
107 pub indexes: Vec<crate::analysis::graph::DependencyEdge>,
108}
109
110#[derive(Clone)]
111pub struct AnalysisState {
112 pub pg_version_num: Option<u32>,
113 pub baseline_available: bool,
117 pub baseline_schemas: Option<HashSet<String>>,
121 pub baseline_relations: HashSet<ObjectId>,
122 pub baseline_indexes: HashSet<ObjectId>,
123 pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
124 pub baseline_fk_dependencies: HashSet<ObjectId>,
125 pub baseline_sequences: HashSet<ObjectId>,
126 pub local: LocalState,
127}
128
129impl AnalysisState {
130 fn trigger_key(table_id: &ObjectId, name: &str) -> ObjectId {
131 ObjectId::new(&table_id.schema, format!("{}\0{name}", table_id.name))
135 }
136
137 fn publication_object_key(
138 &self,
139 object: &crate::analysis::facts::PublicationObjectFact,
140 ) -> String {
141 match object {
142 crate::analysis::facts::PublicationObjectFact::Table { name, .. } => {
143 format!("table\0{}", self.resolve_relation_id(name))
144 }
145 crate::analysis::facts::PublicationObjectFact::SchemaTables { schema, .. } => {
146 format!("schema\0{schema}")
147 }
148 crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand => {
149 format!(
150 "schema\0{}",
151 self.local
152 .search_path
153 .first()
154 .map(String::as_str)
155 .unwrap_or("public")
156 )
157 }
158 crate::analysis::facts::PublicationObjectFact::Unknown => "unknown".to_string(),
159 }
160 }
161
162 fn replace_publication_edges(
163 &mut self,
164 publication_name: &str,
165 scope: &crate::analysis::facts::PublicationScope,
166 ) {
167 self.snapshot_graph_full();
168 self.local.graph.edges.retain(|edge| {
169 !matches!(
170 &edge.kind,
171 DependencyKind::PublicationIncludes { publication_name: name }
172 if name == publication_name
173 )
174 });
175 if let crate::analysis::facts::PublicationScope::Explicit(objects) = scope {
176 for object in objects {
177 if let crate::analysis::facts::PublicationObjectFact::Table { name, .. } = object {
178 self.local.graph.edges.push(DependencyEdge::new(
179 self.resolve_relation_id(name),
180 ObjectId::new("public", publication_name),
181 DependencyKind::PublicationIncludes {
182 publication_name: publication_name.to_string(),
183 },
184 ));
185 }
186 }
187 }
188 }
189
190 fn validate_publication_scope(
191 &mut self,
192 scope: &crate::analysis::facts::PublicationScope,
193 ) -> Result<(), String> {
194 let crate::analysis::facts::PublicationScope::Explicit(objects) = scope else {
195 return Ok(());
196 };
197 let mut object_keys = HashSet::new();
198 for object in objects {
199 if !object_keys.insert(self.publication_object_key(object)) {
200 return Err("publication contains the same object more than once".to_string());
201 }
202 match object {
203 crate::analysis::facts::PublicationObjectFact::Table { name, columns, .. } => {
204 let id = self.resolve_relation_id(name);
205 match self.local.relations.get(&id) {
206 Some(RelationOverlay::Present(relation))
207 if relation.kind == RelationKind::Table
208 && relation.persistence == Persistence::Permanent =>
209 {
210 if let Some(columns) = columns {
211 let mut seen = HashSet::new();
212 for column in columns {
213 if !seen.insert(column) {
214 return Err(format!(
215 "publication lists column '{}' more than once for '{}'",
216 column, id
217 ));
218 }
219 if !relation.has_column(column) {
220 return Err(format!(
221 "publication column '{}.{}' does not exist",
222 id, column
223 ));
224 }
225 }
226 }
227 }
228 Some(RelationOverlay::Present(_)) => {
229 return Err(format!(
230 "publication target '{}' is not a permanent table",
231 id
232 ));
233 }
234 Some(RelationOverlay::Dropped) => {
235 return Err(format!("publication table '{}' does not exist", id));
236 }
237 None if self.baseline_available && self.baseline_covers_object(&id) => {
238 return Err(format!("publication table '{}' does not exist", id));
239 }
240 None => {
241 self.snapshot_confidence();
242 self.local.confidence = Confidence::Tainted;
243 }
244 }
245 }
246 crate::analysis::facts::PublicationObjectFact::SchemaTables { schema, .. } => {
247 if !self.schema_is_present(schema) {
248 if self.schema_absence_is_authoritative(schema) {
249 return Err(format!("publication schema '{}' does not exist", schema));
250 }
251 self.snapshot_confidence();
252 self.local.confidence = Confidence::Tainted;
253 }
254 }
255 crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand
256 | crate::analysis::facts::PublicationObjectFact::Unknown => {
257 self.snapshot_confidence();
258 self.local.confidence = Confidence::Tainted;
259 }
260 }
261 }
262 Ok(())
263 }
264
265 fn publication_scope_needs_inheritance_knowledge(
266 &self,
267 scope: &crate::analysis::facts::PublicationScope,
268 ) -> bool {
269 let crate::analysis::facts::PublicationScope::Explicit(objects) = scope else {
270 return false;
271 };
272 objects.iter().any(|object| match object {
273 crate::analysis::facts::PublicationObjectFact::Table {
274 name,
275 only,
276 include_partitions,
277 ..
278 } if !only || *include_partitions => {
279 let id = self.resolve_relation_id(name);
280 !matches!(
281 self.local.relations.get(&id),
282 Some(RelationOverlay::Present(relation)) if relation.generation > 0
283 ) || self.local.graph.edges.iter().any(|edge| {
284 matches!(edge.kind, DependencyKind::PartitionOf)
285 && self.local.graph.resolve_rename(&edge.referenced)
286 == self.local.graph.resolve_rename(&id)
287 })
288 }
289 _ => false,
290 })
291 }
292
293 fn taint_inheritance_sensitive_publication_scope(
294 &mut self,
295 scope: &crate::analysis::facts::PublicationScope,
296 ) {
297 if self.publication_scope_needs_inheritance_knowledge(scope) {
298 self.snapshot_confidence();
299 self.local.confidence = Confidence::Tainted;
300 }
301 }
302
303 fn subscription_option<'a>(
304 params: Option<&'a [crate::analysis::facts::AttributeFact]>,
305 name: &str,
306 ) -> Option<&'a str> {
307 params?
308 .iter()
309 .rev()
310 .find(|param| param.name.eq_ignore_ascii_case(name))
311 .map(|param| param.value.as_str())
312 }
313
314 fn postgres_boolean(value: &str) -> Option<bool> {
315 let value = value.trim().to_ascii_lowercase();
316 match value.as_str() {
317 "1" => return Some(true),
318 "0" => return Some(false),
319 "" => return None,
320 _ => {}
321 }
322
323 let mut matched = None;
324 for (spelling, parsed) in [
325 ("true", true),
326 ("yes", true),
327 ("on", true),
328 ("false", false),
329 ("no", false),
330 ("off", false),
331 ] {
332 if spelling.starts_with(&value) {
333 if matched.is_some() {
334 return None;
335 }
336 matched = Some(parsed);
337 }
338 }
339 matched
340 }
341
342 fn subscription_boolean_option(
343 params: Option<&[crate::analysis::facts::AttributeFact]>,
344 name: &str,
345 ) -> Option<bool> {
346 Self::subscription_option(params, name).and_then(Self::postgres_boolean)
347 }
348
349 fn validate_subscription_boolean_options(
350 params: Option<&[crate::analysis::facts::AttributeFact]>,
351 names: &[&str],
352 ) -> Result<(), String> {
353 let Some(params) = params else {
354 return Ok(());
355 };
356 for option in params {
357 if names
358 .iter()
359 .any(|name| option.name.eq_ignore_ascii_case(name))
360 && Self::postgres_boolean(&option.value).is_none()
361 {
362 return Err(format!(
363 "subscription option '{}' requires a PostgreSQL boolean value",
364 option.name
365 ));
366 }
367 }
368 Ok(())
369 }
370
371 fn set_subscription_option(
372 subscription: &mut crate::model::replication::SubscriptionState,
373 option: &crate::analysis::facts::AttributeFact,
374 ) {
375 let params = subscription.params.get_or_insert_with(Vec::new);
376 params.retain(|existing| !existing.name.eq_ignore_ascii_case(&option.name));
377 params.push(option.clone());
378 }
379
380 pub fn new(cache: DbCache) -> Self {
381 Self::with_baseline(cache, true)
382 }
383
384 pub fn with_baseline(cache: DbCache, baseline_available: bool) -> Self {
385 let source_lock_timeout =
386 baseline_available.then_some(cache.metadata.source_lock_timeout_ms);
387 let source_statement_timeout =
388 baseline_available.then_some(cache.metadata.source_statement_timeout_ms);
389 let default_search_path = cache.search_path.clone();
390 let default_search_path_template = if cache.metadata.schemas.is_none() {
391 cache
392 .metadata
393 .source_search_path
394 .clone()
395 .unwrap_or_else(|| default_search_path.clone())
396 } else {
397 default_search_path.clone()
398 };
399 let current_role_known = cache.metadata.source_role.is_some();
400 let current_role = cache
401 .metadata
402 .source_role
403 .clone()
404 .unwrap_or_else(|| "postgres".to_string());
405 let session_role_known = cache.metadata.source_session_role.is_some();
406 let session_role = cache
407 .metadata
408 .source_session_role
409 .clone()
410 .unwrap_or_else(|| current_role.clone());
411 let authenticated_role = session_role.clone();
412 let authenticated_role_known = session_role_known;
413 let persistent_current_role = current_role.clone();
414 let persistent_current_role_known = current_role_known;
415 let persistent_session_role = session_role.clone();
416 let persistent_session_role_known = session_role_known;
417 let roles_known = cache.metadata.source_session_role.is_some();
418 let baseline_schemas = cache
419 .metadata
420 .schemas
421 .as_ref()
422 .map(|schemas| schemas.iter().cloned().collect());
423 let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
424 let mut baseline_relations = HashSet::new();
425 let mut baseline_indexes = HashSet::new();
426 let mut baseline_foreign_keys = HashSet::new();
427 let mut baseline_fk_dependencies = HashSet::new();
428 let mut triggers = HashMap::new();
429 let mut constraints = HashMap::new();
430 let mut types = HashMap::new();
431 let mut graph = DependencyGraph::new();
432
433 let mut schemas: HashMap<String, SchemaOverlay> = cache
434 .schemas
435 .iter()
436 .map(|(name, schema)| (name.clone(), SchemaOverlay::Present(schema.clone())))
437 .collect();
438 let inferred_schema_owner = ObjectId::new(
444 "",
445 cache.metadata.source_role.as_deref().unwrap_or("postgres"),
446 );
447 for name in cache
448 .relations
449 .keys()
450 .map(|id| &id.schema)
451 .chain(cache.types.keys().map(|id| &id.schema))
452 .chain(cache.functions.keys().map(|id| &id.schema))
453 .chain(cache.sequences.keys().map(|id| &id.schema))
454 {
455 schemas.entry(name.clone()).or_insert_with(|| {
456 SchemaOverlay::Present(crate::model::schema::SchemaState {
457 name: name.clone(),
458 owner: inferred_schema_owner.clone(),
459 generation: 0,
460 })
461 });
462 }
463 if cache.schemas.is_empty() && cache.metadata.schemas.is_none() {
464 for name in &cache.search_path {
465 schemas.entry(name.clone()).or_insert_with(|| {
466 SchemaOverlay::Present(crate::model::schema::SchemaState {
467 name: name.clone(),
468 owner: inferred_schema_owner.clone(),
469 generation: 0,
470 })
471 });
472 }
473 }
474
475 let sequences = cache
476 .sequences
477 .iter()
478 .map(|(id, sequence)| (id.clone(), SequenceOverlay::Present(sequence.clone())))
479 .collect();
480 let baseline_sequences = cache.sequences.keys().cloned().collect();
481 for sequence in cache.sequences.values() {
482 if let Some((table, column)) = &sequence.owned_by {
483 graph.edges.push(DependencyEdge::new(
484 sequence.id.clone(),
485 table.clone(),
486 DependencyKind::SequenceOwnedBy {
487 column: column.clone(),
488 },
489 ));
490 }
491 }
492
493 for (id, rel_state) in cache.baseline_relations() {
494 if rel_state.is_fk_dependency {
495 baseline_fk_dependencies.insert(id.clone());
496 }
497 relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
498 baseline_relations.insert(id.clone());
499 }
500
501 for (id, type_state) in &cache.types {
502 types.insert(id.clone(), TypeOverlay::Present(type_state.clone()));
503 }
504 let type_catalog = types.clone();
505 for overlay in relations.values_mut() {
506 if let RelationOverlay::Present(relation) = overlay {
507 for column in &mut relation.columns {
508 column.type_id = column.data_type.as_deref().and_then(|raw| {
509 Self::resolve_type_reference_from_catalog(
510 raw,
511 &type_catalog,
512 &default_search_path,
513 )
514 });
515 }
516 }
517 }
518 for overlay in types.values_mut() {
519 if let TypeOverlay::Present(TypeState {
520 kind:
521 TypeKind::Domain {
522 base_type,
523 base_type_id,
524 },
525 ..
526 }) = overlay
527 {
528 *base_type_id = Self::resolve_type_reference_from_catalog(
529 base_type,
530 &type_catalog,
531 &default_search_path,
532 );
533 }
534 }
535 for fk in cache.foreign_keys {
536 baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
537 graph.edges.push(DependencyEdge::new(
538 fk.from_table,
539 fk.to_table,
540 DependencyKind::ForeignKey {
541 constraint_name: Some(fk.constraint_name),
542 from_columns: Vec::new(),
543 to_columns: Vec::new(),
544 from_generation: 0,
545 },
546 ));
547 }
548
549 for idx in cache.indexes {
550 baseline_indexes.insert(idx.index_id.clone());
552 graph.edges.push(DependencyEdge::new(
553 idx.index_id,
554 idx.table_id,
555 DependencyKind::IndexOnRelation {
556 using_method: None,
557 has_predicate: false,
558 is_concurrent: false,
559 is_unique: false,
560 eligibility_known: false,
561 },
562 ));
563 }
564
565 for dependency in cache.dependencies {
566 if dependency.deptype != "view" {
567 continue;
568 }
569 let (Some(obj_schema), Some(obj_name), Some(ref_schema), Some(ref_name)) = (
570 dependency.obj_schema,
571 dependency.obj_name,
572 dependency.ref_schema,
573 dependency.ref_name,
574 ) else {
575 continue;
576 };
577 let dependent = ObjectId::new(obj_schema, obj_name);
578 let referenced = ObjectId::new(ref_schema, ref_name);
579 if dependent == referenced {
584 continue;
585 }
586 let is_view = relations.get(&dependent).is_some_and(|relation| {
587 matches!(
588 relation,
589 RelationOverlay::Present(state)
590 if matches!(
591 state.kind,
592 crate::model::relation::RelationKind::View
593 | crate::model::relation::RelationKind::MaterializedView
594 )
595 )
596 });
597 if is_view && relations.contains_key(&referenced) {
598 graph.edges.push(DependencyEdge::new(
599 dependent,
600 referenced,
601 DependencyKind::ViewDependency { view_generation: 0 },
602 ));
603 }
604 }
605
606 for constraint in cache.constraints {
607 constraints.insert(
608 (constraint.table_id.clone(), constraint.name.clone()),
609 constraint,
610 );
611 }
612
613 for t in cache.triggers {
614 let trigger_key = Self::trigger_key(&t.table_id, &t.trigger_id.name);
615 triggers.insert(
616 trigger_key.clone(),
617 TriggerOverlay::Present(crate::model::trigger::TriggerState {
618 name: t.trigger_id.name.clone(),
619 id: trigger_key.clone(),
620 table_id: t.table_id.clone(),
621 enabled_mode: t.enabled_mode,
622 generation: 0,
623 }),
624 );
625 graph.edges.push(DependencyEdge::new(
626 trigger_key.clone(),
627 t.table_id,
628 DependencyKind::TriggerOnTable {
629 trigger_id: trigger_key,
630 function_id: t.function_id,
631 },
632 ));
633 }
634
635 let mut functions: HashMap<ObjectId, crate::model::function::FunctionOverlay> =
636 HashMap::new();
637 for (id, func_state) in &cache.functions {
638 functions.insert(
639 id.clone(),
640 crate::model::function::FunctionOverlay::Present(func_state.clone()),
641 );
642 }
643 for overlay in functions.values_mut() {
644 if let crate::model::function::FunctionOverlay::Present(function) = overlay {
645 function.arg_type_ids = function
646 .arg_types
647 .iter()
648 .map(|raw| {
649 Self::resolve_type_reference_from_catalog(
650 raw,
651 &type_catalog,
652 &default_search_path,
653 )
654 })
655 .collect();
656 function.return_type_id = Self::resolve_type_reference_from_catalog(
657 &function.return_type,
658 &type_catalog,
659 &default_search_path,
660 );
661 }
662 }
663
664 let publications = cache
665 .publications
666 .into_iter()
667 .map(|(name, publication)| {
668 if let crate::analysis::facts::PublicationScope::Explicit(objects) =
669 &publication.scope
670 {
671 for object in objects {
672 if let crate::analysis::facts::PublicationObjectFact::Table {
673 name: relation,
674 ..
675 } = object
676 {
677 let table_id = ObjectId::new(
678 relation
679 .schema
680 .as_ref()
681 .map(|schema| schema.resolve())
682 .unwrap_or_else(|| "public".to_string()),
683 relation.name.resolve(),
684 );
685 graph.edges.push(DependencyEdge::new(
686 table_id,
687 ObjectId::new("public", &name),
688 DependencyKind::PublicationIncludes {
689 publication_name: name.clone(),
690 },
691 ));
692 }
693 }
694 }
695 (
696 name,
697 crate::model::replication::PublicationOverlay::Present(publication),
698 )
699 })
700 .collect();
701 let subscriptions = cache
702 .subscriptions
703 .into_iter()
704 .map(|(name, subscription)| {
705 (
706 name,
707 crate::model::replication::SubscriptionOverlay::Present(subscription),
708 )
709 })
710 .collect();
711
712 let mut state = Self {
713 pg_version_num: cache.pg_version_num,
714 baseline_available,
715 baseline_schemas,
716 baseline_relations,
717 baseline_indexes,
718 baseline_foreign_keys,
719 baseline_fk_dependencies,
720 baseline_sequences,
721 local: LocalState {
722 schemas,
723 relations,
724 types,
725 functions,
726 sequences,
727 publications,
728 subscriptions,
729 roles: cache
730 .roles
731 .into_iter()
732 .map(|(id, role)| (id, crate::model::role::RoleOverlay::Present(role)))
733 .collect(),
734 triggers,
735 constraints,
736 graph,
737 search_path: default_search_path.clone(),
738 default_search_path,
739 search_path_template: default_search_path_template.clone(),
740 session_search_path_template: default_search_path_template.clone(),
741 default_search_path_template,
742 lock_timeout: ScopedSetting::new(source_lock_timeout),
743 statement_timeout: ScopedSetting::new(source_statement_timeout),
744 current_role,
745 current_role_known,
746 persistent_current_role,
747 persistent_current_role_known,
748 session_role,
749 session_role_known,
750 persistent_session_role,
751 persistent_session_role_known,
752 authenticated_role,
753 authenticated_role_known,
754 roles_known,
755 confidence: Confidence::Exact,
756 transactions: Vec::new(),
757 transaction_aborted: false,
758 pending_validation: HashSet::new(),
759 generation_counter: 0,
760 },
761 };
762 state.refresh_role_sensitive_search_path();
763 state.local.default_search_path = state.local.search_path.clone();
764 state
765 }
766
767 pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
768 self.local.relations.get(id)
769 }
770
771 pub fn resolve_function_schema(
772 &self,
773 name: &crate::ast::identifiers::QualifiedName,
774 sig_str: &str,
775 ) -> String {
776 if let Some(schema) = &name.schema {
777 return schema.resolve();
778 }
779 for schema in &self.local.search_path {
780 let candidate = ObjectId::new(schema.clone(), sig_str.to_string());
781 if self.local.functions.contains_key(&candidate) {
782 return schema.clone();
783 }
784 }
785 self.local
786 .search_path
787 .first()
788 .cloned()
789 .unwrap_or_else(|| "public".to_string())
790 }
791
792 pub fn resolve_relation_id(&self, name: &crate::ast::identifiers::QualifiedName) -> ObjectId {
793 if let Some(schema) = &name.schema {
794 return ObjectId::new(schema.resolve(), name.name.resolve());
795 }
796 let resolved_name = name.name.resolve();
797 for schema in &self.local.search_path {
798 let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
799 if self.local.relations.contains_key(&candidate) {
800 candidate.inferred_schema = true;
801 return candidate;
802 }
803 }
804 let schema = self
805 .local
806 .search_path
807 .first()
808 .cloned()
809 .unwrap_or_else(|| "public".to_string());
810 let mut id = ObjectId::new(schema, resolved_name);
811 id.inferred_schema = true;
812 id
813 }
814
815 pub fn relation_is_present(&self, id: &ObjectId) -> bool {
816 matches!(
817 self.local.relations.get(id),
818 Some(RelationOverlay::Present(_))
819 )
820 }
821
822 pub fn baseline_covers_object(&self, id: &ObjectId) -> bool {
826 self.baseline_schemas
827 .as_ref()
828 .is_none_or(|schemas| schemas.contains(&id.schema))
829 }
830
831 pub fn baseline_scope_omits_displayed_object<'a>(
832 &self,
833 object_name: &'a str,
834 ) -> Option<&'a str> {
835 let schemas = self.baseline_schemas.as_ref()?;
836 let (schema, _) = object_name.split_once('.')?;
837 (!schemas.contains(schema)).then_some(schema)
838 }
839
840 fn sequence_is_present(&self, id: &ObjectId) -> bool {
841 matches!(
842 self.local.sequences.get(id),
843 Some(SequenceOverlay::Present(_))
844 )
845 }
846
847 fn type_is_present(&self, id: &ObjectId) -> bool {
848 matches!(self.local.types.get(id), Some(TypeOverlay::Present(_)))
849 }
850
851 fn resolve_type_reference_from_catalog(
852 raw: &str,
853 types: &HashMap<ObjectId, TypeOverlay>,
854 search_path: &[String],
855 ) -> Option<ObjectId> {
856 let (schema, name) = Self::parse_type_reference(raw)?;
857 if let Some(schema) = schema {
858 let candidate = ObjectId::new(schema, name);
859 return matches!(types.get(&candidate), Some(TypeOverlay::Present(_)))
860 .then_some(candidate);
861 }
862 search_path.iter().find_map(|schema| {
863 let candidate = ObjectId::new(schema, &name);
864 matches!(types.get(&candidate), Some(TypeOverlay::Present(_))).then_some(candidate)
865 })
866 }
867
868 fn parse_type_reference(raw: &str) -> Option<(Option<String>, String)> {
872 let mut token = raw.trim();
873 while let Some(without_array) = token.strip_suffix("[]") {
874 token = without_array.trim_end();
875 }
876
877 let mut parts = Vec::new();
878 let mut current = String::new();
879 let mut quoted = false;
880 let mut part_is_quoted = false;
881 let mut chars = token.chars().peekable();
882 while let Some(character) = chars.next() {
883 match character {
884 '"' if quoted && chars.peek() == Some(&'"') => {
885 current.push('"');
886 chars.next();
887 }
888 '"' => {
889 quoted = !quoted;
890 part_is_quoted = true;
891 }
892 '.' if !quoted => {
893 parts.push(Self::resolve_type_identifier(¤t, part_is_quoted)?);
894 current.clear();
895 part_is_quoted = false;
896 }
897 character if !quoted && character.is_whitespace() => {}
898 character => current.push(character),
899 }
900 }
901 if quoted {
902 return None;
903 }
904 parts.push(Self::resolve_type_identifier(¤t, part_is_quoted)?);
905 match parts.as_slice() {
906 [name] => Some((None, name.clone())),
907 [schema, name] => Some((Some(schema.clone()), name.clone())),
908 _ => None,
909 }
910 }
911
912 fn resolve_type_identifier(identifier: &str, quoted: bool) -> Option<String> {
913 (!identifier.is_empty()).then(|| {
914 if quoted {
915 identifier.to_string()
916 } else {
917 identifier.to_lowercase()
918 }
919 })
920 }
921
922 fn resolve_type_reference(&self, raw: &str) -> Option<ObjectId> {
923 Self::resolve_type_reference_from_catalog(raw, &self.local.types, &self.local.search_path)
924 }
925
926 fn type_reference_name(id: &ObjectId, qualified: bool) -> String {
927 let quote = |identifier: &str| {
928 let unquoted = identifier
929 .chars()
930 .enumerate()
931 .all(|(index, character)| match index {
932 0 => character.is_ascii_lowercase() || character == '_',
933 _ => {
934 character.is_ascii_lowercase()
935 || character.is_ascii_digit()
936 || character == '_'
937 || character == '$'
938 }
939 });
940 if unquoted {
941 identifier.to_string()
942 } else {
943 format!("\"{}\"", identifier.replace('"', "\"\""))
944 }
945 };
946
947 if qualified {
948 format!("{}.{}", quote(&id.schema), quote(&id.name))
949 } else {
950 quote(&id.name)
951 }
952 }
953
954 fn remapped_type_display(raw: &str, new_id: &ObjectId, schema_changed: bool) -> String {
955 let suffix = raw.find('[').map(|index| &raw[index..]).unwrap_or("");
956 format!(
957 "{}{}",
958 Self::type_reference_name(new_id, schema_changed),
959 suffix
960 )
961 }
962
963 fn index_is_present(&self, id: &ObjectId) -> bool {
964 self.local.graph.edges.iter().any(|edge| {
965 matches!(edge.kind, DependencyKind::IndexOnRelation { .. }) && edge.dependent == *id
966 })
967 }
968
969 fn next_generated_constraint_name(
970 &self,
971 table: &ObjectId,
972 name1: &str,
973 name2: Option<&str>,
974 label: &str,
975 ) -> String {
976 (0..)
977 .map(|suffix| {
978 let label = if suffix == 0 {
979 label.to_string()
980 } else {
981 format!("{label}{suffix}")
982 };
983 Self::postgres_object_name(name1, name2, &label)
984 })
985 .find(|candidate| {
986 !self
987 .local
988 .constraints
989 .contains_key(&(table.clone(), candidate.clone()))
990 })
991 .expect("constraint suffix space is unbounded")
992 }
993
994 fn postgres_object_name(name1: &str, name2: Option<&str>, label: &str) -> String {
995 const MAX_IDENTIFIER_BYTES: usize = 63;
996
997 fn truncate(value: &str, max_bytes: usize) -> &str {
998 let mut end = max_bytes.min(value.len());
999 while !value.is_char_boundary(end) {
1000 end -= 1;
1001 }
1002 &value[..end]
1003 }
1004
1005 let separators = usize::from(name2.is_some()) + 1;
1006 let available = MAX_IDENTIFIER_BYTES.saturating_sub(label.len() + separators);
1007 let mut name1_bytes = name1.len();
1008 let mut name2_bytes = name2.map_or(0, str::len);
1009 while name1_bytes + name2_bytes > available {
1010 if name1_bytes > name2_bytes {
1011 name1_bytes -= 1;
1012 } else {
1013 name2_bytes -= 1;
1014 }
1015 }
1016
1017 let name1 = truncate(name1, name1_bytes);
1018 match name2 {
1019 Some(name2) => format!("{name1}_{}_{}", truncate(name2, name2_bytes), label),
1020 None => format!("{name1}_{label}"),
1021 }
1022 }
1023
1024 fn relation_namespace_is_taken(&self, id: &ObjectId) -> bool {
1025 self.relation_is_present(id)
1026 || self.sequence_is_present(id)
1027 || self.index_is_present(id)
1028 || self.type_is_present(id)
1029 }
1030
1031 fn next_implicit_sequence_id(
1032 &self,
1033 table: &ObjectId,
1034 column: &str,
1035 reserved: &HashSet<ObjectId>,
1036 ) -> ObjectId {
1037 (0..)
1038 .map(|suffix| {
1039 let label = if suffix == 0 {
1040 "seq".to_string()
1041 } else {
1042 format!("seq{suffix}")
1043 };
1044 ObjectId::new(
1045 &table.schema,
1046 Self::postgres_object_name(&table.name, Some(column), &label),
1047 )
1048 })
1049 .find(|candidate| {
1050 !reserved.contains(candidate) && !self.relation_namespace_is_taken(candidate)
1051 })
1052 .expect("implicit sequence suffix space is unbounded")
1053 }
1054
1055 fn sequence_nextval_default(id: &ObjectId) -> crate::analysis::expr_ir::ExprIr {
1056 crate::analysis::expr_ir::ExprIr::FunctionCall {
1057 name: "nextval".to_string(),
1058 args: vec![crate::analysis::expr_ir::ExprIr::Literal(format!(
1059 "{}.{}",
1060 id.schema, id.name
1061 ))],
1062 }
1063 }
1064
1065 pub fn column_was_added_in_transaction(&self, table_id: &ObjectId, column: &str) -> bool {
1066 if self.local.transactions.is_empty() {
1067 return false;
1068 }
1069
1070 for frame in &self.local.transactions {
1072 for change in &frame.undo_log {
1073 if let StateChange::RelationSnapshot { id, previous } = change
1074 && id == table_id
1075 {
1076 match previous.as_ref() {
1077 None | Some(RelationOverlay::Dropped) => {
1078 return true;
1079 }
1080 Some(RelationOverlay::Present(r)) => {
1081 let col_existed = r.columns.iter().any(|c| c.name == column);
1082 return !col_existed;
1083 }
1084 }
1085 }
1086 }
1087 }
1088 false
1089 }
1090
1091 pub fn capture_pre_state(&self) -> PreState {
1092 let mut relations = HashMap::new();
1093 for (id, overlay) in &self.local.relations {
1094 if let RelationOverlay::Present(s) = overlay {
1095 relations.insert(id.clone(), s.clone());
1096 }
1097 }
1098
1099 let mut functions = HashMap::new();
1100 for (id, overlay) in &self.local.functions {
1101 if let crate::model::function::FunctionOverlay::Present(s) = overlay {
1102 functions.insert(id.clone(), s.clone());
1103 }
1104 }
1105
1106 let mut roles = HashMap::new();
1107 for (name, overlay) in &self.local.roles {
1108 if let crate::model::role::RoleOverlay::Present(s) = overlay {
1109 roles.insert(name.clone(), s.clone());
1110 }
1111 }
1112
1113 let mut publications = HashMap::new();
1114 for (name, overlay) in &self.local.publications {
1115 if let crate::model::replication::PublicationOverlay::Present(s) = overlay {
1116 publications.insert(name.clone(), s.clone());
1117 }
1118 }
1119
1120 let mut subscriptions = HashMap::new();
1121 for (name, overlay) in &self.local.subscriptions {
1122 if let crate::model::replication::SubscriptionOverlay::Present(s) = overlay {
1123 subscriptions.insert(name.clone(), s.clone());
1124 }
1125 }
1126
1127 let mut sequences = HashMap::new();
1128 for (id, overlay) in &self.local.sequences {
1129 if let SequenceOverlay::Present(s) = overlay {
1130 sequences.insert(id.clone(), s.clone());
1131 }
1132 }
1133
1134 let mut types = HashMap::new();
1135 for (id, overlay) in &self.local.types {
1136 if let TypeOverlay::Present(s) = overlay {
1137 types.insert(id.clone(), s.clone());
1138 }
1139 }
1140
1141 let indexes = self
1142 .local
1143 .graph
1144 .edges
1145 .iter()
1146 .filter(|e| matches!(e.kind, DependencyKind::IndexOnRelation { .. }))
1147 .cloned()
1148 .collect();
1149
1150 PreState {
1151 relations,
1152 functions,
1153 roles,
1154 publications,
1155 subscriptions,
1156 sequences,
1157 types,
1158 indexes,
1159 }
1160 }
1161
1162 pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
1163 let mut result = CascadeResult::default();
1164 let mut visited = HashSet::new();
1165 self.walk_cascade(target_oid, &mut visited, &mut result);
1166 result
1167 }
1168
1169 fn walk_cascade(
1170 &self,
1171 current: &ObjectId,
1172 visited: &mut HashSet<ObjectId>,
1173 result: &mut CascadeResult,
1174 ) {
1175 let resolved_current = self.local.graph.resolve_rename(current).clone();
1176
1177 if !visited.insert(resolved_current.clone()) {
1178 return;
1179 }
1180
1181 result.dropped_relations.insert(resolved_current.clone());
1182
1183 for edge in &self.local.graph.edges {
1184 match &edge.kind {
1185 DependencyKind::ViewDependency { .. } => {
1186 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
1187 let resolved_view_id =
1188 self.local.graph.resolve_rename(&edge.dependent).clone();
1189 if !visited.contains(&resolved_view_id) {
1190 self.walk_cascade(&resolved_view_id, visited, result);
1191 }
1192 }
1193 }
1194 DependencyKind::IndexOnRelation { .. } => {
1195 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
1196 result
1197 .dropped_indexes
1198 .insert(self.local.graph.resolve_rename(&edge.dependent).clone());
1199 }
1200 }
1201 DependencyKind::ForeignKey {
1202 constraint_name, ..
1203 } => {
1204 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current
1205 && let Some(cname) = constraint_name
1206 {
1207 result.dropped_constraints.insert((
1208 self.local.graph.resolve_rename(&edge.dependent).clone(),
1209 cname.clone(),
1210 ));
1211 }
1212 }
1213 DependencyKind::PartitionOf
1214 if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current =>
1215 {
1216 let resolved_child = self.local.graph.resolve_rename(&edge.dependent).clone();
1217 if !visited.contains(&resolved_child) {
1218 self.walk_cascade(&resolved_child, visited, result);
1219 }
1220 }
1221 _ => {}
1222 }
1223 }
1224 }
1225
1226 fn resolve_grant_privileges(
1227 spec: &crate::analysis::facts::PrivilegeSpec,
1228 ) -> HashSet<Privilege> {
1229 match spec {
1230 crate::analysis::facts::PrivilegeSpec::All => vec![
1231 Privilege::Select,
1232 Privilege::Insert,
1233 Privilege::Update,
1234 Privilege::Delete,
1235 Privilege::Truncate,
1236 Privilege::References,
1237 Privilege::Trigger,
1238 ]
1239 .into_iter()
1240 .collect(),
1241 crate::analysis::facts::PrivilegeSpec::List(list) => list
1242 .iter()
1243 .filter_map(|p| match p {
1244 crate::analysis::facts::PrivilegeFact::Select => Some(Privilege::Select),
1245 crate::analysis::facts::PrivilegeFact::Insert => Some(Privilege::Insert),
1246 crate::analysis::facts::PrivilegeFact::Update => Some(Privilege::Update),
1247 crate::analysis::facts::PrivilegeFact::Delete => Some(Privilege::Delete),
1248 crate::analysis::facts::PrivilegeFact::Truncate => Some(Privilege::Truncate),
1249 crate::analysis::facts::PrivilegeFact::References => {
1250 Some(Privilege::References)
1251 }
1252 crate::analysis::facts::PrivilegeFact::Trigger => Some(Privilege::Trigger),
1253 _ => None,
1254 })
1255 .collect(),
1256 }
1257 }
1258
1259 fn resolve_role_name(
1260 role: &crate::analysis::facts::RoleFact,
1261 current_role: &str,
1262 session_role: &str,
1263 ) -> Option<ObjectId> {
1264 let name = match role {
1265 crate::analysis::facts::RoleFact::Named { name, .. } => Some(name.clone()),
1266 crate::analysis::facts::RoleFact::CurrentUser
1267 | crate::analysis::facts::RoleFact::CurrentRole => Some(current_role.to_string()),
1268 crate::analysis::facts::RoleFact::SessionUser => Some(session_role.to_string()),
1269 crate::analysis::facts::RoleFact::Unknown => None,
1270 }?;
1271 Some(ObjectId::new("", name))
1272 }
1273
1274 fn role_fact_identity(
1275 &self,
1276 role: &crate::analysis::facts::RoleFact,
1277 ) -> Option<(String, bool)> {
1278 match role {
1279 crate::analysis::facts::RoleFact::Named { name, .. } => Some((name.clone(), true)),
1280 crate::analysis::facts::RoleFact::CurrentUser
1281 | crate::analysis::facts::RoleFact::CurrentRole => Some((
1282 self.local.current_role.clone(),
1283 self.local.current_role_known,
1284 )),
1285 crate::analysis::facts::RoleFact::SessionUser => Some((
1286 self.local.session_role.clone(),
1287 self.local.session_role_known,
1288 )),
1289 crate::analysis::facts::RoleFact::Unknown => None,
1290 }
1291 }
1292
1293 fn present_role(&self, name: &str) -> Option<&crate::model::role::RoleState> {
1294 match self.local.roles.get(&ObjectId::new("", name)) {
1295 Some(crate::model::role::RoleOverlay::Present(role)) => Some(role),
1296 _ => None,
1297 }
1298 }
1299
1300 fn can_set_role_to(&self, target: &str) -> Option<bool> {
1301 if !self.local.roles_known || !self.local.session_role_known {
1302 return None;
1303 }
1304 if self.present_role(target).is_none() {
1305 return Some(false);
1306 }
1307 if self.local.session_role == target {
1308 return Some(true);
1309 }
1310 let session = self.present_role(&self.local.session_role)?;
1311 if session.is_superuser {
1312 return Some(true);
1313 }
1314
1315 let mut pending = session.can_set_role_to.clone();
1316 let mut visited = HashSet::new();
1317 while let Some(role_id) = pending.pop() {
1318 if !visited.insert(role_id.clone()) {
1319 continue;
1320 }
1321 if role_id.name == target {
1322 return Some(true);
1323 }
1324 if let Some(role) = self.present_role(&role_id.name) {
1325 pending.extend(role.can_set_role_to.iter().cloned());
1326 }
1327 }
1328 Some(false)
1329 }
1330
1331 fn can_set_session_authorization_to(&self, target: &str) -> Option<bool> {
1332 if !self.local.roles_known || !self.local.authenticated_role_known {
1333 return None;
1334 }
1335 if self.present_role(target).is_none() {
1336 return Some(false);
1337 }
1338 if self.local.authenticated_role == target {
1339 return Some(true);
1340 }
1341 Some(
1342 self.present_role(&self.local.authenticated_role)
1343 .is_some_and(|role| role.is_superuser),
1344 )
1345 }
1346
1347 fn schema_is_present(&self, name: &str) -> bool {
1348 matches!(
1349 self.local.schemas.get(name),
1350 Some(SchemaOverlay::Present(_))
1351 )
1352 }
1353
1354 fn schema_absence_is_authoritative(&self, name: &str) -> bool {
1355 if matches!(self.local.schemas.get(name), Some(SchemaOverlay::Dropped)) {
1356 return true;
1357 }
1358 self.baseline_available
1359 && self
1360 .baseline_schemas
1361 .as_ref()
1362 .is_none_or(|schemas| schemas.contains(name))
1363 }
1364
1365 fn refresh_role_sensitive_search_path(&mut self) {
1366 let template = self.local.search_path_template.clone();
1367 let mut effective = Vec::new();
1368 for entry in template {
1369 let schema = if entry == "$user" {
1370 if self.local.current_role_known {
1371 self.local.current_role.clone()
1372 } else {
1373 self.local.confidence = Confidence::Tainted;
1374 continue;
1375 }
1376 } else {
1377 entry
1378 };
1379 if self.schema_is_present(&schema) {
1380 if !effective.contains(&schema) {
1381 effective.push(schema);
1382 }
1383 } else if !self.schema_absence_is_authoritative(&schema) {
1384 self.local.confidence = Confidence::Tainted;
1385 if !effective.contains(&schema) {
1386 effective.push(schema);
1387 }
1388 }
1389 }
1390 self.local.search_path = effective;
1391 }
1392
1393 fn remap_schema_id(id: &mut ObjectId, old_name: &str, new_name: &str) {
1394 if id.schema == old_name {
1395 id.schema = new_name.to_string();
1396 }
1397 }
1398
1399 fn rename_schema_namespace(&mut self, old_name: &str, new_name: &str) {
1400 self.snapshot_namespace();
1401
1402 let mut aliases = Vec::new();
1403 let mut relations = HashMap::new();
1404 for (mut id, mut overlay) in std::mem::take(&mut self.local.relations) {
1405 let old_id = id.clone();
1406 Self::remap_schema_id(&mut id, old_name, new_name);
1407 if let RelationOverlay::Present(state) = &mut overlay {
1408 Self::remap_schema_id(&mut state.id, old_name, new_name);
1409 }
1410 if id != old_id {
1411 aliases.push((old_id, id.clone()));
1412 }
1413 relations.insert(id, overlay);
1414 }
1415 self.local.relations = relations;
1416
1417 let mut types = HashMap::new();
1418 for (mut id, mut overlay) in std::mem::take(&mut self.local.types) {
1419 let old_id = id.clone();
1420 Self::remap_schema_id(&mut id, old_name, new_name);
1421 if let TypeOverlay::Present(state) = &mut overlay {
1422 Self::remap_schema_id(&mut state.id, old_name, new_name);
1423 }
1424 if id != old_id {
1425 aliases.push((old_id, id.clone()));
1426 }
1427 types.insert(id, overlay);
1428 }
1429 self.local.types = types;
1430
1431 let mut functions = HashMap::new();
1432 for (mut id, mut overlay) in std::mem::take(&mut self.local.functions) {
1433 let old_id = id.clone();
1434 Self::remap_schema_id(&mut id, old_name, new_name);
1435 if let crate::model::function::FunctionOverlay::Present(state) = &mut overlay {
1436 Self::remap_schema_id(&mut state.id, old_name, new_name);
1437 }
1438 if id != old_id {
1439 aliases.push((old_id, id.clone()));
1440 }
1441 functions.insert(id, overlay);
1442 }
1443 self.local.functions = functions;
1444
1445 let mut sequences = HashMap::new();
1446 for (mut id, mut overlay) in std::mem::take(&mut self.local.sequences) {
1447 let old_id = id.clone();
1448 Self::remap_schema_id(&mut id, old_name, new_name);
1449 if let SequenceOverlay::Present(state) = &mut overlay {
1450 Self::remap_schema_id(&mut state.id, old_name, new_name);
1451 if let Some((table, _)) = &mut state.owned_by {
1452 Self::remap_schema_id(table, old_name, new_name);
1453 }
1454 }
1455 if id != old_id {
1456 aliases.push((old_id, id.clone()));
1457 }
1458 sequences.insert(id, overlay);
1459 }
1460 self.local.sequences = sequences;
1461
1462 let mut triggers = HashMap::new();
1463 for (mut id, mut overlay) in std::mem::take(&mut self.local.triggers) {
1464 let old_id = id.clone();
1465 Self::remap_schema_id(&mut id, old_name, new_name);
1466 if let TriggerOverlay::Present(state) = &mut overlay {
1467 Self::remap_schema_id(&mut state.id, old_name, new_name);
1468 Self::remap_schema_id(&mut state.table_id, old_name, new_name);
1469 }
1470 if id != old_id {
1471 aliases.push((old_id, id.clone()));
1472 }
1473 triggers.insert(id, overlay);
1474 }
1475 self.local.triggers = triggers;
1476
1477 for overlay in self.local.publications.values_mut() {
1478 let crate::model::replication::PublicationOverlay::Present(publication) = overlay
1479 else {
1480 continue;
1481 };
1482 let crate::analysis::facts::PublicationScope::Explicit(objects) =
1483 &mut publication.scope
1484 else {
1485 continue;
1486 };
1487 for object in objects {
1488 match object {
1489 crate::analysis::facts::PublicationObjectFact::Table { name, .. } => {
1490 if name
1491 .schema
1492 .as_ref()
1493 .is_some_and(|schema| schema.resolve() == old_name)
1494 {
1495 name.schema = Some(crate::ast::identifiers::Ident::new(new_name, true));
1496 }
1497 }
1498 crate::analysis::facts::PublicationObjectFact::SchemaTables {
1499 schema, ..
1500 } if schema == old_name => *schema = new_name.to_string(),
1501 _ => {}
1502 }
1503 }
1504 }
1505
1506 self.local.constraints = std::mem::take(&mut self.local.constraints)
1507 .into_iter()
1508 .map(|((mut table, name), mut constraint)| {
1509 Self::remap_schema_id(&mut table, old_name, new_name);
1510 Self::remap_schema_id(&mut constraint.table_id, old_name, new_name);
1511 ((table, name), constraint)
1512 })
1513 .collect();
1514 self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
1515 .into_iter()
1516 .map(|(mut table, name)| {
1517 Self::remap_schema_id(&mut table, old_name, new_name);
1518 (table, name)
1519 })
1520 .collect();
1521
1522 for edge in &mut self.local.graph.edges {
1523 Self::remap_schema_id(&mut edge.dependent, old_name, new_name);
1524 Self::remap_schema_id(&mut edge.referenced, old_name, new_name);
1525 if let DependencyKind::TriggerOnTable {
1526 trigger_id,
1527 function_id,
1528 } = &mut edge.kind
1529 {
1530 Self::remap_schema_id(trigger_id, old_name, new_name);
1531 Self::remap_schema_id(function_id, old_name, new_name);
1532 }
1533 }
1534 for (old_id, new_id) in aliases {
1535 self.local.graph.edges.push(DependencyEdge::new(
1536 old_id,
1537 new_id,
1538 DependencyKind::RenameTo,
1539 ));
1540 }
1541
1542 let remap_set = |set: &mut HashSet<ObjectId>| {
1543 *set = std::mem::take(set)
1544 .into_iter()
1545 .map(|mut id| {
1546 Self::remap_schema_id(&mut id, old_name, new_name);
1547 id
1548 })
1549 .collect();
1550 };
1551 remap_set(&mut self.baseline_relations);
1552 remap_set(&mut self.baseline_indexes);
1553 remap_set(&mut self.baseline_fk_dependencies);
1554 remap_set(&mut self.baseline_sequences);
1555 self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
1556 .into_iter()
1557 .map(|(mut table, name)| {
1558 Self::remap_schema_id(&mut table, old_name, new_name);
1559 (table, name)
1560 })
1561 .collect();
1562
1563 if let Some(SchemaOverlay::Present(mut schema)) = self.local.schemas.remove(old_name) {
1564 schema.name = new_name.to_string();
1565 self.local
1566 .schemas
1567 .insert(new_name.to_string(), SchemaOverlay::Present(schema));
1568 }
1569 self.refresh_role_sensitive_search_path();
1570 }
1571
1572 fn restore_persistent_role_context(&mut self) {
1573 self.local.current_role = self.local.persistent_current_role.clone();
1574 self.local.current_role_known = self.local.persistent_current_role_known;
1575 self.local.session_role = self.local.persistent_session_role.clone();
1576 self.local.session_role_known = self.local.persistent_session_role_known;
1577 self.local.search_path_template = self.local.session_search_path_template.clone();
1578 self.local.lock_timeout.reset_effective_to_session();
1579 self.local.statement_timeout.reset_effective_to_session();
1580 self.refresh_role_sensitive_search_path();
1581 }
1582
1583 fn apply_grant_to_relation(
1584 &mut self,
1585 id: &ObjectId,
1586 privileges: &HashSet<Privilege>,
1587 grantees: &[crate::analysis::facts::RoleFact],
1588 ) {
1589 self.snapshot_relation(id);
1590 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1591 for grantee in grantees {
1592 if let Some(role_id) = Self::resolve_role_name(
1593 grantee,
1594 &self.local.current_role,
1595 &self.local.session_role,
1596 ) {
1597 rel.privileges.grant(role_id, privileges.clone());
1598 }
1599 }
1600 }
1601 }
1602
1603 fn apply_revoke_to_relation(
1604 &mut self,
1605 id: &ObjectId,
1606 privileges: &HashSet<Privilege>,
1607 revokees: &[crate::analysis::facts::RoleFact],
1608 ) {
1609 self.snapshot_relation(id);
1610 if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1611 for revokee in revokees {
1612 if let Some(role_id) = Self::resolve_role_name(
1613 revokee,
1614 &self.local.current_role,
1615 &self.local.session_role,
1616 ) {
1617 rel.privileges.revoke(&role_id, privileges);
1618 }
1619 }
1620 }
1621 }
1622
1623 pub fn apply(
1624 &mut self,
1625 mutation: &Mutation,
1626 precomputed_cascade: Option<&CascadeResult>,
1627 ) -> MutationResult {
1628 if self.local.transaction_aborted
1629 && !matches!(
1630 mutation,
1631 Mutation::CommitTransaction
1632 | Mutation::CommitAndChain
1633 | Mutation::RollbackTransaction
1634 | Mutation::RollbackAndChain
1635 | Mutation::RollbackToSavepoint(_)
1636 )
1637 {
1638 return MutationResult::NotExecuted;
1639 }
1640
1641 let result = self.apply_inner(mutation, precomputed_cascade);
1642 if matches!(result, MutationResult::Conflict { .. }) && !self.local.transactions.is_empty()
1643 {
1644 self.local.transaction_aborted = true;
1645 }
1646 result
1647 }
1648
1649 fn apply_inner(
1650 &mut self,
1651 mutation: &Mutation,
1652 precomputed_cascade: Option<&CascadeResult>,
1653 ) -> MutationResult {
1654 match mutation {
1655 Mutation::CreateSchema(create_schema) => {
1656 if self.schema_is_present(&create_schema.name) {
1657 return if create_schema.if_not_exists {
1658 MutationResult::Skipped
1659 } else {
1660 MutationResult::Conflict {
1661 reason: format!("schema '{}' already exists", create_schema.name),
1662 }
1663 };
1664 }
1665 let (owner_name, owner_known) = match &create_schema.authorization {
1666 Some(role) => match self.role_fact_identity(role) {
1667 Some(identity) => identity,
1668 None => {
1669 self.snapshot_confidence();
1670 self.local.confidence = Confidence::Tainted;
1671 (self.local.current_role.clone(), false)
1672 }
1673 },
1674 None => (
1675 self.local.current_role.clone(),
1676 self.local.current_role_known,
1677 ),
1678 };
1679 if owner_known && self.local.roles_known && self.present_role(&owner_name).is_none()
1680 {
1681 return MutationResult::Conflict {
1682 reason: format!("role '{}' does not exist", owner_name),
1683 };
1684 }
1685 if !owner_known || !self.local.roles_known {
1686 self.snapshot_confidence();
1687 self.local.confidence = Confidence::Tainted;
1688 }
1689 self.snapshot_generation_counter();
1690 self.local.generation_counter += 1;
1691 let generation = self.local.generation_counter;
1692 self.snapshot_schema(&create_schema.name);
1693 self.local.schemas.insert(
1694 create_schema.name.clone(),
1695 SchemaOverlay::Present(crate::model::schema::SchemaState {
1696 name: create_schema.name.clone(),
1697 owner: ObjectId::new("", owner_name),
1698 generation,
1699 }),
1700 );
1701 self.snapshot_search_path();
1702 self.refresh_role_sensitive_search_path();
1703 MutationResult::Applied
1704 }
1705 Mutation::AlterSchema(alter_schema) => match alter_schema {
1706 crate::analysis::mutations::AlterSchemaMutation::OwnerTo { name, new_owner } => {
1707 if !self.schema_is_present(name) {
1708 if self.schema_absence_is_authoritative(name) {
1709 return MutationResult::Conflict {
1710 reason: format!("schema '{}' does not exist", name),
1711 };
1712 }
1713 self.snapshot_confidence();
1714 self.local.confidence = Confidence::Tainted;
1715 return MutationResult::Skipped;
1716 }
1717 let Some((owner_name, owner_known)) = self.role_fact_identity(new_owner) else {
1718 self.snapshot_confidence();
1719 self.local.confidence = Confidence::Tainted;
1720 return MutationResult::Skipped;
1721 };
1722 if owner_known
1723 && self.local.roles_known
1724 && self.present_role(&owner_name).is_none()
1725 {
1726 return MutationResult::Conflict {
1727 reason: format!("role '{}' does not exist", owner_name),
1728 };
1729 }
1730 if !owner_known || !self.local.roles_known {
1731 self.snapshot_confidence();
1732 self.local.confidence = Confidence::Tainted;
1733 }
1734 self.snapshot_schema(name);
1735 if let Some(SchemaOverlay::Present(schema)) = self.local.schemas.get_mut(name) {
1736 schema.owner = ObjectId::new("", owner_name);
1737 }
1738 MutationResult::Applied
1739 }
1740 crate::analysis::mutations::AlterSchemaMutation::Rename { old_name, new_name } => {
1741 if !self.schema_is_present(old_name) {
1742 if !self.schema_absence_is_authoritative(old_name) {
1743 self.snapshot_confidence();
1744 self.local.confidence = Confidence::Tainted;
1745 return MutationResult::Skipped;
1746 }
1747 return MutationResult::Conflict {
1748 reason: format!("schema '{}' does not exist", old_name),
1749 };
1750 }
1751 if self.schema_is_present(new_name) {
1752 return MutationResult::Conflict {
1753 reason: format!("schema '{}' already exists", new_name),
1754 };
1755 }
1756 if !self.schema_absence_is_authoritative(new_name) {
1757 self.snapshot_confidence();
1758 self.local.confidence = Confidence::Tainted;
1759 }
1760 self.snapshot_search_path();
1761 self.rename_schema_namespace(old_name, new_name);
1762 MutationResult::Applied
1763 }
1764 },
1765 Mutation::DropSchema(drop_schema) => {
1766 for name in &drop_schema.names {
1767 if !self.schema_is_present(name) && self.schema_absence_is_authoritative(name) {
1768 if !drop_schema.if_exists {
1769 return MutationResult::Conflict {
1770 reason: format!("schema '{}' does not exist", name),
1771 };
1772 }
1773 } else if !self.schema_is_present(name) {
1774 self.snapshot_confidence();
1775 self.local.confidence = Confidence::Tainted;
1776 }
1777 }
1778 let present_names: Vec<String> = drop_schema
1779 .names
1780 .iter()
1781 .filter(|name| self.schema_is_present(name))
1782 .cloned()
1783 .collect();
1784 if present_names.is_empty() {
1785 return MutationResult::Skipped;
1786 }
1787 if drop_schema.cascade {
1788 self.snapshot_namespace();
1789 let mut relations_to_drop = Vec::new();
1790 for id in self.local.relations.keys() {
1791 if drop_schema.names.contains(&id.schema) {
1792 relations_to_drop.push(id.clone());
1793 }
1794 }
1795 for id in relations_to_drop {
1796 self.snapshot_relation(&id);
1797 self.local.relations.insert(id, RelationOverlay::Dropped);
1798 }
1799
1800 let constraints_to_drop: Vec<(ObjectId, String)> = self
1801 .local
1802 .constraints
1803 .keys()
1804 .filter(|(table_id, _)| drop_schema.names.contains(&table_id.schema))
1805 .cloned()
1806 .collect();
1807 for (table_id, name) in constraints_to_drop {
1808 self.snapshot_constraint(&table_id, &name);
1809 self.local.constraints.remove(&(table_id, name));
1810 }
1811
1812 let mut types_to_drop = Vec::new();
1813 for id in self.local.types.keys() {
1814 if drop_schema.names.contains(&id.schema) {
1815 types_to_drop.push(id.clone());
1816 }
1817 }
1818 for id in types_to_drop {
1819 self.snapshot_type(&id);
1820 self.local.types.insert(id, TypeOverlay::Dropped);
1821 }
1822
1823 let mut seqs_to_drop = Vec::new();
1824 for id in self.local.sequences.keys() {
1825 if drop_schema.names.contains(&id.schema) {
1826 seqs_to_drop.push(id.clone());
1827 }
1828 }
1829 for id in seqs_to_drop {
1830 self.snapshot_sequence(&id);
1831 self.local.sequences.insert(id, SequenceOverlay::Dropped);
1832 }
1833
1834 let functions_to_drop: Vec<ObjectId> = self
1835 .local
1836 .functions
1837 .keys()
1838 .filter(|id| drop_schema.names.contains(&id.schema))
1839 .cloned()
1840 .collect();
1841 for id in functions_to_drop {
1842 self.snapshot_function(&id);
1843 self.local
1844 .functions
1845 .insert(id, crate::model::function::FunctionOverlay::Dropped);
1846 }
1847
1848 let triggers_to_drop: Vec<ObjectId> = self
1849 .local
1850 .triggers
1851 .keys()
1852 .filter(|id| drop_schema.names.contains(&id.schema))
1853 .cloned()
1854 .collect();
1855 for id in triggers_to_drop {
1856 self.snapshot_trigger(&id);
1857 self.local.triggers.insert(id, TriggerOverlay::Dropped);
1858 }
1859
1860 self.local
1861 .pending_validation
1862 .retain(|(table, _)| !drop_schema.names.contains(&table.schema));
1863 let publication_names: Vec<String> =
1864 self.local.publications.keys().cloned().collect();
1865 for publication_name in publication_names {
1866 self.snapshot_publication(&publication_name);
1867 }
1868 for overlay in self.local.publications.values_mut() {
1869 let crate::model::replication::PublicationOverlay::Present(publication) =
1870 overlay
1871 else {
1872 continue;
1873 };
1874 let crate::analysis::facts::PublicationScope::Explicit(objects) =
1875 &mut publication.scope
1876 else {
1877 continue;
1878 };
1879 objects.retain(|object| match object {
1880 crate::analysis::facts::PublicationObjectFact::Table {
1881 name, ..
1882 } => name.schema.as_ref().is_none_or(|schema| {
1883 !drop_schema.names.contains(&schema.resolve())
1884 }),
1885 crate::analysis::facts::PublicationObjectFact::SchemaTables {
1886 schema,
1887 ..
1888 } => !drop_schema.names.contains(schema),
1889 _ => true,
1890 });
1891 }
1892
1893 self.snapshot_graph_full();
1894
1895 let g = &mut self.local.graph;
1896 g.edges.retain(|e| {
1897 !drop_schema.names.contains(&e.dependent.schema)
1898 && !drop_schema.names.contains(&e.referenced.schema)
1899 && match &e.kind {
1900 DependencyKind::TriggerOnTable { function_id, .. } => {
1901 !drop_schema.names.contains(&function_id.schema)
1902 }
1903 _ => true,
1904 }
1905 });
1906 } else {
1907 let has_relation = self.local.relations.iter().any(|(id, ov)| {
1909 drop_schema.names.contains(&id.schema)
1910 && !matches!(ov, RelationOverlay::Dropped)
1911 });
1912 let has_type = self.local.types.iter().any(|(id, ov)| {
1913 drop_schema.names.contains(&id.schema)
1914 && !matches!(ov, TypeOverlay::Dropped)
1915 });
1916 let has_sequence = self.local.sequences.iter().any(|(id, ov)| {
1917 drop_schema.names.contains(&id.schema)
1918 && !matches!(ov, SequenceOverlay::Dropped)
1919 });
1920 let has_function = self.local.functions.iter().any(|(id, ov)| {
1921 drop_schema.names.contains(&id.schema)
1922 && !matches!(ov, crate::model::function::FunctionOverlay::Dropped)
1923 });
1924 let has_trigger = self.local.triggers.iter().any(|(id, ov)| {
1925 drop_schema.names.contains(&id.schema)
1926 && !matches!(ov, TriggerOverlay::Dropped)
1927 });
1928 if has_relation || has_type || has_sequence || has_function || has_trigger {
1929 return MutationResult::Conflict {
1930 reason: format!(
1931 "schema(s) {:?} still contain objects; use CASCADE to drop them",
1932 drop_schema.names
1933 ),
1934 };
1935 }
1936 }
1937 for name in present_names {
1938 self.snapshot_schema(&name);
1939 self.local.schemas.insert(name, SchemaOverlay::Dropped);
1940 }
1941 self.snapshot_search_path();
1942 self.refresh_role_sensitive_search_path();
1943 MutationResult::Applied
1944 }
1945 Mutation::DropTable(drop_table) => {
1946 if !self.relation_is_present(&drop_table.id) {
1947 if drop_table.if_exists {
1948 return MutationResult::Skipped;
1949 }
1950 if self.baseline_available && self.baseline_covers_object(&drop_table.id) {
1951 return MutationResult::Conflict {
1952 reason: format!("table '{}' does not exist", drop_table.id),
1953 };
1954 }
1955 self.snapshot_confidence();
1956 self.local.confidence = Confidence::Tainted;
1957 return MutationResult::Skipped;
1958 }
1959 if !matches!(
1960 self.local.relations.get(&drop_table.id),
1961 Some(RelationOverlay::Present(relation))
1962 if relation.kind == RelationKind::Table
1963 ) {
1964 return MutationResult::Conflict {
1965 reason: format!("'{}' is not a table", drop_table.id),
1966 };
1967 }
1968
1969 let renames: Vec<DependencyEdge> = self
1970 .local
1971 .graph
1972 .edges
1973 .iter()
1974 .filter(|e| matches!(e.kind, DependencyKind::RenameTo))
1975 .cloned()
1976 .collect();
1977 let resolve = |id: &ObjectId| -> ObjectId {
1978 let mut current = id;
1979 let mut visited = HashSet::new();
1980 loop {
1981 if !visited.insert(current.clone()) {
1982 return id.clone();
1983 }
1984 match renames.iter().find(|r| &r.dependent == current) {
1985 Some(edge) => current = &edge.referenced,
1986 None => return current.clone(),
1987 }
1988 }
1989 };
1990
1991 let resolved_drop = resolve(&drop_table.id);
1992 let mut dropped_relations = HashSet::from([resolved_drop.clone()]);
1993
1994 if drop_table.cascade {
1995 let local_closure;
1996 let closure = match precomputed_cascade {
1997 Some(c) => c,
1998 None => {
1999 local_closure = self.get_cascade_closure(&drop_table.id);
2000 &local_closure
2001 }
2002 };
2003 dropped_relations = closure.dropped_relations.clone();
2004
2005 for dropped_rel_id in &closure.dropped_relations {
2006 self.snapshot_relation(dropped_rel_id);
2007 self.local
2008 .relations
2009 .insert(dropped_rel_id.clone(), RelationOverlay::Dropped);
2010 }
2011
2012 self.snapshot_graph_full();
2013 self.local.graph.edges.retain(|e| match &e.kind {
2014 DependencyKind::IndexOnRelation { .. } => {
2015 !closure.dropped_indexes.contains(&resolve(&e.dependent))
2016 }
2017 DependencyKind::ForeignKey {
2018 constraint_name, ..
2019 } => {
2020 let from_dropped =
2021 closure.dropped_relations.contains(&resolve(&e.dependent));
2022 let to_dropped =
2023 closure.dropped_relations.contains(&resolve(&e.referenced));
2024 let constraint_explicitly_dropped = if let Some(cname) = constraint_name
2025 {
2026 closure
2027 .dropped_constraints
2028 .contains(&(resolve(&e.dependent), cname.clone()))
2029 } else {
2030 false
2031 };
2032 !(from_dropped || to_dropped || constraint_explicitly_dropped)
2033 }
2034 DependencyKind::ViewDependency { .. } => {
2035 !closure.dropped_relations.contains(&resolve(&e.dependent))
2036 }
2037 DependencyKind::SequenceOwnedBy { .. } => {
2038 !closure.dropped_relations.contains(&resolve(&e.referenced))
2039 }
2040 _ => true,
2041 });
2042 } else {
2043 let has_view_deps = self.local.graph.edges.iter().any(|e| {
2044 matches!(e.kind, DependencyKind::ViewDependency { .. })
2045 && resolve(&e.referenced) == resolved_drop
2046 });
2047 let has_fk_deps = self.local.graph.edges.iter().any(|e| {
2048 matches!(e.kind, DependencyKind::ForeignKey { .. })
2049 && resolve(&e.referenced) == resolved_drop
2050 && resolve(&e.dependent) != resolved_drop
2051 });
2052 let has_partition_deps = self.local.graph.edges.iter().any(|e| {
2053 matches!(e.kind, DependencyKind::PartitionOf)
2054 && resolve(&e.referenced) == resolved_drop
2055 });
2056
2057 if has_view_deps || has_fk_deps || has_partition_deps {
2058 return MutationResult::Conflict {
2059 reason: format!(
2060 "relation '{}' still has dependent objects; use CASCADE",
2061 drop_table.id
2062 ),
2063 };
2064 }
2065
2066 self.snapshot_relation(&drop_table.id);
2067 self.local
2068 .relations
2069 .insert(drop_table.id.clone(), RelationOverlay::Dropped);
2070
2071 self.snapshot_graph_full();
2072 self.local.graph.edges.retain(|e| {
2073 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
2074 && resolve(&e.referenced) == resolved_drop)
2075 });
2076 }
2077
2078 let owned_sequences_to_drop: Vec<ObjectId> = self
2079 .local
2080 .sequences
2081 .iter()
2082 .filter_map(|(id, overlay)| match overlay {
2083 SequenceOverlay::Present(sequence)
2084 if sequence.owned_by.as_ref().is_some_and(|(table, _)| {
2085 dropped_relations.contains(&resolve(table))
2086 }) =>
2087 {
2088 Some(id.clone())
2089 }
2090 _ => None,
2091 })
2092 .collect();
2093 for sequence_id in owned_sequences_to_drop {
2094 self.snapshot_sequence(&sequence_id);
2095 self.local
2096 .sequences
2097 .insert(sequence_id, SequenceOverlay::Dropped);
2098 }
2099
2100 let constraints_to_drop: Vec<(ObjectId, String)> = self
2101 .local
2102 .constraints
2103 .keys()
2104 .filter(|(table_id, _)| dropped_relations.contains(&resolve(table_id)))
2105 .cloned()
2106 .collect();
2107 for (table_id, name) in constraints_to_drop {
2108 self.snapshot_constraint(&table_id, &name);
2109 self.local.constraints.remove(&(table_id, name));
2110 }
2111
2112 let triggers_to_drop: Vec<ObjectId> = self
2113 .local
2114 .triggers
2115 .iter()
2116 .filter_map(|(id, overlay)| {
2117 let TriggerOverlay::Present(trigger) = overlay else {
2118 return None;
2119 };
2120 let graph_matches = self.local.graph.edges.iter().any(|edge| {
2121 matches!(edge.kind, DependencyKind::TriggerOnTable { .. })
2122 && edge.dependent == *id
2123 && dropped_relations.contains(&resolve(&edge.referenced))
2124 });
2125 (dropped_relations.contains(&resolve(&trigger.table_id)) || graph_matches)
2126 .then(|| id.clone())
2127 })
2128 .collect();
2129 for trigger_id in triggers_to_drop {
2130 self.snapshot_trigger(&trigger_id);
2131 self.local
2132 .triggers
2133 .insert(trigger_id, TriggerOverlay::Dropped);
2134 }
2135
2136 self.snapshot_graph_full();
2138 self.local.graph.edges.retain(|e| {
2139 !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
2140 && dropped_relations.contains(&resolve(&e.referenced)))
2141 });
2142
2143 self.snapshot_graph_full();
2144 self.local.graph.edges.retain(|e| {
2145 if let DependencyKind::PartitionOf = e.kind {
2146 resolve(&e.referenced) != resolved_drop
2147 && resolve(&e.dependent) != resolved_drop
2148 } else {
2149 true
2150 }
2151 });
2152
2153 let publication_updates: Vec<(String, Vec<_>)> = self
2154 .local
2155 .publications
2156 .iter()
2157 .filter_map(|(name, overlay)| {
2158 let crate::model::replication::PublicationOverlay::Present(publication) =
2159 overlay
2160 else {
2161 return None;
2162 };
2163 let crate::analysis::facts::PublicationScope::Explicit(objects) =
2164 &publication.scope
2165 else {
2166 return None;
2167 };
2168 let retained = objects
2169 .iter()
2170 .filter(|object| {
2171 let crate::analysis::facts::PublicationObjectFact::Table {
2172 name,
2173 ..
2174 } = object
2175 else {
2176 return true;
2177 };
2178 !dropped_relations
2179 .contains(&resolve(&self.resolve_relation_id(name)))
2180 })
2181 .cloned()
2182 .collect::<Vec<_>>();
2183 (retained.len() != objects.len()).then(|| (name.clone(), retained))
2184 })
2185 .collect();
2186 for (publication_name, retained) in publication_updates {
2187 self.snapshot_publication(&publication_name);
2188 if let Some(crate::model::replication::PublicationOverlay::Present(publication)) =
2189 self.local.publications.get_mut(&publication_name)
2190 && let crate::analysis::facts::PublicationScope::Explicit(objects) =
2191 &mut publication.scope
2192 {
2193 *objects = retained;
2194 }
2195 }
2196 self.snapshot_graph_full();
2197 self.local.graph.edges.retain(|edge| {
2198 !matches!(edge.kind, DependencyKind::PublicationIncludes { .. })
2199 || !dropped_relations.contains(&resolve(&edge.dependent))
2200 });
2201
2202 MutationResult::Applied
2203 }
2204 Mutation::CreateTable(create) => {
2205 if create.if_not_exists && self.relation_namespace_is_taken(&create.id) {
2206 return MutationResult::Skipped;
2207 }
2208 if self.relation_namespace_is_taken(&create.id) {
2209 return MutationResult::Conflict {
2210 reason: format!("relation '{}' already exists", create.id),
2211 };
2212 }
2213
2214 let mut reserved_sequences = HashSet::new();
2218 let mut implicit_sequences = Vec::new();
2219 for column in &create.columns {
2220 let kind = match column.generation {
2221 crate::analysis::facts::ColumnGeneration::Serial => {
2222 Some(SequenceKind::SerialLike)
2223 }
2224 crate::analysis::facts::ColumnGeneration::Identity => {
2225 Some(SequenceKind::Identity)
2226 }
2227 crate::analysis::facts::ColumnGeneration::Ordinary => None,
2228 };
2229 if let Some(kind) = kind {
2230 let sequence_id = self.next_implicit_sequence_id(
2231 &create.id,
2232 &column.name,
2233 &reserved_sequences,
2234 );
2235 reserved_sequences.insert(sequence_id.clone());
2236 implicit_sequences.push((sequence_id, column.name.clone(), kind));
2237 }
2238 }
2239
2240 self.snapshot_relation(&create.id);
2241
2242 self.snapshot_generation_counter();
2243 self.local.generation_counter += 1;
2244 let generation = self.local.generation_counter;
2245
2246 let resolved_persistence = match create.persistence {
2247 PersistenceMutation::Permanent => {
2248 crate::model::relation::Persistence::Permanent
2249 }
2250 PersistenceMutation::Temporary => {
2251 crate::model::relation::Persistence::Temporary
2252 }
2253 PersistenceMutation::Unlogged => crate::model::relation::Persistence::Unlogged,
2254 };
2255
2256 let mut rel_state = RelationState::new(
2257 create.id.clone(),
2258 ObjectId::new("", &self.local.current_role),
2259 generation,
2260 if create.as_select { None } else { Some(0) },
2261 RelationKind::Table,
2262 resolved_persistence,
2263 self.local.transactions.len(),
2264 );
2265
2266 rel_state.partition_type = create
2268 .partition_by
2269 .as_ref()
2270 .and_then(|pb| pb.split_whitespace().nth(2).map(|s| s.to_uppercase()))
2271 .or_else(|| {
2272 create.partition_of.as_ref().and_then(|parent_id| {
2273 self.local.relations.get(parent_id).and_then(|r| {
2274 if let RelationOverlay::Present(rel) = r {
2275 rel.partition_type.clone()
2276 } else {
2277 None
2278 }
2279 })
2280 })
2281 });
2282 rel_state.partition_by = create.partition_by.clone();
2283
2284 let pk_columns: HashSet<&str> = create
2285 .table_constraints
2286 .iter()
2287 .filter_map(|tc| {
2288 if let TableConstraintFact::PrimaryKey { columns, .. } = tc {
2289 Some(columns.iter().map(|s| s.as_str()))
2290 } else {
2291 None
2292 }
2293 })
2294 .flatten()
2295 .collect();
2296
2297 for col in &create.columns {
2298 let is_pk = col.is_primary_key || pk_columns.contains(col.name.as_str());
2299 rel_state.apply_column_action(&ColumnAction::Add {
2300 name: col.name.clone(),
2301 data_type: col.ty.clone(),
2302 not_null: col.not_null || is_pk,
2303 default: col.default.clone(),
2304 });
2305 if let Some(column) = rel_state
2306 .columns
2307 .iter_mut()
2308 .find(|column| column.name == col.name)
2309 {
2310 column.type_id = column
2311 .data_type
2312 .as_deref()
2313 .and_then(|raw| self.resolve_type_reference(raw));
2314 }
2315 }
2316
2317 for (sequence_id, column_name, _) in &implicit_sequences {
2318 if let Some(column) = rel_state
2319 .columns
2320 .iter_mut()
2321 .find(|column| column.name == *column_name)
2322 {
2323 column.default = Some(Self::sequence_nextval_default(sequence_id));
2324 column.default_expr_text = Some(format!(
2325 "nextval('{}.{}'::regclass)",
2326 sequence_id.schema, sequence_id.name
2327 ));
2328 column.is_nullable = false;
2329 }
2330 }
2331
2332 self.local
2333 .relations
2334 .insert(create.id.clone(), RelationOverlay::Present(rel_state));
2335
2336 for (sequence_id, column_name, kind) in implicit_sequences {
2337 self.snapshot_sequence(&sequence_id);
2338 self.snapshot_generation_counter();
2339 self.local.generation_counter += 1;
2340 self.local.sequences.insert(
2341 sequence_id.clone(),
2342 SequenceOverlay::Present(SequenceState {
2343 id: sequence_id.clone(),
2344 owner: ObjectId::new("", &self.local.current_role),
2345 owned_by: Some((create.id.clone(), column_name.clone())),
2346 kind,
2347 generation: self.local.generation_counter,
2348 }),
2349 );
2350 self.snapshot_graph();
2351 self.local.graph.edges.push(DependencyEdge::new(
2352 sequence_id,
2353 create.id.clone(),
2354 DependencyKind::SequenceOwnedBy {
2355 column: column_name,
2356 },
2357 ));
2358 }
2359
2360 let primary_key_name = create
2361 .columns
2362 .iter()
2363 .find(|column| column.is_primary_key)
2364 .map(|column| column.primary_key_constraint_name.clone())
2365 .or_else(|| {
2366 create.table_constraints.iter().find_map(|constraint| {
2367 if let TableConstraintFact::PrimaryKey {
2368 constraint_name, ..
2369 } = constraint
2370 {
2371 Some(constraint_name.clone())
2372 } else {
2373 None
2374 }
2375 })
2376 });
2377 if let Some(explicit_name) = primary_key_name {
2378 let name = explicit_name.unwrap_or_else(|| {
2379 self.next_generated_constraint_name(
2380 &create.id,
2381 &create.id.name,
2382 None,
2383 "pkey",
2384 )
2385 });
2386 self.snapshot_constraint(&create.id, &name);
2387 self.local.constraints.insert(
2388 (create.id.clone(), name.clone()),
2389 ConstraintState {
2390 table_id: create.id.clone(),
2391 name,
2392 kind: ConstraintKind::PrimaryKey,
2393 validated: true,
2394 },
2395 );
2396 }
2397
2398 let unique_constraints = create
2399 .columns
2400 .iter()
2401 .filter(|column| column.is_unique)
2402 .map(|column| {
2403 (
2404 column.unique_constraint_name.as_ref(),
2405 vec![column.name.as_str()],
2406 )
2407 })
2408 .chain(create.table_constraints.iter().filter_map(|constraint| {
2409 if let TableConstraintFact::Unique {
2410 constraint_name,
2411 columns,
2412 } = constraint
2413 {
2414 Some((
2415 constraint_name.as_ref(),
2416 columns.iter().map(String::as_str).collect(),
2417 ))
2418 } else {
2419 None
2420 }
2421 }))
2422 .collect::<Vec<_>>();
2423 for (explicit_name, columns) in unique_constraints {
2424 let name = explicit_name.cloned().unwrap_or_else(|| {
2425 self.next_generated_constraint_name(
2426 &create.id,
2427 &create.id.name,
2428 Some(&columns.join("_")),
2429 "key",
2430 )
2431 });
2432 self.snapshot_constraint(&create.id, &name);
2433 self.local.constraints.insert(
2434 (create.id.clone(), name.clone()),
2435 ConstraintState {
2436 table_id: create.id.clone(),
2437 name,
2438 kind: ConstraintKind::Unique,
2439 validated: true,
2440 },
2441 );
2442 }
2443
2444 if let Some(parent_id) = &create.partition_of {
2445 self.snapshot_graph();
2446 self.local.graph.edges.push(DependencyEdge::new(
2447 create.id.clone(),
2448 parent_id.clone(),
2449 DependencyKind::PartitionOf,
2450 ));
2451 }
2452
2453 if !create.foreign_keys.is_empty() {
2454 self.snapshot_graph();
2455 }
2456
2457 for fk in &create.foreign_keys {
2458 self.local.graph.edges.push(DependencyEdge::new(
2459 create.id.clone(),
2460 fk.to_table.clone(),
2461 DependencyKind::ForeignKey {
2462 constraint_name: fk.constraint_name.clone(),
2463 from_columns: fk.from_columns.clone(),
2464 to_columns: fk.to_columns.clone(),
2465 from_generation: generation,
2466 },
2467 ));
2468 }
2469 MutationResult::Applied
2470 }
2471 Mutation::CreateView(create_view) => {
2472 if self.relation_namespace_is_taken(&create_view.id) {
2473 let is_replaceable_view = matches!(
2474 self.local.relations.get(&create_view.id),
2475 Some(RelationOverlay::Present(relation))
2476 if relation.kind == RelationKind::View
2477 );
2478 if !create_view.or_replace || !is_replaceable_view {
2479 return MutationResult::Conflict {
2480 reason: format!("relation '{}' already exists", create_view.id),
2481 };
2482 }
2483 }
2484 self.snapshot_relation(&create_view.id);
2485 self.snapshot_generation_counter();
2486 self.local.generation_counter += 1;
2487 let generation = self.local.generation_counter;
2488
2489 self.local.relations.insert(
2490 create_view.id.clone(),
2491 RelationOverlay::Present(RelationState::new(
2492 create_view.id.clone(),
2493 ObjectId::new("", &self.local.current_role),
2494 generation,
2495 None,
2496 RelationKind::View,
2497 Persistence::Permanent,
2498 self.local.transactions.len(),
2499 )),
2500 );
2501
2502 self.snapshot_graph();
2503 for dep in &create_view.depends_on {
2504 self.local.graph.edges.push(DependencyEdge::new(
2505 create_view.id.clone(),
2506 dep.clone(),
2507 DependencyKind::ViewDependency {
2508 view_generation: generation,
2509 },
2510 ));
2511 }
2512 MutationResult::Applied
2513 }
2514 Mutation::CreateMaterializedView(create_mv) => {
2515 if self.relation_namespace_is_taken(&create_mv.id) {
2516 return MutationResult::Conflict {
2517 reason: format!("relation '{}' already exists", create_mv.id),
2518 };
2519 }
2520 self.snapshot_relation(&create_mv.id);
2521 self.snapshot_generation_counter();
2522 self.local.generation_counter += 1;
2523 let generation = self.local.generation_counter;
2524
2525 self.local.relations.insert(
2526 create_mv.id.clone(),
2527 RelationOverlay::Present(RelationState::new(
2528 create_mv.id.clone(),
2529 ObjectId::new("", &self.local.current_role),
2530 generation,
2531 None,
2532 RelationKind::MaterializedView,
2533 Persistence::Permanent,
2534 self.local.transactions.len(),
2535 )),
2536 );
2537
2538 self.snapshot_graph();
2539 for dep in &create_mv.depends_on {
2540 self.local.graph.edges.push(DependencyEdge::new(
2541 create_mv.id.clone(),
2542 dep.clone(),
2543 DependencyKind::ViewDependency {
2544 view_generation: generation,
2545 },
2546 ));
2547 }
2548 MutationResult::Applied
2549 }
2550 Mutation::RefreshMaterializedView(_) => MutationResult::Applied,
2551 Mutation::CreateIndex(create_idx) => {
2552 let exists = self.index_is_present(&create_idx.id);
2553 if create_idx.if_not_exists && exists {
2554 return MutationResult::Skipped;
2555 }
2556 if self.relation_namespace_is_taken(&create_idx.id) {
2557 return MutationResult::Conflict {
2558 reason: format!("relation '{}' already exists", create_idx.id),
2559 };
2560 }
2561 self.snapshot_graph();
2562 self.local.graph.edges.push(DependencyEdge::new(
2563 create_idx.id.clone(),
2564 create_idx.table.clone(),
2565 DependencyKind::IndexOnRelation {
2566 using_method: create_idx.using_method.clone(),
2567 has_predicate: create_idx.has_predicate,
2568 is_concurrent: create_idx.concurrently,
2569 is_unique: create_idx.unique,
2570 eligibility_known: true,
2571 },
2572 ));
2573 MutationResult::Applied
2574 }
2575 Mutation::CreatePolicy(create_policy) => {
2576 self.snapshot_relation(&create_policy.table);
2577 if let Some(RelationOverlay::Present(rel)) =
2578 self.local.relations.get_mut(&create_policy.table)
2579 {
2580 if rel.policies.contains(&create_policy.name) {
2581 return MutationResult::Conflict {
2582 reason: format!(
2583 "policy '{}' already exists on relation '{}'",
2584 create_policy.name, create_policy.table
2585 ),
2586 };
2587 }
2588 rel.policies.insert(create_policy.name.clone());
2589 } else {
2590 return MutationResult::Conflict {
2591 reason: format!("relation '{}' does not exist", create_policy.table),
2592 };
2593 }
2594 MutationResult::Applied
2595 }
2596 Mutation::DropPolicy(drop_policy) => {
2597 self.snapshot_relation(&drop_policy.table);
2598 if let Some(RelationOverlay::Present(rel)) =
2599 self.local.relations.get_mut(&drop_policy.table)
2600 {
2601 if !rel.policies.contains(&drop_policy.name) {
2602 return if drop_policy.if_exists {
2603 MutationResult::Skipped
2604 } else {
2605 MutationResult::Conflict {
2606 reason: format!(
2607 "policy '{}' does not exist on relation '{}'",
2608 drop_policy.name, drop_policy.table
2609 ),
2610 }
2611 };
2612 }
2613 rel.policies.remove(&drop_policy.name);
2614 } else {
2615 return MutationResult::Conflict {
2616 reason: format!("relation '{}' does not exist", drop_policy.table),
2617 };
2618 }
2619 MutationResult::Applied
2620 }
2621 Mutation::CreateTrigger(create_trigger) => {
2622 let trigger_id = Self::trigger_key(&create_trigger.table, &create_trigger.name);
2623 if matches!(
2624 self.local.triggers.get(&trigger_id),
2625 Some(TriggerOverlay::Present(_))
2626 ) {
2627 return MutationResult::Conflict {
2628 reason: format!(
2629 "trigger '{}' already exists on relation '{}'",
2630 create_trigger.name, create_trigger.table
2631 ),
2632 };
2633 }
2634 self.snapshot_trigger(&trigger_id);
2635 self.local.triggers.insert(
2636 trigger_id.clone(),
2637 TriggerOverlay::Present(crate::model::trigger::TriggerState {
2638 name: create_trigger.name.clone(),
2639 id: trigger_id.clone(),
2640 table_id: create_trigger.table.clone(),
2641 enabled_mode: crate::model::trigger::TriggerEnableMode::Origin,
2642 generation: self.local.generation_counter,
2643 }),
2644 );
2645
2646 self.snapshot_relation(&create_trigger.table);
2647 if let Some(RelationOverlay::Present(rel)) =
2648 self.local.relations.get_mut(&create_trigger.table)
2649 {
2650 rel.triggers.insert(create_trigger.name.clone());
2651 }
2652
2653 self.snapshot_graph_full();
2654 self.local.graph.edges.push(DependencyEdge::new(
2655 trigger_id.clone(),
2656 create_trigger.table.clone(),
2657 DependencyKind::TriggerOnTable {
2658 trigger_id: trigger_id.clone(),
2659 function_id: create_trigger.function_id.clone(),
2660 },
2661 ));
2662
2663 MutationResult::Applied
2664 }
2665 Mutation::DropTrigger(drop_trigger) => {
2666 let trigger_id = Self::trigger_key(&drop_trigger.table, &drop_trigger.name);
2667 if !matches!(
2668 self.local.triggers.get(&trigger_id),
2669 Some(TriggerOverlay::Present(_))
2670 ) {
2671 return if drop_trigger.if_exists {
2672 MutationResult::Skipped
2673 } else {
2674 MutationResult::Conflict {
2675 reason: format!(
2676 "trigger '{}' does not exist on relation '{}'",
2677 drop_trigger.name, drop_trigger.table
2678 ),
2679 }
2680 };
2681 }
2682 self.snapshot_trigger(&trigger_id);
2683 self.local
2684 .triggers
2685 .insert(trigger_id.clone(), TriggerOverlay::Dropped);
2686
2687 self.snapshot_relation(&drop_trigger.table);
2688 if let Some(RelationOverlay::Present(rel)) =
2689 self.local.relations.get_mut(&drop_trigger.table)
2690 {
2691 rel.triggers.remove(&drop_trigger.name);
2692 }
2693
2694 self.snapshot_graph_full();
2695 self.local.graph.edges.retain(|e| {
2696 !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
2697 && e.dependent == trigger_id)
2698 });
2699
2700 MutationResult::Applied
2701 }
2702 Mutation::RenameTrigger(rename_trigger) => {
2703 let old_id = Self::trigger_key(&rename_trigger.table, &rename_trigger.name);
2704 let new_id = Self::trigger_key(&rename_trigger.table, &rename_trigger.new_name);
2705 let Some(TriggerOverlay::Present(mut trigger)) =
2706 self.local.triggers.get(&old_id).cloned()
2707 else {
2708 return MutationResult::Conflict {
2709 reason: format!(
2710 "trigger '{}' does not exist on relation '{}'",
2711 rename_trigger.name, rename_trigger.table
2712 ),
2713 };
2714 };
2715 if old_id != new_id
2716 && matches!(
2717 self.local.triggers.get(&new_id),
2718 Some(TriggerOverlay::Present(_))
2719 )
2720 {
2721 return MutationResult::Conflict {
2722 reason: format!(
2723 "trigger '{}' already exists on relation '{}'",
2724 rename_trigger.new_name, rename_trigger.table
2725 ),
2726 };
2727 }
2728 self.snapshot_trigger(&old_id);
2729 self.snapshot_trigger(&new_id);
2730 self.snapshot_relation(&rename_trigger.table);
2731 self.snapshot_graph_full();
2732 self.local.triggers.remove(&old_id);
2733 trigger.id = new_id.clone();
2734 trigger.name = rename_trigger.new_name.clone();
2735 self.local
2736 .triggers
2737 .insert(new_id.clone(), TriggerOverlay::Present(trigger));
2738 if let Some(RelationOverlay::Present(relation)) =
2739 self.local.relations.get_mut(&rename_trigger.table)
2740 {
2741 relation.triggers.remove(&rename_trigger.name);
2742 relation.triggers.insert(rename_trigger.new_name.clone());
2743 }
2744 self.local.graph.propagate_rename(&old_id, &new_id);
2745 self.local.graph.edges.push(DependencyEdge::new(
2746 old_id,
2747 new_id,
2748 DependencyKind::RenameTo,
2749 ));
2750 MutationResult::Applied
2751 }
2752 Mutation::AlterTable(alter) => {
2753 if let AlterTableActionMutation::OwnerTo { new_owner } = &alter.action {
2754 let Some((owner, known)) = self.role_fact_identity(new_owner) else {
2755 self.snapshot_confidence();
2756 self.local.confidence = Confidence::Tainted;
2757 return MutationResult::Skipped;
2758 };
2759 if !known {
2760 self.snapshot_confidence();
2761 self.local.confidence = Confidence::Tainted;
2762 }
2763 self.snapshot_relation(&alter.id);
2764 return match self.local.relations.get_mut(&alter.id) {
2765 Some(RelationOverlay::Present(relation)) => {
2766 relation.owner = ObjectId::new("", owner);
2767 MutationResult::Applied
2768 }
2769 _ => MutationResult::Conflict {
2770 reason: format!("relation '{}' does not exist", alter.id),
2771 },
2772 };
2773 }
2774
2775 let trigger_mode = match &alter.action {
2776 AlterTableActionMutation::DisableTrigger { trigger_name } => Some((
2777 trigger_name.as_deref(),
2778 crate::model::trigger::TriggerEnableMode::Disabled,
2779 )),
2780 AlterTableActionMutation::EnableTrigger { trigger_name } => Some((
2781 trigger_name.as_deref(),
2782 crate::model::trigger::TriggerEnableMode::Origin,
2783 )),
2784 _ => None,
2785 };
2786 if let Some((trigger_name, enabled_mode)) = trigger_mode {
2787 let all = trigger_name.is_none_or(|name| name.eq_ignore_ascii_case("all"));
2788 let trigger_ids: Vec<ObjectId> = self
2789 .local
2790 .triggers
2791 .iter()
2792 .filter_map(|(id, overlay)| {
2793 let TriggerOverlay::Present(trigger) = overlay else {
2794 return None;
2795 };
2796 (trigger.table_id == alter.id
2797 && (all || trigger_name == Some(trigger.name.as_str())))
2798 .then(|| id.clone())
2799 })
2800 .collect();
2801 for trigger_id in trigger_ids {
2802 self.snapshot_trigger(&trigger_id);
2803 if let Some(TriggerOverlay::Present(trigger)) =
2804 self.local.triggers.get_mut(&trigger_id)
2805 {
2806 trigger.enabled_mode = enabled_mode;
2807 }
2808 }
2809 return MutationResult::Applied;
2810 }
2811
2812 if let AlterTableActionMutation::AddForeignKey {
2813 to_table,
2814 from_columns,
2815 to_columns,
2816 ..
2817 } = &alter.action
2818 {
2819 if let Some(RelationOverlay::Present(child)) =
2820 self.local.relations.get(&alter.id)
2821 && let Some(column) =
2822 from_columns.iter().find(|column| !child.has_column(column))
2823 {
2824 return MutationResult::Conflict {
2825 reason: format!(
2826 "foreign key column '{}' does not exist on relation '{}'",
2827 column, alter.id
2828 ),
2829 };
2830 }
2831
2832 let Some(RelationOverlay::Present(parent)) = self.local.relations.get(to_table)
2833 else {
2834 return MutationResult::Conflict {
2835 reason: format!(
2836 "foreign key references relation '{}' which does not exist",
2837 to_table
2838 ),
2839 };
2840 };
2841 if let Some(column) =
2842 to_columns.iter().find(|column| !parent.has_column(column))
2843 {
2844 return MutationResult::Conflict {
2845 reason: format!(
2846 "foreign key references column '{}.{}' which does not exist",
2847 to_table, column
2848 ),
2849 };
2850 }
2851 }
2852
2853 let implicit_add = match &alter.action {
2854 AlterTableActionMutation::AddColumn {
2855 name, generation, ..
2856 } => match generation {
2857 crate::analysis::facts::ColumnGeneration::Serial => Some((
2858 self.next_implicit_sequence_id(&alter.id, name, &HashSet::new()),
2859 name.clone(),
2860 SequenceKind::SerialLike,
2861 )),
2862 crate::analysis::facts::ColumnGeneration::Identity => Some((
2863 self.next_implicit_sequence_id(&alter.id, name, &HashSet::new()),
2864 name.clone(),
2865 SequenceKind::Identity,
2866 )),
2867 crate::analysis::facts::ColumnGeneration::Ordinary => None,
2868 },
2869 _ => None,
2870 };
2871 let owned_sequences_for_column: Vec<ObjectId> = match &alter.action {
2872 AlterTableActionMutation::DropColumn { name, .. }
2873 | AlterTableActionMutation::RenameColumn { from: name, .. } => self
2874 .local
2875 .sequences
2876 .iter()
2877 .filter_map(|(id, overlay)| match overlay {
2878 SequenceOverlay::Present(sequence)
2879 if sequence.owned_by.as_ref()
2880 == Some(&(alter.id.clone(), name.clone())) =>
2881 {
2882 Some(id.clone())
2883 }
2884 _ => None,
2885 })
2886 .collect(),
2887 _ => Vec::new(),
2888 };
2889
2890 let using_index = match &alter.action {
2891 AlterTableActionMutation::AddUniqueConstraint { using_index, .. }
2892 | AlterTableActionMutation::AddPrimaryKeyConstraint { using_index, .. } => {
2893 using_index.as_ref()
2894 }
2895 _ => None,
2896 };
2897 if let Some(index) = using_index {
2898 let Some(edge) = self.local.graph.edges.iter().find(|edge| {
2899 matches!(edge.kind, DependencyKind::IndexOnRelation { .. })
2900 && edge.dependent == *index
2901 }) else {
2902 return MutationResult::Conflict {
2903 reason: format!(
2904 "constraint references index '{}' which does not exist",
2905 index
2906 ),
2907 };
2908 };
2909 if edge.referenced != alter.id {
2910 return MutationResult::Conflict {
2911 reason: format!(
2912 "constraint index '{}' belongs to relation '{}', not '{}'",
2913 index, edge.referenced, alter.id
2914 ),
2915 };
2916 }
2917 if let DependencyKind::IndexOnRelation {
2918 has_predicate,
2919 is_unique,
2920 eligibility_known,
2921 ..
2922 } = &edge.kind
2923 && *eligibility_known
2924 && (!is_unique || *has_predicate)
2925 {
2926 return MutationResult::Conflict {
2927 reason: format!(
2928 "constraint index '{}' must be unique and non-partial",
2929 index
2930 ),
2931 };
2932 }
2933 }
2934
2935 self.snapshot_relation(&alter.id);
2936 let action_type_id = match &alter.action {
2937 AlterTableActionMutation::AddColumn { ty, .. } => ty
2938 .as_deref()
2939 .and_then(|raw| self.resolve_type_reference(raw)),
2940 AlterTableActionMutation::SetType { ty, .. } => self.resolve_type_reference(ty),
2941 _ => None,
2942 };
2943 let rel_overlay = self.local.relations.get_mut(&alter.id);
2944 if let Some(RelationOverlay::Present(rel)) = rel_overlay {
2945 let generation = rel.generation;
2946 match &alter.action {
2947 AlterTableActionMutation::AddColumn {
2948 name,
2949 ty,
2950 if_not_exists,
2951 not_null,
2952 default,
2953 depends_on,
2954 generation: _,
2955 } => {
2956 if let Some(existing_col) = rel.columns.iter().find(|c| c.name == *name)
2957 {
2958 if *if_not_exists {
2959 return MutationResult::Skipped;
2960 }
2961 return MutationResult::Conflict {
2962 reason: format!(
2963 "column '{}' already exists with type {}; this statement adds it again with type {}",
2964 name,
2965 existing_col.data_type.as_deref().unwrap_or("unknown"),
2966 ty.as_deref().unwrap_or("unknown")
2967 ),
2968 };
2969 }
2970 rel.apply_column_action(&ColumnAction::Add {
2971 name: name.clone(),
2972 data_type: ty.clone(),
2973 not_null: *not_null,
2974 default: default.clone(),
2975 });
2976 if let Some(column) =
2977 rel.columns.iter_mut().find(|column| column.name == *name)
2978 {
2979 column.type_id = action_type_id.clone();
2980 }
2981
2982 if let Some((sequence_id, column_name, _)) = &implicit_add
2983 && column_name == name
2984 && let Some(column) =
2985 rel.columns.iter_mut().find(|column| column.name == *name)
2986 {
2987 column.default = Some(Self::sequence_nextval_default(sequence_id));
2988 column.default_expr_text = Some(format!(
2989 "nextval('{}.{}'::regclass)",
2990 sequence_id.schema, sequence_id.name
2991 ));
2992 column.is_nullable = false;
2993 }
2994
2995 if let Some((source_table, source_col)) = depends_on {
2996 self.snapshot_graph();
2997 self.local.graph.edges.push(DependencyEdge::new(
2998 alter.id.clone(),
2999 source_table.clone(),
3000 DependencyKind::ColumnGeneratedFrom {
3001 column: name.clone(),
3002 depends_on_column: source_col.clone(),
3003 },
3004 ));
3005 }
3006 }
3007 AlterTableActionMutation::DropColumn { name, if_exists } => {
3008 if !rel.has_column(name) {
3009 if *if_exists {
3010 return MutationResult::Skipped;
3012 }
3013 return MutationResult::Conflict {
3014 reason: format!(
3015 "column '{}' does not exist on relation '{}'",
3016 name, alter.id
3017 ),
3018 };
3019 }
3020 rel.apply_column_action(&ColumnAction::Drop { name: name.clone() });
3021 }
3022 AlterTableActionMutation::RenameColumn { from, to } => {
3023 rel.apply_column_action(&ColumnAction::Rename {
3024 from: from.clone(),
3025 to: to.clone(),
3026 });
3027 }
3028 AlterTableActionMutation::SetNotNull { column } => {
3029 rel.apply_column_action(&ColumnAction::SetNotNull {
3030 name: column.clone(),
3031 });
3032 }
3033 AlterTableActionMutation::DropNotNull { column } => {
3034 rel.apply_column_action(&ColumnAction::DropNotNull {
3035 name: column.clone(),
3036 });
3037 }
3038 AlterTableActionMutation::SetType { column, ty, .. } => {
3039 if !rel.has_column(column) {
3040 self.local.confidence = Confidence::Tainted;
3041 }
3042 rel.apply_column_action(&ColumnAction::SetType {
3043 name: column.clone(),
3044 data_type: ty.clone(),
3045 });
3046 if let Some(column) =
3047 rel.columns.iter_mut().find(|entry| entry.name == *column)
3048 {
3049 column.type_id = action_type_id.clone();
3050 }
3051 }
3052 AlterTableActionMutation::SetDefault { column, default } => {
3053 if !rel.has_column(column) {
3054 self.local.confidence = Confidence::Tainted;
3055 }
3056 rel.apply_column_action(&ColumnAction::SetDefault {
3057 name: column.clone(),
3058 default: default.clone(),
3059 });
3060 }
3061 AlterTableActionMutation::AddForeignKey {
3062 constraint_name,
3063 to_table,
3064 from_columns,
3065 to_columns,
3066 not_valid,
3067 } => {
3068 let constraint_name = constraint_name.clone().unwrap_or_else(|| {
3069 format!("{}_{}_fkey", alter.id.name, from_columns.join("_"))
3070 });
3071 self.snapshot_constraint(&alter.id, &constraint_name);
3072 self.local.constraints.insert(
3073 (alter.id.clone(), constraint_name.clone()),
3074 ConstraintState {
3075 table_id: alter.id.clone(),
3076 name: constraint_name.clone(),
3077 kind: ConstraintKind::ForeignKey,
3078 validated: !not_valid,
3079 },
3080 );
3081 self.snapshot_graph();
3082 self.local.graph.edges.push(DependencyEdge::new(
3083 alter.id.clone(),
3084 to_table.clone(),
3085 DependencyKind::ForeignKey {
3086 constraint_name: Some(constraint_name),
3087 from_columns: from_columns.clone(),
3088 to_columns: to_columns.clone(),
3089 from_generation: generation,
3090 },
3091 ));
3092 }
3093 AlterTableActionMutation::DropConstraint { name } => {
3094 self.snapshot_constraint(&alter.id, name);
3095 self.local
3096 .constraints
3097 .remove(&(alter.id.clone(), name.clone()));
3098 self.snapshot_graph();
3099 self.local.graph.edges.retain(|e| {
3100 if let DependencyKind::ForeignKey {
3101 constraint_name, ..
3102 } = &e.kind
3103 {
3104 !(e.dependent == alter.id
3105 && constraint_name.as_ref() == Some(name))
3106 } else {
3107 true
3108 }
3109 });
3110 }
3111 AlterTableActionMutation::RenameConstraint { old_name, new_name } => {
3112 self.snapshot_constraint(&alter.id, old_name);
3113 self.snapshot_constraint(&alter.id, new_name);
3114 if let Some(mut constraint) = self
3115 .local
3116 .constraints
3117 .remove(&(alter.id.clone(), old_name.clone()))
3118 {
3119 constraint.name = new_name.clone();
3120 self.local
3121 .constraints
3122 .insert((alter.id.clone(), new_name.clone()), constraint);
3123 }
3124 self.snapshot_graph_full();
3125 for edge in &mut self.local.graph.edges {
3126 if edge.dependent == alter.id
3127 && let DependencyKind::ForeignKey {
3128 constraint_name, ..
3129 } = &mut edge.kind
3130 && constraint_name.as_deref() == Some(old_name)
3131 {
3132 *constraint_name = Some(new_name.clone());
3133 }
3134 }
3135 }
3136 AlterTableActionMutation::AddCheckConstraint {
3137 constraint_name,
3138 not_valid,
3139 } => {
3140 let constraint_name = constraint_name
3141 .clone()
3142 .unwrap_or_else(|| format!("{}_check", alter.id.name));
3143 self.snapshot_constraint(&alter.id, &constraint_name);
3144 self.local.constraints.insert(
3145 (alter.id.clone(), constraint_name.clone()),
3146 ConstraintState {
3147 table_id: alter.id.clone(),
3148 name: constraint_name,
3149 kind: ConstraintKind::Check,
3150 validated: !not_valid,
3151 },
3152 );
3153 }
3154 AlterTableActionMutation::AddUniqueConstraint {
3155 constraint_name,
3156 using_index,
3157 } => {
3158 let constraint_name = constraint_name
3159 .clone()
3160 .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
3161 .unwrap_or_else(|| format!("{}_key", alter.id.name));
3162 self.snapshot_constraint(&alter.id, &constraint_name);
3163 self.local.constraints.insert(
3164 (alter.id.clone(), constraint_name.clone()),
3165 ConstraintState {
3166 table_id: alter.id.clone(),
3167 name: constraint_name,
3168 kind: ConstraintKind::Unique,
3169 validated: true,
3170 },
3171 );
3172 }
3173 AlterTableActionMutation::AddPrimaryKeyConstraint {
3174 constraint_name,
3175 using_index,
3176 } => {
3177 let constraint_name = constraint_name
3178 .clone()
3179 .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
3180 .unwrap_or_else(|| format!("{}_pkey", alter.id.name));
3181 self.snapshot_constraint(&alter.id, &constraint_name);
3182 self.local.constraints.insert(
3183 (alter.id.clone(), constraint_name.clone()),
3184 ConstraintState {
3185 table_id: alter.id.clone(),
3186 name: constraint_name,
3187 kind: ConstraintKind::PrimaryKey,
3188 validated: true,
3189 },
3190 );
3191 }
3192 AlterTableActionMutation::AddExcludeConstraint { constraint_name } => {
3193 let constraint_name = constraint_name
3194 .clone()
3195 .unwrap_or_else(|| format!("{}_excl", alter.id.name));
3196 self.snapshot_constraint(&alter.id, &constraint_name);
3197 self.local.constraints.insert(
3198 (alter.id.clone(), constraint_name.clone()),
3199 ConstraintState {
3200 table_id: alter.id.clone(),
3201 name: constraint_name,
3202 kind: ConstraintKind::Exclusion,
3203 validated: true,
3204 },
3205 );
3206 }
3207 AlterTableActionMutation::ValidateConstraint { constraint_name } => {
3208 self.snapshot_constraint(&alter.id, constraint_name);
3209 if let Some(constraint) = self
3210 .local
3211 .constraints
3212 .get_mut(&(alter.id.clone(), constraint_name.clone()))
3213 {
3214 constraint.validated = true;
3215 }
3216 }
3217 AlterTableActionMutation::AttachPartition { child, .. } => {
3218 if self.local.graph.check_partition_cycle(&alter.id, child) {
3220 self.snapshot_confidence();
3221 self.local.confidence = Confidence::Tainted;
3222 } else {
3223 self.snapshot_graph();
3224 self.local.graph.edges.push(DependencyEdge::new(
3225 child.clone(),
3226 alter.id.clone(),
3227 DependencyKind::PartitionOf,
3228 ));
3229 }
3230 }
3231 AlterTableActionMutation::DetachPartition { child } => {
3232 self.snapshot_graph();
3233 self.local.graph.edges.retain(|e| {
3234 !(matches!(e.kind, DependencyKind::PartitionOf)
3235 && e.dependent == *child
3236 && e.referenced == alter.id)
3237 });
3238 }
3239 _ => {}
3240 }
3241 }
3242 if let Some((sequence_id, column_name, kind)) = implicit_add {
3243 self.snapshot_sequence(&sequence_id);
3244 self.snapshot_generation_counter();
3245 self.local.generation_counter += 1;
3246 self.local.sequences.insert(
3247 sequence_id.clone(),
3248 SequenceOverlay::Present(SequenceState {
3249 id: sequence_id.clone(),
3250 owner: self
3251 .local
3252 .relations
3253 .get(&alter.id)
3254 .and_then(|overlay| match overlay {
3255 RelationOverlay::Present(table) => Some(table.owner.clone()),
3256 RelationOverlay::Dropped => None,
3257 })
3258 .unwrap_or_else(|| ObjectId::new("", &self.local.current_role)),
3259 owned_by: Some((alter.id.clone(), column_name.clone())),
3260 kind,
3261 generation: self.local.generation_counter,
3262 }),
3263 );
3264 self.snapshot_graph();
3265 self.local.graph.edges.push(DependencyEdge::new(
3266 sequence_id,
3267 alter.id.clone(),
3268 DependencyKind::SequenceOwnedBy {
3269 column: column_name,
3270 },
3271 ));
3272 }
3273 match &alter.action {
3274 AlterTableActionMutation::DropColumn { .. } => {
3275 for sequence_id in owned_sequences_for_column {
3276 self.snapshot_sequence(&sequence_id);
3277 self.local
3278 .sequences
3279 .insert(sequence_id.clone(), SequenceOverlay::Dropped);
3280 self.snapshot_graph_full();
3281 self.local.graph.edges.retain(|edge| {
3282 !(matches!(edge.kind, DependencyKind::SequenceOwnedBy { .. })
3283 && edge.dependent == sequence_id)
3284 });
3285 }
3286 }
3287 AlterTableActionMutation::RenameColumn { to, .. } => {
3288 for sequence_id in owned_sequences_for_column {
3289 self.snapshot_sequence(&sequence_id);
3290 if let Some(SequenceOverlay::Present(sequence)) =
3291 self.local.sequences.get_mut(&sequence_id)
3292 && let Some((_, column)) = &mut sequence.owned_by
3293 {
3294 *column = to.clone();
3295 }
3296 self.snapshot_graph_full();
3297 for edge in &mut self.local.graph.edges {
3298 if edge.dependent == sequence_id
3299 && let DependencyKind::SequenceOwnedBy { column } =
3300 &mut edge.kind
3301 {
3302 *column = to.clone();
3303 }
3304 }
3305 }
3306 }
3307 _ => {}
3308 }
3309 MutationResult::Applied
3310 }
3311 Mutation::CreateType(create_type) => {
3312 if self.relation_namespace_is_taken(&create_type.id) {
3313 return MutationResult::Conflict {
3314 reason: format!("type '{}' already exists", create_type.id),
3315 };
3316 }
3317 self.snapshot_type(&create_type.id);
3318 self.snapshot_generation_counter();
3319 self.local.generation_counter += 1;
3320 let generation = self.local.generation_counter;
3321
3322 self.local.types.insert(
3323 create_type.id.clone(),
3324 TypeOverlay::Present(TypeState {
3325 id: create_type.id.clone(),
3326 generation,
3327 kind: create_type.kind.clone(),
3328 }),
3329 );
3330 MutationResult::Applied
3331 }
3332 Mutation::RenameType(rename) => {
3333 if !self.type_is_present(&rename.old_id) {
3334 if self.baseline_covers_object(&rename.old_id) {
3335 return MutationResult::Conflict {
3336 reason: format!("type '{}' does not exist", rename.old_id),
3337 };
3338 }
3339 self.snapshot_confidence();
3340 self.local.confidence = Confidence::Tainted;
3341 return MutationResult::Skipped;
3342 }
3343 if rename.old_id != rename.new_id
3344 && self.relation_namespace_is_taken(&rename.new_id)
3345 {
3346 return MutationResult::Conflict {
3347 reason: format!("type '{}' already exists", rename.new_id),
3348 };
3349 }
3350 if rename.old_id.schema != rename.new_id.schema
3351 && !self.schema_is_present(&rename.new_id.schema)
3352 {
3353 if self.schema_absence_is_authoritative(&rename.new_id.schema) {
3354 return MutationResult::Conflict {
3355 reason: format!("schema '{}' does not exist", rename.new_id.schema),
3356 };
3357 }
3358 self.snapshot_confidence();
3359 self.local.confidence = Confidence::Tainted;
3360 return MutationResult::Skipped;
3361 }
3362
3363 let mut remapped_functions = Vec::new();
3364 for (function_id, overlay) in &self.local.functions {
3365 let crate::model::function::FunctionOverlay::Present(function) = overlay else {
3366 continue;
3367 };
3368 let new_arg_types = function
3369 .arg_types
3370 .iter()
3371 .enumerate()
3372 .map(|(index, raw)| {
3373 if function.arg_type_ids.get(index)
3374 == Some(&Some(rename.old_id.clone()))
3375 {
3376 Self::remapped_type_display(
3377 raw,
3378 &rename.new_id,
3379 rename.old_id.schema != rename.new_id.schema,
3380 )
3381 } else {
3382 raw.clone()
3383 }
3384 })
3385 .collect::<Vec<_>>();
3386 let new_return_type = if function.return_type_id == Some(rename.old_id.clone())
3387 {
3388 Self::remapped_type_display(
3389 &function.return_type,
3390 &rename.new_id,
3391 rename.old_id.schema != rename.new_id.schema,
3392 )
3393 } else {
3394 function.return_type.clone()
3395 };
3396 let base_name = function_id
3397 .name
3398 .split_once('(')
3399 .map(|(name, _)| name)
3400 .unwrap_or(&function_id.name);
3401 let mut new_function_id = ObjectId::new(
3402 &function_id.schema,
3403 format!("{}({})", base_name, new_arg_types.join(",")),
3404 );
3405 new_function_id.inferred_schema = function_id.inferred_schema;
3406 if new_function_id != *function_id
3407 || new_arg_types != function.arg_types
3408 || new_return_type != function.return_type
3409 {
3410 remapped_functions.push((
3411 function_id.clone(),
3412 new_function_id,
3413 new_arg_types,
3414 new_return_type,
3415 ));
3416 }
3417 }
3418 let moved_function_ids = remapped_functions
3419 .iter()
3420 .map(|(old_id, _, _, _)| old_id)
3421 .collect::<HashSet<_>>();
3422 let mut destinations = HashSet::new();
3423 for (_, new_id, _, _) in &remapped_functions {
3424 if !destinations.insert(new_id)
3425 || (self.local.functions.contains_key(new_id)
3426 && !moved_function_ids.contains(new_id))
3427 {
3428 return MutationResult::Conflict {
3429 reason: format!(
3430 "routine '{}' already exists after renaming type '{}'",
3431 new_id, rename.old_id
3432 ),
3433 };
3434 }
3435 }
3436
3437 self.snapshot_namespace();
3438 if let Some(TypeOverlay::Present(mut state)) =
3439 self.local.types.remove(&rename.old_id)
3440 {
3441 state.id = rename.new_id.clone();
3442 self.local
3443 .types
3444 .insert(rename.new_id.clone(), TypeOverlay::Present(state));
3445 }
3446 for overlay in self.local.relations.values_mut() {
3447 if let RelationOverlay::Present(relation) = overlay {
3448 for column in &mut relation.columns {
3449 if column.type_id == Some(rename.old_id.clone()) {
3450 column.data_type = Some(Self::remapped_type_display(
3451 column.data_type.as_deref().unwrap_or_default(),
3452 &rename.new_id,
3453 rename.old_id.schema != rename.new_id.schema,
3454 ));
3455 column.type_id = Some(rename.new_id.clone());
3456 }
3457 }
3458 }
3459 }
3460 for overlay in self.local.types.values_mut() {
3461 if let TypeOverlay::Present(TypeState {
3462 kind:
3463 TypeKind::Domain {
3464 base_type,
3465 base_type_id,
3466 },
3467 ..
3468 }) = overlay
3469 && *base_type_id == Some(rename.old_id.clone())
3470 {
3471 *base_type = Self::remapped_type_display(
3472 base_type,
3473 &rename.new_id,
3474 rename.old_id.schema != rename.new_id.schema,
3475 );
3476 *base_type_id = Some(rename.new_id.clone());
3477 }
3478 }
3479 for (old_id, new_id, arg_types, return_type) in remapped_functions {
3480 if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
3481 self.local.functions.remove(&old_id)
3482 {
3483 function.id = new_id.clone();
3484 function.arg_types = arg_types;
3485 for type_id in &mut function.arg_type_ids {
3486 if *type_id == Some(rename.old_id.clone()) {
3487 *type_id = Some(rename.new_id.clone());
3488 }
3489 }
3490 function.return_type = return_type;
3491 if function.return_type_id == Some(rename.old_id.clone()) {
3492 function.return_type_id = Some(rename.new_id.clone());
3493 }
3494 self.local.functions.insert(
3495 new_id.clone(),
3496 crate::model::function::FunctionOverlay::Present(function),
3497 );
3498 if old_id != new_id {
3499 self.local.graph.propagate_rename(&old_id, &new_id);
3500 self.local.graph.edges.push(DependencyEdge::new(
3501 old_id,
3502 new_id,
3503 DependencyKind::RenameTo,
3504 ));
3505 }
3506 }
3507 }
3508 MutationResult::Applied
3509 }
3510 Mutation::AlterType(alter_type) => {
3511 self.snapshot_type(&alter_type.id);
3512 if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter_type.id) {
3513 match &alter_type.action {
3514 AlterTypeActionMutation::AddValue {
3515 new_value,
3516 neighbor,
3517 before,
3518 } => {
3519 if let TypeKind::Enum { variants } = &mut t.kind {
3520 if variants.contains(new_value) {
3521 return MutationResult::Skipped;
3522 }
3523 let insertion_index = neighbor
3524 .as_ref()
3525 .and_then(|neighbor| {
3526 variants.iter().position(|value| value == neighbor)
3527 })
3528 .map(|index| if *before { index } else { index + 1 })
3529 .unwrap_or(variants.len());
3530 variants.insert(insertion_index, new_value.clone());
3531 }
3532 }
3533 AlterTypeActionMutation::RenameValue {
3534 old_value,
3535 new_value,
3536 } => {
3537 let TypeKind::Enum { variants } = &mut t.kind else {
3538 return MutationResult::Conflict {
3539 reason: format!("type '{}' is not an enum", alter_type.id),
3540 };
3541 };
3542 let Some(old_index) =
3543 variants.iter().position(|value| value == old_value)
3544 else {
3545 return MutationResult::Conflict {
3546 reason: format!(
3547 "'{}' is not an existing label of enum '{}'",
3548 old_value, alter_type.id
3549 ),
3550 };
3551 };
3552 if variants.iter().any(|value| value == new_value) {
3553 return MutationResult::Conflict {
3554 reason: format!(
3555 "enum label '{}' already exists on type '{}'",
3556 new_value, alter_type.id
3557 ),
3558 };
3559 }
3560 variants[old_index] = new_value.clone();
3561 }
3562 }
3563 } else if matches!(
3564 alter_type.action,
3565 AlterTypeActionMutation::RenameValue { .. }
3566 ) {
3567 return MutationResult::Conflict {
3568 reason: format!("type '{}' does not exist", alter_type.id),
3569 };
3570 }
3571 MutationResult::Applied
3572 }
3573 Mutation::CreateDomain(create_domain) => {
3574 if self.relation_namespace_is_taken(&create_domain.id) {
3575 return MutationResult::Conflict {
3576 reason: format!("type '{}' already exists", create_domain.id),
3577 };
3578 }
3579 self.snapshot_type(&create_domain.id);
3580 self.snapshot_generation_counter();
3581 self.local.generation_counter += 1;
3582 let generation = self.local.generation_counter;
3583
3584 self.local.types.insert(
3585 create_domain.id.clone(),
3586 TypeOverlay::Present(TypeState {
3587 id: create_domain.id.clone(),
3588 generation,
3589 kind: TypeKind::Domain {
3590 base_type: create_domain.base_type.clone(),
3591 base_type_id: self.resolve_type_reference(&create_domain.base_type),
3592 },
3593 }),
3594 );
3595 MutationResult::Applied
3596 }
3597 Mutation::AlterDomain(_) => MutationResult::Applied,
3598 Mutation::DropDomain(drop_domain) => {
3599 for id in &drop_domain.ids {
3600 self.snapshot_type(id);
3601 self.local.types.insert(id.clone(), TypeOverlay::Dropped);
3602 }
3603 MutationResult::Applied
3604 }
3605 Mutation::DropType(drop_type) => {
3606 for id in &drop_type.ids {
3607 self.snapshot_type(id);
3608 self.local.types.insert(id.clone(), TypeOverlay::Dropped);
3609 }
3610 MutationResult::Applied
3611 }
3612 Mutation::CreateSequence(create_seq) => {
3613 if create_seq.if_not_exists && self.relation_namespace_is_taken(&create_seq.id) {
3614 return MutationResult::Skipped;
3615 }
3616 if self.relation_namespace_is_taken(&create_seq.id) {
3617 return MutationResult::Conflict {
3618 reason: format!("relation '{}' already exists", create_seq.id),
3619 };
3620 }
3621 if let Some((table_id, column)) = &create_seq.owned_by {
3622 if table_id.schema != create_seq.id.schema {
3623 return MutationResult::Conflict {
3624 reason: "sequence must be in the same schema as its owning table"
3625 .to_string(),
3626 };
3627 }
3628 match self.local.relations.get(table_id) {
3629 Some(RelationOverlay::Present(table)) => {
3630 if !table.has_column(column) {
3631 return MutationResult::Conflict {
3632 reason: format!(
3633 "column '{}.{}' does not exist",
3634 table_id, column
3635 ),
3636 };
3637 }
3638 if self.local.current_role_known
3639 && table.owner.name != self.local.current_role
3640 {
3641 return MutationResult::Conflict {
3642 reason: "sequence and table must have the same owner"
3643 .to_string(),
3644 };
3645 }
3646 }
3647 _ if self.baseline_covers_object(table_id) && self.baseline_available => {
3648 return MutationResult::Conflict {
3649 reason: format!("relation '{}' does not exist", table_id),
3650 };
3651 }
3652 _ => {
3653 self.snapshot_confidence();
3654 self.local.confidence = Confidence::Tainted;
3655 }
3656 }
3657 }
3658 self.snapshot_sequence(&create_seq.id);
3659 self.snapshot_generation_counter();
3660 self.local.generation_counter += 1;
3661 let generation = self.local.generation_counter;
3662
3663 self.local.sequences.insert(
3664 create_seq.id.clone(),
3665 SequenceOverlay::Present(SequenceState {
3666 id: create_seq.id.clone(),
3667 owner: ObjectId::new("", self.local.current_role.clone()),
3668 owned_by: create_seq.owned_by.clone(),
3669 kind: if create_seq.owned_by.is_some() {
3670 SequenceKind::Owned
3671 } else {
3672 SequenceKind::Standalone
3673 },
3674 generation,
3675 }),
3676 );
3677
3678 if let Some((table_id, col)) = &create_seq.owned_by {
3679 self.snapshot_graph();
3680 self.local.graph.edges.push(DependencyEdge::new(
3681 create_seq.id.clone(),
3682 table_id.clone(),
3683 DependencyKind::SequenceOwnedBy {
3684 column: col.clone(),
3685 },
3686 ));
3687 }
3688 MutationResult::Applied
3689 }
3690 Mutation::AlterSequence(alter_seq) => {
3691 if !self.sequence_is_present(&alter_seq.id) {
3692 if alter_seq.if_exists {
3693 return MutationResult::Skipped;
3694 }
3695 if self.baseline_covers_object(&alter_seq.id) && self.baseline_available {
3696 return MutationResult::Conflict {
3697 reason: format!("sequence '{}' does not exist", alter_seq.id),
3698 };
3699 }
3700 self.snapshot_confidence();
3701 self.local.confidence = Confidence::Tainted;
3702 return MutationResult::Skipped;
3703 }
3704 let current = match self.local.sequences.get(&alter_seq.id) {
3705 Some(SequenceOverlay::Present(sequence)) => sequence.clone(),
3706 _ => unreachable!("presence checked above"),
3707 };
3708 match &alter_seq.action {
3709 crate::analysis::mutations::AlterSequenceActionMutation::OwnedBy(owned_by) => {
3710 if current.kind == SequenceKind::Identity {
3711 return MutationResult::Conflict {
3712 reason: "cannot change ownership of an identity sequence"
3713 .to_string(),
3714 };
3715 }
3716 if let Some((table_id, column)) = owned_by {
3717 if table_id.schema != alter_seq.id.schema {
3718 return MutationResult::Conflict {
3719 reason:
3720 "sequence must be in the same schema as its owning table"
3721 .to_string(),
3722 };
3723 }
3724 let Some(RelationOverlay::Present(table)) =
3725 self.local.relations.get(table_id)
3726 else {
3727 return MutationResult::Conflict {
3728 reason: format!("relation '{}' does not exist", table_id),
3729 };
3730 };
3731 if !table.has_column(column) {
3732 return MutationResult::Conflict {
3733 reason: format!(
3734 "column '{}.{}' does not exist",
3735 table_id, column
3736 ),
3737 };
3738 }
3739 if table.owner != current.owner {
3740 return MutationResult::Conflict {
3741 reason: "sequence and table must have the same owner"
3742 .to_string(),
3743 };
3744 }
3745 }
3746 self.snapshot_sequence(&alter_seq.id);
3747 self.snapshot_graph();
3748 self.local.graph.edges.retain(|edge| {
3749 !(matches!(edge.kind, DependencyKind::SequenceOwnedBy { .. })
3750 && edge.dependent == alter_seq.id)
3751 });
3752 if let Some(SequenceOverlay::Present(sequence)) =
3753 self.local.sequences.get_mut(&alter_seq.id)
3754 {
3755 sequence.owned_by = owned_by.clone();
3756 sequence.kind = if owned_by.is_some() {
3757 SequenceKind::Owned
3758 } else {
3759 SequenceKind::Standalone
3760 };
3761 }
3762 if let Some((table_id, column)) = owned_by {
3763 self.local.graph.edges.push(DependencyEdge::new(
3764 alter_seq.id.clone(),
3765 table_id.clone(),
3766 DependencyKind::SequenceOwnedBy {
3767 column: column.clone(),
3768 },
3769 ));
3770 }
3771 MutationResult::Applied
3772 }
3773 crate::analysis::mutations::AlterSequenceActionMutation::OwnerTo(owner) => {
3774 if current.kind == SequenceKind::Identity {
3775 return MutationResult::Conflict {
3776 reason: "cannot alter an identity sequence independently"
3777 .to_string(),
3778 };
3779 }
3780 let Some((owner_name, known)) = self.role_fact_identity(owner) else {
3781 self.snapshot_confidence();
3782 self.local.confidence = Confidence::Tainted;
3783 return MutationResult::Skipped;
3784 };
3785 if known
3786 && self.local.roles_known
3787 && self.present_role(&owner_name).is_none()
3788 {
3789 return MutationResult::Conflict {
3790 reason: format!("role '{}' does not exist", owner_name),
3791 };
3792 }
3793 if let Some((table_id, _)) = ¤t.owned_by
3794 && let Some(RelationOverlay::Present(table)) =
3795 self.local.relations.get(table_id)
3796 && table.owner.name != owner_name
3797 {
3798 return MutationResult::Conflict {
3799 reason: "sequence and table must have the same owner".to_string(),
3800 };
3801 }
3802 self.snapshot_sequence(&alter_seq.id);
3803 if let Some(SequenceOverlay::Present(sequence)) =
3804 self.local.sequences.get_mut(&alter_seq.id)
3805 {
3806 sequence.owner = ObjectId::new("", owner_name);
3807 }
3808 MutationResult::Applied
3809 }
3810 crate::analysis::mutations::AlterSequenceActionMutation::RenameTo(new_id)
3811 | crate::analysis::mutations::AlterSequenceActionMutation::SetSchema(new_id) => {
3812 if current.kind == SequenceKind::Identity {
3813 return MutationResult::Conflict {
3814 reason: "cannot alter an identity sequence independently"
3815 .to_string(),
3816 };
3817 }
3818 if self.relation_namespace_is_taken(new_id) {
3819 return MutationResult::Conflict {
3820 reason: format!("relation '{}' already exists", new_id),
3821 };
3822 }
3823 if let Some((table_id, _)) = ¤t.owned_by
3824 && table_id.schema != new_id.schema
3825 {
3826 return MutationResult::Conflict {
3827 reason: "sequence must be in the same schema as its owning table"
3828 .to_string(),
3829 };
3830 }
3831 self.snapshot_namespace();
3832 let mut moved = current;
3833 moved.id = new_id.clone();
3834 self.local.sequences.remove(&alter_seq.id);
3835 self.local
3836 .sequences
3837 .insert(new_id.clone(), SequenceOverlay::Present(moved));
3838 self.local.graph.propagate_rename(&alter_seq.id, new_id);
3839 self.local.graph.edges.push(DependencyEdge::new(
3840 alter_seq.id.clone(),
3841 new_id.clone(),
3842 DependencyKind::RenameTo,
3843 ));
3844 if self.baseline_sequences.remove(&alter_seq.id) {
3845 self.baseline_sequences.insert(new_id.clone());
3846 }
3847 MutationResult::Applied
3848 }
3849 crate::analysis::mutations::AlterSequenceActionMutation::Other => {
3850 MutationResult::Applied
3851 }
3852 }
3853 }
3854 Mutation::DropSequence(drop_seq) => {
3855 if !drop_seq.if_exists {
3856 let missing: Vec<ObjectId> = drop_seq
3857 .ids
3858 .iter()
3859 .filter(|id| !self.sequence_is_present(id))
3860 .cloned()
3861 .collect();
3862 for id in &missing {
3863 if self.baseline_covers_object(id) && self.baseline_available {
3864 return MutationResult::Conflict {
3865 reason: format!("sequence '{}' does not exist", id),
3866 };
3867 }
3868 self.snapshot_confidence();
3869 self.local.confidence = Confidence::Tainted;
3870 }
3871 }
3872 let present: Vec<ObjectId> = drop_seq
3873 .ids
3874 .iter()
3875 .filter(|id| self.sequence_is_present(id))
3876 .cloned()
3877 .collect();
3878 if present.is_empty() {
3879 return MutationResult::Skipped;
3880 }
3881 for id in &present {
3882 let Some(SequenceOverlay::Present(sequence)) = self.local.sequences.get(id)
3883 else {
3884 continue;
3885 };
3886 if sequence.kind == SequenceKind::Identity {
3887 return MutationResult::Conflict {
3888 reason: format!("cannot drop identity sequence '{}' independently", id),
3889 };
3890 }
3891 if sequence.kind == SequenceKind::SerialLike && !drop_seq.cascade {
3892 return MutationResult::Conflict {
3893 reason: format!("sequence '{}' still has dependent defaults", id),
3894 };
3895 }
3896 }
3897 if drop_seq.cascade {
3898 let serial_owners: Vec<(ObjectId, String)> = present
3899 .iter()
3900 .filter_map(|id| match self.local.sequences.get(id) {
3901 Some(SequenceOverlay::Present(sequence))
3902 if sequence.kind == SequenceKind::SerialLike =>
3903 {
3904 sequence.owned_by.clone()
3905 }
3906 _ => None,
3907 })
3908 .collect();
3909 for (table_id, column) in serial_owners {
3910 self.snapshot_relation(&table_id);
3911 if let Some(RelationOverlay::Present(table)) =
3912 self.local.relations.get_mut(&table_id)
3913 && let Some(column) =
3914 table.columns.iter_mut().find(|item| item.name == column)
3915 {
3916 column.default = None;
3917 column.default_expr_text = None;
3918 }
3919 }
3920 }
3921 for id in &present {
3922 self.snapshot_sequence(id);
3923 self.local
3924 .sequences
3925 .insert(id.clone(), SequenceOverlay::Dropped);
3926 }
3927 self.snapshot_graph_full();
3928 self.local.graph.edges.retain(|e| {
3929 !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
3930 && present.contains(&e.dependent))
3931 });
3932 MutationResult::Applied
3933 }
3934 Mutation::Rename(rename) => {
3935 let renames_relation = self.relation_is_present(&rename.old_id);
3936 let renames_index = self.index_is_present(&rename.old_id);
3937 if !renames_relation && !renames_index {
3938 if self.baseline_covers_object(&rename.old_id) {
3939 return MutationResult::Conflict {
3940 reason: format!("relation '{}' does not exist", rename.old_id),
3941 };
3942 }
3943 self.snapshot_confidence();
3944 self.local.confidence = Confidence::Tainted;
3945 return MutationResult::Skipped;
3946 }
3947 if rename.old_id != rename.new_id
3948 && self.relation_namespace_is_taken(&rename.new_id)
3949 {
3950 return MutationResult::Conflict {
3951 reason: format!("relation '{}' already exists", rename.new_id),
3952 };
3953 }
3954 if rename.old_id.schema != rename.new_id.schema
3955 && !self.schema_is_present(&rename.new_id.schema)
3956 {
3957 if self.schema_absence_is_authoritative(&rename.new_id.schema) {
3958 return MutationResult::Conflict {
3959 reason: format!("schema '{}' does not exist", rename.new_id.schema),
3960 };
3961 }
3962 self.snapshot_confidence();
3963 self.local.confidence = Confidence::Tainted;
3964 return MutationResult::Skipped;
3965 }
3966
3967 self.snapshot_namespace();
3968 if let Some(RelationOverlay::Present(mut state)) =
3969 self.local.relations.remove(&rename.old_id)
3970 {
3971 state.id = rename.new_id.clone();
3972 self.local
3973 .relations
3974 .insert(rename.new_id.clone(), RelationOverlay::Present(state));
3975 }
3976 let owned_sequence_ids: Vec<ObjectId> = self
3977 .local
3978 .sequences
3979 .iter()
3980 .filter_map(|(id, overlay)| match overlay {
3981 SequenceOverlay::Present(sequence)
3982 if sequence
3983 .owned_by
3984 .as_ref()
3985 .is_some_and(|(table, _)| table == &rename.old_id) =>
3986 {
3987 Some(id.clone())
3988 }
3989 _ => None,
3990 })
3991 .collect();
3992 for sequence_id in owned_sequence_ids {
3993 self.snapshot_sequence(&sequence_id);
3994 if let Some(SequenceOverlay::Present(sequence)) =
3995 self.local.sequences.get_mut(&sequence_id)
3996 && let Some((table, _)) = &mut sequence.owned_by
3997 {
3998 *table = rename.new_id.clone();
3999 }
4000 }
4001 let triggers_to_move: Vec<(ObjectId, crate::model::trigger::TriggerState)> = self
4002 .local
4003 .triggers
4004 .iter()
4005 .filter_map(|(id, overlay)| match overlay {
4006 TriggerOverlay::Present(trigger) if trigger.table_id == rename.old_id => {
4007 Some((id.clone(), trigger.clone()))
4008 }
4009 _ => None,
4010 })
4011 .collect();
4012 for (old_trigger_id, mut trigger) in triggers_to_move {
4013 let new_trigger_id = Self::trigger_key(&rename.new_id, &trigger.name);
4014 self.local.triggers.remove(&old_trigger_id);
4015 trigger.id = new_trigger_id.clone();
4016 trigger.table_id = rename.new_id.clone();
4017 self.local
4018 .triggers
4019 .insert(new_trigger_id.clone(), TriggerOverlay::Present(trigger));
4020 self.local
4021 .graph
4022 .propagate_rename(&old_trigger_id, &new_trigger_id);
4023 self.local.graph.edges.push(DependencyEdge::new(
4024 old_trigger_id,
4025 new_trigger_id,
4026 DependencyKind::RenameTo,
4027 ));
4028 }
4029 let constraints_to_move: Vec<(String, ConstraintState)> = self
4030 .local
4031 .constraints
4032 .iter()
4033 .filter(|((table_id, _), _)| table_id == &rename.old_id)
4034 .map(|((_, name), constraint)| (name.clone(), constraint.clone()))
4035 .collect();
4036 for (name, mut constraint) in constraints_to_move {
4037 self.snapshot_constraint(&rename.old_id, &name);
4038 self.snapshot_constraint(&rename.new_id, &name);
4039 self.local
4040 .constraints
4041 .remove(&(rename.old_id.clone(), name.clone()));
4042 constraint.table_id = rename.new_id.clone();
4043 self.local
4044 .constraints
4045 .insert((rename.new_id.clone(), name), constraint);
4046 }
4047 self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
4048 .into_iter()
4049 .map(|(table, name)| {
4050 if table == rename.old_id {
4051 (rename.new_id.clone(), name)
4052 } else {
4053 (table, name)
4054 }
4055 })
4056 .collect();
4057 self.local.graph.edges.push(DependencyEdge::new(
4058 rename.old_id.clone(),
4059 rename.new_id.clone(),
4060 DependencyKind::RenameTo,
4061 ));
4062 self.local
4063 .graph
4064 .propagate_rename(&rename.old_id, &rename.new_id);
4065
4066 if renames_relation {
4067 if self.baseline_relations.remove(&rename.old_id) {
4068 self.baseline_relations.insert(rename.new_id.clone());
4069 }
4070 if self.baseline_fk_dependencies.remove(&rename.old_id) {
4071 self.baseline_fk_dependencies.insert(rename.new_id.clone());
4072 }
4073 self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
4074 .into_iter()
4075 .map(|(table, name)| {
4076 if table == rename.old_id {
4077 (rename.new_id.clone(), name)
4078 } else {
4079 (table, name)
4080 }
4081 })
4082 .collect();
4083 }
4084 if renames_index && self.baseline_indexes.remove(&rename.old_id) {
4085 self.baseline_indexes.insert(rename.new_id.clone());
4086 }
4087
4088 MutationResult::Applied
4089 }
4090 Mutation::DropView(drop_view) => {
4091 let mut present = Vec::new();
4092 for id in &drop_view.ids {
4093 match self.local.relations.get(id) {
4094 Some(RelationOverlay::Present(relation))
4095 if relation.kind == RelationKind::View =>
4096 {
4097 present.push(id.clone());
4098 }
4099 Some(RelationOverlay::Present(_)) => {
4100 return MutationResult::Conflict {
4101 reason: format!("'{}' is not a view", id),
4102 };
4103 }
4104 _ if drop_view.if_exists => {}
4105 _ if self.baseline_available && self.baseline_covers_object(id) => {
4106 return MutationResult::Conflict {
4107 reason: format!("view '{}' does not exist", id),
4108 };
4109 }
4110 _ => {
4111 self.snapshot_confidence();
4112 self.local.confidence = Confidence::Tainted;
4113 continue;
4114 }
4115 }
4116 }
4117 if present.is_empty() {
4118 return MutationResult::Skipped;
4119 }
4120 for id in &present {
4121 self.snapshot_relation(id);
4122 self.local
4123 .relations
4124 .insert(id.clone(), RelationOverlay::Dropped);
4125 }
4126 self.snapshot_graph_full();
4127 self.local.graph.edges.retain(|e| {
4128 !(matches!(e.kind, DependencyKind::ViewDependency { .. })
4129 && present.contains(&e.dependent))
4130 });
4131 MutationResult::Applied
4132 }
4133 Mutation::DropMaterializedView(drop_mv) => {
4134 let mut present = Vec::new();
4135 for id in &drop_mv.ids {
4136 match self.local.relations.get(id) {
4137 Some(RelationOverlay::Present(relation))
4138 if relation.kind == RelationKind::MaterializedView =>
4139 {
4140 present.push(id.clone());
4141 }
4142 Some(RelationOverlay::Present(_)) => {
4143 return MutationResult::Conflict {
4144 reason: format!("'{}' is not a materialized view", id),
4145 };
4146 }
4147 _ if drop_mv.if_exists => {}
4148 _ if self.baseline_available && self.baseline_covers_object(id) => {
4149 return MutationResult::Conflict {
4150 reason: format!("materialized view '{}' does not exist", id),
4151 };
4152 }
4153 _ => {
4154 self.snapshot_confidence();
4155 self.local.confidence = Confidence::Tainted;
4156 continue;
4157 }
4158 }
4159 }
4160 if present.is_empty() {
4161 return MutationResult::Skipped;
4162 }
4163 for id in &present {
4164 self.snapshot_relation(id);
4165 self.local
4166 .relations
4167 .insert(id.clone(), RelationOverlay::Dropped);
4168 }
4169 self.snapshot_graph_full();
4170 self.local.graph.edges.retain(|e| {
4171 !((matches!(e.kind, DependencyKind::ViewDependency { .. })
4172 && present.contains(&e.dependent))
4173 || (matches!(e.kind, DependencyKind::IndexOnRelation { .. })
4174 && present.contains(&e.referenced)))
4175 });
4176 MutationResult::Applied
4177 }
4178 Mutation::DropIndex(drop_idx) => {
4179 let present = self.local.graph.edges.iter().any(|edge| {
4180 matches!(edge.kind, DependencyKind::IndexOnRelation { .. })
4181 && edge.dependent == drop_idx.id
4182 });
4183 if !present {
4184 if drop_idx.if_exists {
4185 return MutationResult::Skipped;
4186 }
4187 if self.baseline_available && self.baseline_covers_object(&drop_idx.id) {
4188 return MutationResult::Conflict {
4189 reason: format!("index '{}' does not exist", drop_idx.id),
4190 };
4191 }
4192 self.snapshot_confidence();
4193 self.local.confidence = Confidence::Tainted;
4194 return MutationResult::Skipped;
4195 }
4196 self.snapshot_graph();
4197 self.local.graph.edges.retain(|e| {
4198 !(matches!(e.kind, DependencyKind::IndexOnRelation { .. })
4199 && e.dependent == drop_idx.id)
4200 });
4201 MutationResult::Applied
4202 }
4203 Mutation::ChangeRelationOwner { id, new_owner } => {
4204 let Some((owner, known)) = self.role_fact_identity(new_owner) else {
4205 self.snapshot_confidence();
4206 self.local.confidence = Confidence::Tainted;
4207 return MutationResult::Skipped;
4208 };
4209 if !known {
4210 self.snapshot_confidence();
4211 self.local.confidence = Confidence::Tainted;
4212 }
4213 self.snapshot_relation(id);
4214 if let Some(RelationOverlay::Present(relation)) = self.local.relations.get_mut(id) {
4215 relation.owner = ObjectId::new("", owner);
4216 MutationResult::Applied
4217 } else {
4218 MutationResult::Conflict {
4219 reason: format!("relation '{}' does not exist", id),
4220 }
4221 }
4222 }
4223 Mutation::SearchPath(sp) => {
4224 if sp.local && self.local.transactions.is_empty() {
4225 return MutationResult::Skipped;
4228 }
4229 self.snapshot_search_path();
4230 self.snapshot_confidence();
4231 let template = match &sp.target {
4232 SearchPathTarget::Default => self.local.default_search_path_template.clone(),
4233 SearchPathTarget::Schemas(schemas) => schemas.clone(),
4234 };
4235 self.local.search_path_template = template.clone();
4236 if !sp.local {
4237 self.local.session_search_path_template = template;
4238 }
4239 self.refresh_role_sensitive_search_path();
4240 MutationResult::Applied
4241 }
4242 Mutation::TimeoutSetting(change) => {
4243 if change.local && self.local.transactions.is_empty() {
4244 return MutationResult::Skipped;
4245 }
4246 let next = match &change.value {
4247 TimeoutSettingValue::Default => match change.setting {
4248 TimeoutSetting::Lock => self.local.lock_timeout.default,
4249 TimeoutSetting::Statement => self.local.statement_timeout.default,
4250 },
4251 TimeoutSettingValue::Milliseconds(milliseconds) => Some(*milliseconds),
4252 TimeoutSettingValue::Current => match change.setting {
4253 TimeoutSetting::Lock => self.local.lock_timeout.effective,
4254 TimeoutSetting::Statement => self.local.statement_timeout.effective,
4255 },
4256 TimeoutSettingValue::Invalid(reason) => {
4257 return MutationResult::Conflict {
4258 reason: reason.clone(),
4259 };
4260 }
4261 };
4262 self.snapshot_timeout_settings();
4263 let setting = match change.setting {
4264 TimeoutSetting::Lock => &mut self.local.lock_timeout,
4265 TimeoutSetting::Statement => &mut self.local.statement_timeout,
4266 };
4267 setting.effective = next;
4268 if !change.local {
4269 setting.session = next;
4270 }
4271 MutationResult::Applied
4272 }
4273 Mutation::ResetSettings(target) => {
4274 if matches!(
4275 target,
4276 ResetSettingTarget::All | ResetSettingTarget::SearchPath
4277 ) {
4278 self.snapshot_search_path();
4279 self.snapshot_confidence();
4280 let template = self.local.default_search_path_template.clone();
4281 self.local.session_search_path_template = template.clone();
4282 self.local.search_path_template = template;
4283 self.refresh_role_sensitive_search_path();
4284 }
4285 if matches!(
4286 target,
4287 ResetSettingTarget::All
4288 | ResetSettingTarget::LockTimeout
4289 | ResetSettingTarget::StatementTimeout
4290 ) {
4291 self.snapshot_timeout_settings();
4292 if matches!(
4293 target,
4294 ResetSettingTarget::All | ResetSettingTarget::LockTimeout
4295 ) {
4296 self.local.lock_timeout.session = self.local.lock_timeout.default;
4297 self.local.lock_timeout.effective = self.local.lock_timeout.default;
4298 }
4299 if matches!(
4300 target,
4301 ResetSettingTarget::All | ResetSettingTarget::StatementTimeout
4302 ) {
4303 self.local.statement_timeout.session = self.local.statement_timeout.default;
4304 self.local.statement_timeout.effective =
4305 self.local.statement_timeout.default;
4306 }
4307 }
4308 MutationResult::Applied
4309 }
4310 Mutation::CheckTimeouts => MutationResult::Applied,
4311 Mutation::SwitchRole {
4312 role,
4313 local,
4314 is_session_auth,
4315 } => {
4316 if *local && self.local.transactions.is_empty() {
4317 return MutationResult::Skipped;
4319 }
4320
4321 let target = if let Some(role) = role {
4322 let Some(identity) = self.role_fact_identity(role) else {
4323 self.snapshot_confidence();
4324 self.local.confidence = Confidence::Tainted;
4325 return MutationResult::Skipped;
4326 };
4327 Some(identity)
4328 } else if *is_session_auth {
4329 Some((
4330 self.local.authenticated_role.clone(),
4331 self.local.authenticated_role_known,
4332 ))
4333 } else {
4334 Some((
4335 self.local.session_role.clone(),
4336 self.local.session_role_known,
4337 ))
4338 };
4339 let (target_name, target_known) = target.expect("role reset always has a target");
4340 let persistent_role_reset_target = if role.is_none() && !*is_session_auth {
4341 Some((
4342 self.local.persistent_session_role.clone(),
4343 self.local.persistent_session_role_known,
4344 ))
4345 } else {
4346 None
4347 };
4348
4349 let authorized = if role.is_none() {
4350 Some(true)
4351 } else if *is_session_auth {
4352 self.can_set_session_authorization_to(&target_name)
4353 } else {
4354 self.can_set_role_to(&target_name)
4355 };
4356 match authorized {
4357 Some(false) => {
4358 return MutationResult::Conflict {
4359 reason: if self.present_role(&target_name).is_none() {
4360 format!("role '{}' does not exist", target_name)
4361 } else {
4362 format!("permission denied to set role '{}'", target_name)
4363 },
4364 };
4365 }
4366 None => {
4367 self.snapshot_confidence();
4368 self.local.confidence = Confidence::Tainted;
4369 }
4370 Some(true) => {}
4371 }
4372
4373 self.snapshot_role_context();
4374 self.snapshot_search_path();
4375 self.snapshot_confidence();
4376 if *is_session_auth {
4377 self.local.session_role = target_name.clone();
4378 self.local.session_role_known = target_known;
4379 self.local.current_role = target_name.clone();
4380 self.local.current_role_known = target_known;
4381 if !local {
4382 self.local.persistent_session_role = target_name.clone();
4383 self.local.persistent_session_role_known = target_known;
4384 self.local.persistent_current_role = target_name;
4385 self.local.persistent_current_role_known = target_known;
4386 }
4387 } else {
4388 self.local.current_role = target_name.clone();
4389 self.local.current_role_known = target_known;
4390 if !local {
4391 let (persistent_name, persistent_known) =
4392 persistent_role_reset_target.unwrap_or((target_name, target_known));
4393 self.local.persistent_current_role = persistent_name;
4394 self.local.persistent_current_role_known = persistent_known;
4395 }
4396 }
4397 self.refresh_role_sensitive_search_path();
4398 MutationResult::Applied
4399 }
4400 Mutation::BeginTransaction => {
4401 if self.local.transactions.is_empty() {
4402 self.local.transactions.push(TransactionFrame::root());
4403 MutationResult::Applied
4404 } else {
4405 MutationResult::Skipped
4408 }
4409 }
4410 Mutation::CommitTransaction => {
4411 if self.local.transaction_aborted {
4412 while let Some(frame) = self.local.transactions.pop() {
4413 self.rollback_frame(frame);
4414 }
4415 } else {
4416 while self.local.transactions.pop().is_some() {}
4417 self.restore_persistent_role_context();
4418 }
4419 self.local.transaction_aborted = false;
4420 MutationResult::Applied
4421 }
4422 Mutation::CommitAndChain => {
4423 if self.local.transactions.is_empty() {
4424 self.local.confidence = Confidence::Tainted;
4425 return MutationResult::Conflict {
4426 reason: "COMMIT AND CHAIN can only be used in transaction blocks"
4427 .to_string(),
4428 };
4429 }
4430 if self.local.transaction_aborted {
4431 while let Some(frame) = self.local.transactions.pop() {
4432 self.rollback_frame(frame);
4433 }
4434 } else {
4435 while self.local.transactions.pop().is_some() {}
4436 self.restore_persistent_role_context();
4437 }
4438 self.local.transaction_aborted = false;
4439 self.local.transactions.push(TransactionFrame::root());
4440 MutationResult::Applied
4441 }
4442 Mutation::RollbackTransaction => {
4443 while let Some(frame) = self.local.transactions.pop() {
4444 self.rollback_frame(frame);
4445 }
4446 self.local.transaction_aborted = false;
4447 MutationResult::Applied
4448 }
4449 Mutation::RollbackAndChain => {
4450 if self.local.transactions.is_empty() {
4451 self.local.confidence = Confidence::Tainted;
4452 return MutationResult::Conflict {
4453 reason: "ROLLBACK AND CHAIN can only be used in transaction blocks"
4454 .to_string(),
4455 };
4456 }
4457 while let Some(frame) = self.local.transactions.pop() {
4458 self.rollback_frame(frame);
4459 }
4460 self.local.transaction_aborted = false;
4461 self.local.transactions.push(TransactionFrame::root());
4462 MutationResult::Applied
4463 }
4464 Mutation::RollbackToSavepoint(rts) => {
4465 let Some(position) = self
4466 .local
4467 .transactions
4468 .iter()
4469 .rposition(|frame| frame.is_named_savepoint(&rts.name))
4470 else {
4471 self.local.confidence = Confidence::Tainted;
4472 if !self.local.transactions.is_empty() {
4473 self.local.transaction_aborted = true;
4474 }
4475 return MutationResult::Conflict {
4476 reason: format!("savepoint '{}' does not exist", rts.name),
4477 };
4478 };
4479 let rolled_back = self.local.transactions.split_off(position + 1);
4480 for frame in rolled_back.into_iter().rev() {
4484 self.rollback_frame(frame);
4485 }
4486 let undo_log = std::mem::take(&mut self.local.transactions[position].undo_log);
4487 self.rollback_undo_log(undo_log);
4488 self.local.transaction_aborted = false;
4489 MutationResult::Applied
4490 }
4491 Mutation::Savepoint(sp) => {
4492 if self.local.transactions.is_empty() {
4493 self.local.confidence = Confidence::Tainted;
4494 return MutationResult::Conflict {
4495 reason: "SAVEPOINT can only be used in transaction blocks".to_string(),
4496 };
4497 }
4498 self.local
4499 .transactions
4500 .push(TransactionFrame::savepoint(sp.name.clone()));
4501 MutationResult::Applied
4502 }
4503 Mutation::ReleaseSavepoint(rsp) => {
4504 let Some(position) = self
4505 .local
4506 .transactions
4507 .iter()
4508 .rposition(|frame| frame.is_named_savepoint(&rsp.name))
4509 else {
4510 self.local.confidence = Confidence::Tainted;
4511 if !self.local.transactions.is_empty() {
4512 self.local.transaction_aborted = true;
4513 }
4514 return MutationResult::Conflict {
4515 reason: format!("savepoint '{}' does not exist", rsp.name),
4516 };
4517 };
4518 if position == 0 {
4519 self.local.confidence = Confidence::Tainted;
4520 return MutationResult::Conflict {
4521 reason: format!("savepoint '{}' is not inside a transaction", rsp.name),
4522 };
4523 }
4524
4525 let released = self.local.transactions.split_off(position);
4526 let outer = self
4527 .local
4528 .transactions
4529 .last_mut()
4530 .expect("a released savepoint always has an outer transaction frame");
4531 for frame in released {
4532 outer.undo_log.extend(frame.undo_log);
4533 }
4534 MutationResult::Applied
4535 }
4536 Mutation::Opaque(_) => {
4537 self.snapshot_confidence();
4538 self.local.confidence = Confidence::Tainted;
4539 MutationResult::Applied
4540 }
4541 Mutation::CreateFunction(f) => {
4542 let routine_kind =
4543 if f.options.iter().any(|option| {
4544 matches!(option, crate::analysis::facts::FuncOptionFact::Window)
4545 }) {
4546 crate::model::function::RoutineKind::Window
4547 } else {
4548 crate::model::function::RoutineKind::Function
4549 };
4550 match self.local.functions.get(&f.id) {
4551 Some(crate::model::function::FunctionOverlay::Present(existing))
4552 if existing.routine_kind != routine_kind || !f.or_replace =>
4553 {
4554 return MutationResult::Conflict {
4555 reason: format!("routine '{}' already exists", f.id),
4556 };
4557 }
4558 None if !self.baseline_available || !self.baseline_covers_object(&f.id) => {
4559 self.snapshot_confidence();
4560 self.local.confidence = Confidence::Tainted;
4561 }
4562 _ => {}
4563 }
4564 self.snapshot_function(&f.id);
4565 self.snapshot_generation_counter();
4566 self.local.generation_counter += 1;
4567 let _generation = self.local.generation_counter;
4568
4569 let volatility = f
4570 .options
4571 .iter()
4572 .find_map(|opt| {
4573 if let crate::analysis::facts::FuncOptionFact::Volatility(v) = opt {
4574 Some(match v {
4575 crate::analysis::facts::VolatilityKind::Volatile => {
4576 crate::model::function::Volatility::Volatile
4577 }
4578 crate::analysis::facts::VolatilityKind::Stable => {
4579 crate::model::function::Volatility::Stable
4580 }
4581 crate::analysis::facts::VolatilityKind::Immutable => {
4582 crate::model::function::Volatility::Immutable
4583 }
4584 })
4585 } else {
4586 None
4587 }
4588 })
4589 .unwrap_or(crate::model::function::Volatility::Volatile);
4590
4591 let security = f
4592 .options
4593 .iter()
4594 .find_map(|opt| {
4595 if let crate::analysis::facts::FuncOptionFact::Security(s) = opt {
4596 Some(match s {
4597 crate::analysis::facts::SecurityKind::Invoker => {
4598 crate::model::function::SecurityMode::Invoker
4599 }
4600 crate::analysis::facts::SecurityKind::Definer => {
4601 crate::model::function::SecurityMode::Definer
4602 }
4603 })
4604 } else {
4605 None
4606 }
4607 })
4608 .unwrap_or(crate::model::function::SecurityMode::Invoker);
4609
4610 let language = f
4611 .options
4612 .iter()
4613 .find_map(|opt| {
4614 if let crate::analysis::facts::FuncOptionFact::Language(l) = opt {
4615 Some(l.clone())
4616 } else {
4617 None
4618 }
4619 })
4620 .unwrap_or_else(|| "sql".to_string());
4621
4622 self.local.functions.insert(
4623 f.id.clone(),
4624 crate::model::function::FunctionOverlay::Present(
4625 crate::model::function::FunctionState {
4626 id: f.id.clone(),
4627 routine_kind,
4628 arg_types: f
4629 .params
4630 .iter()
4631 .filter(|p| {
4632 !matches!(&p.mode, crate::analysis::facts::ParamModeFact::Out)
4633 })
4634 .map(|p| p.ty.clone())
4635 .collect(),
4636 arg_type_ids: f
4637 .params
4638 .iter()
4639 .filter(|p| {
4640 !matches!(&p.mode, crate::analysis::facts::ParamModeFact::Out)
4641 })
4642 .map(|parameter| self.resolve_type_reference(¶meter.ty))
4643 .collect(),
4644 return_type: f
4645 .return_type
4646 .as_ref()
4647 .map(|rt| match rt {
4648 crate::analysis::facts::RetTypeFact::Scalar(ty) => ty.clone(),
4649 crate::analysis::facts::RetTypeFact::Table(columns) => columns
4650 .iter()
4651 .map(|column| {
4652 format!(
4653 "{} {}",
4654 column.name,
4655 column.ty.as_deref().unwrap_or("unknown")
4656 )
4657 })
4658 .collect::<Vec<_>>()
4659 .join(", "),
4660 })
4661 .unwrap_or_default(),
4662 return_type_id: f.return_type.as_ref().and_then(|return_type| {
4663 match return_type {
4664 crate::analysis::facts::RetTypeFact::Scalar(ty) => {
4665 self.resolve_type_reference(ty)
4666 }
4667 crate::analysis::facts::RetTypeFact::Table(_) => None,
4668 }
4669 }),
4670 volatility,
4671 language,
4672 security,
4673 },
4674 ),
4675 );
4676 MutationResult::Applied
4677 }
4678 Mutation::AlterFunction(f) => {
4679 use crate::analysis::facts::{AlterFunctionAction, FuncOptionFact};
4680 use crate::model::function::{
4681 FunctionOverlay, RoutineKind, SecurityMode, Volatility,
4682 };
4683
4684 match self.local.functions.get(&f.id) {
4685 Some(FunctionOverlay::Present(function))
4686 if matches!(
4687 function.routine_kind,
4688 RoutineKind::Function | RoutineKind::Window
4689 ) => {}
4690 Some(FunctionOverlay::Present(_)) => {
4691 return MutationResult::Conflict {
4692 reason: format!("'{}' is not a function", f.id),
4693 };
4694 }
4695 Some(FunctionOverlay::Dropped) => {
4696 return MutationResult::Conflict {
4697 reason: format!("function '{}' does not exist", f.id),
4698 };
4699 }
4700 _ if self.baseline_available && self.baseline_covers_object(&f.id) => {
4701 return MutationResult::Conflict {
4702 reason: format!("function '{}' does not exist", f.id),
4703 };
4704 }
4705 _ => {
4706 self.snapshot_confidence();
4707 self.local.confidence = Confidence::Tainted;
4708 return MutationResult::Skipped;
4709 }
4710 }
4711
4712 match &f.action {
4713 AlterFunctionAction::OptionsChange(options) => {
4714 self.snapshot_function(&f.id);
4715 if let Some(FunctionOverlay::Present(function)) =
4716 self.local.functions.get_mut(&f.id)
4717 {
4718 for option in options {
4719 match option {
4720 FuncOptionFact::Volatility(volatility) => {
4721 function.volatility = match volatility {
4722 crate::analysis::facts::VolatilityKind::Volatile => {
4723 Volatility::Volatile
4724 }
4725 crate::analysis::facts::VolatilityKind::Stable => {
4726 Volatility::Stable
4727 }
4728 crate::analysis::facts::VolatilityKind::Immutable => {
4729 Volatility::Immutable
4730 }
4731 };
4732 }
4733 FuncOptionFact::Security(security) => {
4734 function.security = match security {
4735 crate::analysis::facts::SecurityKind::Invoker => {
4736 SecurityMode::Invoker
4737 }
4738 crate::analysis::facts::SecurityKind::Definer => {
4739 SecurityMode::Definer
4740 }
4741 };
4742 }
4743 FuncOptionFact::Language(language) => {
4744 function.language = language.clone();
4745 }
4746 _ => {}
4747 }
4748 }
4749 }
4750 }
4751 AlterFunctionAction::Rename { to, .. } => {
4752 let signature =
4753 f.id.name
4754 .find('(')
4755 .map(|index| &f.id.name[index..])
4756 .unwrap_or("");
4757 let new_id = ObjectId::new(f.id.schema.clone(), format!("{to}{signature}"));
4758 self.move_function(&f.id, &new_id);
4759 }
4760 AlterFunctionAction::SchemaChange { new_schema } => {
4761 let new_id = ObjectId::new(new_schema.clone(), f.id.name.clone());
4762 self.move_function(&f.id, &new_id);
4763 }
4764 AlterFunctionAction::OwnerChange(_)
4765 | AlterFunctionAction::DependsOnExtension { .. }
4766 | AlterFunctionAction::NoDependsOnExtension { .. } => {
4767 self.snapshot_function(&f.id);
4768 }
4769 }
4770 MutationResult::Applied
4771 }
4772 Mutation::DropFunction(f) => {
4773 let mut any_applied = false;
4774 for sig in &f.signatures {
4775 let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
4776 let schema = self.resolve_function_schema(&sig.name, &sig_str);
4777 let id = ObjectId::new(schema, sig_str);
4778 let is_function = matches!(
4779 self.local.functions.get(&id),
4780 Some(crate::model::function::FunctionOverlay::Present(function))
4781 if matches!(
4782 function.routine_kind,
4783 crate::model::function::RoutineKind::Function
4784 | crate::model::function::RoutineKind::Window
4785 )
4786 );
4787 if !is_function {
4788 let routine_exists = self.local.functions.contains_key(&id);
4789 let absence_is_exact =
4790 self.baseline_available && self.baseline_covers_object(&id);
4791 if routine_exists || (!f.if_exists && absence_is_exact) {
4792 return MutationResult::Conflict {
4793 reason: format!("function '{}' does not exist", id),
4794 };
4795 } else if !f.if_exists && !absence_is_exact {
4796 self.snapshot_confidence();
4797 self.local.confidence = Confidence::Tainted;
4798 }
4799 } else {
4800 let dependent_triggers: Vec<(ObjectId, ObjectId)> = self
4801 .local
4802 .graph
4803 .edges
4804 .iter()
4805 .filter_map(|edge| {
4806 let DependencyKind::TriggerOnTable { function_id, .. } = &edge.kind
4807 else {
4808 return None;
4809 };
4810 (function_id == &id)
4811 .then(|| (edge.dependent.clone(), edge.referenced.clone()))
4812 })
4813 .collect();
4814 if !dependent_triggers.is_empty() && !f.cascade {
4815 return MutationResult::Conflict {
4816 reason: format!(
4817 "function '{}' still has dependent triggers; use CASCADE",
4818 id
4819 ),
4820 };
4821 }
4822
4823 any_applied = true;
4824 self.snapshot_function(&id);
4825 self.local
4826 .functions
4827 .insert(id.clone(), crate::model::function::FunctionOverlay::Dropped);
4828
4829 if f.cascade {
4830 for (trigger_id, table_id) in &dependent_triggers {
4831 let trigger_name =
4832 self.local.triggers.get(trigger_id).and_then(|overlay| {
4833 match overlay {
4834 TriggerOverlay::Present(trigger) => {
4835 Some(trigger.name.clone())
4836 }
4837 TriggerOverlay::Dropped => None,
4838 }
4839 });
4840 self.snapshot_trigger(trigger_id);
4841 self.local
4842 .triggers
4843 .insert(trigger_id.clone(), TriggerOverlay::Dropped);
4844 self.snapshot_relation(table_id);
4845 if let Some(RelationOverlay::Present(relation)) =
4846 self.local.relations.get_mut(table_id)
4847 && let Some(trigger_name) = trigger_name
4848 {
4849 relation.triggers.remove(&trigger_name);
4850 }
4851 }
4852 if !dependent_triggers.is_empty() {
4853 self.snapshot_graph_full();
4854 self.local.graph.edges.retain(|edge| {
4855 !dependent_triggers
4856 .iter()
4857 .any(|(trigger_id, _)| edge.dependent == *trigger_id)
4858 });
4859 }
4860 }
4861 }
4862 }
4863 if any_applied {
4864 MutationResult::Applied
4865 } else {
4866 MutationResult::Skipped
4867 }
4868 }
4869 Mutation::CreateProcedure(p) => {
4870 match self.local.functions.get(&p.id) {
4871 Some(crate::model::function::FunctionOverlay::Present(existing))
4872 if existing.routine_kind
4873 == crate::model::function::RoutineKind::Procedure
4874 && p.or_replace => {}
4875 Some(crate::model::function::FunctionOverlay::Present(_)) => {
4876 return MutationResult::Conflict {
4877 reason: format!("routine '{}' already exists", p.id),
4878 };
4879 }
4880 None if !self.baseline_available || !self.baseline_covers_object(&p.id) => {
4881 self.snapshot_confidence();
4882 self.local.confidence = Confidence::Tainted;
4883 }
4884 None => {}
4885 Some(crate::model::function::FunctionOverlay::Dropped) => {}
4886 }
4887 self.snapshot_function(&p.id);
4888 self.snapshot_generation_counter();
4889 self.local.generation_counter += 1;
4890 let _generation = self.local.generation_counter;
4891
4892 self.local.functions.insert(
4893 p.id.clone(),
4894 crate::model::function::FunctionOverlay::Present(
4895 crate::model::function::FunctionState {
4896 id: p.id.clone(),
4897 routine_kind: crate::model::function::RoutineKind::Procedure,
4898 arg_types: p
4899 .params
4900 .iter()
4901 .filter(|p| {
4902 !matches!(&p.mode, crate::analysis::facts::ParamModeFact::Out)
4903 })
4904 .map(|p| p.ty.clone())
4905 .collect(),
4906 arg_type_ids: p
4907 .params
4908 .iter()
4909 .filter(|p| {
4910 !matches!(&p.mode, crate::analysis::facts::ParamModeFact::Out)
4911 })
4912 .map(|parameter| self.resolve_type_reference(¶meter.ty))
4913 .collect(),
4914 return_type: "void".to_string(),
4915 return_type_id: None,
4916 volatility: crate::model::function::Volatility::Volatile,
4917 language: "sql".to_string(),
4918 security: crate::model::function::SecurityMode::Invoker,
4919 },
4920 ),
4921 );
4922 MutationResult::Applied
4923 }
4924 Mutation::AlterProcedure(p) => {
4925 use crate::analysis::facts::AlterFunctionAction;
4926 use crate::model::function::{FunctionOverlay, RoutineKind};
4927
4928 match self.local.functions.get(&p.id) {
4929 Some(FunctionOverlay::Present(function))
4930 if function.routine_kind == RoutineKind::Procedure => {}
4931 Some(FunctionOverlay::Present(_)) => {
4932 return MutationResult::Conflict {
4933 reason: format!("'{}' is not a procedure", p.id),
4934 };
4935 }
4936 Some(FunctionOverlay::Dropped) => {
4937 return MutationResult::Conflict {
4938 reason: format!("procedure '{}' does not exist", p.id),
4939 };
4940 }
4941 None if self.baseline_available && self.baseline_covers_object(&p.id) => {
4942 return MutationResult::Conflict {
4943 reason: format!("procedure '{}' does not exist", p.id),
4944 };
4945 }
4946 None => {
4947 self.snapshot_confidence();
4948 self.local.confidence = Confidence::Tainted;
4949 return MutationResult::Skipped;
4950 }
4951 }
4952
4953 match &p.action {
4954 AlterFunctionAction::Rename { to, .. } => {
4955 let signature =
4956 p.id.name
4957 .find('(')
4958 .map(|index| &p.id.name[index..])
4959 .unwrap_or("");
4960 let new_id = ObjectId::new(p.id.schema.clone(), format!("{to}{signature}"));
4961 self.move_function(&p.id, &new_id);
4962 }
4963 AlterFunctionAction::SchemaChange { new_schema } => {
4964 let new_id = ObjectId::new(new_schema.clone(), p.id.name.clone());
4965 self.move_function(&p.id, &new_id);
4966 }
4967 _ => self.snapshot_function(&p.id),
4968 }
4969 MutationResult::Applied
4970 }
4971 Mutation::DropProcedure(p) => {
4972 let mut any_applied = false;
4973 for sig in &p.signatures {
4974 let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
4975 let schema = self.resolve_function_schema(&sig.name, &sig_str);
4976 let id = ObjectId::new(schema, sig_str);
4977 let is_procedure = matches!(
4978 self.local.functions.get(&id),
4979 Some(crate::model::function::FunctionOverlay::Present(function))
4980 if function.routine_kind
4981 == crate::model::function::RoutineKind::Procedure
4982 );
4983 if is_procedure {
4984 any_applied = true;
4985 self.snapshot_function(&id);
4986 self.local
4987 .functions
4988 .insert(id, crate::model::function::FunctionOverlay::Dropped);
4989 } else if self.local.functions.contains_key(&id)
4990 || !p.if_exists
4991 && self.baseline_available
4992 && self.baseline_covers_object(&id)
4993 {
4994 return MutationResult::Conflict {
4995 reason: format!("procedure '{}' does not exist", id),
4996 };
4997 } else if !p.if_exists {
4998 self.snapshot_confidence();
4999 self.local.confidence = Confidence::Tainted;
5000 return MutationResult::Skipped;
5001 }
5002 }
5003 if any_applied {
5004 MutationResult::Applied
5005 } else {
5006 MutationResult::Skipped
5007 }
5008 }
5009 Mutation::CreateAggregate(a) => {
5010 match self.local.functions.get(&a.id) {
5011 Some(crate::model::function::FunctionOverlay::Present(existing))
5012 if existing.routine_kind
5013 == crate::model::function::RoutineKind::Aggregate
5014 && a.or_replace => {}
5015 Some(crate::model::function::FunctionOverlay::Present(_)) => {
5016 return MutationResult::Conflict {
5017 reason: format!("routine '{}' already exists", a.id),
5018 };
5019 }
5020 None if !self.baseline_available || !self.baseline_covers_object(&a.id) => {
5021 self.snapshot_confidence();
5022 self.local.confidence = Confidence::Tainted;
5023 }
5024 None | Some(crate::model::function::FunctionOverlay::Dropped) => {}
5025 }
5026
5027 self.snapshot_function(&a.id);
5028 self.local.functions.insert(
5029 a.id.clone(),
5030 crate::model::function::FunctionOverlay::Present(
5031 crate::model::function::FunctionState {
5032 id: a.id.clone(),
5033 routine_kind: crate::model::function::RoutineKind::Aggregate,
5034 arg_types: a
5035 .params
5036 .iter()
5037 .filter(|parameter| {
5038 !matches!(
5039 parameter.mode,
5040 crate::analysis::facts::ParamModeFact::Out
5041 )
5042 })
5043 .map(|parameter| parameter.ty.clone())
5044 .collect(),
5045 arg_type_ids: a
5046 .params
5047 .iter()
5048 .filter(|parameter| {
5049 !matches!(
5050 parameter.mode,
5051 crate::analysis::facts::ParamModeFact::Out
5052 )
5053 })
5054 .map(|parameter| self.resolve_type_reference(¶meter.ty))
5055 .collect(),
5056 return_type: String::new(),
5057 return_type_id: None,
5058 volatility: crate::model::function::Volatility::Volatile,
5059 language: "internal".to_string(),
5060 security: crate::model::function::SecurityMode::Invoker,
5061 },
5062 ),
5063 );
5064 MutationResult::Applied
5065 }
5066 Mutation::AlterAggregate(a) => {
5067 use crate::analysis::facts::AlterFunctionAction;
5068 use crate::model::function::{FunctionOverlay, RoutineKind};
5069
5070 match self.local.functions.get(&a.id) {
5071 Some(FunctionOverlay::Present(aggregate))
5072 if aggregate.routine_kind == RoutineKind::Aggregate => {}
5073 Some(FunctionOverlay::Present(_)) => {
5074 return MutationResult::Conflict {
5075 reason: format!("'{}' is not an aggregate", a.id),
5076 };
5077 }
5078 Some(FunctionOverlay::Dropped) => {
5079 return MutationResult::Conflict {
5080 reason: format!("aggregate '{}' does not exist", a.id),
5081 };
5082 }
5083 None if self.baseline_available && self.baseline_covers_object(&a.id) => {
5084 return MutationResult::Conflict {
5085 reason: format!("aggregate '{}' does not exist", a.id),
5086 };
5087 }
5088 None => {
5089 self.snapshot_confidence();
5090 self.local.confidence = Confidence::Tainted;
5091 return MutationResult::Skipped;
5092 }
5093 }
5094
5095 match &a.action {
5096 AlterFunctionAction::Rename { to, .. } => {
5097 let signature =
5098 a.id.name
5099 .find('(')
5100 .map(|index| &a.id.name[index..])
5101 .unwrap_or("");
5102 let new_id = ObjectId::new(a.id.schema.clone(), format!("{to}{signature}"));
5103 self.move_function(&a.id, &new_id);
5104 }
5105 AlterFunctionAction::SchemaChange { new_schema } => {
5106 let new_id = ObjectId::new(new_schema.clone(), a.id.name.clone());
5107 self.move_function(&a.id, &new_id);
5108 }
5109 AlterFunctionAction::OwnerChange(_) => self.snapshot_function(&a.id),
5110 _ => unreachable!("aggregate extraction only emits rename, owner, or schema"),
5111 }
5112 MutationResult::Applied
5113 }
5114 Mutation::DropAggregate(a) => {
5115 let mut any_applied = false;
5116 for signature in &a.signatures {
5117 let signature_name = format!(
5118 "{}({})",
5119 signature.name.name.resolve(),
5120 signature.params.join(",")
5121 );
5122 let schema = self.resolve_function_schema(&signature.name, &signature_name);
5123 let id = ObjectId::new(schema, signature_name);
5124 let is_aggregate = matches!(
5125 self.local.functions.get(&id),
5126 Some(crate::model::function::FunctionOverlay::Present(routine))
5127 if routine.routine_kind
5128 == crate::model::function::RoutineKind::Aggregate
5129 );
5130 if is_aggregate {
5131 any_applied = true;
5132 self.snapshot_function(&id);
5133 self.local
5134 .functions
5135 .insert(id, crate::model::function::FunctionOverlay::Dropped);
5136 } else if self.local.functions.contains_key(&id)
5137 || self.baseline_available && self.baseline_covers_object(&id)
5138 {
5139 if a.if_exists {
5140 continue;
5141 }
5142 return MutationResult::Conflict {
5143 reason: format!("aggregate '{}' does not exist", id),
5144 };
5145 } else {
5146 self.snapshot_confidence();
5147 self.local.confidence = Confidence::Tainted;
5148 if !a.if_exists {
5149 return MutationResult::Skipped;
5150 }
5151 }
5152 }
5153 if a.cascade && any_applied {
5154 self.snapshot_confidence();
5155 self.local.confidence = Confidence::Tainted;
5156 }
5157 if any_applied {
5158 MutationResult::Applied
5159 } else {
5160 MutationResult::Skipped
5161 }
5162 }
5163 Mutation::CreatePublication(p) => {
5164 match self.local.publications.get(&p.name) {
5165 Some(crate::model::replication::PublicationOverlay::Present(_)) => {
5166 return MutationResult::Conflict {
5167 reason: format!("publication '{}' already exists", p.name),
5168 };
5169 }
5170 None => {
5171 if !self.baseline_available {
5172 self.snapshot_confidence();
5173 self.local.confidence = Confidence::Tainted;
5174 }
5175 }
5176 Some(crate::model::replication::PublicationOverlay::Dropped) => {}
5177 }
5178 if let Err(reason) = self.validate_publication_scope(&p.scope) {
5179 return MutationResult::Conflict { reason };
5180 }
5181 self.taint_inheritance_sensitive_publication_scope(&p.scope);
5182 self.snapshot_publication(&p.name);
5183 self.snapshot_generation_counter();
5184 self.local.generation_counter += 1;
5185 let generation = self.local.generation_counter;
5186
5187 let owner = self
5188 .local
5189 .current_role_known
5190 .then(|| self.local.current_role.clone());
5191 self.local.publications.insert(
5192 p.name.clone(),
5193 crate::model::replication::PublicationOverlay::Present(
5194 crate::model::replication::PublicationState {
5195 name: p.name.clone(),
5196 owner,
5197 scope: p.scope.clone(),
5198 params: p.params.clone(),
5199 generation,
5200 },
5201 ),
5202 );
5203
5204 if let crate::analysis::facts::PublicationScope::Explicit(objects) = &p.scope {
5205 self.snapshot_graph_full();
5206 for obj in objects {
5207 if let crate::analysis::facts::PublicationObjectFact::Table {
5208 name, ..
5209 } = obj
5210 {
5211 let table_id = self.resolve_relation_id(name);
5212 self.local.graph.edges.push(DependencyEdge::new(
5213 table_id,
5214 ObjectId::new("public", &p.name),
5215 DependencyKind::PublicationIncludes {
5216 publication_name: p.name.clone(),
5217 },
5218 ));
5219 }
5220 }
5221 }
5222 MutationResult::Applied
5223 }
5224 Mutation::AlterPublication(p) => {
5225 match self.local.publications.get(&p.name) {
5226 Some(crate::model::replication::PublicationOverlay::Present(_)) => {}
5227 Some(crate::model::replication::PublicationOverlay::Dropped) => {
5228 return MutationResult::Conflict {
5229 reason: format!("publication '{}' does not exist", p.name),
5230 };
5231 }
5232 None => {
5233 if self.baseline_available {
5234 return MutationResult::Conflict {
5235 reason: format!("publication '{}' does not exist", p.name),
5236 };
5237 }
5238 self.snapshot_confidence();
5239 self.local.confidence = Confidence::Tainted;
5240 return MutationResult::Skipped;
5241 }
5242 }
5243 self.snapshot_publication(&p.name);
5244 self.snapshot_generation_counter();
5245 self.local.generation_counter += 1;
5246 let new_gen = self.local.generation_counter;
5247 use crate::analysis::facts::AlterPublicationActionFact;
5248
5249 let current_scope = match self.local.publications.get(&p.name) {
5250 Some(crate::model::replication::PublicationOverlay::Present(publication)) => {
5251 publication.scope.clone()
5252 }
5253 _ => unreachable!("publication existence checked above"),
5254 };
5255 let mut replacement_scope = None;
5256 let mut rename_to = None;
5257 match &p.action {
5258 AlterPublicationActionFact::AddObjects(additions) => {
5259 let additions_scope =
5260 crate::analysis::facts::PublicationScope::Explicit(additions.clone());
5261 if let Err(reason) = self.validate_publication_scope(&additions_scope) {
5262 return MutationResult::Conflict { reason };
5263 }
5264 self.taint_inheritance_sensitive_publication_scope(&additions_scope);
5265 let crate::analysis::facts::PublicationScope::Explicit(mut objects) =
5266 current_scope
5267 else {
5268 return MutationResult::Conflict {
5269 reason: format!(
5270 "publication '{}' already includes all tables",
5271 p.name
5272 ),
5273 };
5274 };
5275 let mut keys: HashSet<String> = objects
5276 .iter()
5277 .map(|object| self.publication_object_key(object))
5278 .collect();
5279 for addition in additions {
5280 let key = self.publication_object_key(addition);
5281 if !keys.insert(key) {
5282 return MutationResult::Conflict {
5283 reason: format!(
5284 "publication '{}' already contains the requested object",
5285 p.name
5286 ),
5287 };
5288 }
5289 objects.push(addition.clone());
5290 }
5291 replacement_scope =
5292 Some(crate::analysis::facts::PublicationScope::Explicit(objects));
5293 }
5294 AlterPublicationActionFact::SetObjects(scope) => {
5295 if let Err(reason) = self.validate_publication_scope(scope) {
5296 return MutationResult::Conflict { reason };
5297 }
5298 self.taint_inheritance_sensitive_publication_scope(scope);
5299 replacement_scope = Some(scope.clone());
5300 }
5301 AlterPublicationActionFact::DropObjects(removals) => {
5302 self.taint_inheritance_sensitive_publication_scope(
5303 &crate::analysis::facts::PublicationScope::Explicit(removals.clone()),
5304 );
5305 let crate::analysis::facts::PublicationScope::Explicit(mut objects) =
5306 current_scope
5307 else {
5308 return MutationResult::Conflict {
5309 reason: format!("publication '{}' includes all tables", p.name),
5310 };
5311 };
5312 for removal in removals {
5313 let key = self.publication_object_key(removal);
5314 let Some(position) = objects
5315 .iter()
5316 .position(|object| self.publication_object_key(object) == key)
5317 else {
5318 return MutationResult::Conflict {
5319 reason: format!(
5320 "publication '{}' does not contain the requested object",
5321 p.name
5322 ),
5323 };
5324 };
5325 objects.remove(position);
5326 }
5327 replacement_scope =
5328 Some(crate::analysis::facts::PublicationScope::Explicit(objects));
5329 }
5330 AlterPublicationActionFact::SetOptions(options) => {
5331 if let Some(crate::model::replication::PublicationOverlay::Present(
5332 publication,
5333 )) = self.local.publications.get_mut(&p.name)
5334 {
5335 for option in options {
5336 publication
5337 .params
5338 .retain(|existing| existing.name != option.name);
5339 publication.params.push(option.clone());
5340 }
5341 }
5342 }
5343 AlterPublicationActionFact::OwnerChange(role) => {
5344 if let Some((owner, known)) = self.role_fact_identity(role) {
5345 if known {
5346 if let Some(
5347 crate::model::replication::PublicationOverlay::Present(
5348 publication,
5349 ),
5350 ) = self.local.publications.get_mut(&p.name)
5351 {
5352 publication.owner = Some(owner);
5353 }
5354 } else {
5355 self.snapshot_confidence();
5356 self.local.confidence = Confidence::Tainted;
5357 if let Some(
5358 crate::model::replication::PublicationOverlay::Present(
5359 publication,
5360 ),
5361 ) = self.local.publications.get_mut(&p.name)
5362 {
5363 publication.owner = None;
5364 }
5365 }
5366 } else {
5367 self.snapshot_confidence();
5368 self.local.confidence = Confidence::Tainted;
5369 if let Some(crate::model::replication::PublicationOverlay::Present(
5370 publication,
5371 )) = self.local.publications.get_mut(&p.name)
5372 {
5373 publication.owner = None;
5374 }
5375 }
5376 }
5377 AlterPublicationActionFact::Rename { to } => {
5378 if matches!(
5379 self.local.publications.get(to),
5380 Some(crate::model::replication::PublicationOverlay::Present(_))
5381 ) {
5382 return MutationResult::Conflict {
5383 reason: format!("publication '{}' already exists", to),
5384 };
5385 }
5386 rename_to = Some(to.clone());
5387 }
5388 }
5389
5390 if let Some(scope) = replacement_scope {
5391 if let Some(crate::model::replication::PublicationOverlay::Present(
5392 publication,
5393 )) = self.local.publications.get_mut(&p.name)
5394 {
5395 publication.scope = scope.clone();
5396 }
5397 self.replace_publication_edges(&p.name, &scope);
5398 }
5399 if let Some(crate::model::replication::PublicationOverlay::Present(publication)) =
5400 self.local.publications.get_mut(&p.name)
5401 {
5402 publication.generation = new_gen;
5403 }
5404 if let Some(to) = rename_to {
5405 self.snapshot_publication(&to);
5406 let Some(crate::model::replication::PublicationOverlay::Present(
5407 mut publication,
5408 )) = self.local.publications.remove(&p.name)
5409 else {
5410 unreachable!("publication existence checked above");
5411 };
5412 publication.name = to.clone();
5413 self.local.publications.insert(
5414 to.clone(),
5415 crate::model::replication::PublicationOverlay::Present(publication),
5416 );
5417 self.snapshot_graph_full();
5418 for edge in &mut self.local.graph.edges {
5419 if let DependencyKind::PublicationIncludes { publication_name } =
5420 &mut edge.kind
5421 && publication_name == &p.name
5422 {
5423 *publication_name = to.clone();
5424 edge.referenced = ObjectId::new("public", &to);
5425 }
5426 }
5427 }
5428 MutationResult::Applied
5429 }
5430 Mutation::DropPublication(p) => {
5431 let mut present_names = Vec::new();
5432 for name in &p.names {
5433 match self.local.publications.get(name) {
5434 Some(crate::model::replication::PublicationOverlay::Present(_)) => {
5435 present_names.push(name.clone());
5436 }
5437 Some(crate::model::replication::PublicationOverlay::Dropped) => {
5438 if !p.if_exists {
5439 return MutationResult::Conflict {
5440 reason: format!("publication '{}' does not exist", name),
5441 };
5442 }
5443 }
5444 None if p.if_exists && self.baseline_available => {}
5445 None if p.if_exists => {
5446 self.snapshot_confidence();
5447 self.local.confidence = Confidence::Tainted;
5448 }
5449 None => {
5450 if self.baseline_available {
5451 return MutationResult::Conflict {
5452 reason: format!("publication '{}' does not exist", name),
5453 };
5454 }
5455 self.snapshot_confidence();
5456 self.local.confidence = Confidence::Tainted;
5457 return MutationResult::Skipped;
5458 }
5459 }
5460 }
5461 for name in &present_names {
5462 self.snapshot_publication(name);
5463 self.local.publications.insert(
5464 name.clone(),
5465 crate::model::replication::PublicationOverlay::Dropped,
5466 );
5467 }
5468 self.snapshot_graph_full();
5469 self.local.graph.edges.retain(|e| {
5470 !(matches!(e.kind, DependencyKind::PublicationIncludes { .. })
5471 && present_names.contains(&e.referenced.name))
5472 });
5473 if present_names.is_empty() {
5474 MutationResult::Skipped
5475 } else {
5476 MutationResult::Applied
5477 }
5478 }
5479 Mutation::CreateSubscription(s) => {
5480 let name = s.name.clone().unwrap_or_else(|| "unnamed_sub".into());
5481 match self.local.subscriptions.get(&name) {
5482 Some(crate::model::replication::SubscriptionOverlay::Present(_)) => {
5483 return MutationResult::Conflict {
5484 reason: format!("subscription '{}' already exists", name),
5485 };
5486 }
5487 None => {
5488 if !self.baseline_available {
5489 self.snapshot_confidence();
5490 self.local.confidence = Confidence::Tainted;
5491 }
5492 }
5493 Some(crate::model::replication::SubscriptionOverlay::Dropped) => {}
5494 }
5495
5496 let params = s.params.as_deref();
5497 if let Err(reason) = Self::validate_subscription_boolean_options(
5498 params,
5499 &[
5500 "connect",
5501 "create_slot",
5502 "enabled",
5503 "copy_data",
5504 "binary",
5505 "disable_on_error",
5506 "password_required",
5507 "run_as_owner",
5508 "failover",
5509 "two_phase",
5510 ],
5511 ) {
5512 return MutationResult::Conflict { reason };
5513 }
5514 let connects_to_publisher =
5515 Self::subscription_boolean_option(params, "connect") != Some(false);
5516 if !connects_to_publisher
5517 && ["create_slot", "enabled", "copy_data"]
5518 .iter()
5519 .any(|name| Self::subscription_boolean_option(params, name) == Some(true))
5520 {
5521 return MutationResult::Conflict {
5522 reason: format!(
5523 "subscription '{}' cannot enable connection-dependent options when connect is false",
5524 name
5525 ),
5526 };
5527 }
5528 let creates_slot = connects_to_publisher
5529 && Self::subscription_boolean_option(params, "create_slot") != Some(false);
5530 if !self.local.transactions.is_empty() && connects_to_publisher && creates_slot {
5531 return MutationResult::Conflict {
5532 reason: format!(
5533 "subscription '{}' cannot create a replication slot inside a transaction",
5534 name
5535 ),
5536 };
5537 }
5538 self.snapshot_subscription(&name);
5539 self.snapshot_generation_counter();
5540 self.local.generation_counter += 1;
5541 let generation = self.local.generation_counter;
5542
5543 if connects_to_publisher {
5544 self.snapshot_confidence();
5545 self.local.confidence = Confidence::Tainted;
5546 }
5547
5548 let enabled = connects_to_publisher
5549 && Self::subscription_boolean_option(params, "enabled") != Some(false);
5550 let slot_name = match Self::subscription_option(params, "slot_name") {
5551 Some(value) if value.eq_ignore_ascii_case("none") => None,
5552 Some(value) => Some(value.to_string()),
5553 None => Some(name.clone()),
5554 };
5555 if slot_name.is_none() && (enabled || creates_slot) {
5556 return MutationResult::Conflict {
5557 reason: format!(
5558 "subscription '{}' with slot_name NONE must disable enabled and create_slot",
5559 name
5560 ),
5561 };
5562 }
5563 let mut unique_publications = HashSet::new();
5564 if !s
5565 .publications
5566 .iter()
5567 .all(|publication| unique_publications.insert(publication))
5568 {
5569 return MutationResult::Conflict {
5570 reason: format!(
5571 "subscription '{}' lists the same publication more than once",
5572 name
5573 ),
5574 };
5575 }
5576
5577 let owner = self
5578 .local
5579 .current_role_known
5580 .then(|| self.local.current_role.clone());
5581 self.local.subscriptions.insert(
5582 name.clone(),
5583 crate::model::replication::SubscriptionOverlay::Present(
5584 crate::model::replication::SubscriptionState {
5585 name,
5586 owner,
5587 connection: s.connection.clone(),
5588 publications: s.publications.clone(),
5589 params: s.params.clone(),
5590 enabled,
5591 slot_name,
5592 generation,
5593 },
5594 ),
5595 );
5596 MutationResult::Applied
5597 }
5598 Mutation::AlterSubscription(s) => {
5599 match self.local.subscriptions.get(&s.name) {
5600 Some(crate::model::replication::SubscriptionOverlay::Present(_)) => {}
5601 Some(crate::model::replication::SubscriptionOverlay::Dropped) => {
5602 return MutationResult::Conflict {
5603 reason: format!("subscription '{}' does not exist", s.name),
5604 };
5605 }
5606 None => {
5607 if self.baseline_available {
5608 return MutationResult::Conflict {
5609 reason: format!("subscription '{}' does not exist", s.name),
5610 };
5611 }
5612 self.snapshot_confidence();
5613 self.local.confidence = Confidence::Tainted;
5614 return MutationResult::Skipped;
5615 }
5616 }
5617 let existing = match self.local.subscriptions.get(&s.name) {
5618 Some(crate::model::replication::SubscriptionOverlay::Present(subscription)) => {
5619 subscription
5620 }
5621 _ => unreachable!("subscription existence checked above"),
5622 };
5623 let in_transaction = !self.local.transactions.is_empty();
5624 match &s.action {
5625 crate::analysis::facts::AlterSubscriptionActionFact::Publications {
5626 mode,
5627 publications,
5628 params,
5629 } => {
5630 if let Err(reason) = Self::validate_subscription_boolean_options(
5631 Some(params),
5632 &["refresh", "copy_data"],
5633 ) {
5634 return MutationResult::Conflict { reason };
5635 }
5636 if in_transaction
5637 && Self::subscription_boolean_option(Some(params), "refresh")
5638 != Some(false)
5639 {
5640 return MutationResult::Conflict {
5641 reason: format!(
5642 "subscription '{}' cannot refresh publications inside a transaction",
5643 s.name
5644 ),
5645 };
5646 }
5647
5648 let mut unique = HashSet::new();
5649 match mode {
5650 crate::analysis::facts::SubscriptionPublicationMode::Set => {
5651 if !publications
5652 .iter()
5653 .all(|publication| unique.insert(publication))
5654 {
5655 return MutationResult::Conflict {
5656 reason: format!(
5657 "subscription '{}' lists the same publication more than once",
5658 s.name
5659 ),
5660 };
5661 }
5662 }
5663 crate::analysis::facts::SubscriptionPublicationMode::Add => {
5664 for publication in publications {
5665 if !unique.insert(publication)
5666 || existing.publications.contains(publication)
5667 {
5668 return MutationResult::Conflict {
5669 reason: format!(
5670 "subscription '{}' already includes publication '{}'",
5671 s.name, publication
5672 ),
5673 };
5674 }
5675 }
5676 }
5677 crate::analysis::facts::SubscriptionPublicationMode::Drop => {
5678 for publication in publications {
5679 if !unique.insert(publication)
5680 || !existing.publications.contains(publication)
5681 {
5682 return MutationResult::Conflict {
5683 reason: format!(
5684 "subscription '{}' does not include publication '{}'",
5685 s.name, publication
5686 ),
5687 };
5688 }
5689 }
5690 }
5691 }
5692 }
5693 crate::analysis::facts::AlterSubscriptionActionFact::RefreshPublication(_)
5694 if in_transaction =>
5695 {
5696 return MutationResult::Conflict {
5697 reason: format!(
5698 "subscription '{}' cannot refresh publications inside a transaction",
5699 s.name
5700 ),
5701 };
5702 }
5703 crate::analysis::facts::AlterSubscriptionActionFact::SetOptions(options) => {
5704 if let Err(reason) = Self::validate_subscription_boolean_options(
5705 Some(options),
5706 &[
5707 "binary",
5708 "disable_on_error",
5709 "password_required",
5710 "run_as_owner",
5711 "failover",
5712 "two_phase",
5713 ],
5714 ) {
5715 return MutationResult::Conflict { reason };
5716 }
5717 if existing.enabled
5718 && options
5719 .iter()
5720 .any(|option| option.name.eq_ignore_ascii_case("slot_name"))
5721 {
5722 return MutationResult::Conflict {
5723 reason: format!(
5724 "subscription '{}' must be disabled before changing slot_name",
5725 s.name
5726 ),
5727 };
5728 }
5729 let changes_failover_or_two_phase = options.iter().any(|option| {
5730 option.name.eq_ignore_ascii_case("failover")
5731 || option.name.eq_ignore_ascii_case("two_phase")
5732 });
5733 if changes_failover_or_two_phase && existing.enabled {
5734 return MutationResult::Conflict {
5735 reason: format!(
5736 "subscription '{}' must be disabled before changing failover or two_phase",
5737 s.name
5738 ),
5739 };
5740 }
5741 let forbidden_in_transaction = options.iter().any(|option| {
5742 option.name.eq_ignore_ascii_case("failover")
5743 || (option.name.eq_ignore_ascii_case("two_phase")
5744 && Self::postgres_boolean(&option.value) == Some(false))
5745 });
5746 if in_transaction && forbidden_in_transaction {
5747 return MutationResult::Conflict {
5748 reason: format!(
5749 "subscription '{}' cannot change this setting inside a transaction",
5750 s.name
5751 ),
5752 };
5753 }
5754 }
5755 crate::analysis::facts::AlterSubscriptionActionFact::SetEnabled(true)
5756 if existing.slot_name.is_none() =>
5757 {
5758 return MutationResult::Conflict {
5759 reason: format!(
5760 "subscription '{}' cannot be enabled without a slot_name",
5761 s.name
5762 ),
5763 };
5764 }
5765 _ => {}
5766 }
5767 self.snapshot_subscription(&s.name);
5768 self.snapshot_generation_counter();
5769 self.local.generation_counter += 1;
5770 let new_gen = self.local.generation_counter;
5771 use crate::analysis::facts::{
5772 AlterSubscriptionActionFact, SubscriptionPublicationMode,
5773 };
5774 let mut rename_to = None;
5775 match &s.action {
5776 AlterSubscriptionActionFact::SetConnection(connection) => {
5777 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5778 subscription,
5779 )) = self.local.subscriptions.get_mut(&s.name)
5780 {
5781 subscription.connection = connection.clone();
5782 }
5783 }
5784 AlterSubscriptionActionFact::SetServer(server) => {
5785 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5786 subscription,
5787 )) = self.local.subscriptions.get_mut(&s.name)
5788 {
5789 subscription.connection =
5790 crate::analysis::facts::ConnectionTarget::Server(server.clone());
5791 }
5792 self.snapshot_confidence();
5793 self.local.confidence = Confidence::Tainted;
5794 }
5795 AlterSubscriptionActionFact::Publications {
5796 mode,
5797 publications,
5798 params,
5799 } => {
5800 let refreshes = Self::subscription_boolean_option(Some(params), "refresh")
5801 != Some(false);
5802 if refreshes {
5803 self.snapshot_confidence();
5804 self.local.confidence = Confidence::Tainted;
5805 }
5806 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5807 subscription,
5808 )) = self.local.subscriptions.get_mut(&s.name)
5809 {
5810 match mode {
5811 SubscriptionPublicationMode::Set => {
5812 subscription.publications = publications.clone();
5813 }
5814 SubscriptionPublicationMode::Add => {
5815 subscription
5816 .publications
5817 .extend(publications.iter().cloned());
5818 }
5819 SubscriptionPublicationMode::Drop => {
5820 subscription
5821 .publications
5822 .retain(|existing| !publications.contains(existing));
5823 }
5824 }
5825 }
5826 }
5827 AlterSubscriptionActionFact::RefreshPublication(_) => {
5828 self.snapshot_confidence();
5829 self.local.confidence = Confidence::Tainted;
5830 }
5831 AlterSubscriptionActionFact::SetEnabled(enabled) => {
5832 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5833 subscription,
5834 )) = self.local.subscriptions.get_mut(&s.name)
5835 {
5836 subscription.enabled = *enabled;
5837 }
5838 }
5839 AlterSubscriptionActionFact::SetOptions(options) => {
5840 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5841 subscription,
5842 )) = self.local.subscriptions.get_mut(&s.name)
5843 {
5844 for option in options {
5845 Self::set_subscription_option(subscription, option);
5846 if option.name.eq_ignore_ascii_case("slot_name") {
5847 subscription.slot_name =
5848 (!option.value.eq_ignore_ascii_case("none"))
5849 .then(|| option.value.clone());
5850 }
5851 }
5852 }
5853 }
5854 AlterSubscriptionActionFact::Skip(options) => {
5855 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5856 subscription,
5857 )) = self.local.subscriptions.get_mut(&s.name)
5858 {
5859 for option in options {
5860 let mut normalized = option.clone();
5861 normalized.name = "skip_lsn".to_string();
5862 Self::set_subscription_option(subscription, &normalized);
5863 }
5864 }
5865 }
5866 AlterSubscriptionActionFact::OwnerChange(role) => {
5867 if let Some((owner, known)) = self.role_fact_identity(role) {
5868 if known {
5869 if let Some(
5870 crate::model::replication::SubscriptionOverlay::Present(
5871 subscription,
5872 ),
5873 ) = self.local.subscriptions.get_mut(&s.name)
5874 {
5875 subscription.owner = Some(owner);
5876 }
5877 } else {
5878 self.snapshot_confidence();
5879 self.local.confidence = Confidence::Tainted;
5880 if let Some(
5881 crate::model::replication::SubscriptionOverlay::Present(
5882 subscription,
5883 ),
5884 ) = self.local.subscriptions.get_mut(&s.name)
5885 {
5886 subscription.owner = None;
5887 }
5888 }
5889 } else {
5890 self.snapshot_confidence();
5891 self.local.confidence = Confidence::Tainted;
5892 if let Some(crate::model::replication::SubscriptionOverlay::Present(
5893 subscription,
5894 )) = self.local.subscriptions.get_mut(&s.name)
5895 {
5896 subscription.owner = None;
5897 }
5898 }
5899 }
5900 AlterSubscriptionActionFact::Rename { to } => {
5901 if matches!(
5902 self.local.subscriptions.get(to),
5903 Some(crate::model::replication::SubscriptionOverlay::Present(_))
5904 ) {
5905 return MutationResult::Conflict {
5906 reason: format!("subscription '{}' already exists", to),
5907 };
5908 }
5909 rename_to = Some(to.clone());
5910 }
5911 }
5912
5913 if let Some(crate::model::replication::SubscriptionOverlay::Present(subscription)) =
5914 self.local.subscriptions.get_mut(&s.name)
5915 {
5916 subscription.generation = new_gen;
5917 }
5918 if let Some(to) = rename_to {
5919 self.snapshot_subscription(&to);
5920 let Some(crate::model::replication::SubscriptionOverlay::Present(
5921 mut subscription,
5922 )) = self.local.subscriptions.remove(&s.name)
5923 else {
5924 unreachable!("subscription existence checked above");
5925 };
5926 subscription.name = to.clone();
5927 self.local.subscriptions.insert(
5928 to,
5929 crate::model::replication::SubscriptionOverlay::Present(subscription),
5930 );
5931 }
5932 MutationResult::Applied
5933 }
5934 Mutation::DropSubscription(s) => {
5935 let has_slot = match self.local.subscriptions.get(&s.name) {
5936 Some(crate::model::replication::SubscriptionOverlay::Present(subscription)) => {
5937 subscription.slot_name.is_some()
5938 }
5939 Some(crate::model::replication::SubscriptionOverlay::Dropped) => {
5940 if !s.if_exists {
5941 return MutationResult::Conflict {
5942 reason: format!("subscription '{}' does not exist", s.name),
5943 };
5944 }
5945 return MutationResult::Skipped;
5946 }
5947 None if s.if_exists && self.baseline_available => {
5948 return MutationResult::Skipped;
5949 }
5950 None if s.if_exists => {
5951 self.snapshot_confidence();
5952 self.local.confidence = Confidence::Tainted;
5953 return MutationResult::Skipped;
5954 }
5955 None => {
5956 if self.baseline_available {
5957 return MutationResult::Conflict {
5958 reason: format!("subscription '{}' does not exist", s.name),
5959 };
5960 }
5961 self.snapshot_confidence();
5962 self.local.confidence = Confidence::Tainted;
5963 return MutationResult::Skipped;
5964 }
5965 };
5966 if has_slot && !self.local.transactions.is_empty() {
5967 return MutationResult::Conflict {
5968 reason: format!(
5969 "subscription '{}' has a replication slot and cannot be dropped inside a transaction",
5970 s.name
5971 ),
5972 };
5973 }
5974 self.snapshot_confidence();
5975 self.local.confidence = Confidence::Tainted;
5976 self.snapshot_subscription(&s.name);
5977 self.local.subscriptions.insert(
5978 s.name.clone(),
5979 crate::model::replication::SubscriptionOverlay::Dropped,
5980 );
5981 MutationResult::Applied
5982 }
5983 Mutation::CreateRole(r) => {
5984 let role_id = ObjectId::new("", &r.name);
5985 if matches!(
5986 self.local.roles.get(&role_id),
5987 Some(crate::model::role::RoleOverlay::Present(_))
5988 ) {
5989 return MutationResult::Conflict {
5990 reason: format!("role '{}' already exists", r.name),
5991 };
5992 }
5993 self.snapshot_role(&role_id);
5994 self.snapshot_generation_counter();
5995 self.local.generation_counter += 1;
5996 let _generation = self.local.generation_counter;
5997
5998 self.local.roles.insert(
5999 role_id.clone(),
6000 crate::model::role::RoleOverlay::Present(crate::model::role::RoleState {
6001 id: role_id,
6002 can_login: r.can_login,
6003 is_superuser: false,
6004 member_of: Vec::new(),
6005 can_set_role_to: Vec::new(),
6006 granted_privileges: Vec::new(),
6007 }),
6008 );
6009 MutationResult::Applied
6010 }
6011 Mutation::AlterRole(r) => {
6012 if let Some(role_id) = Self::resolve_role_name(
6013 &r.name,
6014 &self.local.current_role,
6015 &self.local.session_role,
6016 ) {
6017 self.snapshot_role(&role_id);
6018 if !self.local.roles.contains_key(&role_id) {
6019 self.local.confidence = Confidence::Tainted;
6020 return MutationResult::Skipped;
6021 }
6022 self.snapshot_generation_counter();
6023 self.local.generation_counter += 1;
6024 let _new_gen = self.local.generation_counter;
6025
6026 MutationResult::Applied
6027 } else {
6028 MutationResult::Skipped
6029 }
6030 }
6031 Mutation::DropRole(r) => {
6032 for name in &r.names {
6033 if let Some(role_id) = Self::resolve_role_name(
6034 &crate::analysis::facts::RoleFact::Named {
6035 name: name.clone(),
6036 via_legacy_group_syntax: false,
6037 },
6038 &self.local.current_role,
6039 &self.local.session_role,
6040 ) {
6041 self.snapshot_role(&role_id);
6042 if !r.if_exists
6043 && !matches!(
6044 self.local.roles.get(&role_id),
6045 Some(crate::model::role::RoleOverlay::Present(_))
6046 )
6047 {
6048 return MutationResult::Conflict {
6049 reason: format!("role '{}' does not exist", name),
6050 };
6051 }
6052 self.local
6053 .roles
6054 .insert(role_id, crate::model::role::RoleOverlay::Dropped);
6055 }
6056 }
6057 MutationResult::Applied
6058 }
6059 Mutation::Grant(grant) => {
6060 let privileges = Self::resolve_grant_privileges(&grant.privileges);
6061 let grantees = &grant.grantees;
6062 match &grant.target {
6063 crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
6064 for id in ids {
6065 self.apply_grant_to_relation(id, &privileges, grantees);
6066 }
6067 }
6068 crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
6069 let target_ids: Vec<ObjectId> = self
6070 .local
6071 .relations
6072 .keys()
6073 .filter(|id| schemas.contains(&id.schema))
6074 .cloned()
6075 .collect();
6076 for id in &target_ids {
6077 self.apply_grant_to_relation(id, &privileges, grantees);
6078 }
6079 }
6080 }
6081 MutationResult::Applied
6082 }
6083 Mutation::Revoke(revoke) => {
6084 let privileges = Self::resolve_grant_privileges(&revoke.privileges);
6085 let revokees = &revoke.revokees;
6086 match &revoke.target {
6087 crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
6088 for id in ids {
6089 self.apply_revoke_to_relation(id, &privileges, revokees);
6090 }
6091 }
6092 crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
6093 let target_ids: Vec<ObjectId> = self
6094 .local
6095 .relations
6096 .keys()
6097 .filter(|id| schemas.contains(&id.schema))
6098 .cloned()
6099 .collect();
6100 for id in &target_ids {
6101 self.apply_revoke_to_relation(id, &privileges, revokees);
6102 }
6103 }
6104 }
6105 MutationResult::Applied
6106 }
6107 Mutation::CreateDatabase(_) => MutationResult::Applied,
6108 Mutation::AlterDatabase(_) => MutationResult::Applied,
6109 Mutation::DropDatabase(_) => MutationResult::Applied,
6110 Mutation::Vacuum { .. } => MutationResult::Applied,
6111 }
6112 }
6113
6114 fn snapshot_relation(&mut self, id: &ObjectId) {
6115 if let Some(frame) = self.local.transactions.last_mut() {
6116 let previous = self.local.relations.get(id).cloned();
6117 frame.undo_log.push(StateChange::RelationSnapshot {
6118 id: id.clone(),
6119 previous: Box::new(previous),
6120 });
6121 }
6122 }
6123
6124 fn snapshot_schema(&mut self, name: &str) {
6125 if let Some(frame) = self.local.transactions.last_mut() {
6126 frame.undo_log.push(StateChange::SchemaSnapshot {
6127 name: name.to_string(),
6128 previous: self.local.schemas.get(name).cloned(),
6129 });
6130 }
6131 }
6132
6133 fn snapshot_namespace(&mut self) {
6134 if let Some(frame) = self.local.transactions.last_mut() {
6135 frame.undo_log.push(StateChange::NamespaceSnapshot(Box::new(
6136 NamespaceSnapshot {
6137 schemas: self.local.schemas.clone(),
6138 relations: self.local.relations.clone(),
6139 types: self.local.types.clone(),
6140 functions: self.local.functions.clone(),
6141 sequences: self.local.sequences.clone(),
6142 publications: self.local.publications.clone(),
6143 triggers: self.local.triggers.clone(),
6144 constraints: self.local.constraints.clone(),
6145 graph: self.local.graph.edges.clone(),
6146 pending_validation: self.local.pending_validation.clone(),
6147 baseline_relations: self.baseline_relations.clone(),
6148 baseline_indexes: self.baseline_indexes.clone(),
6149 baseline_foreign_keys: self.baseline_foreign_keys.clone(),
6150 baseline_fk_dependencies: self.baseline_fk_dependencies.clone(),
6151 baseline_sequences: self.baseline_sequences.clone(),
6152 },
6153 )));
6154 }
6155 }
6156
6157 fn snapshot_type(&mut self, id: &ObjectId) {
6158 if let Some(frame) = self.local.transactions.last_mut() {
6159 let previous = self.local.types.get(id).cloned();
6160 frame.undo_log.push(StateChange::TypeSnapshot {
6161 id: id.clone(),
6162 previous,
6163 });
6164 }
6165 }
6166
6167 fn snapshot_sequence(&mut self, id: &ObjectId) {
6168 if let Some(frame) = self.local.transactions.last_mut() {
6169 let previous = self.local.sequences.get(id).cloned();
6170 frame.undo_log.push(StateChange::SequenceSnapshot {
6171 id: id.clone(),
6172 previous,
6173 });
6174 }
6175 }
6176
6177 fn move_function(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
6178 self.snapshot_function(old_id);
6179 self.snapshot_function(new_id);
6180 if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
6181 self.local.functions.remove(old_id)
6182 {
6183 function.id = new_id.clone();
6184 self.local.functions.insert(
6185 new_id.clone(),
6186 crate::model::function::FunctionOverlay::Present(function),
6187 );
6188 }
6189
6190 self.snapshot_graph_full();
6191 self.local.graph.propagate_rename(old_id, new_id);
6192 self.local.graph.edges.push(DependencyEdge::new(
6193 old_id.clone(),
6194 new_id.clone(),
6195 DependencyKind::RenameTo,
6196 ));
6197 }
6198
6199 fn snapshot_function(&mut self, id: &ObjectId) {
6200 if let Some(frame) = self.local.transactions.last_mut() {
6201 let previous = self.local.functions.get(id).cloned();
6202 frame.undo_log.push(StateChange::FunctionSnapshot {
6203 id: id.clone(),
6204 previous,
6205 });
6206 }
6207 }
6208
6209 fn snapshot_publication(&mut self, name: &str) {
6210 if let Some(frame) = self.local.transactions.last_mut() {
6211 let previous = self.local.publications.get(name).cloned();
6212 frame.undo_log.push(StateChange::PublicationSnapshot {
6213 id: ObjectId::new("", name),
6214 previous,
6215 });
6216 }
6217 }
6218
6219 fn snapshot_subscription(&mut self, name: &str) {
6220 if let Some(frame) = self.local.transactions.last_mut() {
6221 let previous = self.local.subscriptions.get(name).cloned();
6222 frame.undo_log.push(StateChange::SubscriptionSnapshot {
6223 id: ObjectId::new("", name),
6224 previous,
6225 });
6226 }
6227 }
6228
6229 fn snapshot_role(&mut self, id: &ObjectId) {
6230 if let Some(frame) = self.local.transactions.last_mut() {
6231 let previous = self.local.roles.get(id).cloned();
6232 frame.undo_log.push(StateChange::RoleSnapshot {
6233 id: id.clone(),
6234 previous,
6235 });
6236 }
6237 }
6238
6239 fn snapshot_trigger(&mut self, id: &ObjectId) {
6240 if let Some(frame) = self.local.transactions.last_mut() {
6241 let previous = self.local.triggers.get(id).cloned();
6242 frame.undo_log.push(StateChange::TriggerSnapshot {
6243 id: id.clone(),
6244 previous,
6245 });
6246 }
6247 }
6248
6249 fn snapshot_constraint(&mut self, table_id: &ObjectId, name: &str) {
6250 if let Some(frame) = self.local.transactions.last_mut() {
6251 let key = (table_id.clone(), name.to_string());
6252 let previous = self.local.constraints.get(&key).cloned();
6253 frame.undo_log.push(StateChange::ConstraintSnapshot {
6254 table_id: table_id.clone(),
6255 name: name.to_string(),
6256 previous,
6257 });
6258 }
6259 }
6260
6261 fn snapshot_role_context(&mut self) {
6262 if let Some(frame) = self.local.transactions.last_mut() {
6263 frame.undo_log.push(StateChange::RoleContextSnapshot {
6264 current_role: self.local.current_role.clone(),
6265 current_role_known: self.local.current_role_known,
6266 persistent_current_role: self.local.persistent_current_role.clone(),
6267 persistent_current_role_known: self.local.persistent_current_role_known,
6268 session_role: self.local.session_role.clone(),
6269 session_role_known: self.local.session_role_known,
6270 persistent_session_role: self.local.persistent_session_role.clone(),
6271 persistent_session_role_known: self.local.persistent_session_role_known,
6272 });
6273 }
6274 }
6275
6276 fn snapshot_search_path(&mut self) {
6277 if let Some(frame) = self.local.transactions.last_mut() {
6278 frame.undo_log.push(StateChange::SearchPathSnapshot {
6279 previous: self.local.search_path.clone(),
6280 previous_template: self.local.search_path_template.clone(),
6281 previous_session_template: self.local.session_search_path_template.clone(),
6282 });
6283 }
6284 }
6285
6286 fn snapshot_timeout_settings(&mut self) {
6287 if let Some(frame) = self.local.transactions.last_mut() {
6288 frame.undo_log.push(StateChange::TimeoutSettingsSnapshot {
6289 lock_timeout: self.local.lock_timeout.clone(),
6290 statement_timeout: self.local.statement_timeout.clone(),
6291 });
6292 }
6293 }
6294
6295 fn snapshot_generation_counter(&mut self) {
6296 if let Some(frame) = self.local.transactions.last_mut() {
6297 frame.undo_log.push(StateChange::GenerationCounterSnapshot {
6298 previous: self.local.generation_counter,
6299 });
6300 }
6301 }
6302
6303 #[allow(dead_code)]
6304 fn snapshot_pending_validation(&mut self) {
6305 if let Some(frame) = self.local.transactions.last_mut() {
6306 frame.undo_log.push(StateChange::PendingValidationSnapshot {
6307 previous: self.local.pending_validation.clone(),
6308 });
6309 }
6310 }
6311
6312 fn snapshot_confidence(&mut self) {
6313 if let Some(frame) = self.local.transactions.last_mut() {
6314 frame.undo_log.push(StateChange::ConfidenceSnapshot {
6315 previous: self.local.confidence.clone(),
6316 });
6317 }
6318 }
6319
6320 fn snapshot_graph(&mut self) {
6321 if let Some(frame) = self.local.transactions.last_mut() {
6322 frame.undo_log.push(StateChange::GraphLengthMarker {
6323 len: self.local.graph.edges.len(),
6324 });
6325 }
6326 }
6327
6328 fn snapshot_graph_full(&mut self) {
6329 if let Some(frame) = self.local.transactions.last_mut() {
6330 frame.undo_log.push(StateChange::GraphSnapshot {
6331 previous: self.local.graph.edges.clone(),
6332 });
6333 }
6334 }
6335
6336 fn rollback_frame(&mut self, mut frame: TransactionFrame) {
6337 self.rollback_undo_log(std::mem::take(&mut frame.undo_log));
6338 }
6339
6340 fn rollback_undo_log(&mut self, mut undo_log: Vec<StateChange>) {
6341 while let Some(change) = undo_log.pop() {
6342 match change {
6343 StateChange::SchemaSnapshot { name, previous } => match previous {
6344 Some(overlay) => {
6345 self.local.schemas.insert(name, overlay);
6346 }
6347 None => {
6348 self.local.schemas.remove(&name);
6349 }
6350 },
6351 StateChange::NamespaceSnapshot(snapshot) => {
6352 self.local.schemas = snapshot.schemas;
6353 self.local.relations = snapshot.relations;
6354 self.local.types = snapshot.types;
6355 self.local.functions = snapshot.functions;
6356 self.local.sequences = snapshot.sequences;
6357 self.local.publications = snapshot.publications;
6358 self.local.triggers = snapshot.triggers;
6359 self.local.constraints = snapshot.constraints;
6360 self.local.graph.edges = snapshot.graph;
6361 self.local.pending_validation = snapshot.pending_validation;
6362 self.baseline_relations = snapshot.baseline_relations;
6363 self.baseline_indexes = snapshot.baseline_indexes;
6364 self.baseline_foreign_keys = snapshot.baseline_foreign_keys;
6365 self.baseline_fk_dependencies = snapshot.baseline_fk_dependencies;
6366 self.baseline_sequences = snapshot.baseline_sequences;
6367 }
6368 StateChange::RelationSnapshot { id, previous } => {
6369 if let Some(prev) = *previous {
6370 self.local.relations.insert(id, prev);
6371 } else {
6372 self.local.relations.remove(&id);
6373 }
6374 }
6375 StateChange::TypeSnapshot { id, previous } => {
6376 if let Some(prev) = previous {
6377 self.local.types.insert(id, prev);
6378 } else {
6379 self.local.types.remove(&id);
6380 }
6381 }
6382 StateChange::SequenceSnapshot { id, previous } => {
6383 if let Some(prev) = previous {
6384 self.local.sequences.insert(id, prev);
6385 } else {
6386 self.local.sequences.remove(&id);
6387 }
6388 }
6389 StateChange::FunctionSnapshot { id, previous } => {
6390 if let Some(prev) = previous {
6391 self.local.functions.insert(id, prev);
6392 } else {
6393 self.local.functions.remove(&id);
6394 }
6395 }
6396 StateChange::PublicationSnapshot { id, previous } => {
6397 if let Some(prev) = previous {
6398 self.local.publications.insert(id.name, prev);
6399 } else {
6400 self.local.publications.remove(&id.name);
6401 }
6402 }
6403 StateChange::SubscriptionSnapshot { id, previous } => {
6404 if let Some(prev) = previous {
6405 self.local.subscriptions.insert(id.name, prev);
6406 } else {
6407 self.local.subscriptions.remove(&id.name);
6408 }
6409 }
6410 StateChange::RoleSnapshot { id, previous } => {
6411 if let Some(prev) = previous {
6412 self.local.roles.insert(id, prev);
6413 } else {
6414 self.local.roles.remove(&id);
6415 }
6416 }
6417 StateChange::TriggerSnapshot { id, previous } => {
6418 if let Some(prev) = previous {
6419 self.local.triggers.insert(id, prev);
6420 } else {
6421 self.local.triggers.remove(&id);
6422 }
6423 }
6424 StateChange::ConstraintSnapshot {
6425 table_id,
6426 name,
6427 previous,
6428 } => {
6429 let key = (table_id, name);
6430 if let Some(previous) = previous {
6431 self.local.constraints.insert(key, previous);
6432 } else {
6433 self.local.constraints.remove(&key);
6434 }
6435 }
6436 StateChange::GraphLengthMarker { len } => {
6437 self.local.graph.edges.truncate(len);
6438 }
6439 StateChange::GraphSnapshot { previous } => {
6440 self.local.graph.edges = previous;
6441 }
6442 StateChange::RoleContextSnapshot {
6443 current_role,
6444 current_role_known,
6445 persistent_current_role,
6446 persistent_current_role_known,
6447 session_role,
6448 session_role_known,
6449 persistent_session_role,
6450 persistent_session_role_known,
6451 } => {
6452 self.local.current_role = current_role;
6453 self.local.current_role_known = current_role_known;
6454 self.local.persistent_current_role = persistent_current_role;
6455 self.local.persistent_current_role_known = persistent_current_role_known;
6456 self.local.session_role = session_role;
6457 self.local.session_role_known = session_role_known;
6458 self.local.persistent_session_role = persistent_session_role;
6459 self.local.persistent_session_role_known = persistent_session_role_known;
6460 }
6461 StateChange::SearchPathSnapshot {
6462 previous,
6463 previous_template,
6464 previous_session_template,
6465 } => {
6466 self.local.search_path = previous;
6467 self.local.search_path_template = previous_template;
6468 self.local.session_search_path_template = previous_session_template;
6469 }
6470 StateChange::TimeoutSettingsSnapshot {
6471 lock_timeout,
6472 statement_timeout,
6473 } => {
6474 self.local.lock_timeout = lock_timeout;
6475 self.local.statement_timeout = statement_timeout;
6476 }
6477 StateChange::GenerationCounterSnapshot { previous } => {
6478 self.local.generation_counter = previous;
6479 }
6480 StateChange::PendingValidationSnapshot { previous } => {
6481 self.local.pending_validation = previous;
6482 }
6483 StateChange::ConfidenceSnapshot { previous } => {
6484 self.local.confidence = previous;
6485 }
6486 }
6487 }
6488 }
6489}