Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for uuid PostgreSQL type #172

Merged
merged 3 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Feel free to join our [Discord Server](https://discord.gg/hPb93Y9).
- Return `impl std::iter::Iterator<Item = T> where T: IntoDatum` for automatic set-returning-functions (both `RETURNS SETOF` and `RETURNS TABLE (...)` variants
- DDL automatically generated

#### Most Postgres Datatypes Transparently Converted to Rust
#### Most Postgres Data Types Transparently Converted to Rust

Postgres Type | Rust Type (as `Option<T>`)
--------------|-----------
Expand Down Expand Up @@ -88,6 +88,7 @@ Postgres Type | Rust Type (as `Option<T>`)
`ARRAY[]::<type>` | `Vec<Option<T>>` or `pgx::Array<T>` (zero-copy)
`NULL` | `Option::None`
`internal` | `pgx::PgBox<T>` where `T` is any Rust/Postgres struct
`uuid` | `pgx::Uuid([u8; 16])`

There are also `IntoDatum` and `FromDatum` traits for implementing additional type conversions,
along with `#[derive(PostgresType)]` and `#[derive(PostgresEnum)]` for automatic conversion of
Expand All @@ -99,7 +100,7 @@ custom types.
- `#[derive(PostgresEnum)]` to use a Rust enum as a Postgres enum
- DDL automatically generated

#### Server Prgramming Interface (SPI)
#### Server Programming Interface (SPI)
- Safe access into SPI
- Transparently return owned Datums from an SPI context

Expand Down Expand Up @@ -166,7 +167,7 @@ my_extension/
└── lib.rs
```

The new extension includes an example, so you can go ahead an run it right away.
The new extension includes an example, so you can go ahead and run it right away.

### 4. Run your extension

Expand Down
1 change: 1 addition & 0 deletions pgx-tests/sql/load-order.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ tests_pg_try_tests.generated.sql
tests_spi_tests.generated.sql
tests_xact_callback_tests.generated.sql
tests_xid64_tests.generated.sql
tests_uuid_tests.generated.sql
1 change: 1 addition & 0 deletions pgx-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod schema_tests;
mod spi_tests;
mod srf_tests;
mod struct_type_tests;
mod uuid_tests;
mod variadic_tests;
mod xact_callback_tests;
mod xid64_tests;
Expand Down
74 changes: 74 additions & 0 deletions pgx-tests/src/tests/uuid_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use pgx::*;

pub const TEST_UUID_V4: UuidBytes = [
0x12, 0x3e, 0x45, 0x67, 0xe8, 0x9b, 0x12, 0xd3, 0xa4, 0x56, 0x42, 0x66, 0x14, 0x17, 0x40, 0x00,
];

#[pg_extern]
fn accept_uuid(uuid: Uuid) -> Uuid {
uuid
}

#[pg_extern]
fn return_uuid() -> Uuid {
Uuid::from_bytes(TEST_UUID_V4)
}

#[pg_extern]
fn display_uuid(uuid: Uuid) -> String {
format!("{}", uuid)
}

#[cfg(any(test, feature = "pg_test"))]
mod tests {
#[allow(unused_imports)]
use crate as pgx_tests;
use pgx::*;

#[pg_test]
fn test_display_uuid() {
let result = Spi::get_one::<bool>("SELECT display_uuid('123e4567-e89b-12d3-a456-426614174000'::uuid) = '123e4567-e89b-12d3-a456-426614174000';")
.expect("failed to get SPI result");
assert!(result);

let uuid = Uuid::from_bytes(super::TEST_UUID_V4);
assert_eq!(format!("{}", uuid), "123e4567-e89b-12d3-a456-426614174000");

// Lowercase hex formatting
assert_eq!(
format!("{:-x}", uuid),
"123e4567-e89b-12d3-a456-426614174000"
);
assert_eq!(format!("{:x}", uuid), "123e4567e89b12d3a456426614174000");

// Uppercase hex formatting
assert_eq!(
format!("{:-X}", uuid),
"123E4567-E89B-12D3-A456-426614174000"
);
assert_eq!(format!("{:X}", uuid), "123E4567E89B12D3A456426614174000");
}

#[pg_test]
fn test_accept_uuid() {
let result = Spi::get_one::<bool>("SELECT accept_uuid('123e4567-e89b-12d3-a456-426614174000'::uuid) = '123e4567-e89b-12d3-a456-426614174000'::uuid;")
.expect("failed to get SPI result");
assert!(result)
}

#[pg_test]
fn test_return_uuid() {
let result = Spi::get_one::<bool>(
"SELECT return_uuid() = '123e4567-e89b-12d3-a456-426614174000'::uuid;",
)
.expect("SPI result was null");
assert!(result)
}

#[pg_test]
fn test_parse_uuid_v4() {
Hoverbear marked this conversation as resolved.
Show resolved Hide resolved
let uuid = Spi::get_one::<Uuid>("SELECT '123e4567-e89b-12d3-a456-426614174000'::uuid;")
.expect("SPI result was null");
assert_eq!(uuid, Uuid::from_bytes(super::TEST_UUID_V4))
}
}
2 changes: 2 additions & 0 deletions pgx/src/datum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ mod time_stamp;
mod time_stamp_with_timezone;
mod time_with_timezone;
mod tuples;
mod uuid;
mod varlena;

pub use self::time::*;
pub use self::uuid::*;
pub use anyarray::*;
pub use anyelement::*;
pub use array::*;
Expand Down
116 changes: 116 additions & 0 deletions pgx/src/datum/uuid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::{pg_sys, FromDatum, IntoDatum, PgMemoryContexts};
use core::fmt::Write;
use std::ops::{Deref, DerefMut};

const UUID_BYTES_LEN: usize = 16;
pub type UuidBytes = [u8; UUID_BYTES_LEN];

/// A Universally Unique Identifier (UUID).
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug)]
#[repr(transparent)]
pub struct Uuid(UuidBytes);

impl IntoDatum for Uuid {
#[inline]
fn into_datum(self) -> Option<pg_sys::Datum> {
let ptr = PgMemoryContexts::CurrentMemoryContext.palloc_slice::<u8>(UUID_BYTES_LEN);
ptr.clone_from_slice(&self.0);

Some(ptr.as_ptr() as pg_sys::Datum)
}

#[inline]
fn type_oid() -> u32 {
pg_sys::UUIDOID
}
}

impl FromDatum for Uuid {
#[inline]
unsafe fn from_datum(datum: usize, is_null: bool, _typoid: pg_sys::Oid) -> Option<Uuid> {
if is_null {
None
} else if datum == 0 {
panic!("a uuid Datum as flagged as non-null but the datum is zero");
} else {
let bytes = std::slice::from_raw_parts(datum as *const u8, UUID_BYTES_LEN);
if let Ok(uuid) = Uuid::from_slice(bytes) {
Some(uuid)
} else {
None
}
}
}
}

enum UuidFormatCase {
Lowercase,
Uppercase,
}

impl Uuid {
pub fn from_bytes(b: UuidBytes) -> Self {
Uuid(b)
}

pub fn from_slice(b: &[u8]) -> Result<Uuid, String> {
let len = b.len();

if len != UUID_BYTES_LEN {
Err(format!(
"Expected UUID to be {} bytes, got {}",
UUID_BYTES_LEN, len
))?;
}

let mut bytes = [0; UUID_BYTES_LEN];
bytes.copy_from_slice(b);
Ok(Uuid::from_bytes(bytes))
}

fn format(&self, f: &mut std::fmt::Formatter<'_>, case: UuidFormatCase) -> std::fmt::Result {
let hyphenated = f.sign_minus();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such a good trick. :)

for (i, b) in self.0.iter().enumerate() {
if hyphenated && (i == 4 || i == 6 || i == 8 || i == 10) {
f.write_char('-')?;
}
match case {
UuidFormatCase::Lowercase => write!(f, "{:02x}", b)?,
UuidFormatCase::Uppercase => write!(f, "{:02X}", b)?,
};
}
Ok(())
}
}

impl Deref for Uuid {
type Target = UuidBytes;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Uuid {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl std::fmt::Display for Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:-x}", self)
}
}

impl<'a> std::fmt::LowerHex for Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
self.format(f, UuidFormatCase::Lowercase)
}
}

impl<'a> std::fmt::UpperHex for Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
self.format(f, UuidFormatCase::Uppercase)
}
}