use super::{CatalogReadView, RelationNameResolution};
use uqa_sql::catalog::{
roles::identity::RoleSubject,
security::{
schema::{role_has_schema_privilege, SchemaAclPrivilege},
BoundSchemaSecurity,
},
};
pub(super) fn schema_security(
catalog: &CatalogReadView,
temporary_schema: &str,
name: &str,
) -> Option<BoundSchemaSecurity> {
if let Some(security) = catalog.schema_security(name) {
return Some(security.clone());
}
match name {
name if name == temporary_schema => Some(BoundSchemaSecurity::with_public_privileges(true)),
name if catalog.snapshot().definitions.graphs.contains_key(name) => {
Some(BoundSchemaSecurity::bootstrap(name))
}
_ => BoundSchemaSecurity::builtin(name),
}
}
fn usable_namespace(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
role: &(impl RoleSubject + ?Sized),
name: &str,
) -> bool {
if name == resolution.temporary_schema && !resolution.temporary_namespace_allocated {
return false;
}
let Some(security) = schema_security(catalog, &resolution.temporary_schema, name) else {
return false;
};
let definitions = &catalog.snapshot().definitions;
security.resolve(&definitions.roles).is_ok_and(|security| {
role_has_schema_privilege(
&security,
role,
SchemaAclPrivilege::Usage,
&definitions.roles,
&definitions.role_memberships,
)
})
}
fn explicit_schema_names(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
role: &(impl RoleSubject + ?Sized),
) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
for name in resolution.search_path() {
let name = if name == "pg_temp" {
&resolution.temporary_schema
} else {
name
};
if !names.contains(name) && usable_namespace(catalog, resolution, role, name) {
names.push(name.clone());
}
}
names
}
pub fn current_schema_name(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
role: &(impl RoleSubject + ?Sized),
) -> Option<String> {
explicit_schema_names(catalog, resolution, role)
.into_iter()
.next()
}
pub fn current_schema_names(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
role: &(impl RoleSubject + ?Sized),
include_implicit: bool,
) -> Vec<String> {
let explicit = explicit_schema_names(catalog, resolution, role);
let mut out = Vec::new();
if include_implicit {
if resolution.temporary_namespace_allocated
&& !explicit.contains(&resolution.temporary_schema)
{
out.push(resolution.temporary_schema.clone());
}
if !explicit.iter().any(|name| name == "pg_catalog") {
out.push("pg_catalog".to_owned());
}
}
out.extend(explicit);
out
}
#[cfg(test)]
mod tests;