Skip to content

Commit

Permalink
Low-level interop for PG arrays (#636)
Browse files Browse the repository at this point in the history
* c-shims for PG array functions
* suggested changes for shims
* Introduce RawArray abstraction

This offers safe accessors to the underlying ArrayType's fields,
without imposing any more burden on the data in question.

* Port ARR_HASNULL to RawArray
* Introduce fat pointer fns into RawArray

This also ports over ARR_DIMS(ArrayType*),
and ARR_NELEMS, which wraps ArrayGetNItems(ndim, *dims)

* Port ARR_DATA_PTR to RawArray::data
* Clarify safety requirements for RawArray::{dims, data}
* Port ARR_NULLBITMAP to RawArray::nulls
* Document test safety remarks
* sub pub from pgx::array extern fn
* Explain Rust type init requirements
* Expand on RawArray description

Includes a general usage note on lengths.

* Note reborrow in test
* Introduce even-lower-level ArrayPtr
* Rough draft of ArrayPtr and RawArray
* Merge ArrayPtr back into RawArray
* Cleanup and explanations for RawArray::{dims_mut, nulls}
* Add hint about lens
* Remove unnecessary unsafe on test
* Lift remarks into public docs

Includes links that are permanent to some version of Postgres.

* One last cleanup
* Remove dubious dims_mut function

Co-authored-by: Jubilee Young <[email protected]>
Co-authored-by: Jubilee <[email protected]>
  • Loading branch information
3 people authored Aug 26, 2022
1 parent ab650e6 commit 1d80c95
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 0 deletions.
31 changes: 31 additions & 0 deletions pgx-pg-sys/cshim/pgx-cshim.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Use of this source code is governed by the MIT license that can be found in the
#include "parser/parsetree.h"
#include "utils/memutils.h"
#include "utils/builtins.h"
#include "utils/array.h"


PGDLLEXPORT MemoryContext pgx_GetMemoryContextChunk(void *ptr);
Expand Down Expand Up @@ -110,3 +111,33 @@ PGDLLEXPORT char *pgx_GETSTRUCT(HeapTuple tuple);
char *pgx_GETSTRUCT(HeapTuple tuple) {
return GETSTRUCT(tuple);
}

PGDLLEXPORT char *pgx_ARR_DATA_PTR(ArrayType *arr);
char *pgx_ARR_DATA_PTR(ArrayType *arr) {
return ARR_DATA_PTR(arr);
}

PGDLLEXPORT int pgx_ARR_NELEMS(ArrayType *arr);
int pgx_ARR_NELEMS(ArrayType *arr) {
return ArrayGetNItems(arr->ndim, ARR_DIMS(arr));
}

PGDLLEXPORT bits8 *pgx_ARR_NULLBITMAP(ArrayType *arr);
bits8 *pgx_ARR_NULLBITMAP(ArrayType *arr) {
return ARR_NULLBITMAP(arr);
}

PGDLLEXPORT int pgx_ARR_NDIM(ArrayType *arr);
int pgx_ARR_NDIM(ArrayType *arr) {
return ARR_NDIM(arr);
}

PGDLLEXPORT bool pgx_ARR_HASNULL(ArrayType *arr);
bool pgx_ARR_HASNULL(ArrayType *arr) {
return ARR_HASNULL(arr);
}

PGDLLEXPORT int *pgx_ARR_DIMS(ArrayType *arr);
int *pgx_ARR_DIMS(ArrayType *arr){
return ARR_DIMS(arr);
}
84 changes: 84 additions & 0 deletions pgx-tests/src/tests/array_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All rights reserved.
Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/

use pgx::array::RawArray;
use pgx::*;
use serde_json::*;

Expand Down Expand Up @@ -99,6 +100,44 @@ fn return_zero_length_vec() -> Vec<i32> {
Vec::new()
}

#[pg_extern]
fn get_arr_nelems(arr: Array<i32>) -> libc::c_int {
// SAFETY: Eh it's fine, it's just a len check.
unsafe { RawArray::from_array(arr) }.unwrap().len() as _
}

#[pg_extern]
fn get_arr_data_ptr_nth_elem(arr: Array<i32>, elem: i32) -> Option<i32> {
// SAFETY: this is Known to be an Array from ArrayType,
// and it's valid-ish to see any bitpattern of an i32 inbounds of a slice.
unsafe {
let raw = RawArray::from_array(arr).unwrap().data::<i32>();
let slice = &(*raw.as_ptr());
slice.get(elem as usize).copied()
}
}

#[pg_extern]
fn display_get_arr_nullbitmap(arr: Array<i32>) -> String {
let raw = unsafe { RawArray::from_array(arr) }.unwrap();

if let Some(slice) = raw.nulls() {
// SAFETY: If the test has gotten this far, the ptr is good for 0+ bytes,
// so reborrow NonNull<[u8]> as &[u8] for the hot second we're looking at it.
let slice = unsafe { &*slice.as_ptr() };
// might panic if the array is len 0
format!("{:#010b}", slice[0])
} else {
String::from("")
}
}

#[pg_extern]
fn get_arr_ndim(arr: Array<i32>) -> libc::c_int {
// SAFETY: This is a valid ArrayType and it's just a field access.
unsafe { RawArray::from_array(arr) }.unwrap().dims().len() as _
}

#[pg_extern]
fn over_implicit_drop() -> Vec<i64> {
// Create an array of exactly Datum-sized numbers.
Expand Down Expand Up @@ -255,6 +294,51 @@ mod tests {
assert_eq!(json.0, json! {{"values": [1, 2, 3, null, 4]}});
}

#[pg_test]
fn test_arr_data_ptr() {
let len = Spi::get_one::<i32>("SELECT get_arr_nelems('{1,2,3,4,5}'::int[])")
.expect("failed to get SPI result");

assert_eq!(len, 5);
}

#[pg_test]
fn test_get_arr_data_ptr_nth_elem() {
let nth = Spi::get_one::<i32>("SELECT get_arr_data_ptr_nth_elem('{1,2,3,4,5}'::int[], 2)")
.expect("failed to get SPI result");

assert_eq!(nth, 3);
}

#[pg_test]
fn test_display_get_arr_nullbitmap() {
let bitmap_str = Spi::get_one::<String>(
"SELECT display_get_arr_nullbitmap(ARRAY[1,NULL,3,NULL,5]::int[])",
)
.expect("failed to get SPI result");

assert_eq!(bitmap_str, "0b00010101");

let bitmap_str =
Spi::get_one::<String>("SELECT display_get_arr_nullbitmap(ARRAY[1,2,3,4,5]::int[])")
.expect("failed to get SPI result");

assert_eq!(bitmap_str, "");
}

#[pg_test]
fn test_get_arr_ndim() {
let ndim = Spi::get_one::<i32>("SELECT get_arr_ndim(ARRAY[1,2,3,4,5]::int[])")
.expect("failed to get SPI result");

assert_eq!(ndim, 1);

let ndim = Spi::get_one::<i32>("SELECT get_arr_ndim('{{1,2,3},{4,5,6}}'::int[])")
.expect("failed to get SPI result");

assert_eq!(ndim, 2);
}

#[pg_test]
fn test_array_over_direct() {
let vals = crate::tests::array_tests::over_implicit_drop();
Expand Down
Loading

0 comments on commit 1d80c95

Please sign in to comment.