diff --git a/pgx-examples/bgworker/src/lib.rs b/pgx-examples/bgworker/src/lib.rs index c3b23604e..9b7bdfa5a 100644 --- a/pgx-examples/bgworker/src/lib.rs +++ b/pgx-examples/bgworker/src/lib.rs @@ -40,7 +40,7 @@ pub extern "C" fn _PG_init() { #[pg_guard] #[no_mangle] pub extern "C" fn background_worker_main(arg: pg_sys::Datum) { - let arg = unsafe { i32::from_datum(arg, false, pg_sys::INT4OID) }; + let arg = unsafe { i32::from_datum(arg, false) }; // these are the signals we want to receive. If we don't attach the SIGTERM handler, then // we'll never be able to exit via an external notification diff --git a/pgx-macros/src/lib.rs b/pgx-macros/src/lib.rs index 4ca631db5..9dea84fd3 100644 --- a/pgx-macros/src/lib.rs +++ b/pgx-macros/src/lib.rs @@ -642,7 +642,7 @@ fn impl_postgres_enum(ast: DeriveInput) -> proc_macro2::TokenStream { stream.extend(quote! { impl pgx::FromDatum for #enum_ident { #[inline] - unsafe fn from_datum(datum: pgx::pg_sys::Datum, is_null: bool, typeoid: pgx::pg_sys::Oid) -> Option<#enum_ident> { + unsafe fn from_datum(datum: pgx::pg_sys::Datum, is_null: bool) -> Option<#enum_ident> { if is_null { None } else { diff --git a/pgx/src/datum/anyarray.rs b/pgx/src/datum/anyarray.rs index 12806bb1d..459c59b6c 100644 --- a/pgx/src/datum/anyarray.rs +++ b/pgx/src/datum/anyarray.rs @@ -12,7 +12,6 @@ use crate::{pg_sys, FromDatum, IntoDatum}; #[derive(Debug, Clone, Copy)] pub struct AnyArray { datum: pg_sys::Datum, - typoid: pg_sys::Oid, } impl AnyArray { @@ -20,27 +19,19 @@ impl AnyArray { self.datum } - pub fn oid(&self) -> pg_sys::Oid { - self.typoid - } - #[inline] pub fn into(&self) -> Option { - unsafe { T::from_datum(self.datum(), false, self.oid()) } + unsafe { T::from_datum(self.datum(), false) } } } impl FromDatum for AnyArray { #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - typoid: pg_sys::Oid, - ) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { - Some(AnyArray { datum, typoid }) + Some(AnyArray { datum }) } } } diff --git a/pgx/src/datum/anyelement.rs b/pgx/src/datum/anyelement.rs index 3e3cc4fc7..d393a1d16 100644 --- a/pgx/src/datum/anyelement.rs +++ b/pgx/src/datum/anyelement.rs @@ -12,7 +12,6 @@ use crate::{pg_sys, FromDatum, IntoDatum}; #[derive(Debug, Clone, Copy)] pub struct AnyElement { datum: pg_sys::Datum, - typoid: pg_sys::Oid, } impl AnyElement { @@ -20,27 +19,19 @@ impl AnyElement { self.datum } - pub fn oid(&self) -> pg_sys::Oid { - self.typoid - } - #[inline] pub fn into(&self) -> Option { - unsafe { T::from_datum(self.datum(), false, self.oid()) } + unsafe { T::from_datum(self.datum(), false) } } } impl FromDatum for AnyElement { #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - typoid: pg_sys::Oid, - ) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { - Some(AnyElement { datum, typoid }) + Some(AnyElement { datum }) } } } diff --git a/pgx/src/datum/array.rs b/pgx/src/datum/array.rs index 16525f9a9..3a6017315 100644 --- a/pgx/src/datum/array.rs +++ b/pgx/src/datum/array.rs @@ -18,7 +18,6 @@ pub struct Array<'a, T: FromDatum> { array_type: *mut pg_sys::ArrayType, elements: *mut pg_sys::Datum, nulls: *mut bool, - typoid: pg_sys::Oid, nelems: usize, elem_slice: &'a [pg_sys::Datum], null_slice: &'a [bool], @@ -63,7 +62,6 @@ impl<'a, T: FromDatum> Array<'a, T> { array_type: std::ptr::null_mut(), elements, nulls, - typoid: pg_sys::InvalidOid, nelems, elem_slice: std::slice::from_raw_parts(elements, nelems), null_slice: std::slice::from_raw_parts(nulls, nelems), @@ -76,7 +74,6 @@ impl<'a, T: FromDatum> Array<'a, T> { array_type: *mut pg_sys::ArrayType, elements: *mut pg_sys::Datum, nulls: *mut bool, - typoid: pg_sys::Oid, nelems: usize, ) -> Self { Array:: { @@ -84,7 +81,6 @@ impl<'a, T: FromDatum> Array<'a, T> { array_type, elements, nulls, - typoid, nelems, elem_slice: std::slice::from_raw_parts(elements, nelems), null_slice: std::slice::from_raw_parts(nulls, nelems), @@ -153,7 +149,7 @@ impl<'a, T: FromDatum> Array<'a, T> { if i >= self.nelems { None } else { - Some(unsafe { T::from_datum(self.elem_slice[i], self.null_slice[i], self.typoid) }) + Some(unsafe { T::from_datum(self.elem_slice[i], self.null_slice[i]) }) } } } @@ -277,11 +273,9 @@ impl<'a, T: FromDatum> Drop for Array<'a, T> { impl<'a, T: FromDatum> FromDatum for Array<'a, T> { #[inline] - unsafe fn from_datum(datum: usize, is_null: bool, typoid: u32) -> Option> { + unsafe fn from_datum(datum: usize, is_null: bool) -> Option> { if is_null { None - } else if datum == 0 { - panic!("array was flagged not null but datum is zero"); } else { let ptr = datum as *mut pg_sys::varlena; let array = @@ -316,31 +310,18 @@ impl<'a, T: FromDatum> FromDatum for Array<'a, T> { &mut nelems, ); - Some(Array::from_pg( - ptr, - array, - elements, - nulls, - typoid, - nelems as usize, - )) + Some(Array::from_pg(ptr, array, elements, nulls, nelems as usize)) } } } impl FromDatum for Vec { #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - typoid: pg_sys::Oid, - ) -> Option> { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option> { if is_null { None - } else if datum == 0 { - panic!("array was flagged not null but datum is zero"); } else { - let array = Array::::from_datum(datum, is_null, typoid).unwrap(); + let array = Array::::from_datum(datum, is_null).unwrap(); let mut v = Vec::with_capacity(array.len()); for element in array.iter() { @@ -353,17 +334,11 @@ impl FromDatum for Vec { impl FromDatum for Vec> { #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - typoid: pg_sys::Oid, - ) -> Option>> { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option>> { if is_null { None - } else if datum == 0 { - panic!("array was flagged not null but datum is zero"); } else { - let array = Array::::from_datum(datum, is_null, typoid).unwrap(); + let array = Array::::from_datum(datum, is_null).unwrap(); let mut v = Vec::with_capacity(array.len()); for element in array.iter() { @@ -414,11 +389,16 @@ where fn type_oid() -> u32 { unsafe { pg_sys::get_array_type(T::type_oid()) } } + + #[inline] + fn is_compatible_with(other: pg_sys::Oid) -> bool { + Self::type_oid() == other || other == unsafe { pg_sys::get_array_type(T::type_oid()) } + } } impl<'a, T> IntoDatum for &'a [T] where - T: IntoDatum + Copy, + T: IntoDatum + Copy + 'a, { fn into_datum(self) -> Option { let mut state = unsafe { @@ -456,4 +436,9 @@ where fn type_oid() -> u32 { unsafe { pg_sys::get_array_type(T::type_oid()) } } + + #[inline] + fn is_compatible_with(other: pg_sys::Oid) -> bool { + Self::type_oid() == other || other == unsafe { pg_sys::get_array_type(T::type_oid()) } + } } diff --git a/pgx/src/datum/date.rs b/pgx/src/datum/date.rs index 08983acef..980681a8b 100644 --- a/pgx/src/datum/date.rs +++ b/pgx/src/datum/date.rs @@ -14,9 +14,8 @@ use time::format_description::FormatItem; #[derive(Debug)] pub struct Date(time::Date); impl FromDatum for Date { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _typoid: u32) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { diff --git a/pgx/src/datum/from.rs b/pgx/src/datum/from.rs index 6b6f02e4f..e12b2cb35 100644 --- a/pgx/src/datum/from.rs +++ b/pgx/src/datum/from.rs @@ -10,36 +10,48 @@ Use of this source code is governed by the MIT license that can be found in the //! for converting a pg_sys::Datum and a corresponding "is_null" bool into a typed Option use crate::{ - pg_sys, text_to_rust_str_unchecked, varlena_to_byte_slice, AllocatedByPostgres, PgBox, - PgMemoryContexts, + pg_sys, text_to_rust_str_unchecked, varlena_to_byte_slice, AllocatedByPostgres, IntoDatum, + PgBox, PgMemoryContexts, }; use std::ffi::CStr; -/// Convert a `(pg_sys::Datum, is_null:bool, type_oid:pg_sys::Oid)` tuple into a Rust type +/// If converting a Datum to a Rust type fails, this is the set of possible reasons why. +pub enum TryFromDatumError { + /// The specified type of the Datum is not compatible with the desired Rust type. + IncompatibleTypes, + + /// We were asked to convert a Datum that is NULL (but flagged as "not null") + NullDatumPointer, +} + +/// A [Result] type that is used to indicate whether a conversion from a Datum to a Rust type +/// succeeded or failed. +pub type FromDatumResult = std::result::Result, TryFromDatumError>; + +/// Convert a `(pg_sys::Datum, is_null:bool` pair into a Rust type /// /// Default implementations are provided for the common Rust types. /// /// If implementing this, also implement `IntoDatum` for the reverse /// conversion. pub trait FromDatum { - const NEEDS_TYPID: bool = true; /// ## Safety /// /// This method is inherently unsafe as the `datum` argument can represent an arbitrary /// memory address in the case of pass-by-reference Datums. Referencing that memory address /// can cause Postgres to crash if it's invalid. /// - /// If the `(datum, is_null)` tuple comes from Postgres, it's generally okay to consider this + /// If the `(datum, is_null)` pair comes from Postgres, it's generally okay to consider this /// a safe call (ie, wrap it in `unsafe {}`) and move on with life. /// /// If, however, you're providing an arbitrary datum value, it needs to be considered unsafe /// and that unsafeness should be propagated through your API. - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, typoid: pg_sys::Oid) -> Option + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option where Self: Sized; /// Default implementation switched to the specified memory context and then simply calls - /// `From::from_datum(...)` from within that context. + /// `FromDatum::from_datum(...)` from within that context. /// /// For certain Datums (such as `&str`), this is likely not enough and this function /// should be overridden in the type's trait implementation. @@ -50,29 +62,48 @@ pub trait FromDatum { /// /// ## Safety /// - /// Same caveats as `From::from_datum(...)` + /// Same caveats as `FromDatum::from_datum(...)` unsafe fn from_datum_in_memory_context( mut memory_context: PgMemoryContexts, datum: pg_sys::Datum, is_null: bool, - typoid: pg_sys::Oid, ) -> Option where Self: Sized, { - memory_context.switch_to(|_| FromDatum::from_datum(datum, is_null, typoid)) + memory_context.switch_to(|_| FromDatum::from_datum(datum, is_null)) + } + + /// `try_from_datum` is a convenience wrapper around `FromDatum::from_datum` that returns a + /// a [FromDatumResult] instead of an `Option`. It's intended to be used in situations where + /// the caller needs to know whether the type conversion succeeded or failed. + /// + /// ## Safety + /// + /// Same caveats as `FromDatum::from_datum(...)` + #[inline] + unsafe fn try_from_datum( + datum: pg_sys::Datum, + is_null: bool, + type_oid: pg_sys::Oid, + ) -> FromDatumResult + where + Self: Sized + IntoDatum + 'static, + { + if !Self::is_compatible_with(type_oid) { + Err(TryFromDatumError::IncompatibleTypes) + } else if !is_null && datum == 0 && !Self::is_pass_by_value() { + Err(TryFromDatumError::NullDatumPointer) + } else { + Ok(FromDatum::from_datum(datum, is_null)) + } } } /// for pg_sys::Datum impl FromDatum for pg_sys::Datum { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - _: pg_sys::Oid, - ) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -83,9 +114,8 @@ impl FromDatum for pg_sys::Datum { /// for bool impl FromDatum for bool { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -96,9 +126,8 @@ impl FromDatum for bool { /// for `"char"` impl FromDatum for i8 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -109,9 +138,8 @@ impl FromDatum for i8 { /// for smallint impl FromDatum for i16 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -122,9 +150,8 @@ impl FromDatum for i16 { /// for integer impl FromDatum for i32 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -135,9 +162,8 @@ impl FromDatum for i32 { /// for oid impl FromDatum for u32 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -148,9 +174,8 @@ impl FromDatum for u32 { /// for bigint impl FromDatum for i64 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -161,9 +186,8 @@ impl FromDatum for i64 { /// for real impl FromDatum for f32 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -174,9 +198,8 @@ impl FromDatum for f32 { /// for double precision impl FromDatum for f64 { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { @@ -187,13 +210,10 @@ impl FromDatum for f64 { /// for text, varchar impl<'a> FromDatum for &'a str { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option<&'a str> { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option<&'a str> { if is_null { None - } else if datum == 0 { - panic!("a varlena Datum was flagged as non-null but the datum is zero"); } else { let varlena = pg_sys::pg_detoast_datum_packed(datum as *mut pg_sys::varlena); Some(text_to_rust_str_unchecked(varlena)) @@ -204,15 +224,12 @@ impl<'a> FromDatum for &'a str { mut memory_context: PgMemoryContexts, datum: usize, is_null: bool, - _typoid: u32, ) -> Option where Self: Sized, { if is_null { None - } else if datum == 0 { - panic!("a varlena Datum was flagged as non-null but the datum is zero"); } else { memory_context.switch_to(|_| { // this gets the varlena Datum copied into this memory context @@ -232,14 +249,9 @@ impl<'a> FromDatum for &'a str { /// /// This returns a **copy**, allocated and managed by Rust, of the underlying `varlena` Datum impl FromDatum for String { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - typoid: pg_sys::Oid, - ) -> Option { - let refstr: Option<&str> = FromDatum::from_datum(datum, is_null, typoid); + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { + let refstr: Option<&str> = FromDatum::from_datum(datum, is_null); match refstr { Some(refstr) => Some(refstr.to_owned()), None => None, @@ -248,10 +260,9 @@ impl FromDatum for String { } impl FromDatum for char { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, typoid: pg_sys::Oid) -> Option { - let refstr: Option<&str> = FromDatum::from_datum(datum, is_null, typoid); + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { + let refstr: Option<&str> = FromDatum::from_datum(datum, is_null); match refstr { Some(refstr) => refstr.chars().next(), None => None, @@ -261,13 +272,10 @@ impl FromDatum for char { /// for cstring impl<'a> FromDatum for &'a std::ffi::CStr { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option<&'a CStr> { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option<&'a CStr> { if is_null { None - } else if datum == 0 { - panic!("a cstring Datum was flagged as non-null but the datum is zero"); } else { Some(std::ffi::CStr::from_ptr( datum as *const std::os::raw::c_char, @@ -277,17 +285,13 @@ impl<'a> FromDatum for &'a std::ffi::CStr { } impl<'a> FromDatum for &'a crate::cstr_core::CStr { - const NEEDS_TYPID: bool = false; #[inline] unsafe fn from_datum( datum: pg_sys::Datum, is_null: bool, - _: pg_sys::Oid, ) -> Option<&'a crate::cstr_core::CStr> { if is_null { None - } else if datum == 0 { - panic!("a cstring Datum was flagged as non-null but the datum is zero"); } else { Some(crate::cstr_core::CStr::from_ptr( datum as *const std::os::raw::c_char, @@ -298,13 +302,10 @@ impl<'a> FromDatum for &'a crate::cstr_core::CStr { /// for bytea impl<'a> FromDatum for &'a [u8] { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: usize, is_null: bool, _typoid: u32) -> Option<&'a [u8]> { + unsafe fn from_datum(datum: usize, is_null: bool) -> Option<&'a [u8]> { if is_null { None - } else if datum == 0 { - panic!("a bytea Datum was flagged as non-null but the datum is zero"); } else { let varlena = pg_sys::pg_detoast_datum_packed(datum as *mut pg_sys::varlena); Some(varlena_to_byte_slice(varlena)) @@ -315,15 +316,12 @@ impl<'a> FromDatum for &'a [u8] { mut memory_context: PgMemoryContexts, datum: usize, is_null: bool, - _typoid: u32, ) -> Option where Self: Sized, { if is_null { None - } else if datum == 0 { - panic!("a bytea Datum was flagged as non-null but the datum is zero"); } else { memory_context.switch_to(|_| { // this gets the varlena Datum copied into this memory context @@ -340,16 +338,13 @@ impl<'a> FromDatum for &'a [u8] { } impl FromDatum for Vec { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: usize, is_null: bool, typoid: u32) -> Option> { + unsafe fn from_datum(datum: usize, is_null: bool) -> Option> { if is_null { None - } else if datum == 0 { - panic!("a bytea Datum as flagged as non-null but the datum is zero"); } else { // Vec conversion is initially the same as for &[u8] - let bytes: Option<&[u8]> = FromDatum::from_datum(datum, is_null, typoid); + let bytes: Option<&[u8]> = FromDatum::from_datum(datum, is_null); match bytes { // but then we need to convert it into an owned Vec where the backing @@ -363,25 +358,18 @@ impl FromDatum for Vec { /// for NULL -- always converts to a `None`, even if the is_null argument is false impl FromDatum for () { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(_datum: pg_sys::Datum, _is_null: bool, _: pg_sys::Oid) -> Option<()> { + unsafe fn from_datum(_datum: pg_sys::Datum, _is_null: bool) -> Option<()> { None } } /// for user types impl FromDatum for PgBox { - const NEEDS_TYPID: bool = false; #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None - } else if datum == 0 { - panic!( - "user type {} Datum was flagged as non-null but the datum is zero", - std::any::type_name::() - ); } else { Some(PgBox::::from_pg(datum as *mut T)) } @@ -391,7 +379,6 @@ impl FromDatum for PgBox { mut memory_context: PgMemoryContexts, datum: usize, is_null: bool, - _typoid: u32, ) -> Option where Self: Sized, @@ -399,11 +386,6 @@ impl FromDatum for PgBox { memory_context.switch_to(|context| { if is_null { None - } else if datum == 0 { - panic!( - "user type {} Datum was flagged as non-null but the datum is zero", - std::any::type_name::() - ); } else { let copied = context.copy_ptr_into(datum as *mut T, std::mem::size_of::()); Some(PgBox::::from_pg(copied)) diff --git a/pgx/src/datum/geo.rs b/pgx/src/datum/geo.rs index f17842056..c7eb5580f 100644 --- a/pgx/src/datum/geo.rs +++ b/pgx/src/datum/geo.rs @@ -10,15 +10,12 @@ Use of this source code is governed by the MIT license that can be found in the use crate::{direct_function_call_as_datum, pg_sys, FromDatum, IntoDatum}; impl FromDatum for pg_sys::BOX { - const NEEDS_TYPID: bool = false; - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option where Self: Sized, { if is_null { None - } else if datum == 0 { - panic!("BOX datum declared not null, but datum is zero") } else { let the_box = datum as *mut pg_sys::BOX; Some(the_box.read()) @@ -43,14 +40,12 @@ impl IntoDatum for pg_sys::BOX { } impl FromDatum for pg_sys::Point { - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option where Self: Sized, { if is_null { None - } else if datum == 0 { - panic!("Point datum declared not null, but datum is zero") } else { let point = datum as *mut pg_sys::Point; Some(point.read()) diff --git a/pgx/src/datum/inet.rs b/pgx/src/datum/inet.rs index 7247c5012..cba193378 100644 --- a/pgx/src/datum/inet.rs +++ b/pgx/src/datum/inet.rs @@ -84,11 +84,9 @@ impl<'de> Deserialize<'de> for Inet { } impl FromDatum for Inet { - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _typoid: u32) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None - } else if datum == 0 { - panic!("inet datum is declared non-null but Datum is zero"); } else { let cstr = direct_function_call::<&CStr>(pg_sys::inet_out, vec![Some(datum)]); Some(Inet( diff --git a/pgx/src/datum/internal.rs b/pgx/src/datum/internal.rs index 4eacfe860..20673db79 100644 --- a/pgx/src/datum/internal.rs +++ b/pgx/src/datum/internal.rs @@ -157,7 +157,7 @@ impl From> for Internal { impl FromDatum for Internal { #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { Some(Internal(if is_null { None } else { Some(datum) })) } } diff --git a/pgx/src/datum/into.rs b/pgx/src/datum/into.rs index bc5c3d21d..e8e8538b8 100644 --- a/pgx/src/datum/into.rs +++ b/pgx/src/datum/into.rs @@ -16,6 +16,7 @@ use crate::{ pg_sys, rust_byte_slice_to_bytea, rust_regtypein, rust_str_to_text_p, PgBox, PgOid, WhoAllocated, }; +use pgx_pg_sys::Oid; /// Convert a Rust type into a `pg_sys::Datum`. /// @@ -32,6 +33,60 @@ pub trait IntoDatum { fn array_type_oid() -> pg_sys::Oid { unsafe { pg_sys::get_array_type(Self::type_oid()) } } + + /// Is a Datum of this type compatible with another Postgres type? + /// + /// An example of this are the Postgres `text` and `varchar` types, which are both + /// technically compatible from a Rust type perspective. They're both represented in Rust as + /// `String` (or `&str`), but the underlying Postgres types are different. + /// + /// If implementing this yourself, you likely want to follow a pattern like this: + /// + /// ```text + /// fn is_compatible_with(other: pg_sys::Oid) -> bool { + /// // first, if our type is the other type, then we're compatible + /// Self::type_oid() == other + /// + /// // and here's the other type we're compatible with + /// || other == pg_sys::TEXTOID + /// } + /// ``` + #[inline] + fn is_compatible_with(other: pg_sys::Oid) -> bool { + Self::type_oid() == other + } + + /// Is a Datum of this type pass by value or pass by reference? + /// + /// We provide a hardcoded list of known Postgres types that are pass by value, + /// but you are free to implement this yourself for custom types. + #[inline] + fn is_pass_by_value() -> bool + where + Self: 'static, + { + let my_type = std::any::TypeId::of::(); + my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::<()>() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::() + || my_type == std::any::TypeId::of::>() + } } /// for supporting NULL as the None value of an Option @@ -175,6 +230,11 @@ impl<'a> IntoDatum for &'a str { fn type_oid() -> u32 { pg_sys::TEXTOID } + + #[inline] + fn is_compatible_with(other: Oid) -> bool { + Self::type_oid() == other || other == pg_sys::VARCHAROID + } } impl IntoDatum for String { @@ -186,6 +246,11 @@ impl IntoDatum for String { fn type_oid() -> u32 { pg_sys::TEXTOID } + + #[inline] + fn is_compatible_with(other: Oid) -> bool { + Self::type_oid() == other || other == pg_sys::VARCHAROID + } } impl IntoDatum for &String { @@ -197,6 +262,11 @@ impl IntoDatum for &String { fn type_oid() -> u32 { pg_sys::TEXTOID } + + #[inline] + fn is_compatible_with(other: Oid) -> bool { + Self::type_oid() == other || other == pg_sys::VARCHAROID + } } impl IntoDatum for char { @@ -208,6 +278,11 @@ impl IntoDatum for char { fn type_oid() -> u32 { pg_sys::VARCHAROID } + + #[inline] + fn is_compatible_with(other: Oid) -> bool { + Self::type_oid() == other || other == pg_sys::VARCHAROID + } } /// for cstring diff --git a/pgx/src/datum/item_pointer_data.rs b/pgx/src/datum/item_pointer_data.rs index e902f48ae..49eef05c3 100644 --- a/pgx/src/datum/item_pointer_data.rs +++ b/pgx/src/datum/item_pointer_data.rs @@ -13,11 +13,7 @@ use crate::{ impl FromDatum for pg_sys::ItemPointerData { #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - _typoid: u32, - ) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None } else { diff --git a/pgx/src/datum/json.rs b/pgx/src/datum/json.rs index 70cae4d1c..636db4ac9 100644 --- a/pgx/src/datum/json.rs +++ b/pgx/src/datum/json.rs @@ -26,11 +26,9 @@ pub struct JsonString(pub String); /// for json impl FromDatum for Json { #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None - } else if datum == 0 { - panic!("a json Datum was flagged as non-null but the datum is zero"); } else { let varlena = pg_sys::pg_detoast_datum(datum as *mut pg_sys::varlena); let len = varsize_any_exhdr(varlena); @@ -44,11 +42,9 @@ impl FromDatum for Json { /// for jsonb impl FromDatum for JsonB { - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _: pg_sys::Oid) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None - } else if datum == 0 { - panic!("a jsonb Datum was flagged as non-null but the datum is zero") } else { let varlena = datum as *mut pg_sys::varlena; let detoasted = pg_sys::pg_detoast_datum_packed(varlena); @@ -84,15 +80,9 @@ impl FromDatum for JsonB { /// This returns a **copy**, allocated and managed by Rust, of the underlying `varlena` Datum impl FromDatum for JsonString { #[inline] - unsafe fn from_datum( - datum: pg_sys::Datum, - is_null: bool, - _: pg_sys::Oid, - ) -> Option { + unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool) -> Option { if is_null { None - } else if datum == 0 { - panic!("a varlena Datum was flagged as non-null but the datum is zero"); } else { let varlena = datum as *mut pg_sys::varlena; let detoasted = pg_sys::pg_detoast_datum_packed(varlena); diff --git a/pgx/src/datum/numeric.rs b/pgx/src/datum/numeric.rs index 4ecec5ae9..6db7751a9 100644 --- a/pgx/src/datum/numeric.rs +++ b/pgx/src/datum/numeric.rs @@ -152,7 +152,7 @@ impl Into for f64 { } impl FromDatum for Numeric { - unsafe fn from_datum(datum: usize, is_null: bool, _typoid: u32) -> Option + unsafe fn from_datum(datum: usize, is_null: bool) -> Option where Self: Sized, { diff --git a/pgx/src/datum/time.rs b/pgx/src/datum/time.rs index 72e37af99..23d357b4e 100644 --- a/pgx/src/datum/time.rs +++ b/pgx/src/datum/time.rs @@ -21,7 +21,7 @@ pub(crate) const SEC_PER_MIN: i64 = 60; pub struct Time(pub(crate) time::Time); impl FromDatum for Time { #[inline] - unsafe fn from_datum(datum: pg_sys::Datum, is_null: bool, _typoid: u32) -> Option