use core::ffi::CStr;
use core::ptr::NonNull;
pub trait PgNode: crate::seal::Sealed + Sized {
const CAST_TAGS: &'static [crate::NodeTag];
fn try_cast<T: PgNode>(node: &T) -> Option<&Self> {
if Self::CAST_TAGS.contains(&node.node_tag()) {
Some(unsafe { &*core::ptr::from_ref(node).cast() })
} else {
None
}
}
#[inline]
fn as_node(&self) -> &crate::Node {
unsafe { &*core::ptr::from_ref(self).cast() }
}
#[inline]
fn display_node(&self) -> String {
unsafe { display_node_impl(NonNull::from(self).cast()) }
}
#[doc(alias = "IsA")]
#[inline]
fn is_a(&self, tag: crate::NodeTag) -> bool {
self.node_tag() == tag
}
#[inline]
fn node_tag(&self) -> crate::NodeTag {
self.as_node().type_
}
#[inline]
fn try_as<T: PgNode>(&self) -> Option<&T> {
T::try_cast(self)
}
}
#[warn(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn display_node_impl(node: NonNull<crate::Node>) -> String {
unsafe {
let node_cstr = crate::nodeToString(node.as_ptr().cast());
let result = match CStr::from_ptr(node_cstr).to_str() {
Ok(cstr) => cstr.to_string(),
Err(e) => format!("<ffi error: {e:?}>"),
};
crate::pfree(node_cstr.cast());
result
}
}
#[cfg(test)]
mod tests {
use super::PgNode;
#[test]
fn cast_roundtrip() {
let rt = crate::RangeTblRef { type_: crate::NodeTag::T_RangeTblRef, rtindex: 9 };
let node = rt.try_as::<crate::Node>().expect("upcast to Node should succeed");
let next = node.try_as::<crate::RangeTblRef>().expect("downcast to self should succeed");
assert_eq!(next.type_, crate::NodeTag::T_RangeTblRef);
assert_eq!(next.rtindex, 9);
}
#[test]
fn inheritance_downcast() {
let alt_subplan = crate::AlternativeSubPlan {
xpr: crate::Expr { type_: crate::NodeTag::T_AlternativeSubPlan },
subplans: std::ptr::null_mut(),
};
let node = alt_subplan.as_node(); let expr = node.try_as::<crate::Expr>().expect("downcast to parent of self should succeed");
assert_eq!(expr.node_tag(), crate::NodeTag::T_AlternativeSubPlan, "tag remains");
expr.try_as::<crate::AlternativeSubPlan>().expect("downcast to self should succeed");
assert!(node.try_as::<crate::Var>().is_none(), "downcast to an unrelated type should fail");
}
#[test]
fn inheritance_upcast() {
let alt_subplan = crate::AlternativeSubPlan {
xpr: crate::Expr { type_: crate::NodeTag::T_AlternativeSubPlan },
subplans: std::ptr::null_mut(),
};
let expr = alt_subplan.try_as::<crate::Expr>().expect("upcast to a parent should succeed");
assert_eq!(expr.node_tag(), crate::NodeTag::T_AlternativeSubPlan, "tag remains");
let node = expr.try_as::<crate::Node>().expect("upcast to Node should succeed");
assert_eq!(node.node_tag(), crate::NodeTag::T_AlternativeSubPlan, "tag remains");
}
#[cfg(any(feature = "pg13", feature = "pg14"))]
#[test]
fn value_struct_cast() {
let value =
crate::Value { type_: crate::NodeTag::T_Integer, val: crate::ValUnion { ival: 42 } };
let node = value.as_node(); assert_eq!(node.node_tag(), crate::NodeTag::T_Integer, "tag remains");
let next = node.try_as::<crate::Value>().expect("downcast to self should succeed");
assert_eq!(next.node_tag(), crate::NodeTag::T_Integer, "tag remains");
assert_eq!(unsafe { next.val.ival }, 42);
for tag in &[
crate::NodeTag::T_Float,
crate::NodeTag::T_String,
crate::NodeTag::T_BitString,
crate::NodeTag::T_Null,
] {
let value = crate::Value {
type_: *tag,
val: crate::ValUnion { str_: c"something".as_ptr() as *mut _ },
};
let node = value.as_node(); assert_eq!(node.node_tag(), *tag, "tag remains");
let next = node.try_as::<crate::Value>().expect("downcast to self should succeed");
assert_eq!(next.node_tag(), *tag, "tag remains");
assert_eq!(unsafe { next.val.str_ }, c"something".as_ptr() as *mut _);
}
}
#[cfg(any(
feature = "pg15",
feature = "pg16",
feature = "pg17",
feature = "pg18",
feature = "pg19"
))]
#[test]
fn value_union_cast() {
let vu = crate::ValUnion {
sval: crate::String {
type_: crate::NodeTag::T_String,
sval: c"something".as_ptr() as *mut _,
},
};
let node = vu.try_as::<crate::Node>().expect("upcast to Node should succeed");
let next = node.try_as::<crate::String>().expect("downcast to self should succeed");
assert_eq!(next.type_, crate::NodeTag::T_String);
assert_eq!(next.sval, c"something".as_ptr() as *mut _);
assert!(
node.try_as::<crate::ValUnion>().is_none(),
"downcast to untagged union should fail"
);
}
}