use rupg_pgcatalog::builtin::{self, TypeRow};
use rupg_types::oid;
use rupg_types::typmod::VARHDRSZ;
pub const STRING: u8 = b'S';
pub const INVALID: u8 = 0;
const ARRAY_SUBSCRIPT_HANDLER: u32 = 6179;
pub fn row(ty: u32) -> Option<&'static TypeRow> {
builtin::type_by_oid(ty)
}
pub fn is_complex(ty: u32) -> bool {
row(ty).is_some_and(|t| t.kind == b'c')
}
pub fn category_preferred(ty: u32) -> (u8, bool) {
row(ty).map_or((INVALID, false), |t| (t.category, t.preferred))
}
pub fn category(ty: u32) -> u8 {
category_preferred(ty).0
}
pub fn is_preferred(category: u8, ty: u32) -> bool {
let (own, preferred) = category_preferred(ty);
(category == own || category == INVALID) && preferred
}
pub fn base(ty: u32) -> u32 {
let mut ty = ty;
while let Some(t) = row(ty) {
if t.kind != b'd' || t.base == 0 {
break;
}
ty = t.base;
}
ty
}
pub fn base_typmod(ty: u32, typmod: i32) -> (u32, i32) {
let (mut ty, mut typmod) = (ty, typmod);
while let Some(t) = row(ty) {
if t.kind != b'd' || t.base == 0 {
break;
}
(ty, typmod) = (t.base, t.typmod);
}
(ty, typmod)
}
pub fn element(ty: u32) -> u32 {
row(ty).map_or(0, |t| {
if t.elem != 0 && t.subscript == ARRAY_SUBSCRIPT_HANDLER { t.elem } else { 0 }
})
}
pub fn array_of(ty: u32) -> u32 {
row(ty).map_or(0, |t| t.array)
}
pub fn range_subtype(ty: u32) -> u32 {
builtin::range_subtype(ty).unwrap_or(0)
}
pub fn range_multirange(ty: u32) -> u32 {
builtin::range_multirange(ty).unwrap_or(0)
}
pub fn multirange_range(ty: u32) -> u32 {
builtin::multirange_range(ty).unwrap_or(0)
}
pub fn collatable(ty: u32) -> bool {
row(base(ty)).is_some_and(|t| t.collation != 0)
}
pub fn is_polymorphic(ty: u32) -> bool {
matches!(
ty,
oid::ANYELEMENT
| oid::ANYARRAY
| oid::ANYNONARRAY
| oid::ANYENUM
| oid::ANYRANGE
| oid::ANYMULTIRANGE
| oid::ANYCOMPATIBLE
| oid::ANYCOMPATIBLEARRAY
| oid::ANYCOMPATIBLENONARRAY
| oid::ANYCOMPATIBLERANGE
| oid::ANYCOMPATIBLEMULTIRANGE
)
}
pub fn name(ty: u32) -> String {
let name = rupg_types::format_type(ty);
if name != "???" {
return name.into_owned();
}
match row(ty) {
Some(t) if t.category == b'A' && t.elem != 0 => format!("{}[]", self::name(t.elem)),
Some(t) => t.name.to_owned(),
None => name.into_owned(),
}
}
pub(crate) fn with_typmod(ty: u32, typmod: i32) -> String {
let name = name(ty);
if typmod < VARHDRSZ {
return name;
}
match ty {
oid::VARCHAR | oid::BPCHAR => format!("{name}({})", typmod - VARHDRSZ),
oid::NUMERIC => {
let bits = typmod - VARHDRSZ;
let scale = ((bits & 0x7ff) ^ 1024) - 1024;
format!("numeric({},{scale})", (bits >> 16) & 0xffff)
}
_ => name,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn facts() {
assert_eq!(category_preferred(oid::FLOAT8), (b'N', true));
assert_eq!(category(oid::UNKNOWN), b'X');
assert!(is_preferred(b'S', oid::TEXT) && !is_preferred(b'S', oid::VARCHAR));
assert!(is_preferred(INVALID, oid::TEXT) && !is_preferred(b'N', oid::TEXT));
assert_eq!(
(element(oid::INT4_ARRAY), element(oid::OIDVECTOR), element(oid::NAME)),
(23, 26, 0)
);
assert_eq!((array_of(oid::INT4), base(oid::INT4)), (oid::INT4_ARRAY, oid::INT4));
assert!(collatable(oid::NAME) && !collatable(oid::INT4));
assert_eq!(name(oid::INT4_ARRAY), "integer[]");
}
}