Skip to main content

pgevolve_core/diff/
owner_op.rs

1//! `AlterObjectOwner` — uniform owner-change op across grantable families.
2
3use serde::{Deserialize, Serialize};
4
5use crate::identifier::{Identifier, QualifiedName};
6
7/// Object kind discriminant for the renderer (`ALTER TABLE x OWNER TO`,
8/// `ALTER SCHEMA x OWNER TO`, etc.).
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum OwnerObjectKind {
12    /// `ALTER SCHEMA x OWNER TO`.
13    Schema,
14    /// `ALTER SEQUENCE x OWNER TO`.
15    Sequence,
16    /// `ALTER TABLE x OWNER TO`.
17    Table,
18    /// `ALTER VIEW x OWNER TO`.
19    View,
20    /// `ALTER MATERIALIZED VIEW x OWNER TO`.
21    MaterializedView,
22    /// `ALTER FUNCTION x() OWNER TO`.
23    Function,
24    /// `ALTER PROCEDURE x() OWNER TO`.
25    Procedure,
26    /// `ALTER TYPE x OWNER TO`.
27    UserType,
28    /// `ALTER PUBLICATION x OWNER TO`.
29    Publication,
30    /// `ALTER SUBSCRIPTION x OWNER TO`.
31    Subscription,
32    /// `ALTER STATISTICS x OWNER TO`.
33    Statistic,
34    /// `ALTER COLLATION x OWNER TO`.
35    Collation,
36}
37
38impl OwnerObjectKind {
39    /// The SQL keyword(s) used in `ALTER <keyword> <name> OWNER TO <role>`.
40    #[must_use]
41    pub const fn sql_keyword(self) -> &'static str {
42        match self {
43            Self::Schema => "SCHEMA",
44            Self::Sequence => "SEQUENCE",
45            Self::Table => "TABLE",
46            Self::View => "VIEW",
47            Self::MaterializedView => "MATERIALIZED VIEW",
48            Self::Function => "FUNCTION",
49            Self::Procedure => "PROCEDURE",
50            Self::UserType => "TYPE",
51            Self::Publication => "PUBLICATION",
52            Self::Subscription => "SUBSCRIPTION",
53            Self::Statistic => "STATISTICS",
54            Self::Collation => "COLLATION",
55        }
56    }
57}
58
59/// Identifies the object being re-owned by name shape.
60///
61/// Replaces the older convention of stuffing every kind into a
62/// [`QualifiedName`] (which forced workarounds like
63/// `QualifiedName::new(name, name)` for schemas and
64/// `QualifiedName::new("__cluster__", name)` for publications /
65/// subscriptions). The renderer dispatches on this enum directly.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
68pub enum OwnedObjectId {
69    /// A schema-qualified object: table, view, MV, sequence, function,
70    /// procedure, user-type, statistic.
71    Qualified(QualifiedName),
72    /// A schema itself. Rendered as the bare schema name.
73    Schema(Identifier),
74    /// A cluster-level object — publication or subscription. Rendered
75    /// as the bare name (no schema qualifier; PG does not schema-qualify
76    /// these).
77    Cluster(Identifier),
78}
79
80impl OwnedObjectId {
81    /// Render the object's target name for use in
82    /// `ALTER <kind> <here>{signature} OWNER TO <role>;`.
83    #[must_use]
84    pub fn render_sql(&self) -> String {
85        match self {
86            Self::Qualified(q) => q.render_sql(),
87            Self::Schema(name) | Self::Cluster(name) => name.render_sql(),
88        }
89    }
90}
91
92impl std::fmt::Display for OwnedObjectId {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        match self {
95            Self::Qualified(q) => write!(f, "{q}"),
96            Self::Schema(name) | Self::Cluster(name) => write!(f, "{name}"),
97        }
98    }
99}
100
101/// An `ALTER <kind> <id> OWNER TO <to>` statement, paired with the previous
102/// owner for audit / rollback purposes.
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct AlterObjectOwner {
105    /// Which kind of object is being re-owned.
106    pub kind: OwnerObjectKind,
107    /// Identifies the object by name shape (qualified / schema / cluster).
108    pub id: OwnedObjectId,
109    /// Optional argument-signature suffix for routines (e.g., `(int, text)`).
110    /// Empty for non-routine kinds.
111    #[serde(default)]
112    pub signature: String,
113    /// Previous owner (taken from the target catalog; `None` when the
114    /// catalog did not record an owner).
115    pub from: Option<Identifier>,
116    /// Desired new owner (taken from the source catalog).
117    pub to: Identifier,
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn sql_keywords_match_pg() {
126        assert_eq!(OwnerObjectKind::Schema.sql_keyword(), "SCHEMA");
127        assert_eq!(OwnerObjectKind::Sequence.sql_keyword(), "SEQUENCE");
128        assert_eq!(OwnerObjectKind::Table.sql_keyword(), "TABLE");
129        assert_eq!(OwnerObjectKind::View.sql_keyword(), "VIEW");
130        assert_eq!(
131            OwnerObjectKind::MaterializedView.sql_keyword(),
132            "MATERIALIZED VIEW"
133        );
134        assert_eq!(OwnerObjectKind::Function.sql_keyword(), "FUNCTION");
135        assert_eq!(OwnerObjectKind::Procedure.sql_keyword(), "PROCEDURE");
136        assert_eq!(OwnerObjectKind::UserType.sql_keyword(), "TYPE");
137        assert_eq!(OwnerObjectKind::Publication.sql_keyword(), "PUBLICATION");
138        assert_eq!(OwnerObjectKind::Subscription.sql_keyword(), "SUBSCRIPTION");
139        assert_eq!(OwnerObjectKind::Statistic.sql_keyword(), "STATISTICS");
140        assert_eq!(OwnerObjectKind::Collation.sql_keyword(), "COLLATION");
141    }
142
143    #[test]
144    fn owned_object_id_renders_each_shape() {
145        let q = OwnedObjectId::Qualified(QualifiedName::new(
146            Identifier::from_unquoted("app").unwrap(),
147            Identifier::from_unquoted("users").unwrap(),
148        ));
149        assert_eq!(q.render_sql(), "app.users");
150
151        let s = OwnedObjectId::Schema(Identifier::from_unquoted("billing").unwrap());
152        assert_eq!(s.render_sql(), "billing");
153
154        let c = OwnedObjectId::Cluster(Identifier::from_unquoted("my_pub").unwrap());
155        assert_eq!(c.render_sql(), "my_pub");
156    }
157}