From d130652d4daf79dd34b3c7a36651a8ea49c72f6a Mon Sep 17 00:00:00 2001 From: Eric Ridge Date: Wed, 20 Apr 2022 15:05:27 -0400 Subject: [PATCH 01/90] Improved "HeapTuple" support (#532) * Remove FromDatum::NEEDS_TYPID const This constant was set to false in all every `FromDatum` implementation, and simply isn't necessary. Further changes to `FromDatum` are going to change how this idea worked in the first place. * Remove the `typid` argument from `FromDatum::from_datum()`. This argument was never actually used, only passed around. And in many cases it was set to `pg_sys::InvalidOid` anyways. * Eliminate this pattern during `FromDatum::from_datum()`: ```rust } else if datum == 0 { panic!("array was flagged not null but datum is zero"); } ``` As the function is already unsafe, there's no need for us to have a check for a case that is likely never going to happen. Postgres may send us a null datum, but as long as we check `is_null` first, we're okay. * `FromDatum` gets a new function, `try_from_datum()` This function ensures the desired Rust type is compatible with the backing Postgres type of the Datum being converted. If they're not compatible, an `Err` is returned rather than either panicking or leading to undefined behavior. It's not actually used anywhere yet. * Initial work on a new `PgHeapTuple` type. Currently it only knows how to construct itself in a few ways, along with retrieving named/indexed datum values (as runtime type-checked Rust values). There's a few breaking API changes in other places, especially in `htup.rs` and `PgTupleDesc` * default impl of a new IntoDatum::is_pass_by_value() function * fix compilation issues after merging from-datum-overhaul * a few more pass-by-value types * WIP: ability to set attribute values on a HeapTuple * Finish up the `PgHeapTuple` API, plus docs. Update the trigger example. --- pgx-examples/bgworker/src/lib.rs | 2 +- pgx-examples/triggers/src/lib.rs | 43 ++- pgx-macros/src/lib.rs | 2 +- pgx/src/datum/anyarray.rs | 15 +- pgx/src/datum/anyelement.rs | 15 +- pgx/src/datum/array.rs | 51 ++-- pgx/src/datum/date.rs | 3 +- pgx/src/datum/from.rs | 173 ++++++----- pgx/src/datum/geo.rs | 9 +- pgx/src/datum/inet.rs | 4 +- pgx/src/datum/internal.rs | 2 +- pgx/src/datum/into.rs | 89 ++++++ pgx/src/datum/item_pointer_data.rs | 6 +- pgx/src/datum/json.rs | 16 +- pgx/src/datum/numeric.rs | 2 +- pgx/src/datum/time.rs | 2 +- pgx/src/datum/time_stamp.rs | 7 +- pgx/src/datum/time_stamp_with_timezone.rs | 10 +- pgx/src/datum/time_with_timezone.rs | 4 +- pgx/src/datum/tuples.rs | 20 +- pgx/src/datum/uuid.rs | 4 +- pgx/src/datum/varlena.rs | 9 +- pgx/src/fcinfo.rs | 22 +- pgx/src/heap_tuple.rs | 355 ++++++++++++++++++++++ pgx/src/htup.rs | 28 +- pgx/src/lib.rs | 2 + pgx/src/rel.rs | 3 +- pgx/src/spi.rs | 6 +- pgx/src/trigger_support.rs | 15 + pgx/src/tupdesc.rs | 67 ++-- 30 files changed, 679 insertions(+), 307 deletions(-) create mode 100644 pgx/src/heap_tuple.rs 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-examples/triggers/src/lib.rs b/pgx-examples/triggers/src/lib.rs index 17c5b951e..05f085eb1 100644 --- a/pgx-examples/triggers/src/lib.rs +++ b/pgx-examples/triggers/src/lib.rs @@ -23,21 +23,29 @@ unsafe fn trigger_example(fcinfo: pg_sys::FunctionCallInfo) -> pg_sys::Datum { panic!("not called by trigger manager"); } - let trigdata: PgBox = PgBox::from_pg( - fcinfo.as_ref().expect("fcinfo is NULL").context as *mut pg_sys::TriggerData, - ); + let trigdata = (fcinfo.as_ref().expect("fcinfo is NULL").context as *mut pg_sys::TriggerData) + .as_ref() + .unwrap(); // and for this example, we're only going to operate as an ON BEFORE INSERT FOR EACH ROW trigger if trigger_fired_before(trigdata.tg_event) && trigger_fired_by_insert(trigdata.tg_event) && trigger_fired_for_row(trigdata.tg_event) { - let tupdesc = PgTupleDesc::from_pg_copy(trigdata.tg_relation.as_ref().unwrap().rd_att); - let tuple = PgBox::::from_pg(trigdata.tg_trigtuple); - let id = heap_getattr::(&tuple, 1, &tupdesc); - let title = heap_getattr::<&str, AllocatedByPostgres>(&tuple, 2, &tupdesc); - let description = heap_getattr::<&str, AllocatedByPostgres>(&tuple, 3, &tupdesc); - let payload = heap_getattr::(&tuple, 4, &tupdesc); + let tuple = + PgHeapTuple::from_trigger_data(trigdata, TriggerTuple::Current).expect("tuple is NULL"); + let id = tuple + .get_by_index::(1.try_into().unwrap()) + .expect("could not get id"); + let title = tuple + .get_by_index::(2.try_into().unwrap()) + .expect("could not get title"); + let description = tuple + .get_by_index::(3.try_into().unwrap()) + .expect("could not get description"); + let payload = tuple + .get_by_index::(4.try_into().unwrap()) + .expect("could not get payload"); warning!( "id={:?}, title={:?}, description={:?}, payload={:?}", @@ -47,8 +55,21 @@ unsafe fn trigger_example(fcinfo: pg_sys::FunctionCallInfo) -> pg_sys::Datum { payload ); - // return the inserting tuple, unchanged - trigdata.tg_trigtuple as pg_sys::Datum + // change the title + let mut tuple = tuple.into_owned(); + tuple + .set_by_name("title", "a new title") + .expect("failed to change the title"); + assert_eq!( + tuple.get_by_name::<&str>("title").unwrap().unwrap(), + "a new title" + ); + + // return the inserting tuple, which includes the changed title + match tuple.into_datum() { + Some(datum) => datum, + None => return pg_return_null(fcinfo), + } } else { panic!("not fired in the ON BEFORE INSERT context"); } diff --git a/pgx-macros/src/lib.rs b/pgx-macros/src/lib.rs index 65eac25fc..38f85f90c 100644 --- a/pgx-macros/src/lib.rs +++ b/pgx-macros/src/lib.rs @@ -641,7 +641,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..7c72556f9 100644 --- a/pgx/src/datum/from.rs +++ b/pgx/src/datum/from.rs @@ -10,36 +10,71 @@ 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::error::Error; use std::ffi::CStr; +use std::fmt::{Display, Formatter}; +use std::num::NonZeroUsize; -/// 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. +#[derive(Debug)] +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, + + /// The specified attribute number is invalid + NoSuchAttributeNumber(NonZeroUsize), + + /// The specified attribute name is invalid + NoSuchAttributeName(String), +} + +impl Display for TryFromDatumError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TryFromDatumError::IncompatibleTypes => f.write_str("Incompatible types"), + TryFromDatumError::NullDatumPointer => f.write_str("Null Datum pointer"), + TryFromDatumError::NoSuchAttributeNumber(_) => f.write_str("No such attribute number"), + TryFromDatumError::NoSuchAttributeName(_) => f.write_str("No such attribute name"), + } + } +} + +impl Error for TryFromDatumError {} + +/// 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 +85,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 +137,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 +149,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 +161,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 +173,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 +185,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 +197,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 +209,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 +221,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 +233,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 +247,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 +272,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 +283,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 +295,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 +308,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 +325,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 +339,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 +361,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 +381,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 +402,6 @@ impl FromDatum for PgBox { mut memory_context: PgMemoryContexts, datum: usize, is_null: bool, - _typoid: u32, ) -> Option where Self: Sized, @@ -399,11 +409,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..b4e74681a 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,73 @@ 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: + /// + /// ```rust,no_run + /// # use pgx::*; + /// # #[repr(transparent)] + /// # struct FooType(String); + /// # impl pgx::IntoDatum for FooType { + /// 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::VARCHAROID + /// } + /// + /// # fn into_datum(self) -> Option { + /// # todo!() + /// # } + /// # + /// # fn type_oid() -> pg_sys::Oid { + /// # 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 +243,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 +259,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 +275,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 +291,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 @@ -281,6 +369,7 @@ impl IntoDatum for () { /// for user types impl> IntoDatum for PgBox { + #[inline] fn into_datum(self) -> Option { if self.is_null() { None 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