#![allow(clippy::manual_map, clippy::map_clone, clippy::into_iter_on_ref)]
use crate::{
AllocatedByPostgres, IntoDatum, PgBox, PgMemoryContexts, pg_sys, varlena, varlena_to_byte_slice,
};
use alloc::ffi::CString;
use core::{ffi::CStr, mem::size_of};
use std::num::NonZeroUsize;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum TryFromDatumError {
#[error(
"Postgres type {datum_type} (Oid({datum_oid})) is not compatible with the Rust type {rust_type} (Oid({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: Sized {
const GET_TYPOID: bool = false;
unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option<Self> {
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>;
unsafe fn from_datum_in_memory_context(
mut memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
typoid: pg_sys::Oid,
) -> Option<Self> {
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: 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: 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 {
let oid_as_u32 = datum.value() as u32;
Some(pg_sys::Oid::from(oid_as_u32))
}
}
}
impl FromDatum for pg_sys::TransactionId {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<Self> {
if is_null { None } else { datum.value().try_into().ok().map(Self::from_inner) }
}
}
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 {
let value = if size_of::<i64>() <= size_of::<pg_sys::Datum>() {
datum.value() as _
} else {
*(datum.cast_mut_ptr() as *const _)
};
Some(value)
}
}
}
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 {
let value = if size_of::<i64>() <= size_of::<pg_sys::Datum>() {
f64::from_bits(datum.value() as _)
} else {
*(datum.cast_mut_ptr() as *const _)
};
Some(value)
}
}
}
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(convert_varlena_to_str_memoized(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(convert_varlena_to_str_memoized(varlena))
})
}
}
}
unsafe fn convert_varlena_to_str_memoized<'a>(varlena: *const pg_sys::varlena) -> &'a str {
match *crate::UTF8DATABASE {
crate::Utf8Compat::Yes => varlena::text_to_rust_str_unchecked(varlena),
crate::Utf8Compat::Maybe => varlena::text_to_rust_str(varlena)
.expect("datums converted to &str should be valid UTF-8"),
crate::Utf8Compat::Ascii => {
let bytes = varlena_to_byte_slice(varlena);
if bytes.is_ascii() {
core::str::from_utf8_unchecked(bytes)
} else {
panic!(
"datums converted to &str should be valid UTF-8, database encoding is only UTF-8 compatible for ASCII"
)
}
}
}
}
impl FromDatum for String {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<String> {
if is_null || datum.is_null() {
None
} else {
let varlena = pg_sys::pg_detoast_datum_packed(datum.cast_mut_ptr());
let converted_varlena = convert_varlena_to_str_memoized(varlena);
let ret_string = converted_varlena.to_owned();
if varlena::varatt_is_1b_e(datum.cast_mut_ptr())
|| varlena::varatt_is_4b_c(datum.cast_mut_ptr())
{
pg_sys::pfree(varlena.cast());
}
Some(ret_string)
}
}
}
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 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(CStr::from_ptr(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,
{
if is_null || datum.is_null() {
None
} else {
let copy =
memory_context.switch_to(|_| CStr::from_ptr(pg_sys::pstrdup(datum.cast_mut_ptr())));
Some(copy)
}
}
}
impl FromDatum for CString {
#[inline]
unsafe fn from_polymorphic_datum(
datum: pg_sys::Datum,
is_null: bool,
_: pg_sys::Oid,
) -> Option<CString> {
if is_null || datum.is_null() {
None
} else {
Some(CStr::from_ptr(datum.cast_mut_ptr()).to_owned())
}
}
unsafe fn from_datum_in_memory_context(
_memory_context: PgMemoryContexts,
datum: pg_sys::Datum,
is_null: bool,
_typoid: pg_sys::Oid,
) -> Option<Self>
where
Self: Sized,
{
Self::from_polymorphic_datum(datum, is_null, _typoid)
}
}
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))
}
})
}
}