use super::{
roles::dependencies::{prepare_role_owner, RoleDependencyCandidate},
table_grants::context::TableSecurityWrite,
table_inquiry::TablePrivilegeState,
};
use crate::schema::{
namespaces::SchemaStatementWriter,
publication::dependencies::CatalogPublicationChanges,
relation_alteration::RoleTransferContext,
sequences::role_ownership::{
table_owned_sequence_owner_updates, OwnedSequenceSecurityCatalog,
OwnedSequenceSecurityWrite, SequenceSecurityPublication,
},
};
use uqa_core::RelationIdentity;
use uqa_sql::{
ast::{ColumnDef, RelationPersistence, TableConstraintSet},
catalog::security::{
ownership::OwnerChangeAuthority, table::rewrite_acl_owner, BoundTableSecurity,
},
SQLError,
};
use uqa_storage::StorageBackendResult;
pub struct TableOwnerSchema {
pub columns: Vec<ColumnDef>,
pub constraints: TableConstraintSet,
}
pub trait TableOwnerState: TablePrivilegeState {
fn object_id(&self) -> [u8; 16];
fn persistence(&self) -> RelationPersistence;
fn schema(&self) -> TableOwnerSchema;
fn persist_schema(
&self,
name: &str,
schema: &TableOwnerSchema,
security: &BoundTableSecurity,
) -> StorageBackendResult<()>;
fn security_write(&self) -> TableSecurityWrite<'_>;
}
pub trait TableOwnerRegistry {
fn table(&self, relation: &RelationIdentity) -> Option<Box<dyn TableOwnerState + '_>>;
}
type BoundTableOwner<'a> = (RelationIdentity, Box<dyn TableOwnerState + 'a>);
pub trait TableOwnerPublication {
fn sequence_security_write(&self) -> OwnedSequenceSecurityWrite<'_>;
}
pub struct TableOwnershipContext<'a> {
pub writer: &'a dyn SchemaStatementWriter,
pub tables: &'a dyn TableOwnerRegistry,
pub roles: RoleTransferContext<'a>,
pub owned_sequences: &'a dyn OwnedSequenceSecurityCatalog,
pub sequences: &'a dyn SequenceSecurityPublication,
pub publication: &'a dyn TableOwnerPublication,
pub changes: &'a dyn CatalogPublicationChanges,
}
impl TableOwnershipContext<'_> {
pub fn alter_table_role_owner(
&self,
name: &str,
requested_owner: &uqa_sql::ast::RoleSpecification,
) -> Result<(), SQLError> {
self.roles.locks.refresh_shared_catalog()?;
let owner = self.roles.bind(requested_owner)?;
let current_user = self.roles.session.current_role();
let RoleDependencyCandidate {
roles,
memberships,
value,
..
} = prepare_role_owner(
self.roles.lock_context(),
&owner,
|| self.writer.prepare_writer(),
|roles, memberships, new_owner| {
let (relation, table) = self.bound_table_for_security(name)?;
let mut security = table
.security()
.resolve(roles)
.map_err(SQLError::Internal)?;
if security.role_owner == new_owner {
return Ok(None);
}
let authority = OwnerChangeAuthority {
roles,
memberships,
current_user: ¤t_user,
new_owner,
};
authority.require_owner_change(&security.role_owner, "table", &relation.name)?;
authority.require_schema_create(self.roles.schemas, &relation.schema)?;
rewrite_acl_owner(&mut security, new_owner);
Ok(Some((
table,
BoundTableSecurity::bind(&security, roles).map_err(SQLError::Internal)?,
)))
},
)?;
let Some((table, table_security)) = value else {
return Ok(());
};
let sequence_updates = table_owned_sequence_owner_updates(
self.owned_sequences,
table.object_id(),
owner.require_name(&roles)?,
&roles,
)?;
for (sequence, security) in &sequence_updates {
self.sequences
.persist_security(&sequence.qualified_name(), sequence, security)?;
}
let schema = table.schema();
table
.persist_schema(name, &schema, &table_security)
.map_err(|error| SQLError::Internal(format!("persist table owner: {error}")))?;
table.security_write().clone_from(&table_security);
let sequence_changed = !sequence_updates.is_empty();
if sequence_changed {
let mut registry = self.publication.sequence_security_write();
for (sequence, security) in sequence_updates {
registry.insert(sequence, security);
}
drop(registry);
}
let temporary = table.persistence() == RelationPersistence::Temporary;
drop(table);
drop(memberships);
drop(roles);
if sequence_changed {
self.changes.catalog_registry_changed();
}
if temporary {
self.changes.table_catalog_changed();
}
Ok(())
}
fn bound_table_for_security(&self, name: &str) -> Result<BoundTableOwner<'_>, SQLError> {
let relation = RelationIdentity::from_legacy_name(name)
.map_err(|error| SQLError::Internal(format!("resolve table `{name}`: {error}")))?;
let table = self
.tables
.table(&relation)
.ok_or_else(|| SQLError::Internal(format!("table `{name}` disappeared")))?;
Ok((relation, table))
}
}