pgevolve-core 0.4.6

Postgres declarative schema management — core library (parser, IR, diff, planner) powering the pgevolve CLI.
Documentation
//! `Schema` — a Postgres namespace.

use serde::{Deserialize, Serialize};

use crate::identifier::Identifier;
use crate::ir::difference::Difference;
use crate::ir::eq::{Equiv, field_difference};

/// A Postgres schema (namespace).
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Schema {
    /// Schema name.
    pub name: Identifier,
    /// Optional comment.
    pub comment: Option<String>,
    /// Object owner. `None` = unmanaged (the differ ignores ownership).
    /// `Some(role)` = managed: diff emits `ALTER SCHEMA ... OWNER TO role`.
    pub owner: Option<Identifier>,
    /// Grants on this object. Empty = no grants. Canonicalized.
    pub grants: Vec<crate::ir::grant::Grant>,
}

impl Equiv for Schema {
    fn differences(&self, other: &Self) -> Vec<Difference> {
        let Self {
            name: _,
            comment: _,
            owner: _,
            grants: _,
        } = self;
        let mut out = Vec::new();
        out.extend(field_difference("name", &self.name, &other.name));
        out.extend(field_difference(
            "comment",
            &format!("{:?}", self.comment),
            &format!("{:?}", other.comment),
        ));
        out.extend(field_difference(
            "owner",
            &format!("{:?}", self.owner),
            &format!("{:?}", other.owner),
        ));
        out.extend(field_difference(
            "grants",
            &format!("{:?}", self.grants),
            &format!("{:?}", other.grants),
        ));
        out
    }
}

impl Schema {
    /// Construct a `Schema`.
    pub const fn new(name: Identifier) -> Self {
        Self {
            name,
            comment: None,
            owner: None,
            grants: Vec::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::identifier::Identifier;
    use crate::ir::eq::Equiv;

    fn id(s: &str) -> Identifier {
        Identifier::from_unquoted(s).unwrap()
    }

    fn base() -> Schema {
        Schema::new(id("app"))
    }

    #[test]
    fn equal_schemas_have_no_diff() {
        assert!(base().canonical_eq(&base()));
    }

    #[test]
    fn different_names_diff() {
        let a = base();
        let b = Schema::new(id("billing"));
        let d = a.differences(&b);
        assert_eq!(d.len(), 1);
        assert_eq!(d[0].path, "name");
    }

    #[test]
    fn comment_diffs() {
        let mut b = base();
        b.comment = Some("v2".into());
        let d = base().differences(&b);
        assert_eq!(d.len(), 1);
        assert_eq!(d[0].path, "comment");
    }

    #[test]
    fn owner_change_diffs() {
        let mut b = base();
        b.owner = Some(id("new_owner"));
        assert!(base().differences(&b).iter().any(|x| x.path == "owner"));
    }

    #[test]
    fn grants_change_diffs() {
        let mut b = base();
        b.grants.push(crate::ir::grant::Grant {
            grantee: crate::ir::grant::GrantTarget::Public,
            privilege: crate::ir::grant::Privilege::Usage,
            with_grant_option: false,
            columns: None,
        });
        assert!(base().differences(&b).iter().any(|x| x.path == "grants"));
    }
}