safe-migrate 0.8.0

Check PostgreSQL migrations against a synchronized database baseline
Documentation
use super::{AnalysisState, MutationResult};
use crate::_internal::analysis::evidence::{EvidenceCode, EvidenceScope};
use crate::_internal::analysis::facts::{
    ResetSettingTarget, RoleFact, SearchPathTarget, TimeoutSetting, TimeoutSettingValue,
};
use crate::_internal::analysis::mutations::{
    OpaqueMutation, SearchPathChange, TimeoutSettingChange,
};

impl AnalysisState {
    pub(super) fn apply_search_path(&mut self, change: &SearchPathChange) -> MutationResult {
        if change.local && self.local.transactions.is_empty() {
            return MutationResult::Skipped;
        }
        self.snapshot_search_path();
        self.snapshot_confidence();
        let template = match &change.target {
            SearchPathTarget::Default => self.local.default_search_path_template.clone(),
            SearchPathTarget::Schemas(schemas) => schemas.clone(),
        };
        self.local.search_path_template = template.clone();
        if !change.local {
            self.local.session_search_path_template = template;
        }
        self.refresh_role_sensitive_search_path();
        MutationResult::Applied
    }

    pub(super) fn apply_timeout_setting(
        &mut self,
        change: &TimeoutSettingChange,
    ) -> MutationResult {
        if change.local && self.local.transactions.is_empty() {
            return MutationResult::Skipped;
        }
        let next = match &change.value {
            TimeoutSettingValue::Default => match change.setting {
                TimeoutSetting::Lock => self.local.lock_timeout.default,
                TimeoutSetting::Statement => self.local.statement_timeout.default,
            },
            TimeoutSettingValue::Milliseconds(milliseconds) => Some(*milliseconds),
            TimeoutSettingValue::Current => match change.setting {
                TimeoutSetting::Lock => self.local.lock_timeout.effective,
                TimeoutSetting::Statement => self.local.statement_timeout.effective,
            },
            TimeoutSettingValue::Invalid(reason) => {
                return MutationResult::Conflict {
                    reason: reason.clone(),
                };
            }
        };
        self.snapshot_timeout_settings();
        let setting = match change.setting {
            TimeoutSetting::Lock => &mut self.local.lock_timeout,
            TimeoutSetting::Statement => &mut self.local.statement_timeout,
        };
        setting.effective = next;
        if !change.local {
            setting.session = next;
        }
        MutationResult::Applied
    }

    pub(super) fn apply_reset_settings(&mut self, target: &ResetSettingTarget) -> MutationResult {
        if matches!(
            target,
            ResetSettingTarget::All | ResetSettingTarget::SearchPath
        ) {
            self.snapshot_search_path();
            self.snapshot_confidence();
            let template = self.local.default_search_path_template.clone();
            self.local.session_search_path_template = template.clone();
            self.local.search_path_template = template;
            self.refresh_role_sensitive_search_path();
        }
        if matches!(
            target,
            ResetSettingTarget::All
                | ResetSettingTarget::LockTimeout
                | ResetSettingTarget::StatementTimeout
        ) {
            self.snapshot_timeout_settings();
            if matches!(
                target,
                ResetSettingTarget::All | ResetSettingTarget::LockTimeout
            ) {
                self.local.lock_timeout.session = self.local.lock_timeout.default;
                self.local.lock_timeout.effective = self.local.lock_timeout.default;
            }
            if matches!(
                target,
                ResetSettingTarget::All | ResetSettingTarget::StatementTimeout
            ) {
                self.local.statement_timeout.session = self.local.statement_timeout.default;
                self.local.statement_timeout.effective = self.local.statement_timeout.default;
            }
        }
        MutationResult::Applied
    }

    pub(super) fn apply_switch_role(
        &mut self,
        role: &Option<RoleFact>,
        local: bool,
        is_session_auth: bool,
    ) -> MutationResult {
        if local && self.local.transactions.is_empty() {
            return MutationResult::Skipped;
        }

        let (target_name, target_known) = if let Some(role) = role {
            let Some(identity) = self.role_fact_identity(role) else {
                self.taint(EvidenceCode::UnresolvedReference, EvidenceScope::Chain);
                return MutationResult::Skipped;
            };
            identity
        } else if is_session_auth {
            (
                self.local.authenticated_role.clone(),
                self.local.authenticated_role_known,
            )
        } else {
            (
                self.local.session_role.clone(),
                self.local.session_role_known,
            )
        };
        let persistent_role_reset_target = if role.is_none() && !is_session_auth {
            Some((
                self.local.persistent_session_role.clone(),
                self.local.persistent_session_role_known,
            ))
        } else {
            None
        };

        let authorized = if role.is_none() {
            Some(true)
        } else if is_session_auth {
            self.can_set_session_authorization_to(&target_name)
        } else {
            self.can_set_role_to(&target_name)
        };
        match authorized {
            Some(false) => {
                return MutationResult::Conflict {
                    reason: if self.present_role(&target_name).is_none() {
                        format!("role '{}' does not exist", target_name)
                    } else {
                        format!("permission denied to set role '{}'", target_name)
                    },
                };
            }
            None => {
                self.taint(
                    EvidenceCode::CatalogCoverageIncomplete,
                    EvidenceScope::Chain,
                );
            }
            Some(true) => {}
        }

        self.snapshot_role_context();
        self.snapshot_search_path();
        self.snapshot_confidence();
        if is_session_auth {
            self.local.session_role = target_name.clone();
            self.local.session_role_known = target_known;
            self.local.current_role = target_name.clone();
            self.local.current_role_known = target_known;
            if !local {
                self.local.persistent_session_role = target_name.clone();
                self.local.persistent_session_role_known = target_known;
                self.local.persistent_current_role = target_name;
                self.local.persistent_current_role_known = target_known;
            }
        } else {
            self.local.current_role = target_name.clone();
            self.local.current_role_known = target_known;
            if !local {
                let (persistent_name, persistent_known) =
                    persistent_role_reset_target.unwrap_or((target_name, target_known));
                self.local.persistent_current_role = persistent_name;
                self.local.persistent_current_role_known = persistent_known;
            }
        }
        self.refresh_role_sensitive_search_path();
        MutationResult::Applied
    }

    pub(super) fn apply_opaque(&mut self, opaque: &OpaqueMutation) -> MutationResult {
        let code = match opaque {
            OpaqueMutation::UnsupportedStatement => EvidenceCode::UnsupportedStatement,
            OpaqueMutation::UnresolvedReference { .. } => EvidenceCode::UnresolvedReference,
            _ => EvidenceCode::UnsupportedSemantics,
        };
        self.taint(code, EvidenceScope::Chain);
        MutationResult::Applied
    }
}