diff --git a/pgx-tests/sql/load-order.txt b/pgx-tests/sql/load-order.txt index 7f73f9bcc..cc942ecfa 100644 --- a/pgx-tests/sql/load-order.txt +++ b/pgx-tests/sql/load-order.txt @@ -23,3 +23,4 @@ tests_node_tests.generated.sql tests_pg_try_tests.generated.sql tests_xact_callback_tests.generated.sql tests_xid64_tests.generated.sql +tests_postgres_type_tests.generated.sql diff --git a/pgx-tests/src/tests/mod.rs b/pgx-tests/src/tests/mod.rs index 61557b020..fcaef57d7 100644 --- a/pgx-tests/src/tests/mod.rs +++ b/pgx-tests/src/tests/mod.rs @@ -19,6 +19,7 @@ mod node_tests; mod numeric_tests; mod pg_extern_args_tests; mod pg_try_tests; +mod postgres_type_tests; mod schema_tests; mod spi_tests; mod srf_tests; diff --git a/pgx-tests/src/tests/postgres_type_tests.rs b/pgx-tests/src/tests/postgres_type_tests.rs new file mode 100644 index 000000000..082aecb6a --- /dev/null +++ b/pgx-tests/src/tests/postgres_type_tests.rs @@ -0,0 +1,101 @@ +use pgx::*; +use serde::{Deserialize, Serialize}; +use std::ffi::CStr; +use std::str::FromStr; + +#[derive(Copy, Clone, PostgresType)] +#[pgvarlena_inoutfuncs] +pub struct VarlenaType { + a: f32, + b: f32, + c: i64, +} + +impl PgVarlenaInOutFuncs for VarlenaType { + fn input(input: &CStr) -> PgVarlena where { + let mut iter = input.to_str().unwrap().split(','); + let (a, b, c) = (iter.next(), iter.next(), iter.next()); + + let mut result = PgVarlena::::new(); + result.a = f32::from_str(a.unwrap()).expect("a is not a valid f32"); + result.b = f32::from_str(b.unwrap()).expect("b is not a valid f32"); + result.c = i64::from_str(c.unwrap()).expect("c is not a valid i64"); + result + } + + fn output(&self, buffer: &mut StringInfo) { + buffer.push_str(&format!("{},{},{}", self.a, self.b, self.c)) + } +} + +#[derive(Serialize, Deserialize, PostgresType)] +#[inoutfuncs] +pub struct CustomTextFormatSerializedType { + a: f32, + b: f32, + c: i64, +} + +impl InOutFuncs for CustomTextFormatSerializedType { + fn input(input: &CStr) -> Self { + let mut iter = input.to_str().unwrap().split(','); + let (a, b, c) = (iter.next(), iter.next(), iter.next()); + + CustomTextFormatSerializedType { + a: f32::from_str(a.unwrap()).expect("a is not a valid f32"), + b: f32::from_str(b.unwrap()).expect("b is not a valid f32"), + c: i64::from_str(c.unwrap()).expect("c is not a valid i64"), + } + } + + fn output(&self, buffer: &mut StringInfo) { + buffer.push_str(&format!("{},{},{}", self.a, self.b, self.c)) + } +} + +#[derive(Serialize, Deserialize, PostgresType)] +pub struct JsonType { + a: f32, + b: f32, + c: i64, +} + +#[cfg(any(test, feature = "pg_test"))] +mod tests { + #[allow(unused_imports)] + use crate as pgx_tests; + + use crate::tests::postgres_type_tests::{ + CustomTextFormatSerializedType, JsonType, VarlenaType, + }; + use pgx::*; + + #[pg_test] + fn test_mytype() { + let result = Spi::get_one::>("SELECT '1.0,2.0,3'::VarlenaType") + .expect("SPI returned NULL"); + assert_eq!(result.a, 1.0); + assert_eq!(result.b, 2.0); + assert_eq!(result.c, 3); + } + + #[pg_test] + fn test_serializedtype() { + let result = Spi::get_one::( + "SELECT '1.0,2.0,3'::CustomTextFormatSerializedType", + ) + .expect("SPI returned NULL"); + assert_eq!(result.a, 1.0); + assert_eq!(result.b, 2.0); + assert_eq!(result.c, 3); + } + + #[pg_test] + fn test_jsontype() { + let result = Spi::get_one::(r#"SELECT '{"a": 1.0, "b": 2.0, "c": 3}'::JsonType"#) + .expect("SPI returned NULL"); + assert_eq!(result.a, 1.0); + assert_eq!(result.b, 2.0); + assert_eq!(result.c, 3); + } +} diff --git a/pgx/src/datum/varlena.rs b/pgx/src/datum/varlena.rs index 13de07184..18ef5eeea 100644 --- a/pgx/src/datum/varlena.rs +++ b/pgx/src/datum/varlena.rs @@ -280,6 +280,30 @@ where Some(PgVarlena::::from_datum(datum)) } } + + unsafe fn from_datum_in_memory_context( + mut memory_context: PgMemoryContexts, + datum: usize, + is_null: bool, + _typoid: u32, + ) -> Option { + 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 + let detoasted = pg_sys::pg_detoast_datum_copy(datum as *mut pg_sys::varlena); + + // and we need to unpack it (if necessary), which will decompress it too + let varlena = pg_sys::pg_detoast_datum_packed(detoasted); + + // and now we return it as a &str + Some(PgVarlena::::from_datum(varlena as pg_sys::Datum)) + }) + } + } } impl IntoDatum for T