pub mod aggregate;
pub mod create;
pub mod drop;
pub(crate) mod privileges;
pub mod rename;
pub mod replace;
pub(crate) mod uncascaded;
pub use drop::drop_tview;
use crate::error::{TViewError, TViewResult};
use pgrx::prelude::*;
pub(crate) fn backing_view_name(schema: &str, table: &str) -> (String, String) {
(
crate::utils::ext_schema().to_string(),
crate::utils::fit_identifier(format!("{schema}__{table}")),
)
}
pub(crate) fn follow_table_move(table: pg_sys::Oid) -> TViewResult<()> {
let Some(view) = crate::catalog::row::view_of_table(table)? else {
return Ok(());
};
let (schema, name) = relation_name(table)?;
let (view_schema, wanted) = backing_view_name(&schema, &name);
let (_, current) = relation_name(view)?;
if current == wanted {
return Ok(());
}
let qualified = format!(
"{}.{}",
crate::utils::ident::quoted(&view_schema),
crate::utils::ident::quoted(&wanted)
);
let taken = Spi::get_one_with_args::<bool>(
"SELECT pg_catalog.to_regclass($1) IS NOT NULL",
&[crate::utils::spi::text(qualified.as_str())],
)
.map_err(|e| TViewError::CatalogError {
operation: format!("Check {view_schema}.{wanted}"),
pg_error: e.to_string(),
})?
.unwrap_or(false);
if taken {
return Err(TViewError::InvalidInput {
parameter: "table name".to_string(),
reason: format!(
"the backing view of {schema}.{name} would be {view_schema}.{wanted}, which is \
already taken by another relation"
),
});
}
let sql = format!(
"ALTER VIEW {} RENAME TO {}",
crate::utils::qualified_relname_from_oid(view)?,
crate::utils::ident::quoted(&wanted)
);
{
let _owner = crate::owner::AsOwner::of_table(view)?;
crate::utils::spi::run_ddl(&sql)?;
}
crate::cache::invalidate_all();
unsafe { pg_sys::CacheInvalidateRelcacheByRelid(table) };
Ok(())
}
pub(crate) fn in_extension_schema<T>(ddl: impl FnOnce() -> TViewResult<T>) -> TViewResult<T> {
in_extension_schema_for(unsafe { pg_sys::GetUserId() }, ddl)
}
pub(crate) fn in_extension_schema_for<T>(
role: pg_sys::Oid,
ddl: impl FnOnce() -> TViewResult<T>,
) -> TViewResult<T> {
let schema = crate::utils::ext_schema();
let args = [
crate::utils::spi::oid(role),
crate::utils::spi::text(schema),
];
let (can_create, name) = Spi::get_two_with_args::<bool, String>(
"SELECT pg_catalog.has_schema_privilege($1, $2, 'CREATE'), \
pg_catalog.quote_ident(pg_catalog.pg_get_userbyid($1))",
&args,
)
.map_err(|e| TViewError::CatalogError {
operation: format!("Check CREATE on schema {schema}"),
pg_error: e.to_string(),
})?;
if can_create.unwrap_or(false) {
return ddl();
}
let role = name.unwrap_or_default();
let privilege = |verb: &str| -> TViewResult<()> {
let sql = if verb == "GRANT" {
format!("GRANT CREATE ON SCHEMA {schema} TO {role}")
} else {
format!("REVOKE CREATE ON SCHEMA {schema} FROM {role}")
};
let _owner = crate::owner::AsOwner::of_extension()?;
crate::utils::spi::run_ddl(&sql)
};
privilege("GRANT")?;
let result = ddl()?;
privilege("REVOKE")?;
Ok(result)
}
pub(crate) fn relation_name(oid: pg_sys::Oid) -> TViewResult<(String, String)> {
let args = [crate::utils::spi::oid(oid)];
let (name, schema) = Spi::get_two_with_args::<String, String>(
"SELECT c.relname::text, n.nspname::text FROM pg_catalog.pg_class c \
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = $1",
&args,
)
.map_err(|e| TViewError::CatalogError {
operation: format!("Name relation {oid:?}"),
pg_error: e.to_string(),
})?;
match (schema, name) {
(Some(schema), Some(name)) => Ok((schema, name)),
_ => Err(TViewError::CatalogError {
operation: format!("Name relation {oid:?}"),
pg_error: "relation not found".to_string(),
}),
}
}
const REGISTRATION_LOCK_CLASS: i32 = 0x7476_6965;
pub(crate) fn lock_entity(entity: &str) -> TViewResult<()> {
Spi::run_with_args(
"SELECT pg_catalog.pg_advisory_xact_lock($1, pg_catalog.hashtext($2))",
&[
crate::utils::spi::int4(REGISTRATION_LOCK_CLASS),
crate::utils::spi::text(entity),
],
)
.map_err(|e| TViewError::CatalogError {
operation: format!("Lock the registration of TVIEW {entity}"),
pg_error: e.to_string(),
})
}