use crate::error::{TViewError, TViewResult};
use pgrx::datum::{DatumWithOid, FromDatum, IntoDatum};
use pgrx::pg_sys::Oid;
use pgrx::prelude::*;
use pgrx::spi::SpiHeapTupleData;
pub trait Text: IntoDatum {}
impl Text for &str {}
impl Text for String {}
impl Text for &String {}
impl<T: Text> Text for Option<T> {}
pub trait OidValue: IntoDatum {}
impl OidValue for Oid {}
impl OidValue for Option<Oid> {}
pub trait JsonbValue: IntoDatum {}
impl JsonbValue for pgrx::JsonB {}
impl JsonbValue for Option<pgrx::JsonB> {}
pub fn text<'a, T: Text>(value: T) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn text_array<'a, T: Text>(values: Vec<T>) -> DatumWithOid<'a> {
DatumWithOid::from(values)
}
pub fn text_array_of<'a>(values: &[String]) -> DatumWithOid<'a> {
DatumWithOid::from(values.to_vec())
}
pub fn oid<'a, T: OidValue>(value: T) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn regclass<'a>(relid: Oid) -> DatumWithOid<'a> {
unsafe { DatumWithOid::new(relid, pgrx::pg_sys::REGCLASSOID) }
}
pub fn oid_array<'a>(values: Vec<Oid>) -> DatumWithOid<'a> {
DatumWithOid::from(values)
}
pub fn int8<'a>(value: i64) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn int8_array<'a>(values: Vec<i64>) -> DatumWithOid<'a> {
DatumWithOid::from(values)
}
pub fn int4<'a>(value: i32) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn int2<'a>(value: i16) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn int2_array<'a>(values: Vec<i16>) -> DatumWithOid<'a> {
DatumWithOid::from(values)
}
pub fn boolean<'a>(value: bool) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn jsonb<'a, T: JsonbValue>(value: T) -> DatumWithOid<'a> {
DatumWithOid::from(value)
}
pub fn error(query: &str, e: &impl std::fmt::Display) -> TViewError {
TViewError::SpiError {
query: query.to_string(),
error: e.to_string(),
}
}
pub fn catalog_error(operation: &str, e: &impl std::fmt::Display) -> TViewError {
TViewError::CatalogError {
operation: operation.to_string(),
pg_error: e.to_string(),
}
}
pub fn strings(sql: &str, args: &[DatumWithOid<'_>]) -> TViewResult<Vec<String>> {
Ok(rows(sql, args, |row| Ok(row.get::<String>(1)?))?
.into_iter()
.flatten()
.collect())
}
pub fn oids(sql: &str, args: &[DatumWithOid<'_>]) -> TViewResult<Vec<Oid>> {
Ok(rows(sql, args, |row| Ok(row.get::<Oid>(1)?))?
.into_iter()
.flatten()
.collect())
}
pub fn one<T: FromDatum + IntoDatum>(
sql: &str,
args: &[DatumWithOid<'_>],
) -> TViewResult<Option<T>> {
match Spi::get_one_with_args::<T>(sql, args) {
Ok(value) => Ok(value),
Err(pgrx::spi::Error::InvalidPosition) => Ok(None),
Err(e) => Err(error(sql, &e)),
}
}
pub fn run(sql: &str, args: &[DatumWithOid<'_>]) -> TViewResult<()> {
Spi::run_with_args(sql, args).map_err(|e| error(sql, &e))
}
pub fn rows<T>(
sql: &str,
args: &[DatumWithOid<'_>],
mut read: impl FnMut(&SpiHeapTupleData<'_>) -> TViewResult<T>,
) -> TViewResult<Vec<T>> {
Spi::connect(|client| {
let mut out = Vec::new();
for row in client.select(sql, None, args).map_err(|e| error(sql, &e))? {
out.push(read(&row)?);
}
Ok(out)
})
}
pub fn run_ddl(sql: &str) -> TViewResult<()> {
super::spi_run_ddl(sql).map_err(|e| error(sql, &e))
}
pub type TextRow = Vec<Option<String>>;
pub fn latest_rows_connected(
sql: &str,
args: &[DatumWithOid<'_>],
keep: bool,
) -> TViewResult<Vec<TextRow>> {
let (mut types, mut values, nulls) = bind(args);
let kept = if keep {
Some(kept_plan(sql, &mut types)?)
} else {
None
};
unsafe {
let plan = if let Some(plan) = &kept {
plan.0.as_ptr()
} else {
let src = std::ffi::CString::new(sql).map_err(|e| error(sql, &e))?;
let nargs = i32::try_from(types.len()).map_err(|e| error(sql, &e))?;
let plan = pg_sys::SPI_prepare(src.as_ptr(), nargs, types.as_mut_ptr());
if plan.is_null() {
return Err(error(
sql,
&format!("SPI_prepare failed ({})", { pg_sys::SPI_result }),
));
}
plan
};
let rc = pg_sys::SPI_execute_snapshot(
plan,
values.as_mut_ptr(),
nulls.as_ptr(),
pg_sys::GetLatestSnapshot(),
std::ptr::null_mut(),
true,
false,
0,
);
let rows = tuptable_rows(sql, rc);
if kept.is_none() {
pg_sys::SPI_freeplan(plan);
}
rows
}
}
pub struct KeptPlan(std::ptr::NonNull<pg_sys::_SPI_plan>);
impl Drop for KeptPlan {
fn drop(&mut self) {
unsafe {
pg_sys::SPI_freeplan(self.0.as_ptr());
}
}
}
pub fn kept_rows(sql: &str, args: &[DatumWithOid<'_>]) -> TViewResult<Vec<TextRow>> {
let (mut types, mut values, nulls) = bind(args);
Spi::connect(|_| {
let plan = kept_plan(sql, &mut types)?;
unsafe {
let rc = pg_sys::SPI_execute_plan(
plan.0.as_ptr(),
values.as_mut_ptr(),
nulls.as_ptr(),
false,
0,
);
tuptable_rows(sql, rc)
}
})
}
fn kept_plan(sql: &str, types: &mut [Oid]) -> TViewResult<std::rc::Rc<KeptPlan>> {
if let Some(plan) = crate::cache::PLANS.with(|m| m.get(&sql.to_string())) {
return Ok(plan);
}
let src = std::ffi::CString::new(sql).map_err(|e| error(sql, &e))?;
let nargs = i32::try_from(types.len()).map_err(|e| error(sql, &e))?;
let plan = unsafe {
let plan = pg_sys::SPI_prepare_cursor(
src.as_ptr(),
nargs,
types.as_mut_ptr(),
pg_sys::CURSOR_OPT_GENERIC_PLAN.cast_signed(),
);
let plan = std::ptr::NonNull::new(plan).ok_or_else(|| {
error(
sql,
&format!("SPI_prepare failed ({})", { pg_sys::SPI_result }),
)
})?;
pg_sys::SPI_keepplan(plan.as_ptr());
std::rc::Rc::new(KeptPlan(plan))
};
crate::cache::PLANS.with(|m| m.insert(sql.to_string(), plan.clone()));
Ok(plan)
}
fn bind(args: &[DatumWithOid<'_>]) -> (Vec<Oid>, Vec<pg_sys::Datum>, Vec<std::ffi::c_char>) {
let types = args.iter().map(DatumWithOid::oid).collect();
let values = args
.iter()
.map(|a| {
a.datum()
.map_or_else(|| pg_sys::Datum::from(0), pgrx::datum::Datum::sans_lifetime)
})
.collect();
let nulls = args
.iter()
.map(|a| if a.datum().is_some() { b' ' } else { b'n' }.cast_signed())
.collect();
(types, values, nulls)
}
unsafe fn tuptable_rows(sql: &str, rc: i32) -> TViewResult<Vec<TextRow>> {
if rc != pg_sys::SPI_OK_SELECT.cast_signed() {
return Err(error(sql, &format!("SPI returned {rc}")));
}
unsafe {
let table = pg_sys::SPI_tuptable;
let tupdesc = (*table).tupdesc;
let mut rows = Vec::new();
for i in 0..usize::try_from(pg_sys::SPI_processed).unwrap_or(0) {
let tuple = *(*table).vals.add(i);
let mut row = Vec::new();
for column in 1..=(*tupdesc).natts {
let value = pg_sys::SPI_getvalue(tuple, tupdesc, column);
row.push(if value.is_null() {
None
} else {
let text = std::ffi::CStr::from_ptr(value)
.to_string_lossy()
.into_owned();
pg_sys::pfree(value.cast());
Some(text)
});
}
rows.push(row);
}
pg_sys::SPI_freetuptable(table);
Ok(rows)
}
}