use super::{
security::{table_binding::BoundTableAclEntry, BoundTableSecurity, TablePrivileges},
VirtualRelation,
};
mod columns;
macro_rules! system_relations {
($($variant:ident => ($schema:literal, $name:literal, $oid:literal, $kind:literal)),* $(,)?) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SystemRelation {
Projected(VirtualRelation),
$($variant),*
}
impl SystemRelation {
pub fn all() -> impl Iterator<Item = Self> {
VirtualRelation::ALL.iter().copied().map(Self::Projected)
.chain([$(Self::$variant),*])
}
pub const fn namespace(self) -> &'static str {
match self {
Self::Projected(relation) => relation.namespace(),
$(Self::$variant => $schema),*
}
}
pub const fn name(self) -> &'static str {
match self {
Self::Projected(relation) => relation.name(),
$(Self::$variant => $name),*
}
}
pub fn oid(self) -> i64 {
match self {
Self::Projected(relation) => relation.oid(),
$(Self::$variant => $oid),*
}
}
pub const fn kind(self) -> &'static str {
match self {
Self::Projected(relation) => relation.kind(),
$(Self::$variant => $kind),*
}
}
pub fn at(schema: &str, name: &str) -> Option<Self> {
VirtualRelation::at(schema, name).map(Self::Projected).or_else(|| {
match (schema, name) {
$(($schema, $name) => Some(Self::$variant),)*
_ => None,
}
})
}
}
};
}
system_relations! {
PgDbRoleSetting => ("pg_catalog", "pg_db_role_setting", 2964, "table"),
PgShadow => ("pg_catalog", "pg_shadow", 12005, "view"),
PgTablespace => ("pg_catalog", "pg_tablespace", 1213, "table"),
PgCollation => ("pg_catalog", "pg_collation", 3456, "table"),
PgDepend => ("pg_catalog", "pg_depend", 2608, "table"),
PgSequence => ("pg_catalog", "pg_sequence", 2224, "table"),
PgLanguage => ("pg_catalog", "pg_language", 2612, "table"),
InformationEnabledRoles => ("information_schema", "enabled_roles", 13410, "view"),
}
impl SystemRelation {
pub const fn tracks_serializable_reads(self) -> bool {
matches!(
self,
Self::Projected(VirtualRelation::AgGraph | VirtualRelation::AgLabel)
)
}
pub fn qualified_name(self) -> String {
format!("{}.{}", self.namespace(), self.name())
}
pub fn from_qualified_name(name: &str) -> Option<Self> {
let (schema, local) = uqa_core::RelationIdentity::parse_reference(name).ok()?;
Self::at(schema.as_deref()?, &local)
}
pub fn object_id(self) -> [u8; 16] {
let mut identity = [0; 16];
identity[..8].copy_from_slice(b"UQA:cat:");
identity[8..].copy_from_slice(&self.oid().to_be_bytes());
identity
}
pub fn bootstrap_security(self) -> BoundTableSecurity {
let mut security = BoundTableSecurity::owner(super::roles::RoleIdentity::BOOTSTRAP);
let mut acl = vec![BoundTableAclEntry {
role: Some(security.role_owner),
grantor: security.role_owner,
privileges: TablePrivileges::ALL,
grant_options: TablePrivileges::default(),
}];
if !matches!(
self,
Self::PgShadow
| Self::Projected(
VirtualRelation::PgAuthid | VirtualRelation::AgGraph | VirtualRelation::AgLabel
)
) {
acl.push(BoundTableAclEntry {
role: None,
grantor: security.role_owner,
privileges: TablePrivileges {
select: true,
update: self == Self::Projected(VirtualRelation::PgSettings),
..TablePrivileges::default()
},
grant_options: TablePrivileges::default(),
});
}
security.acl = Some(acl);
security
}
pub const fn view_sources(self) -> &'static [Self] {
use SystemRelation::Projected as P;
use VirtualRelation::{
InformationColumnPrivileges, PgAttrdef, PgAttribute, PgClass, PgConstraint, PgIndex,
PgNamespace, PgProc, PgRewrite, PgTrigger, PgType,
};
match self {
P(VirtualRelation::InformationSchemata) => {
&[P(PgNamespace), Self::Projected(VirtualRelation::PgAuthid)]
}
P(VirtualRelation::InformationTables) => {
&[P(PgNamespace), P(PgClass), P(PgType), P(PgNamespace)]
}
P(VirtualRelation::InformationColumns) => &[
P(PgAttribute),
P(PgAttrdef),
P(PgClass),
P(PgNamespace),
P(PgType),
P(PgNamespace),
P(PgType),
P(PgNamespace),
Self::PgCollation,
P(PgNamespace),
Self::PgDepend,
Self::PgSequence,
],
P(InformationColumnPrivileges) => &[
P(PgNamespace),
Self::Projected(VirtualRelation::PgAuthid),
P(PgAttribute),
P(PgClass),
P(PgClass),
P(PgAttribute),
P(PgClass),
Self::Projected(VirtualRelation::PgAuthid),
],
P(VirtualRelation::InformationRoleColumnGrants) => &[
P(InformationColumnPrivileges),
Self::InformationEnabledRoles,
Self::InformationEnabledRoles,
],
P(VirtualRelation::InformationViews) => &[
P(PgNamespace),
P(PgClass),
P(PgTrigger),
P(PgTrigger),
P(PgTrigger),
],
P(VirtualRelation::InformationRoutines) => &[
P(PgNamespace),
P(PgProc),
Self::PgLanguage,
P(PgType),
P(PgNamespace),
],
P(VirtualRelation::InformationSequences) => {
&[P(PgNamespace), P(PgClass), Self::PgSequence, Self::PgDepend]
}
P(VirtualRelation::InformationTableConstraints) => &[
P(PgNamespace),
P(PgNamespace),
P(PgConstraint),
P(PgClass),
P(PgIndex),
],
P(VirtualRelation::InformationKeyColumnUsage) => &[
P(PgAttribute),
P(PgNamespace),
P(PgClass),
P(PgNamespace),
P(PgConstraint),
],
P(VirtualRelation::PgRules) => &[P(PgRewrite), P(PgClass), P(PgNamespace)],
P(VirtualRelation::PgTables | VirtualRelation::PgMatviews) => {
&[P(PgClass), P(PgNamespace), Self::PgTablespace]
}
P(VirtualRelation::PgViews) => &[P(PgClass), P(PgNamespace)],
P(VirtualRelation::PgIndexes) => &[
P(PgIndex),
P(PgClass),
P(PgClass),
P(PgNamespace),
Self::PgTablespace,
],
P(VirtualRelation::PgRoles) | Self::PgShadow => &[
Self::Projected(VirtualRelation::PgAuthid),
Self::PgDbRoleSetting,
],
P(VirtualRelation::PgUser) => &[Self::PgShadow],
P(VirtualRelation::PgSequences) => &[Self::PgSequence, P(PgClass), P(PgNamespace)],
Self::InformationEnabledRoles => &[Self::Projected(VirtualRelation::PgAuthid)],
_ => &[],
}
}
}
#[cfg(test)]
mod tests;