use std::{collections::BTreeMap, sync::Arc};
use uqa_sql::{
catalog::roles::{restoration, RoleDefinition, RoleMembership, RoleMembershipKey},
SQLError,
};
use uqa_storage::{CatalogFacade, StorageBackendError, StorageBackendResult};
mod memberships;
mod records;
pub const ROLES_METADATA_KEY: &str = "sql_roles_json";
pub const ROLE_MEMBERSHIPS_METADATA_KEY: &str = "sql_role_memberships_json";
pub struct RoleCatalogValues {
pub roles: BTreeMap<String, RoleDefinition>,
pub memberships: BTreeMap<RoleMembershipKey, RoleMembership>,
}
#[derive(Clone)]
pub struct RoleCatalogSnapshot {
pub roles: Arc<BTreeMap<String, RoleDefinition>>,
pub memberships: Arc<BTreeMap<RoleMembershipKey, RoleMembership>>,
}
impl RoleCatalogSnapshot {
pub fn merge_private(
mut self,
catalog: Option<&dyn CatalogFacade>,
current: &Self,
) -> StorageBackendResult<Self> {
if let Some(catalog) = catalog {
let names = self
.roles
.keys()
.chain(current.roles.keys())
.collect::<std::collections::BTreeSet<_>>();
let mut roles = Arc::clone(&self.roles);
for name in names {
if catalog.metadata_has_private_changes(&records::role_key(name))? {
if let Some(role) = current.roles.get(name) {
Arc::make_mut(&mut roles).insert(name.clone(), role.clone());
} else {
Arc::make_mut(&mut roles).remove(name);
}
}
}
self.roles = roles;
let identities = self
.memberships
.keys()
.chain(current.memberships.keys())
.collect::<std::collections::BTreeSet<_>>();
let mut memberships = Arc::clone(&self.memberships);
for identity in identities {
if catalog.metadata_has_private_changes(&memberships::key(identity))? {
if let Some(membership) = current.memberships.get(identity) {
Arc::make_mut(&mut memberships).insert(*identity, membership.clone());
} else {
Arc::make_mut(&mut memberships).remove(identity);
}
}
}
self.memberships = memberships;
}
Ok(self)
}
}
pub fn restore(catalog: &dyn CatalogFacade) -> StorageBackendResult<RoleCatalogValues> {
restore_catalog(catalog, false)
}
pub fn restore_and_migrate(catalog: &dyn CatalogFacade) -> StorageBackendResult<RoleCatalogValues> {
restore_catalog(catalog, true)
}
fn restore_catalog(
catalog: &dyn CatalogFacade,
allow_migration: bool,
) -> StorageBackendResult<RoleCatalogValues> {
let (mut roles, format) = records::read(catalog)?;
restoration::restore_role_definitions(&mut roles).map_err(StorageBackendError::Other)?;
if format != records::RoleRecordFormat::Aggregate {
records::validate_oids(catalog, &roles)?;
}
if matches!(
format,
records::RoleRecordFormat::Aggregate | records::RoleRecordFormat::Definitions
) {
for role in roles.values_mut() {
if role.object_id == [0; 16] {
role.object_id = if role.oid == 10 {
RoleDefinition::bootstrap().object_id
} else {
crate::catalog::identity::new_nonzero_catalog_identity("role", "identity")?
};
}
}
}
restoration::validate_role_identities(&roles).map_err(StorageBackendError::Other)?;
if format != records::RoleRecordFormat::Revisions {
for role in roles.values_mut() {
role.revision = 1;
}
}
uqa_sql::catalog::roles::tuple::validate_revisions(&roles)
.map_err(StorageBackendError::Other)?;
let (memberships, membership_format) = memberships::read(catalog, &roles)?;
if !allow_migration
&& (format != records::RoleRecordFormat::Revisions
|| membership_format != memberships::MembershipFormat::Records)
{
return Err(StorageBackendError::Other(
"role metadata requires initial-open record migration".into(),
));
}
if format != records::RoleRecordFormat::Revisions {
records::migrate(catalog, &roles)?;
}
if membership_format != memberships::MembershipFormat::Records {
memberships::migrate(catalog, &memberships)?;
}
Ok(RoleCatalogValues { roles, memberships })
}
pub fn persist_roles(
catalog: Option<&dyn CatalogFacade>,
before: &BTreeMap<String, RoleDefinition>,
roles: &BTreeMap<String, RoleDefinition>,
) -> Result<(), SQLError> {
let Some(catalog) = catalog else {
return Ok(());
};
records::persist(catalog, before, roles)
}
pub fn persist_memberships(
catalog: Option<&dyn CatalogFacade>,
before: &BTreeMap<RoleMembershipKey, RoleMembership>,
after: &BTreeMap<RoleMembershipKey, RoleMembership>,
) -> Result<(), SQLError> {
let Some(catalog) = catalog else {
return Ok(());
};
memberships::persist(catalog, before, after)
}
#[cfg(test)]
mod tests;