use crate::config::UncascadedPolicy;
use crate::error::TViewResult;
use pgrx::pg_sys::{self, Oid};
use pgrx::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct UncascadedTable {
pub oid: Oid,
pub name: String,
pub reason: String,
}
#[derive(Debug, Clone)]
pub(crate) struct Uncascaded {
pub tables: Vec<UncascadedTable>,
pub policy: UncascadedPolicy,
}
impl Uncascaded {
pub(crate) fn oids(&self) -> Vec<Oid> {
self.tables.iter().map(|t| t.oid).collect()
}
}
fn describe(tview: &str, tables: &[UncascadedTable], verb: &str) -> String {
let names = tables
.iter()
.map(|t| t.name.as_str())
.collect::<Vec<_>>()
.join(", ");
let reasons = match tables {
[one] => one.reason.clone(),
_ => tables
.iter()
.map(|t| format!("{}: {}", t.name, t.reason))
.collect::<Vec<_>>()
.join("; "),
};
format!("writes to {names} {verb} {tview} ({reasons})")
}
fn how_to_declare(tview: &str, policy: &str) -> String {
format!(
"pg_tviews_create_or_replace('{tview}', <definition>, options => \
'{{\"uncascaded_policy\": \"{policy}\"}}'); before CREATE TABLE … AS or \
pg_tviews_create(): SET pg_tviews.uncascaded_policy = '{policy}'"
)
}
pub(crate) fn report(
tview: &str,
tables: &[UncascadedTable],
policy: UncascadedPolicy,
) -> TViewResult<()> {
if tables.is_empty() {
return Ok(());
}
match policy {
UncascadedPolicy::Warn => {
pg_sys::panic::ErrorReport::new(
PgSqlErrorCode::ERRCODE_WARNING,
describe(tview, tables, "will not refresh"),
function_name!(),
)
.set_hint(format!(
"To refresh it in full on such writes instead: {}.",
how_to_declare(tview, "full_refresh")
))
.report(PgLogLevel::WARNING);
Ok(())
}
UncascadedPolicy::FullRefresh => {
notice!("{}", describe(tview, tables, "will refresh all rows of"));
Ok(())
}
UncascadedPolicy::Error => {
pg_sys::panic::ErrorReport::new(
PgSqlErrorCode::ERRCODE_INVALID_PARAMETER_VALUE,
format!(
"{}: declare what such a write does with the TVIEW's uncascaded_policy",
describe(tview, tables, "would not refresh")
),
function_name!(),
)
.set_hint(format!(
"To refresh {tview} in full on such writes: {}. \"warn\" accepts stale \
rows instead. Or join the tables on a column pg_tviews can trace.",
how_to_declare(tview, "full_refresh")
))
.report(PgLogLevel::ERROR);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn describe_lists_tables_and_reasons() {
let t = |n: &str, r: &str| UncascadedTable {
oid: Oid::INVALID,
name: n.to_string(),
reason: r.to_string(),
};
assert_eq!(
describe("public.tv_o", &[t("public.a", "x")], "will not refresh"),
"writes to public.a will not refresh public.tv_o (x)"
);
assert_eq!(
describe(
"public.tv_o",
&[t("public.a", "x"), t("public.b", "y")],
"will not refresh"
),
"writes to public.a, public.b will not refresh public.tv_o (public.a: x; public.b: y)"
);
}
}