use crate::catalog::Catalog;
use crate::catalog::grant::{Grant, GranteeType, target_key};
use crate::catalog::id::DbObjectId;
use crate::catalog::target::AttrTarget;
use crate::diff::operations::{ColumnGrants, GrantOperation, MigrationStep};
use std::collections::{BTreeMap, BTreeSet};
pub fn is_owner_grant(grant: &Grant) -> bool {
match &grant.grantee {
GranteeType::Role(role_name) => role_name == &grant.object_owner,
GranteeType::Public => false, }
}
pub fn grant_target_object(op: &GrantOperation) -> DbObjectId {
match op {
GrantOperation::Grant { grant } | GrantOperation::Revoke { grant } => {
grant.target.db_object_id()
}
GrantOperation::GrantColumns(cg) | GrantOperation::RevokeColumns(cg) => cg.relation.clone(),
}
}
pub fn desired_acl_steps(id: &DbObjectId, new_catalog: &Catalog) -> Vec<MigrationStep> {
let object_grants: Vec<&Grant> = new_catalog
.grants
.iter()
.filter(|g| &g.target.db_object_id() == id)
.collect();
if !object_grants.iter().any(|g| !g.is_default_acl) {
return Vec::new();
}
let mut steps: Vec<MigrationStep> = object_grants
.iter()
.filter(|g| !is_owner_grant(g))
.map(|g| {
MigrationStep::Grant(GrantOperation::Grant {
grant: (*g).clone(),
})
})
.collect();
steps.extend(revoke_missing_default_public(id, &object_grants));
steps
}
fn revoke_missing_default_public(id: &DbObjectId, object_grants: &[&Grant]) -> Vec<MigrationStep> {
let Some(sample) = object_grants.first() else {
return Vec::new();
};
get_default_public_privileges(id)
.into_iter()
.filter(|privilege| {
!object_grants.iter().any(|g| {
matches!(&g.grantee, GranteeType::Public) && g.privileges.contains(privilege)
})
})
.map(|privilege| {
MigrationStep::Grant(GrantOperation::Revoke {
grant: Grant {
grantee: GranteeType::Public,
target: AttrTarget::object(id.clone()),
privileges: vec![privilege],
with_grant_option: false,
depends_on: vec![id.clone()],
object_owner: sample.object_owner.clone(),
is_default_acl: false,
},
})
})
.collect()
}
pub fn diff(old_grant: Option<&Grant>, new_grant: Option<&Grant>) -> Vec<MigrationStep> {
match (old_grant, new_grant) {
(None, Some(new)) => {
if is_owner_grant(new) {
vec![] } else {
vec![MigrationStep::Grant(GrantOperation::Grant {
grant: new.clone(),
})]
}
}
(Some(old), None) => {
if is_owner_grant(old) {
vec![] } else {
vec![MigrationStep::Grant(GrantOperation::Revoke {
grant: old.clone(),
})]
}
}
(Some(old), Some(new)) => {
if is_owner_grant(old) || is_owner_grant(new) {
vec![] } else {
diff_privilege_change(old, new)
}
}
(None, None) => vec![], }
}
fn diff_privilege_change(old: &Grant, new: &Grant) -> Vec<MigrationStep> {
if old.with_grant_option != new.with_grant_option {
return vec![
MigrationStep::Grant(GrantOperation::Revoke { grant: old.clone() }),
MigrationStep::Grant(GrantOperation::Grant { grant: new.clone() }),
];
}
let old_privs: BTreeSet<&str> = old.privileges.iter().map(String::as_str).collect();
let new_privs: BTreeSet<&str> = new.privileges.iter().map(String::as_str).collect();
let to_revoke: Vec<String> = old_privs
.difference(&new_privs)
.map(|p| p.to_string())
.collect();
let to_grant: Vec<String> = new_privs
.difference(&old_privs)
.map(|p| p.to_string())
.collect();
let mut steps = Vec::new();
if !to_revoke.is_empty() {
steps.push(MigrationStep::Grant(GrantOperation::Revoke {
grant: Grant {
privileges: to_revoke,
..old.clone()
},
}));
}
if !to_grant.is_empty() {
steps.push(MigrationStep::Grant(GrantOperation::Grant {
grant: Grant {
privileges: to_grant,
..new.clone()
},
}));
}
steps
}
pub fn coalesce_column_grants(steps: Vec<MigrationStep>) -> Vec<MigrationStep> {
enum Slot {
Step(Box<MigrationStep>),
Group(usize),
}
struct Acc {
grantee: GranteeType,
relation: DbObjectId,
with_grant_option: bool,
is_grant: bool,
privilege_columns: BTreeMap<String, BTreeSet<String>>,
rep_id: String,
}
type Key = (bool, bool, String, DbObjectId, bool);
let mut slots: Vec<Slot> = Vec::new();
let mut group_index: BTreeMap<Key, usize> = BTreeMap::new();
let mut accs: Vec<Acc> = Vec::new();
for step in steps {
let column_grant = match &step {
MigrationStep::Grant(GrantOperation::Grant { grant })
if grant.target.column_name().is_some() =>
{
Some((true, grant))
}
MigrationStep::Grant(GrantOperation::Revoke { grant })
if grant.target.column_name().is_some() =>
{
Some((false, grant))
}
_ => None,
};
let Some((is_grant, grant)) = column_grant else {
slots.push(Slot::Step(Box::new(step)));
continue;
};
let (is_public, role_name) = match &grant.grantee {
GranteeType::Public => (true, String::new()),
GranteeType::Role(name) => (false, name.clone()),
};
let relation = grant.target.object.clone();
let column = grant
.target
.column_name()
.expect("filtered to column grants above")
.to_string();
let id = grant.id();
let key: Key = (
is_grant,
is_public,
role_name,
relation.clone(),
grant.with_grant_option,
);
let idx = if let Some(&i) = group_index.get(&key) {
i
} else {
let i = accs.len();
accs.push(Acc {
grantee: grant.grantee.clone(),
relation,
with_grant_option: grant.with_grant_option,
is_grant,
privilege_columns: BTreeMap::new(),
rep_id: id.clone(),
});
slots.push(Slot::Group(i));
group_index.insert(key, i);
i
};
let acc = &mut accs[idx];
for privilege in &grant.privileges {
acc.privilege_columns
.entry(privilege.clone())
.or_default()
.insert(column.clone());
}
if id < acc.rep_id {
acc.rep_id = id;
}
}
let mut group_ops: Vec<Option<MigrationStep>> = accs
.into_iter()
.map(|acc| {
let cg = ColumnGrants {
grantee: acc.grantee,
relation: acc.relation.clone(),
with_grant_option: acc.with_grant_option,
privilege_columns: acc.privilege_columns,
depends_on: vec![acc.relation],
rep_id: acc.rep_id,
};
Some(MigrationStep::Grant(if acc.is_grant {
GrantOperation::GrantColumns(cg)
} else {
GrantOperation::RevokeColumns(cg)
}))
})
.collect();
slots
.into_iter()
.map(|slot| match slot {
Slot::Step(step) => *step,
Slot::Group(i) => group_ops[i]
.take()
.expect("each group placeholder is emitted exactly once"),
})
.collect()
}
pub fn diff_grants(old_grants: &[Grant], new_grants: &[Grant]) -> Vec<MigrationStep> {
let mut old_map = BTreeMap::new();
let mut new_map = BTreeMap::new();
for grant in old_grants {
old_map.insert(grant.id(), grant);
}
for grant in new_grants {
new_map.insert(grant.id(), grant);
}
let all_ids: BTreeSet<_> = old_map.keys().chain(new_map.keys()).cloned().collect();
let mut steps: Vec<MigrationStep> = all_ids
.into_iter()
.flat_map(|id| {
let old = old_map.get(&id).cloned();
let new = new_map.get(&id).cloned();
diff(old, new)
})
.collect();
steps.extend(generate_revoke_for_new_explicit_acls(
old_grants, new_grants,
));
steps
}
fn generate_revoke_for_new_explicit_acls(
old_grants: &[Grant],
new_grants: &[Grant],
) -> Vec<MigrationStep> {
let mut steps = Vec::new();
let mut old_by_object: BTreeMap<String, Vec<&Grant>> = BTreeMap::new();
for grant in old_grants {
old_by_object
.entry(target_key(&grant.target))
.or_default()
.push(grant);
}
let mut new_by_object: BTreeMap<String, Vec<&Grant>> = BTreeMap::new();
for grant in new_grants {
new_by_object
.entry(target_key(&grant.target))
.or_default()
.push(grant);
}
for (obj_key, new_object_grants) in &new_by_object {
let new_has_explicit_acl = new_object_grants.iter().any(|g| !g.is_default_acl);
if !new_has_explicit_acl {
continue; }
let old_had_explicit_acl = old_by_object
.get(obj_key)
.is_some_and(|old_grants| old_grants.iter().any(|g| !g.is_default_acl));
if old_had_explicit_acl {
continue;
}
let object = new_object_grants[0].target.db_object_id();
steps.extend(revoke_missing_default_public(&object, new_object_grants));
}
steps
}
fn get_default_public_privileges(object: &DbObjectId) -> Vec<String> {
match object {
DbObjectId::Function { .. }
| DbObjectId::Procedure { .. }
| DbObjectId::Aggregate { .. } => {
vec!["EXECUTE".to_string()]
}
DbObjectId::Type { .. } | DbObjectId::Domain { .. } => {
vec!["USAGE".to_string()]
}
_ => {
vec![]
}
}
}