use crate::{
pg_sys, text_to_rust_str_unchecked, varlena_to_byte_slice, AllocatedByPostgres, IntoDatum,
PgBox, PgMemoryContexts,
};
use core::ffi::CStr;
use std::num::NonZeroUsize;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum TryFromDatumError {
#[error("Postgres type {datum_type} {datum_oid} is not compatible with the Rust type {rust_type} {rust_oid}")]
IncompatibleTypes {
rust_type: &'static str,
rust_oid: pg_sys::Oid,
datum_type: String,
datum_oid: pg_sys::Oid,
},
#[error("The specified attribute number `{0}` is not present")]
NoSuchAttributeNumber(NonZeroUsize),
#[error("The specified attribute name `{0}` is not present")]
NoSuchAttributeName(String),
}
pub trait FromDatum {
const GET_TYPOID: bool = false;
unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option<Self>
where
Self: Sized,
{
FromDatum::from_polymorphic_datum(datum, is_null, pg_sys::InvalidOid)
}
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
typoid: pg_sys::Oid,
) -> Option<Self>
where
Self: Sized;
unsafe fn from_datum_in_memory_context(
mut memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
typoid: pg_sys::Oid,
) -> Option<Self>
where
Self: Sized,
{
memory_context.switch_to(|_| FromDatum::from_polymorphic_datum(datum, is_null, typoid))
}
#[inline]
unsafe fn try_from_datum(
datum: pg_sys::Datum,
is_null: bool,
type_oid: pg_sys::Oid,
) -> Result<Option<Self>, TryFromDatumError>
where
Self: Sized + IntoDatum,
{
if !is_binary_coercible::<Self>(type_oid) {
Err(TryFromDatumError::IncompatibleTypes {
rust_type: std::any::type_name::<Self>(),
rust_oid: Self::type_oid(),
datum_type: lookup_type_name(type_oid),
datum_oid: type_oid,
})
} else {
Ok(FromDatum::from_polymorphic_datum(datum, is_null, type_oid))
}
}
#[inline]
unsafe fn try_from_datum_in_memory_context(
memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
type_oid: pg_sys::Oid,
) -> Result<Option<Self>, TryFromDatumError>
where
Self: Sized + IntoDatum,
{
if !is_binary_coercible::<Self>(type_oid) {
Err(TryFromDatumError::IncompatibleTypes {
rust_type: std::any::type_name::<Self>(),
rust_oid: Self::type_oid(),
datum_type: lookup_type_name(type_oid),
datum_oid: type_oid,
})
} else {
Ok(FromDatum::from_datum_in_memory_context(memory_context, datum, is_null, type_oid))
}
}
}
fn is_binary_coercible<T: IntoDatum>(type_oid: pg_sys::Oid) -> bool {
T::is_compatible_with(type_oid) || unsafe { pg_sys::IsBinaryCoercible(type_oid, T::type_oid()) }
}
pub(crate) fn lookup_type_name(oid: pg_sys::Oid) -> String {
unsafe {
let cstr_name = pg_sys::format_type_extended(oid, -1, 0);
let cstr = CStr::from_ptr(cstr_name);
let typname = cstr.to_string_lossy().to_string();
pg_sys::pfree(cstr_name as _); typname
}
}
impl FromDatum for pg_sys::Datum {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<pg_sys::Datum> {
if is_null {
None
} else {
Some(datum)
}
}
}
impl FromDatum for pg_sys::Oid {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<pg_sys::Oid> {
if is_null {
None
} else {
datum
.value()
.try_into()
.ok()
.map(|uint| unsafe { pg_sys::Oid::from_u32_unchecked(uint) })
}
}
}
impl FromDatum for bool {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<bool> {
if is_null {
None
} else {
Some(datum.value() != 0)
}
}
}
impl FromDatum for i8 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<i8> {
if is_null {
None
} else {
Some(datum.value() as _)
}
}
}
impl FromDatum for i16 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<i16> {
if is_null {
None
} else {
Some(datum.value() as _)
}
}
}
impl FromDatum for i32 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<i32> {
if is_null {
None
} else {
Some(datum.value() as _)
}
}
}
impl FromDatum for u32 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<u32> {
if is_null {
None
} else {
Some(datum.value() as _)
}
}
}
impl FromDatum for i64 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<i64> {
if is_null {
None
} else {
Some(datum.value() as _)
}
}
}
impl FromDatum for f32 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<f32> {
if is_null {
None
} else {
Some(f32::from_bits(datum.value() as _))
}
}
}
impl FromDatum for f64 {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<f64> {
if is_null {
None
} else {
Some(f64::from_bits(datum.value() as _))
}
}
}
impl<'a> FromDatum for &'a str {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<&'a str> {
if is_null || datum.is_null() {
None
} else {
let varlena = pg_sys::pg_detoast_datum_packed(datum.cast_mut_ptr());
Some(text_to_rust_str_unchecked(varlena))
}
}
unsafe fn from_datum_in_memory_context(
mut memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<Self>
where
Self: Sized,
{
if is_null || datum.is_null() {
None
} else {
memory_context.switch_to(|_| {
let detoasted = pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
let varlena = pg_sys::pg_detoast_datum_packed(detoasted);
Some(text_to_rust_str_unchecked(varlena))
})
}
}
}
impl FromDatum for String {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
typoid: pg_sys::Oid,
) -> Option<String> {
FromDatum::from_polymorphic_datum(datum, is_null, typoid).map(|s: &str| s.to_owned())
}
}
impl FromDatum for char {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
typoid: pg_sys::Oid,
) -> Option<char> {
FromDatum::from_polymorphic_datum(datum, is_null, typoid)
.and_then(|s: &str| s.chars().next())
}
}
impl<'a> FromDatum for &'a core::ffi::CStr {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<&'a CStr> {
if is_null || datum.is_null() {
None
} else {
Some(core::ffi::CStr::from_ptr(datum.cast_mut_ptr()))
}
}
}
impl<'a> FromDatum for &'a [u8] {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<&'a [u8]> {
if is_null || datum.is_null() {
None
} else {
let varlena = pg_sys::pg_detoast_datum_packed(datum.cast_mut_ptr());
Some(varlena_to_byte_slice(varlena))
}
}
unsafe fn from_datum_in_memory_context(
mut memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<Self>
where
Self: Sized,
{
if is_null || datum.is_null() {
None
} else {
memory_context.switch_to(|_| {
let detoasted = pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
let varlena = pg_sys::pg_detoast_datum_packed(detoasted);
Some(varlena_to_byte_slice(varlena))
})
}
}
}
impl FromDatum for Vec<u8> {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
typoid: pg_sys::Oid,
) -> Option<Vec<u8>> {
if is_null || datum.is_null() {
None
} else {
let bytes: Option<&[u8]> = FromDatum::from_polymorphic_datum(datum, is_null, typoid);
match bytes {
Some(bytes) => Some(bytes.into_iter().map(|b| *b).collect::<Vec<u8>>()),
None => None,
}
}
}
}
impl FromDatum for () {
#[inline]
unsafe fn from_polymorphic_datum(
_datum: pg_sys::Datum,
_is_null: bool,
_: pg_sys::Oid,
) -> Option<()> {
Some(())
}
}
impl<T> FromDatum for PgBox<T, AllocatedByPostgres> {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<Self> {
if is_null || datum.is_null() {
None
} else {
Some(PgBox::<T>::from_pg(datum.cast_mut_ptr()))
}
}
unsafe fn from_datum_in_memory_context(
mut memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<Self>
where
Self: Sized,
{
memory_context.switch_to(|context| {
if is_null || datum.is_null() {
None
} else {
let copied = context.copy_ptr_into(datum.cast_mut_ptr(), std::mem::size_of::<T>());
Some(PgBox::<T>::from_pg(copied))
}
})
}
}