use rupg_common::{Error, Result, SqlState};
use rupg_types::{Numeric, NumericSign, Value};
use crate::{Call, Kernel, bad_value, type_error};
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
Some(match src {
"pg_lsn_mi" | "pg_wal_lsn_diff" => lsn_mi,
"pg_lsn_pli" => lsn_pli,
"pg_lsn_mii" => lsn_mii,
"numeric_pg_lsn" => numeric_lsn,
_ => return None,
})
}
fn lsn(args: &[Value], n: usize) -> Result<u64> {
match args.get(n) {
Some(Value::Int8(v)) => Ok(v.cast_unsigned()),
_ => Err(bad_value()),
}
}
fn bytes(args: &[Value]) -> Result<&Numeric> {
match args.get(1) {
Some(Value::Numeric(v)) => Ok(v),
_ => Err(bad_value()),
}
}
fn lsn_mi(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let diff = i128::from(lsn(args, 0)?) - i128::from(lsn(args, 1)?);
Ok(Value::Numeric(Numeric::from_integer(diff)))
}
fn to_lsn(n: &Numeric) -> Result<Value> {
let range = || Error::new(SqlState::INVALID_PARAMETER_VALUE, "pg_lsn out of range".to_string());
match n.to_integer("pg_lsn") {
Ok(v) => u64::try_from(v).map(|v| Value::Int8(v.cast_signed())).map_err(|_| range()),
Err(error) if error.sqlstate == SqlState::NUMERIC_VALUE_OUT_OF_RANGE => Err(range()),
Err(error) => Err(type_error(error)),
}
}
fn numeric_lsn(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let Some(Value::Numeric(n)) = args.first() else { return Err(bad_value()) };
to_lsn(n)
}
fn lsn_pli(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let n = bytes(args)?;
if matches!(n.sign(), NumericSign::NaN) {
return Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
"cannot add NaN to pg_lsn".to_string(),
));
}
let start = Numeric::from_integer(i128::from(lsn(args, 0)?));
to_lsn(&start.add(n).map_err(type_error)?)
}
fn lsn_mii(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let n = bytes(args)?;
if matches!(n.sign(), NumericSign::NaN) {
return Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
"cannot subtract NaN from pg_lsn".to_string(),
));
}
let start = Numeric::from_integer(i128::from(lsn(args, 0)?));
to_lsn(&start.sub(n).map_err(type_error)?)
}