use crate::error::{TViewError, TViewResult};
use pgrx::datum::DatumWithOid;
use pgrx::prelude::*;
const SELECT_GRANT_CHANGES: &str = "\
WITH tview AS ( \
SELECT m.table_oid::pg_catalog.oid AS tab, v.oid AS view, v.relowner AS owner, \
pg_catalog.format('%I.%I', n.nspname, v.relname) AS name \
FROM {meta} m JOIN pg_catalog.pg_class v ON v.oid = m.view_oid::pg_catalog.oid \
JOIN pg_catalog.pg_namespace n ON n.oid = v.relnamespace \
WHERE $1 IS NULL OR m.table_oid::pg_catalog.oid = $1 \
), wanted AS ( \
SELECT t.view, t.name, a.grantee FROM tview t JOIN pg_catalog.pg_class c ON c.oid = t.tab, \
pg_catalog.aclexplode(c.relacl) a \
WHERE a.privilege_type = 'SELECT' AND a.grantee <> t.owner \
), held AS ( \
SELECT t.view, t.name, a.grantee FROM tview t JOIN pg_catalog.pg_class c ON c.oid = t.view, \
pg_catalog.aclexplode(c.relacl) a \
WHERE a.privilege_type = 'SELECT' AND a.grantee <> t.owner \
), changes AS ( \
SELECT view, name, grantee, true AS adds FROM (TABLE wanted EXCEPT TABLE held) g \
UNION ALL \
SELECT view, name, grantee, false FROM (TABLE held EXCEPT TABLE wanted) r \
) \
SELECT view, pg_catalog.format(CASE WHEN adds THEN 'GRANT SELECT ON %s TO %s' \
ELSE 'REVOKE SELECT ON %s FROM %s CASCADE' END, \
name, \
pg_catalog.string_agg(CASE WHEN grantee = 0 THEN 'PUBLIC' \
ELSE pg_catalog.quote_ident(pg_catalog.pg_get_userbyid(grantee)) END, \
', ' ORDER BY grantee)) \
FROM changes GROUP BY view, name, adds ORDER BY view, adds";
pub(crate) fn follow(table: Option<pg_sys::Oid>, owners: bool) -> TViewResult<()> {
let _internal = crate::hooks::InternalDdl::begin();
unsafe { pg_sys::CommandCounterIncrement() };
if owners {
follow_owners(table)?;
}
follow_grants(table)
}
fn follow_grants(table: Option<pg_sys::Oid>) -> TViewResult<()> {
let args = [unsafe { DatumWithOid::new(table, PgOid::BuiltIn(PgBuiltInOids::OIDOID).value()) }];
let changes = Spi::connect_mut(|client| {
client
.update(
&SELECT_GRANT_CHANGES.replace("{meta}", &crate::utils::meta_table()),
None,
&args,
)?
.map(|row| Ok((row.get::<pg_sys::Oid>(1)?, row.get::<String>(2)?)))
.collect::<Result<Vec<_>, spi::Error>>()
})
.map_err(|e| TViewError::CatalogError {
operation: "Compare backing view grants with their tables'".to_string(),
pg_error: e.to_string(),
})?;
for (view, statement) in changes {
let (Some(view), Some(statement)) = (view, statement) else {
continue;
};
let _owner = crate::owner::AsOwner::of_table(view)?;
crate::utils::spi_run_ddl(&statement).map_err(|error| TViewError::SpiError {
query: statement.clone(),
error,
})?;
}
Ok(())
}
fn follow_owners(table: Option<pg_sys::Oid>) -> TViewResult<()> {
let args = [unsafe { DatumWithOid::new(table, PgOid::BuiltIn(PgBuiltInOids::OIDOID).value()) }];
let moves = Spi::connect_mut(|client| {
client
.update(
&format!(
"SELECT t.relowner, pg_catalog.format('ALTER VIEW %I.%I OWNER TO %I', \
n.nspname, v.relname, pg_catalog.pg_get_userbyid(t.relowner)) \
FROM {} m \
JOIN pg_catalog.pg_class t ON t.oid = m.table_oid::pg_catalog.oid \
JOIN pg_catalog.pg_class v ON v.oid = m.view_oid::pg_catalog.oid \
JOIN pg_catalog.pg_namespace n ON n.oid = v.relnamespace \
WHERE v.relowner <> t.relowner AND ($1 IS NULL OR t.oid = $1) \
ORDER BY v.oid",
crate::utils::meta_table()
),
None,
&args,
)?
.map(|row| Ok((row.get::<pg_sys::Oid>(1)?, row.get::<String>(2)?)))
.collect::<Result<Vec<_>, spi::Error>>()
})
.map_err(|e| TViewError::CatalogError {
operation: "Compare backing view owners with their tables'".to_string(),
pg_error: e.to_string(),
})?;
for (owner, statement) in moves {
let (Some(owner), Some(statement)) = (owner, statement) else {
continue;
};
super::in_extension_schema_for(owner, || {
crate::utils::spi_run_ddl(&statement).map_err(|error| TViewError::SpiError {
query: statement.clone(),
error,
})
})?;
}
Ok(())
}