Skip to content

Commit c0ce2a8

Browse files
Use Layout::size_matches::<T> for Array cleanup (#881)
* Add `Layout.size_matches::<T>` and PassBy enum * New Layout cleans up `Array::as_slice` innards * Relax lifetime bounds on variadic array test This test fn erroneously asked for overly specific lifetime bounds.
1 parent 1cda3da commit c0ce2a8

File tree

3 files changed

+46
-47
lines changed

3 files changed

+46
-47
lines changed

pgx-tests/src/tests/variadic_tests.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ mod test {
1313
use pgx::VariadicArray;
1414

1515
#[pg_extern]
16-
fn func_with_variadic_array_args<'a>(
17-
_field: &'a str,
18-
values: VariadicArray<&'a str>,
19-
) -> String {
16+
fn func_with_variadic_array_args(_field: &str, values: VariadicArray<&str>) -> String {
2017
values.get(0).unwrap().unwrap().to_string()
2118
}
2219
}
@@ -31,10 +28,10 @@ mod tests {
3128

3229
#[pg_test]
3330
fn test_func_with_variadic_array_args() {
34-
let result = Spi::get_one::<&str>(
31+
let result = Spi::get_one::<String>(
3532
"SELECT test.func_with_variadic_array_args('test', 'a', 'b', 'c');",
3633
)
3734
.expect("didn't get SPI result");
38-
assert_eq!(result, "a");
35+
assert_eq!(result, String::from("a"));
3936
}
4037
}

pgx/src/datum/array.rs

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub struct Array<'a, T: FromDatum> {
6060
datum_palloc: Option<NonNull<pg_sys::Datum>>,
6161
elem_slice: &'a [pg_sys::Datum],
6262
null_slice: NullKind<'a>,
63-
elem_layout: Option<Layout>,
63+
elem_layout: Layout,
6464
_marker: PhantomData<T>,
6565
}
6666

@@ -129,7 +129,7 @@ impl<'a, T: FromDatum> Array<'a, T> {
129129
unsafe fn deconstruct_from(
130130
ptr: NonNull<pg_sys::varlena>,
131131
raw: RawArray,
132-
layout: Layout,
132+
elem_layout: Layout,
133133
) -> Array<'a, T> {
134134
let oid = raw.oid();
135135
let len = raw.len();
@@ -153,9 +153,9 @@ impl<'a, T: FromDatum> Array<'a, T> {
153153
pg_sys::deconstruct_array(
154154
array,
155155
oid,
156-
layout.size.as_typlen().into(),
157-
layout.passbyval,
158-
layout.align.as_typalign(),
156+
elem_layout.size.as_typlen().into(),
157+
matches!(elem_layout.pass, PassBy::Value),
158+
elem_layout.align.as_typalign(),
159159
&mut elements,
160160
&mut nulls,
161161
&mut nelems,
@@ -195,7 +195,7 @@ impl<'a, T: FromDatum> Array<'a, T> {
195195
datum_palloc: NonNull::new(elements),
196196
elem_slice: /* SAFETY: &[Datum] from palloc'd [Datum] */ unsafe { slice::from_raw_parts(elements, nelems) },
197197
null_slice,
198-
elem_layout: Some(layout),
198+
elem_layout,
199199
_marker: PhantomData,
200200
}
201201
}
@@ -217,33 +217,16 @@ impl<'a, T: FromDatum> Array<'a, T> {
217217
if you are sure your usage is sound, consider RawArray"
218218
)]
219219
pub fn as_slice(&self) -> &[T] {
220-
if let Some(Layout { size, passbyval, .. }) = &self.elem_layout {
221-
if self.null_slice.any() {
222-
panic!("null detected: can't expose potentially uninit data as a slice!")
223-
}
224-
const DATUM_SIZE: usize = mem::size_of::<pg_sys::Datum>();
225-
let sizeof_type = match (passbyval, mem::size_of::<T>(), size.try_as_usize()) {
226-
(true, rs @ (1 | 2 | 4 | 8), Some(pg @ (1 | 2 | 4 | 8))) if rs == pg => rs,
227-
(true, _, _) => panic!("invalid sizes for pass-by-value datum"),
228-
(false, DATUM_SIZE, _) => DATUM_SIZE,
229-
(false, _, _) => panic!("invalid sizes for pass-by-reference datum"),
230-
};
231-
match (sizeof_type, self.raw.as_ref()) {
232-
// SAFETY: Rust slice layout matches Postgres data layout and this array is "owned"
233-
(1 | 2 | 4, Some(raw)) => unsafe { raw.assume_init_data_slice::<T>() },
234-
(DATUM_SIZE, _) => {
235-
let sizeof_datums = mem::size_of_val(self.elem_slice);
236-
unsafe {
237-
slice::from_raw_parts(
238-
self.elem_slice.as_ptr() as *const T,
239-
sizeof_datums / sizeof_type,
240-
)
241-
}
242-
}
243-
(_, _) => panic!("no correctly-sized slice exists"),
244-
}
245-
} else {
246-
panic!("not enough type information to slice correctly")
220+
const DATUM_SIZE: usize = mem::size_of::<pg_sys::Datum>();
221+
if self.null_slice.any() {
222+
panic!("null detected: can't expose potentially uninit data as a slice!")
223+
}
224+
match (self.elem_layout.size_matches::<T>(), self.raw.as_ref()) {
225+
// SAFETY: Rust slice layout matches Postgres data layout and this array is "owned"
226+
(Some(1 | 2 | 4 | DATUM_SIZE), Some(raw)) => unsafe {
227+
raw.assume_init_data_slice::<T>()
228+
},
229+
(_, _) => panic!("no correctly-sized slice exists"),
247230
}
248231
}
249232

pgx/src/layout.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/*!
22
Code for interfacing with the layout in memory (and on disk?) of various data types within Postgres.
3-
Prefer to minimize exposing the contents of this module to public callers of the pgx library:
43
This is not a mature module yet so prefer to avoid exposing the contents to public callers of pgx,
54
as this is error-prone stuff to tamper with. It is easy to corrupt the database if an error is made.
65
Yes, even though its main block of code duplicates htup::DatumWithTypeInfo.
@@ -17,12 +16,18 @@ use core::mem;
1716

1817
/// Postgres type information, corresponds to part of a row in pg_type
1918
/// This layout describes T, not &T, even if passbyval: false, which would mean the datum array is effectively &[&T]
20-
#[derive(Debug, Clone)]
19+
#[derive(Clone, Copy, Debug)]
2120
pub(crate) struct Layout {
2221
// We could add more fields to this if we are curious enough, as the function we call pulls an entire row
23-
pub align: Align, // typalign
24-
pub size: Size, // typlen
25-
pub passbyval: bool, // typbyval
22+
pub align: Align, // typalign
23+
pub size: Size, // typlen
24+
pub pass: PassBy, // typbyval
25+
}
26+
27+
#[derive(Clone, Copy, Debug, PartialEq)]
28+
pub(crate) enum PassBy {
29+
Ref,
30+
Value,
2631
}
2732

2833
impl Layout {
@@ -41,13 +46,27 @@ impl Layout {
4146
Layout {
4247
align: Align::try_from(typalign).unwrap(),
4348
size: Size::try_from(typlen).unwrap(),
44-
passbyval,
49+
pass: if passbyval { PassBy::Value } else { PassBy::Ref },
50+
}
51+
}
52+
53+
// Attempt to discern if a given Postgres and Rust layout are "matching" in some sense
54+
// Some(usize) if they seem to agree on one, None if they do not
55+
pub(crate) fn size_matches<T>(&self) -> Option<usize> {
56+
const DATUM_SIZE: usize = mem::size_of::<pg_sys::Datum>();
57+
match (self.pass, mem::size_of::<T>(), self.size.try_as_usize()) {
58+
(PassBy::Value, rs @ (1 | 2 | 4 | 8), Some(pg @ (1 | 2 | 4 | 8))) if rs == pg => {
59+
Some(rs)
60+
}
61+
(PassBy::Value, _, _) => None,
62+
(PassBy::Ref, DATUM_SIZE, _) => Some(DATUM_SIZE),
63+
(PassBy::Ref, _, _) => None,
4564
}
4665
}
4766
}
4867

4968
#[repr(usize)]
50-
#[derive(Debug, Clone, PartialEq)]
69+
#[derive(Clone, Copy, Debug, PartialEq)]
5170
pub(crate) enum Align {
5271
Byte = mem::align_of::<u8>(),
5372
Short = mem::align_of::<libc::c_short>(),
@@ -80,7 +99,7 @@ impl Align {
8099
}
81100
}
82101

83-
#[derive(Debug, Clone, PartialEq)]
102+
#[derive(Clone, Copy, Debug, PartialEq)]
84103
pub(crate) enum Size {
85104
CStr,
86105
Varlena,

0 commit comments

Comments
 (0)