1use std::collections::{BTreeMap, BTreeSet};
8
9use crate::identifier::Identifier;
10use crate::ir::default_privileges::{DefaultPrivObjectType, DefaultPrivilegeRule};
11use crate::ir::grant::Grant;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct DefaultPrivilegeChange {
16 pub target_role: Identifier,
18 pub schema: Option<Identifier>,
20 pub object_type: DefaultPrivObjectType,
22 pub is_grant: bool,
24 pub grant: Grant,
26}
27
28type RuleKey = (Identifier, Option<Identifier>, DefaultPrivObjectType);
29
30#[must_use]
37pub fn diff_default_privileges(
38 target: &[DefaultPrivilegeRule],
39 source: &[DefaultPrivilegeRule],
40 managed_roles: &BTreeSet<Identifier>,
41) -> Vec<DefaultPrivilegeChange> {
42 let key = |r: &DefaultPrivilegeRule| -> RuleKey {
43 (r.target_role.clone(), r.schema.clone(), r.object_type)
44 };
45
46 let target_map: BTreeMap<RuleKey, &DefaultPrivilegeRule> =
47 target.iter().map(|r| (key(r), r)).collect();
48 let source_map: BTreeMap<RuleKey, &DefaultPrivilegeRule> =
49 source.iter().map(|r| (key(r), r)).collect();
50
51 let mut all_keys: BTreeSet<RuleKey> = target_map.keys().cloned().collect();
52 all_keys.extend(source_map.keys().cloned());
53
54 let mut out = Vec::new();
55 for k in all_keys {
56 let target_grants = target_map
57 .get(&k)
58 .map_or(&[] as &[Grant], |r| r.grants.as_slice());
59 let source_grants = source_map
60 .get(&k)
61 .map_or(&[] as &[Grant], |r| r.grants.as_slice());
62
63 let (to_add, to_revoke, _unmanaged) =
64 super::grants::diff_grants(target_grants, source_grants, managed_roles);
65
66 for g in to_add {
67 out.push(DefaultPrivilegeChange {
68 target_role: k.0.clone(),
69 schema: k.1.clone(),
70 object_type: k.2,
71 is_grant: true,
72 grant: g,
73 });
74 }
75 for g in to_revoke {
76 out.push(DefaultPrivilegeChange {
77 target_role: k.0.clone(),
78 schema: k.1.clone(),
79 object_type: k.2,
80 is_grant: false,
81 grant: g,
82 });
83 }
84 }
85 out
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::identifier::Identifier;
92 use crate::ir::default_privileges::{DefaultPrivObjectType, DefaultPrivilegeRule};
93 use crate::ir::grant::{Grant, GrantTarget, Privilege};
94
95 fn id(s: &str) -> Identifier {
96 Identifier::from_unquoted(s).unwrap()
97 }
98
99 fn grant_to(role: &str, priv_: Privilege) -> Grant {
100 Grant {
101 grantee: GrantTarget::Role(id(role)),
102 privilege: priv_,
103 with_grant_option: false,
104 columns: None,
105 }
106 }
107
108 fn rule(
109 target_role: &str,
110 schema: Option<&str>,
111 object_type: DefaultPrivObjectType,
112 grants: Vec<Grant>,
113 ) -> DefaultPrivilegeRule {
114 DefaultPrivilegeRule {
115 target_role: id(target_role),
116 schema: schema.map(id),
117 object_type,
118 grants,
119 }
120 }
121
122 fn managed(roles: &[&str]) -> BTreeSet<Identifier> {
123 roles.iter().map(|r| id(r)).collect()
124 }
125
126 #[test]
127 fn add_only() {
128 let src = rule(
130 "app_owner",
131 None,
132 DefaultPrivObjectType::Tables,
133 vec![grant_to("web_anon", Privilege::Select)],
134 );
135 let changes = diff_default_privileges(&[], &[src], &managed(&["app_owner", "web_anon"]));
136 assert_eq!(changes.len(), 1);
137 assert!(changes[0].is_grant, "expected is_grant=true");
138 assert_eq!(changes[0].grant.privilege, Privilege::Select);
139 }
140
141 #[test]
142 fn revoke_only() {
143 let tgt = rule(
146 "app_owner",
147 None,
148 DefaultPrivObjectType::Tables,
149 vec![grant_to("web_anon", Privilege::Select)],
150 );
151 let changes = diff_default_privileges(&[tgt], &[], &managed(&["app_owner", "web_anon"]));
152 assert_eq!(changes.len(), 1);
153 assert!(!changes[0].is_grant, "expected is_grant=false");
154 assert_eq!(changes[0].grant.privilege, Privilege::Select);
155 }
156
157 #[test]
158 fn mixed_add_and_revoke() {
159 let tgt = rule(
162 "app_owner",
163 Some("app"),
164 DefaultPrivObjectType::Sequences,
165 vec![grant_to("alice", Privilege::Select)],
166 );
167 let src = rule(
168 "app_owner",
169 Some("app"),
170 DefaultPrivObjectType::Sequences,
171 vec![grant_to("alice", Privilege::Usage)],
172 );
173 let changes = diff_default_privileges(&[tgt], &[src], &managed(&["app_owner", "alice"]));
174 assert_eq!(changes.len(), 2, "expected 2 changes, got: {changes:?}");
175 let adds: Vec<_> = changes.iter().filter(|c| c.is_grant).collect();
176 let revs: Vec<_> = changes.iter().filter(|c| !c.is_grant).collect();
177 assert_eq!(adds.len(), 1);
178 assert_eq!(revs.len(), 1);
179 assert_eq!(adds[0].grant.privilege, Privilege::Usage);
180 assert_eq!(revs[0].grant.privilege, Privilege::Select);
181 }
182}