Skip to content

Commit c019e91

Browse files
Add FlatArray<'_, T> (pgcentralfoundation#2207)
Introduce a new Rust type for what ArrayTypes "really are": FlatArray. As an unsized type, it will primarily be used as `&FlatArray<'_, T>` or similar in actual extension code. The long-term plan is for FlatArray to phase out `pgrx::datum::Array<'_, T>` entirely, as it has several advantages over its predecessor: - It uses `Element`, a subset of `BorrowDatum`, to power its simplicity in iteration, making it more easily verifiable and maintainable. - It is the unsized type itself, allowing composing it with various Rusty pointer types like `&`, `&mut`, or `PBox`. - It is no longer endangered by performance cliffs or hidden allocations depending on the type of `T`. - Because of these previous traits, it is possible to soundly and safely allocate a fresh `FlatArray`, mutate it, and return that one allocation, instead of allocating an intermediate Vec in Rust memory to mutate! It also has an associated cost in that it no longer supports any `T` that requires complex unboxing of items like `datum::Array<'a, String>`. You must instead use `array.iter().map(closure)` to accomplish that. This itself summarizes the difference: `datum::Array` is effectively an iteration protocol, but `array::FlatArray` is an actual data structure. This addresses the implied feature request in, and thus supercedes, pgcentralfoundation#2086.
1 parent c5ad07a commit c019e91

12 files changed

Lines changed: 1137 additions & 21 deletions

pgrx-tests/src/tests/array_borrowed.rs

Lines changed: 533 additions & 0 deletions
Large diffs are not rendered by default.

pgrx-tests/src/tests/array_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ mod tests {
218218

219219
#[pg_test(expected = "attempt to add with overflow")]
220220
fn test_sum_array_i32_overflow() -> Result<Option<i64>, pgrx::spi::Error> {
221+
// Note that this test is calling a builtin, array_agg
221222
Spi::get_one::<i64>(
222223
"SELECT sum_array(a) FROM (SELECT array_agg(s) a FROM generate_series(1, 1000000) s) x;",
223224
)

pgrx-tests/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod aggregate_tests;
1111
mod anyarray_tests;
1212
mod anyelement_tests;
1313
mod anynumeric_tests;
14+
mod array_borrowed;
1415
mod array_tests;
1516
mod attributes_tests;
1617
mod bgworker_tests;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use pgrx::array::FlatArray;
2+
use pgrx::prelude::*;
3+
4+
// We could support this behavior but it would require allocating a fresh instance,
5+
// which is better represented by using PBox or similar for the input array.
6+
#[pg_extern]
7+
fn something<'a>(_arr: &mut FlatArray<'a, i32>) {
8+
todo!()
9+
}
10+
11+
fn main() {}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error[E0277]: cannot generate SQL schema for this function
2+
--> tests/compile-fail/array-as-non-mutable-args.rs:7:1
3+
|
4+
6 | #[pg_extern]
5+
| ------------ in this procedural macro expansion
6+
7 | fn something<'a>(_arr: &mut FlatArray<'a, i32>) {
7+
| ^^ some types in for<'b, 'a> fn(&'b mut FlatArray<'a, i32>) cannot be translated to SQL
8+
|
9+
= help: the trait `FunctionMetadata<_>` is not implemented for `for<'b, 'a> fn(&'b mut FlatArray<'a, i32>)`
10+
= note: this error originates in the attribute macro `pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info)
11+
12+
error[E0599]: `&mut FlatArray<'_, i32>` has no representation in SQL
13+
--> tests/compile-fail/array-as-non-mutable-args.rs:6:1
14+
|
15+
6 | #[pg_extern]
16+
| ^^^^^^^^^^^^ non-SQL type
17+
|
18+
= note: the following trait bounds were not satisfied:
19+
`&mut FlatArray<'_, i32>: SqlTranslatable`
20+
which is required by `&&mut FlatArray<'_, i32>: SqlTranslatable`
21+
= note: this error originates in the attribute macro `pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info)
22+
23+
error[E0277]: `&mut FlatArray<'_, i32>` cannot be passed into a Postgres function as a Datum
24+
--> tests/compile-fail/array-as-non-mutable-args.rs:7:18
25+
|
26+
6 | #[pg_extern]
27+
| ------------ in this procedural macro expansion
28+
7 | fn something<'a>(_arr: &mut FlatArray<'a, i32>) {
29+
| ^^^^ the trait `ArgAbi<'_>` is not implemented for `&mut FlatArray<'_, i32>`
30+
|
31+
= help: the following other types implement trait `ArgAbi<'fcx>`:
32+
&'fcx T
33+
&'fcx [u8]
34+
&'fcx str
35+
&MemCx<'fcx>
36+
*mut FunctionCallInfoBaseData
37+
AnyArray
38+
AnyElement
39+
AnyNumeric
40+
and $N others
41+
= note: `ArgAbi<'_>` is implemented for `&FlatArray<'_, i32>`, but not for `&mut FlatArray<'_, i32>`
42+
note: required by a bound in `pgrx::callconv::Args::<'a, 'fcx>::next_arg_unchecked`
43+
--> $WORKSPACE/pgrx/src/callconv.rs
44+
|
45+
| pub unsafe fn next_arg_unchecked<T: ArgAbi<'fcx>>(&mut self) -> Option<T> {
46+
| ^^^^^^^^^^^^ required by this bound in `Args::<'a, 'fcx>::next_arg_unchecked`
47+
= note: this error originates in the attribute macro `pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use pgrx::array::FlatArray;
2+
use pgrx::memcx::MemCx;
3+
use pgrx::palloc::PBox;
4+
use pgrx::prelude::*;
5+
6+
#[pg_extern]
7+
fn something<'a>(_mcx: &MemCx<'a>) -> PBox<'a, FlatArray<'a, FlatArray<'a, i32>>> {
8+
todo!()
9+
}
10+
11+
fn main() {}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error[E0277]: cannot generate SQL schema for this function
2+
--> tests/compile-fail/no-arrays-of-arrays.rs:7:1
3+
|
4+
6 | #[pg_extern]
5+
| ------------ in this procedural macro expansion
6+
7 | fn something<'a>(_mcx: &MemCx<'a>) -> PBox<'a, FlatArray<'a, FlatArray<'a, i32>>> {
7+
| ^^ some types in for<'b, 'a> fn(&'b MemCx<'a>) -> PBox<'a, FlatArray<'a, FlatArray<'a, i32>>> cannot be translated to SQL
8+
|
9+
= help: the trait `FunctionMetadata<_>` is not implemented for `for<'b, 'a> fn(&'b MemCx<'a>) -> PBox<'a, FlatArray<'a, FlatArray<'a, i32>>>`
10+
= note: this error originates in the attribute macro `pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info)
11+
12+
error[E0599]: `PBox<'_, FlatArray<'_, FlatArray<'_, i32>>>` has no representation in SQL
13+
--> tests/compile-fail/no-arrays-of-arrays.rs:6:1
14+
|
15+
6 | #[pg_extern]
16+
| ^^^^^^^^^^^^ non-SQL type
17+
|
18+
::: $WORKSPACE/pgrx/src/palloc/pbox.rs
19+
|
20+
| pub struct PBox<'mcx, T: ?Sized> {
21+
| -------------------------------- doesn't satisfy `_: SqlTranslatable`
22+
|
23+
::: $WORKSPACE/pgrx/src/array/flat_array.rs
24+
|
25+
| pub struct FlatArray<'mcx, T: ?Sized> {
26+
| ------------------------------------- doesn't satisfy `FlatArray<'_, FlatArray<'_, i32>>: SqlTranslatable`
27+
|
28+
note: if you're trying to build a new `PBox<'_, FlatArray<'_, FlatArray<'_, i32>>>`, consider using `PBox::<'mcx, T>::from_raw_in` which returns `PBox<'_, _>`
29+
--> $WORKSPACE/pgrx/src/palloc/pbox.rs
30+
|
31+
| pub unsafe fn from_raw_in(ptr: NonNull<T>, _cx: &MemCx<'mcx>) -> PBox<'mcx, T> {
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
= note: the following trait bounds were not satisfied:
34+
`FlatArray<'_, FlatArray<'_, i32>>: SqlTranslatable`
35+
which is required by `PBox<'_, FlatArray<'_, FlatArray<'_, i32>>>: SqlTranslatable`
36+
`PBox<'_, FlatArray<'_, FlatArray<'_, i32>>>: SqlTranslatable`
37+
which is required by `&PBox<'_, FlatArray<'_, FlatArray<'_, i32>>>: SqlTranslatable`
38+
= note: this error originates in the attribute macro `pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use pgrx::array::FlatArray;
2+
use pgrx::palloc::PBox;
3+
use pgrx::prelude::*;
4+
5+
// We can support this behavior, it just requires allocating a fresh instance,
6+
// which is something we want to approach systematically for similar datums.
7+
#[pg_extern]
8+
fn something<'a>(_arr: PBox<'a, FlatArray<'a, i32>>) {
9+
todo!()
10+
}
11+
12+
fn main() {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error[E0277]: `PBox<'_, FlatArray<'_, i32>>` cannot be passed into a Postgres function as a Datum
2+
--> tests/todo/array-as-pbox.rs:8:18
3+
|
4+
7 | #[pg_extern]
5+
| ------------ in this procedural macro expansion
6+
8 | fn something<'a>(_arr: PBox<'a, FlatArray<'a, i32>>) {
7+
| ^^^^ the trait `ArgAbi<'_>` is not implemented for `PBox<'_, FlatArray<'_, i32>>`
8+
|
9+
= help: the following other types implement trait `ArgAbi<'fcx>`:
10+
&'fcx T
11+
&'fcx [u8]
12+
&'fcx str
13+
&MemCx<'fcx>
14+
*mut FunctionCallInfoBaseData
15+
AnyArray
16+
AnyElement
17+
AnyNumeric
18+
and $N others
19+
note: required by a bound in `pgrx::callconv::Args::<'a, 'fcx>::next_arg_unchecked`
20+
--> $WORKSPACE/pgrx/src/callconv.rs
21+
|
22+
| pub unsafe fn next_arg_unchecked<T: ArgAbi<'fcx>>(&mut self) -> Option<T> {
23+
| ^^^^^^^^^^^^ required by this bound in `Args::<'a, 'fcx>::next_arg_unchecked`
24+
= note: this error originates in the attribute macro `pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info)

pgrx/src/array.rs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,31 @@
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+
#![allow(unused)]
12+
#![deny(unsafe_op_in_unsafe_fn)]
13+
use crate::datum::{Array, BorrowDatum, Datum};
14+
use crate::layout::{Align, Layout};
15+
use crate::memcx::MemCx;
16+
use crate::nullable::Nullable;
17+
use crate::palloc::PBox;
18+
use crate::pgrx_sql_entity_graph::metadata::{
19+
ArgumentError, Returns, ReturnsError, SqlMapping, SqlTranslatable,
20+
};
1221
use crate::toast::{Toast, Toasty};
1322
use crate::{layout, pg_sys, varlena};
14-
use bitvec::ptr::{self as bitptr, BitPtr, BitPtrError, Mut};
15-
use bitvec::slice::BitSlice;
23+
use bitvec::ptr::{self as bitptr, BitPtr, BitPtrError, Const, Mut};
24+
use bitvec::slice::{self as bitslice, BitSlice};
25+
use core::iter::{ExactSizeIterator, FusedIterator};
26+
use core::marker::PhantomData;
1627
use core::ptr::{self, NonNull};
17-
use core::slice;
28+
use core::{ffi, mem, slice};
1829

1930
mod element;
31+
mod flat_array;
2032
mod port;
2133

2234
pub use element::Element;
35+
pub use flat_array::{ArrayAllocError, FlatArray};
2336

2437
/**
2538
An aligned, dereferenceable `NonNull<ArrayType>` with low-level accessors.
@@ -165,8 +178,7 @@ impl RawArray {
165178
}
166179
}
167180

168-
/**
169-
A slice of the dimensions.
181+
/** A slice describing the array's dimensions.
170182
171183
Oxidized form of [ARR_DIMS(ArrayType*)][ARR_DIMS].
172184
The length will be within 0..=[pg_sys::MAXDIM].
@@ -189,6 +201,7 @@ impl RawArray {
189201
}
190202

191203
/// The flattened length of the array over every single element.
204+
///
192205
/// Includes all items, even the ones that might be null.
193206
///
194207
/// # Panics
@@ -229,8 +242,7 @@ impl RawArray {
229242
// This field is an "int32" in Postgres
230243
}
231244

232-
/**
233-
Equivalent to [ARR_HASNULL(ArrayType*)][ARR_HASNULL].
245+
/** Equivalent to [ARR_HASNULL(ArrayType*)][ARR_HASNULL].
234246
235247
Note this means that it only asserts that there MIGHT be a null
236248
@@ -250,16 +262,26 @@ impl RawArray {
250262
}
251263

252264
#[inline]
253-
fn nulls_bitptr(&mut self) -> Option<BitPtr<Mut, u8>> {
265+
fn nulls_bitptr(&self) -> Option<BitPtr<Const, u8>> {
266+
let nulls_ptr = unsafe { port::ARR_NULLBITMAP(self.ptr.as_ptr()) }.cast_const();
267+
match BitPtr::try_from(nulls_ptr) {
268+
Ok(ptr) => Some(ptr),
269+
Err(BitPtrError::Null(_)) => None,
270+
Err(BitPtrError::Misaligned(_)) => unreachable!(),
271+
}
272+
}
273+
274+
#[inline]
275+
fn nulls_mut_bitptr(&mut self) -> Option<BitPtr<Mut, u8>> {
276+
let nulls_ptr = unsafe { port::ARR_NULLBITMAP(self.ptr.as_ptr()) };
254277
match BitPtr::try_from(self.nulls_mut_ptr()) {
255278
Ok(ptr) => Some(ptr),
256279
Err(BitPtrError::Null(_)) => None,
257-
Err(BitPtrError::Misaligned(_)) => unreachable!("impossible to misalign *mut u8"),
280+
Err(BitPtrError::Misaligned(_)) => unreachable!(),
258281
}
259282
}
260283

261-
/**
262-
Oxidized form of [ARR_NULLBITMAP(ArrayType*)][ARR_NULLBITMAP]
284+
/** Oxidized form of [ARR_NULLBITMAP(ArrayType*)][ARR_NULLBITMAP]
263285
264286
If this returns None, the array cannot have nulls.
265287
If this returns Some, it points to the bitslice that marks nulls in this array.
@@ -278,8 +300,8 @@ impl RawArray {
278300
NonNull::new(ptr::slice_from_raw_parts_mut(self.nulls_mut_ptr(), len))
279301
}
280302

281-
/**
282-
The [bitvec] equivalent of [RawArray::nulls].
303+
/** The [bitvec] equivalent of [RawArray::nulls].
304+
283305
If this returns `None`, the array cannot have nulls.
284306
If this returns `Some`, it points to the bitslice that marks nulls in this array.
285307
@@ -291,23 +313,21 @@ impl RawArray {
291313
[ARR_NULLBITMAP]: <https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/utils/array.h;h=4ae6c3be2f8b57afa38c19af2779f67c782e4efc;hb=278273ccbad27a8834dfdf11895da9cd91de4114#l293>
292314
*/
293315
pub fn nulls_bitslice(&mut self) -> Option<NonNull<BitSlice<u8>>> {
294-
NonNull::new(bitptr::bitslice_from_raw_parts_mut(self.nulls_bitptr()?, self.len()))
316+
NonNull::new(bitptr::bitslice_from_raw_parts_mut(self.nulls_mut_bitptr()?, self.len()))
295317
}
296318

297-
/**
298-
Checks the array for any NULL values by assuming it is a proper varlena array,
319+
/** Checks the array for any NULL values
299320
300321
# Safety
301-
302322
* This requires every index is valid to read or correctly marked as null.
323+
303324
*/
304325
pub unsafe fn any_nulls(&self) -> bool {
305326
// SAFETY: Caller asserted safety conditions.
306327
unsafe { pg_sys::array_contains_nulls(self.ptr.as_ptr()) }
307328
}
308329

309-
/**
310-
Oxidized form of [ARR_DATA_PTR(ArrayType*)][ARR_DATA_PTR]
330+
/** Oxidized form of [ARR_DATA_PTR(ArrayType*)][ARR_DATA_PTR]
311331
312332
# Safety
313333
@@ -395,7 +415,7 @@ impl Toasty for RawArray {
395415
///
396416
/// This allows for it to be copied from a Rust slice to a Postgres array,
397417
/// and obtain a Rust slice from a Postgres array if it contains no nulls.
398-
pub unsafe trait Scalar: Sized + Copy {
418+
pub unsafe trait Scalar: Sized + Copy + Element {
399419
const OID: pg_sys::Oid;
400420
}
401421

0 commit comments

Comments
 (0)