Skip to content

Commit 52b142c

Browse files
committed
Adding support for contrib/intarray style fast-allocated arrays
1 parent a7a95bf commit 52b142c

File tree

5 files changed

+375
-5
lines changed

5 files changed

+375
-5
lines changed

pgrx-tests/src/tests/array_tests.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ fn validate_cstring_array<'a>(
183183
Ok(true)
184184
}
185185

186+
#[pg_extern]
187+
fn int_array_roundtrip(arr: Array<i32>) -> Array<i32> {
188+
arr
189+
}
190+
186191
#[cfg(any(test, feature = "pg_test"))]
187192
#[pgrx::pg_schema]
188193
mod tests {
@@ -506,4 +511,85 @@ mod tests {
506511

507512
Ok(())
508513
}
514+
515+
#[pg_test]
516+
fn test_int_array_roundtrip_test() -> Result<(), Box<dyn std::error::Error>> {
517+
let a = Spi::get_one::<Vec<i32>>("SELECT int_array_roundtrip(ARRAY[1, 2, 3, 4, 5])")?;
518+
519+
assert_eq!(a, Some(vec![1, 2, 3, 4, 5]));
520+
521+
Ok(())
522+
}
523+
524+
#[pg_test]
525+
fn test_array_new_from_slice() -> Result<(), Box<dyn std::error::Error>> {
526+
let a = Spi::get_one::<Array<i8>>("SELECT ARRAY[1, 2, 3, 4, 5]::\"char\"[]")?
527+
.expect("spi result was NULL");
528+
let b = Array::<i8>::new_from_slice(&[1, 2, 3, 4, 5]).expect("failed to create array");
529+
530+
assert_eq!(a.as_slice()?, b.as_slice()?);
531+
532+
let a = Spi::get_one::<Array<i16>>("SELECT ARRAY[1, 2, 3, 4, 5]::smallint[]")?
533+
.expect("spi result was NULL");
534+
let b = Array::<i16>::new_from_slice(&[1, 2, 3, 4, 5]).expect("failed to create array");
535+
536+
assert_eq!(a.as_slice()?, b.as_slice()?);
537+
538+
let a = Spi::get_one::<Array<i32>>("SELECT ARRAY[1, 2, 3, 4, 5]::integer[]")?
539+
.expect("spi result was NULL");
540+
let b = Array::<i32>::new_from_slice(&[1, 2, 3, 4, 5]).expect("failed to create array");
541+
542+
assert_eq!(a.as_slice()?, b.as_slice()?);
543+
544+
let a = Spi::get_one::<Array<i64>>("SELECT ARRAY[1, 2, 3, 4, 5]::bigint[]")?
545+
.expect("spi result was NULL");
546+
let b = Array::<i64>::new_from_slice(&[1, 2, 3, 4, 5]).expect("failed to create array");
547+
548+
assert_eq!(a.as_slice()?, b.as_slice()?);
549+
550+
let a = Spi::get_one::<Array<f32>>("SELECT ARRAY[1.0, 2.0, 3.0, 4.0, 5.0]::float4[]")?
551+
.expect("spi result was NULL");
552+
let b = Array::<f32>::new_from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0])
553+
.expect("failed to create array");
554+
555+
assert_eq!(a.as_slice()?, b.as_slice()?);
556+
557+
let a = Spi::get_one::<Array<f64>>("SELECT ARRAY[1.0, 2.0, 3.0, 4.0, 5.0]::float8[]")?
558+
.expect("spi result was NULL");
559+
let b = Array::<f64>::new_from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0])
560+
.expect("failed to create array");
561+
562+
assert_eq!(a.as_slice()?, b.as_slice()?);
563+
564+
Ok(())
565+
}
566+
567+
#[pg_test]
568+
fn test_new_array_with_len() -> Result<(), Box<dyn std::error::Error>> {
569+
let a = Array::<i8>::new_with_len(5).expect("failed to create array");
570+
571+
assert_eq!(a.as_slice()?, &[0, 0, 0, 0, 0]);
572+
573+
let a = Array::<i16>::new_with_len(5).expect("failed to create array");
574+
575+
assert_eq!(a.as_slice()?, &[0, 0, 0, 0, 0]);
576+
577+
let a = Array::<i32>::new_with_len(5).expect("failed to create array");
578+
579+
assert_eq!(a.as_slice()?, &[0, 0, 0, 0, 0]);
580+
581+
let a = Array::<i64>::new_with_len(5).expect("failed to create array");
582+
583+
assert_eq!(a.as_slice()?, &[0, 0, 0, 0, 0]);
584+
585+
let a = Array::<f32>::new_with_len(5).expect("failed to create array");
586+
587+
assert_eq!(a.as_slice()?, &[0.0, 0.0, 0.0, 0.0, 0.0]);
588+
589+
let a = Array::<f64>::new_with_len(5).expect("failed to create array");
590+
591+
assert_eq!(a.as_slice()?, &[0.0, 0.0, 0.0, 0.0, 0.0]);
592+
593+
Ok(())
594+
}
509595
}

pgrx/src/array.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
//LICENSE
99
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
1010
#![allow(clippy::precedence)]
11-
use crate::datum::Array;
11+
use crate::datum::{Array, IntoDatum, UnboxDatum};
1212
use crate::toast::{Toast, Toasty};
13-
use crate::{layout, pg_sys, varlena};
13+
use crate::{layout, pg_sys, set_varsize_4b, varlena, PgMemoryContexts};
1414
use bitvec::ptr::{self as bitptr, BitPtr, BitPtrError, Mut};
1515
use bitvec::slice::BitSlice;
1616
use core::ptr::{self, NonNull};
1717
use core::slice;
18+
use pgrx_pg_sys::ArrayType;
1819

1920
mod port;
2021

@@ -373,10 +374,65 @@ impl RawArray {
373374
let ptr = self.ptr.as_ptr().cast::<u8>();
374375
ptr.wrapping_add(unsafe { varlena::varsize_any(ptr.cast()) })
375376
}
377+
378+
/// Slightly faster than new_array_type_with_len(0)
379+
pub fn new_empty_array_type<T>() -> Result<RawArray, ArrayAllocError>
380+
where
381+
T: IntoDatum,
382+
T: UnboxDatum,
383+
T: Sized,
384+
{
385+
unsafe {
386+
let array_type = pg_sys::construct_empty_array(T::type_oid());
387+
let array_type =
388+
NonNull::new(array_type).ok_or(ArrayAllocError::MemoryAllocationFailed)?;
389+
Ok(RawArray::from_ptr(array_type))
390+
}
391+
}
392+
393+
/// Rustified version of new_intArrayType(int num) from https://github.com/postgres/postgres/blob/master/contrib/intarray/_int_tool.c#L219
394+
pub fn new_array_type_with_len<T>(len: usize) -> Result<RawArray, ArrayAllocError>
395+
where
396+
T: IntoDatum,
397+
T: UnboxDatum,
398+
T: Sized,
399+
{
400+
if len == 0 {
401+
return Self::new_empty_array_type::<T>();
402+
}
403+
let elem_size = std::mem::size_of::<T>();
404+
let nbytes: usize = port::ARR_OVERHEAD_NONULLS(1) + elem_size * len;
405+
406+
unsafe {
407+
let array_type = PgMemoryContexts::For(pg_sys::CurrentMemoryContext).palloc0(nbytes)
408+
as *mut ArrayType;
409+
if array_type.is_null() {
410+
return Err(ArrayAllocError::MemoryAllocationFailed);
411+
}
412+
set_varsize_4b(array_type as *mut pg_sys::varlena, nbytes as i32);
413+
(*array_type).ndim = 1;
414+
(*array_type).dataoffset = 0; /* marker for no null bitmap */
415+
(*array_type).elemtype = T::type_oid();
416+
417+
let ndims = port::ARR_DIMS(array_type);
418+
*ndims = len as i32; // equivalent of ARR_DIMS(r)[0] = num;
419+
let arr_lbound = port::ARR_LBOUND(array_type);
420+
*arr_lbound = 1;
421+
422+
let array_type = NonNull::new_unchecked(array_type);
423+
Ok(RawArray::from_ptr(array_type))
424+
}
425+
}
376426
}
377427

378428
impl Toasty for RawArray {
379429
unsafe fn drop_toast(&mut self) {
380430
unsafe { pg_sys::pfree(self.ptr.as_ptr().cast()) }
381431
}
382432
}
433+
434+
#[derive(thiserror::Error, Debug, Copy, Clone, Eq, PartialEq)]
435+
pub enum ArrayAllocError {
436+
#[error("Failed to allocate memory for Array")]
437+
MemoryAllocationFailed,
438+
}

pgrx/src/array/port.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,22 @@ pub(super) unsafe fn ARR_DATA_PTR(a: *mut pg_sys::ArrayType) -> *mut u8 {
126126

127127
unsafe { a.cast::<u8>().add(ARR_DATA_OFFSET(a)) }
128128
}
129+
130+
/// Returns a pointer to the lower bounds of the array.
131+
/// # Safety
132+
/// Does a field access, but doesn't deref out of bounds of ArrayType. The caller asserts that
133+
/// `a` is a properly allocated [`pg_sys::ArrayType`]
134+
///
135+
/// [`pg_sys::ArrayType`] is typically allocated past its size, and its somewhere in that region
136+
/// that the returned pointer points, so don't attempt to `pfree` it.
137+
#[inline(always)]
138+
pub(super) unsafe fn ARR_LBOUND(a: *mut pg_sys::ArrayType) -> *mut i32 {
139+
// #define ARR_LBOUND(a) \
140+
// ((int *) (((char *) (a)) + sizeof(ArrayType) + \
141+
// sizeof(int) * ARR_NDIM(a)))
142+
143+
a.cast::<u8>()
144+
.add(std::mem::size_of::<pg_sys::ArrayType>())
145+
.add(std::mem::size_of::<i32>() * ((*a).ndim as usize))
146+
.cast::<i32>()
147+
}

pgrx/src/callconv.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ use crate::datum::{Range, RangeSubType};
2020
use crate::heap_tuple::PgHeapTuple;
2121
use crate::layout::PassBy;
2222
use crate::nullable::Nullable;
23-
use crate::pg_sys;
2423
use crate::pgbox::*;
2524
use crate::rel::PgRelation;
25+
use crate::{pg_sys, Array};
2626
use crate::{PgBox, PgMemoryContexts};
2727

2828
use core::marker::PhantomData;
@@ -600,6 +600,19 @@ where
600600
}
601601
}
602602

603+
unsafe impl<'mcx, T: UnboxDatum> BoxRet for Array<'mcx, T>
604+
where
605+
T: IntoDatum,
606+
{
607+
#[inline]
608+
unsafe fn box_into<'fcx>(self, fcinfo: &mut FcInfo<'fcx>) -> Datum<'fcx> {
609+
match self.into_datum() {
610+
Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
611+
None => fcinfo.return_null(),
612+
}
613+
}
614+
}
615+
603616
unsafe impl<T: Copy> BoxRet for PgVarlena<T> {
604617
unsafe fn box_into<'fcx>(self, fcinfo: &mut FcInfo<'fcx>) -> Datum<'fcx> {
605618
match self.into_datum() {

0 commit comments

Comments
 (0)