use rupg_common::{Error, Result, SqlState};
use rupg_pgcatalog::builtin;
use rupg_types::{Array, Value};
use crate::io::{base_type, check_safe_enum_use, is_enum, type_name};
use crate::{Call, Kernel, bad_value};
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
Some(match src {
"enum_first" => enum_first,
"enum_last" => enum_last,
"enum_range_all" => enum_range_all,
"enum_range_bounds" => enum_range_bounds,
_ => return None,
})
}
fn labels(call: &Call<'_>) -> Result<(u32, &'static [(u32, String)])> {
let ty = call.args.first().map_or(0, |&ty| base_type(ty));
match builtin::enum_labels(ty) {
Some(labels) if is_enum(ty) => Ok((ty, labels)),
_ => {
Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "could not determine actual enum type"))
}
}
}
fn endpoint(call: &Call<'_>, last: bool) -> Result<Value> {
let (ty, labels) = labels(call)?;
let label = if last { labels.last() } else { labels.first() };
match label {
Some((oid, name)) => {
check_safe_enum_use(ty, *oid, name)?;
Ok(Value::Oid(*oid))
}
None => Err(Error::new(
SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE,
format!("enum {} contains no values", type_name(ty)),
)),
}
}
fn enum_first(call: &Call<'_>, _: &[Value]) -> Result<Value> {
endpoint(call, false)
}
fn enum_last(call: &Call<'_>, _: &[Value]) -> Result<Value> {
endpoint(call, true)
}
fn range(call: &Call<'_>, lower: Option<u32>, upper: Option<u32>) -> Result<Value> {
let (ty, labels) = labels(call)?;
let position =
|bound: u32| labels.iter().position(|(oid, _)| *oid == bound).ok_or_else(bad_value);
let start = lower.map_or(Ok(0), position)?;
let end = upper.map_or(Ok(labels.len()), |bound| position(bound).map(|i| i + 1))?;
let values = labels
.get(start..end.max(start))
.unwrap_or_default()
.iter()
.map(|(oid, name)| check_safe_enum_use(ty, *oid, name).map(|()| Some(Value::Oid(*oid))))
.collect::<Result<_>>()?;
Ok(Value::Array(Box::new(Array::one(values))))
}
fn enum_range_all(call: &Call<'_>, _: &[Value]) -> Result<Value> {
range(call, None, None)
}
fn enum_range_bounds(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let bound = |i: usize| match args.get(i) {
Some(Value::Oid(oid)) => Ok(Some(*oid)),
Some(Value::Null) | None => Ok(None),
Some(_) => Err(bad_value()),
};
range(call, bound(0)?, bound(1)?)
}