use crate::catalog::{
context::CatalogContext,
foreign::StoredForeignTable,
projection::{
foreign_table_relation_oid, resolve_regclass_kind_by_oid, snapshot_table_relation_oid,
view_relation_oid,
},
view::StoredView,
};
use std::{collections::BTreeMap, ops::Deref, sync::Arc};
use uqa_core::RelationIdentity;
use uqa_sql::{
catalog::{
resolution::RelationResolution,
roles::{guards::RoleCatalogGuards, RoleReferenceNames},
security::{
sequence_inquiry::SequencePrivilegeInquiry,
table_inquiry::{
ColumnPrivilegeRelation, ResolvedTablePrivilegeTarget, TablePrivilegeCatalog,
TablePrivilegeInquiry,
},
TableSecurity,
},
},
SQLError,
};
use uqa_storage::StorageBackendResult;
pub type TableColumnsRead<'a> = Box<dyn Deref<Target = Vec<uqa_sql::ast::ColumnDef>> + 'a>;
pub trait TablePrivilegeState {
fn role_owner(&self) -> String;
fn columns(&self) -> TableColumnsRead<'_>;
fn security(&self) -> TableSecurity;
fn column_names(&self) -> Vec<String>;
}
pub trait TablePrivilegeRead {
fn security_entries(&self) -> Box<dyn Iterator<Item = (RelationIdentity, TableSecurity)> + '_>;
fn keys(&self) -> Box<dyn Iterator<Item = &RelationIdentity> + '_>;
fn get(&self, relation: &RelationIdentity) -> Option<&dyn TablePrivilegeState>;
fn retained(&self, relation: &RelationIdentity) -> Option<Arc<dyn TablePrivilegeState>>;
}
pub type PrivilegeViewsRead<'a> =
Box<dyn Deref<Target = BTreeMap<RelationIdentity, StoredView>> + 'a>;
pub type PrivilegeForeignTablesRead<'a> =
Box<dyn Deref<Target = BTreeMap<RelationIdentity, StoredForeignTable>> + 'a>;
pub type PrivilegeForeignSecurityRead<'a> =
Box<dyn Deref<Target = BTreeMap<RelationIdentity, TableSecurity>> + 'a>;
pub trait TablePrivilegeRegistry {
fn refresh_tables(&self) -> StorageBackendResult<()>;
fn refresh_catalog(&self) -> StorageBackendResult<()>;
fn tables(&self) -> Box<dyn TablePrivilegeRead + '_>;
fn views(&self) -> PrivilegeViewsRead<'_>;
fn foreign_tables(&self) -> PrivilegeForeignTablesRead<'_>;
fn foreign_security(&self) -> PrivilegeForeignSecurityRead<'_>;
}
pub struct TablePrivilegeContext<'a> {
pub names: &'a dyn RoleReferenceNames,
pub roles: &'a dyn RoleCatalogGuards,
pub sequences: SequencePrivilegeInquiry<'a>,
pub catalog: CatalogContext<'a>,
pub registry: &'a dyn TablePrivilegeRegistry,
}
impl TablePrivilegeContext<'_> {
pub fn inquiry(&self) -> TablePrivilegeInquiry<'_> {
TablePrivilegeInquiry {
names: self.names,
roles: self.roles,
sequences: &self.sequences,
catalog: self,
}
}
}
impl TablePrivilegeCatalog for TablePrivilegeContext<'_> {
fn visible_relation_kind(&self, reference: &str) -> Result<RelationResolution, SQLError> {
self.sequences.resolution.visible_relation_kind(reference)
}
fn table_privilege_security(
&self,
target: &ResolvedTablePrivilegeTarget,
) -> Result<TableSecurity, SQLError> {
match target {
ResolvedTablePrivilegeTarget::Table(relation) => self
.registry
.tables()
.get(relation)
.map(TablePrivilegeState::security)
.ok_or_else(|| disappeared("table", relation)),
ResolvedTablePrivilegeTarget::View(relation) => self
.registry
.views()
.get(relation)
.map(StoredView::security)
.ok_or_else(|| disappeared("view", relation)),
ResolvedTablePrivilegeTarget::ForeignTable(relation) => self
.registry
.foreign_security()
.get(relation)
.cloned()
.ok_or_else(|| missing_foreign_table_security(relation)),
ResolvedTablePrivilegeTarget::Sequence(_) => Err(SQLError::Internal(
"sequence reached table-shaped privilege lookup".into(),
)),
}
}
fn column_privilege_relation(
&self,
target: &ResolvedTablePrivilegeTarget,
) -> Result<ColumnPrivilegeRelation, SQLError> {
match target {
ResolvedTablePrivilegeTarget::Table(relation) => {
let table = self
.registry
.tables()
.retained(relation)
.ok_or_else(|| disappeared("table", relation))?;
let columns = table.column_names();
Ok(ColumnPrivilegeRelation {
relation: relation.clone(),
security: table.security(),
columns,
has_system_columns: true,
})
}
ResolvedTablePrivilegeTarget::View(relation) => {
let view = self
.registry
.views()
.get(relation)
.cloned()
.ok_or_else(|| disappeared("view", relation))?;
let columns = view.output_columns.clone().ok_or_else(|| {
SQLError::Internal(format!(
"loaded view `{}` has no durable public column metadata",
relation.qualified_name()
))
})?;
Ok(ColumnPrivilegeRelation {
relation: relation.clone(),
security: view.security(),
columns,
has_system_columns: false,
})
}
ResolvedTablePrivilegeTarget::ForeignTable(relation) => {
let table = self
.registry
.foreign_tables()
.get(relation)
.cloned()
.ok_or_else(|| disappeared("foreign table", relation))?;
Ok(ColumnPrivilegeRelation {
relation: relation.clone(),
security: self.table_privilege_security(target)?,
columns: table
.columns
.iter()
.map(|column| column.name.clone())
.collect(),
has_system_columns: true,
})
}
ResolvedTablePrivilegeTarget::Sequence(_) => Err(SQLError::Internal(
"sequence reached table-shaped column lookup".into(),
)),
}
}
fn resolve_table_privilege_oid(
&self,
oid: i64,
) -> Result<Option<ResolvedTablePrivilegeTarget>, SQLError> {
self.registry.refresh_tables().map_err(|error| {
SQLError::Internal(format!("load tables for privilege inquiry: {error}"))
})?;
self.registry.refresh_catalog().map_err(|error| {
SQLError::Internal(format!("load views for privilege inquiry: {error}"))
})?;
let catalog = self.catalog.catalog_read_view();
let resolution = self
.catalog
.session_execution_view()
.relation_name_resolution();
for relation in self.registry.tables().keys() {
if snapshot_table_relation_oid(&catalog, &resolution, &relation.qualified_name())?
== oid
{
return Ok(Some(ResolvedTablePrivilegeTarget::Table(relation.clone())));
}
}
for (relation, view) in self.registry.views().iter() {
if view_relation_oid(view) == oid {
return Ok(Some(ResolvedTablePrivilegeTarget::View(relation.clone())));
}
}
for (relation, table) in self.registry.foreign_tables().iter() {
if foreign_table_relation_oid(table) == oid {
return Ok(Some(ResolvedTablePrivilegeTarget::ForeignTable(
relation.clone(),
)));
}
}
if let Some((_name, relation)) = self.sequences.resolution.sequence_privilege_oid(oid)? {
return Ok(Some(ResolvedTablePrivilegeTarget::Sequence(relation)));
}
if let Some((name, kind)) = resolve_regclass_kind_by_oid(&self.catalog, oid)? {
return Err(SQLError::Unsupported(format!(
"has_table_privilege for {kind} `{name}` is not supported"
)));
}
Ok(None)
}
}
fn disappeared(kind: &str, relation: &RelationIdentity) -> SQLError {
SQLError::Internal(format!(
"{kind} `{}` disappeared",
relation.qualified_name()
))
}
fn missing_foreign_table_security(relation: &RelationIdentity) -> SQLError {
SQLError::Internal(format!(
"foreign table `{}` has no loaded security metadata",
relation.qualified_name()
))
}