use pgrx::prelude::*;
use crate::catalog::TviewMeta;
use crate::queue::key::KeyValue;
use crate::jsonb_delta::jsonb_delta_schema;
use crate::utils::{qualified_relname_from_oid, quote_identifier};
pub fn refresh_key(meta: &TviewMeta, key: &KeyValue) -> crate::TViewResult<super::Touched> {
let keys = std::slice::from_ref(key);
let before = super::lock_rows(
meta,
&qualified_relname_from_oid(meta.tview_oid)?,
keys,
!meta.identity.is_pk(&meta.entity_name),
)?;
let (written, deleted) = if meta.plan.set_operation && !view_row_exists(meta, key)? {
(super::Written::default(), delete_tview_row(meta, key)?)
} else {
crate::metrics::metrics_api::record_view_recomputes(1);
let (produced, written) = write_row(meta, key)?;
let deleted = if produced == 0 {
delete_tview_row(meta, key)?
} else {
Vec::new()
};
(written, deleted)
};
Ok(super::touched(meta, keys, before, written, deleted))
}
fn delete_tview_row(meta: &TviewMeta, key: &KeyValue) -> crate::TViewResult<Vec<i64>> {
let key_type = meta.key_type()?;
let qi_tv = qualified_relname_from_oid(meta.tview_oid)?;
let qi_key = quote_identifier(&meta.identity.column);
let qi_pk = quote_identifier(&format!("pk_{}", meta.entity_name));
let sql = format!(
"DELETE FROM {qi_tv} WHERE {qi_key} = {} \
RETURNING {qi_pk}::text, to_jsonb({qi_tv}.*)->>'id'",
super::key_cast(&key_type, "$1", false)
);
super::run_journaled_delete(
&meta.entity_name,
&sql,
&[super::key_scalar(&key_type, key)?],
)
}
fn view_row_exists(meta: &TviewMeta, key: &KeyValue) -> crate::TViewResult<bool> {
let key_type = meta.key_type()?;
let qi_view = qualified_relname_from_oid(meta.view_oid)?;
let sql = format!(
"SELECT 1 FROM {qi_view} WHERE {} = {} LIMIT 2",
quote_identifier(&meta.identity.column),
super::key_cast(&key_type, "$1", false)
);
Spi::connect(|client| {
let args = [super::key_scalar(&key_type, key)?];
let mut rows = client.select(&sql, None, &args)?;
if rows.next().is_none() {
return Ok(false);
}
if meta.plan.set_operation && rows.next().is_some() {
union_duplicate(meta, &key.to_string());
}
Ok(true)
})
}
pub(super) fn union_duplicate(meta: &TviewMeta, key: &str) {
if crate::config::union_duplicate_policy() == "first" {
crate::utils::log_once(
&format!("union_duplicate:{}", meta.entity_name),
&format!(
"TVIEW '{}': UNION ALL backing view returned multiple rows for {}={key}; \
taking the first row (union_duplicate_policy=first). Reported once \
per backend.",
meta.entity_name, meta.identity.column
),
);
return;
}
pgrx::pg_sys::panic::ErrorReport::new(
PgSqlErrorCode::ERRCODE_CARDINALITY_VIOLATION,
format!(
"TVIEW '{}': UNION ALL backing view returned multiple rows for {}={key}",
meta.entity_name, meta.identity.column
),
function_name!(),
)
.set_hint(
"Make the UNION branches' keys disjoint (a sign or an offset per branch), or set \
pg_tviews.union_duplicate_policy = 'first' to keep the first row.",
)
.report(PgLogLevel::ERROR);
}
fn write_row(meta: &TviewMeta, key: &KeyValue) -> crate::TViewResult<(i64, super::Written)> {
if jsonb_delta_schema().is_none() {
crate::utils::log_once(
crate::jsonb_delta::JSONB_DELTA_MISSING,
"jsonb_delta is not installed: writes recompute TVIEW rows instead of patching \
them in place. CREATE EXTENSION jsonb_delta to enable the direct and fan-out \
patches.",
);
}
let key_type = meta.key_type()?;
let qi_tv = qualified_relname_from_oid(meta.tview_oid)?;
let key_col = &meta.identity.column;
let qi_key = quote_identifier(key_col);
let qi_view = qualified_relname_from_oid(meta.view_oid)?;
let col_names = crate::utils::get_view_columns_by_oid(meta.view_oid)?;
let col_list = super::column_list(&col_names);
super::run_counted_upsert(
&meta.entity_name,
&qi_tv,
&col_list,
&format!(
"SELECT {col_list} FROM {qi_view} WHERE {qi_key} = {}",
super::key_cast(&key_type, "$1", false)
),
&format!(
"ON CONFLICT ({qi_key}) {}",
super::upsert_conflict_action(&qi_tv, &col_names, key_col)
),
&[super::key_scalar(&key_type, key)?],
)
}