use super::Policy;
use crate::TViewResult;
use crate::catalog::TviewMeta;
use crate::queue::key::KeyValue;
use crate::utils::quote_identifier;
use pgrx::datum::DatumWithOid;
use pgrx::prelude::*;
#[must_use]
pub fn enabled() -> bool {
Policy::current() == Policy::FailFast
}
pub fn refreshed_rows(meta: &TviewMeta, keys: &[KeyValue]) -> TViewResult<()> {
if keys.is_empty() || !enabled() || meta.time_dependent {
return Ok(());
}
let columns = crate::utils::get_view_columns_by_oid(meta.view_oid)?
.iter()
.map(|c| format!("v.{}", quote_identifier(c)))
.collect::<Vec<_>>()
.join(", ");
let key_type = meta.key_type()?;
let key = quote_identifier(&meta.identity.column);
let view = crate::utils::qualified_relname_from_oid(meta.view_oid)?;
let sql = format!(
"SELECT v.{key}::pg_catalog.text, ROW({columns})::pg_catalog.text FROM {view} v \
WHERE v.{key} OPERATOR(pg_catalog.=) ANY ({})",
crate::refresh::key_cast(&key_type, "$1", true)
);
let args = [crate::refresh::key_array(&key_type, keys)?];
let seen = sorted(crate::utils::spi::kept_rows(&sql, &args)?);
let latest = sorted(Spi::connect(|_| {
crate::utils::spi::latest_rows_connected(&sql, &args, true)
})?);
if seen != latest {
super::serialization_failure(
"a TVIEW row this transaction refreshed changed in a concurrent transaction",
);
}
Ok(())
}
pub use crate::utils::spi::TextRow;
pub fn discovered(found: &[TextRow], latest: &[TextRow]) {
if missed(found, latest) {
super::serialization_failure("a concurrent transaction added rows this write must refresh");
}
}
pub fn discovered_by(sql: &str, args: &[DatumWithOid<'_>], found: &[TextRow]) -> TViewResult<()> {
if !enabled() {
return Ok(());
}
let latest = Spi::connect(|_| crate::utils::spi::latest_rows_connected(sql, args, true))?;
discovered(found, &latest);
Ok(())
}
fn missed(found: &[TextRow], latest: &[TextRow]) -> bool {
latest
.iter()
.filter(|row| row.first().is_some_and(Option::is_some))
.any(|row| !found.contains(row))
}
fn sorted(mut rows: Vec<Vec<Option<String>>>) -> Vec<Vec<Option<String>>> {
rows.sort();
rows
}
#[cfg(test)]
mod tests {
use super::missed;
fn row(cells: &[&str]) -> Vec<Option<String>> {
cells.iter().map(|c| Some((*c).to_string())).collect()
}
#[test]
fn a_row_only_the_latest_snapshot_sees_is_missed() {
let found = vec![row(&["1", "10"])];
assert!(!missed(&found, &[row(&["1", "10"])]));
assert!(missed(&found, &[row(&["1", "10"]), row(&["1", "11"])]));
assert!(!missed(&found, &[]));
}
#[test]
fn a_null_key_names_no_row() {
assert!(!missed(&[], &[vec![None, Some("x".into())]]));
}
}