diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index 34b2d1fe3f..bec83374e1 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -24,7 +24,7 @@ use std::process::{Command, Output}; use std::rc::Rc; use syn::{Item, ItemConst}; -const BLOCKLISTED_TYPES: [&str; 3] = ["Datum", "NullableDatum", "Oid"]; +const BLOCKLISTED_TYPES: [&str; 4] = ["Datum", "NullableDatum", "Oid", "TransactionId"]; // These postgres versions were effectively "yanked" by the community, even tho they still exist // in the wild. pgrx will refuse to compile against them @@ -348,7 +348,7 @@ fn generate_bindings( &bindings_file, quote! { use crate as pg_sys; - use crate::{Datum, Oid, PgNode}; + use crate::{Datum, MultiXactId, Oid, PgNode, TransactionId}; }, is_for_release, ) @@ -864,6 +864,8 @@ pub const {module}_{variant}: {ty} = {value};"#, fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder { bind.blocklist_type("Datum") // manually wrapping datum for correctness .blocklist_type("Oid") // "Oid" is not just any u32 + .blocklist_type("TransactionId") // "TransactionId" is not just any u32 + .blocklist_type("MultiXactId") // it's an alias of "TransactionId" .blocklist_var("CONFIGURE_ARGS") // configuration during build is hopefully irrelevant .blocklist_var("_*(?:HAVE|have)_.*") // header tracking metadata .blocklist_var("_[A-Z_]+_H") // more header metadata diff --git a/pgrx-pg-sys/src/port.rs b/pgrx-pg-sys/src/port.rs index e181d5b030..1858a20e29 100644 --- a/pgrx-pg-sys/src/port.rs +++ b/pgrx-pg-sys/src/port.rs @@ -10,13 +10,13 @@ pub const MaxOffsetNumber: super::OffsetNumber = (super::BLCKSZ as usize / std::mem::size_of::()) as super::OffsetNumber; pub const InvalidBlockNumber: u32 = 0xFFFF_FFFF as crate::BlockNumber; pub const VARHDRSZ: usize = std::mem::size_of::(); -pub const InvalidTransactionId: super::TransactionId = 0 as super::TransactionId; pub const InvalidCommandId: super::CommandId = (!(0 as super::CommandId)) as super::CommandId; pub const FirstCommandId: super::CommandId = 0 as super::CommandId; -pub const BootstrapTransactionId: super::TransactionId = 1 as super::TransactionId; -pub const FrozenTransactionId: super::TransactionId = 2 as super::TransactionId; -pub const FirstNormalTransactionId: super::TransactionId = 3 as super::TransactionId; -pub const MaxTransactionId: super::TransactionId = 0xFFFF_FFFF as super::TransactionId; +pub const InvalidTransactionId: crate::TransactionId = crate::TransactionId::INVALID; +pub const BootstrapTransactionId: crate::TransactionId = crate::TransactionId::BOOTSTRAP; +pub const FrozenTransactionId: crate::TransactionId = crate::TransactionId::FROZEN; +pub const FirstNormalTransactionId: crate::TransactionId = crate::TransactionId::FIRST_NORMAL; +pub const MaxTransactionId: crate::TransactionId = crate::TransactionId::MAX; /// Given a valid HeapTuple pointer, return address of the user data /// diff --git a/pgrx-pg-sys/src/submodules/mod.rs b/pgrx-pg-sys/src/submodules/mod.rs index d3c99725b4..5e3362f014 100644 --- a/pgrx-pg-sys/src/submodules/mod.rs +++ b/pgrx-pg-sys/src/submodules/mod.rs @@ -8,6 +8,7 @@ //LICENSE //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. pub mod datum; +pub mod transaction_id; #[macro_use] pub mod elog; pub mod cmp; @@ -27,6 +28,7 @@ pub mod utils; mod sql_translatable; pub use datum::Datum; +pub use transaction_id::{MultiXactId, TransactionId}; pub use htup::*; pub use oids::*; diff --git a/pgrx-pg-sys/src/submodules/transaction_id.rs b/pgrx-pg-sys/src/submodules/transaction_id.rs new file mode 100644 index 0000000000..2cc1a08793 --- /dev/null +++ b/pgrx-pg-sys/src/submodules/transaction_id.rs @@ -0,0 +1,85 @@ +//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC. +//LICENSE +//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc. +//LICENSE +//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. +//LICENSE +//LICENSE All rights reserved. +//LICENSE +//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. +use pgrx_sql_entity_graph::metadata::{ + ArgumentError, Returns, ReturnsError, SqlMapping, SqlTranslatable, +}; +use std::fmt::{self, Debug, Display, Formatter}; + +pub type MultiXactId = TransactionId; + +/// An `xid` type from PostgreSQL +#[repr(transparent)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(serde::Deserialize, serde::Serialize)] +pub struct TransactionId(u32); + +impl TransactionId { + pub const INVALID: Self = Self(0); + pub const BOOTSTRAP: Self = Self(1); + pub const FROZEN: Self = Self(2); + pub const FIRST_NORMAL: Self = Self(3); + pub const MAX: Self = Self(u32::MAX); + + pub const fn from_inner(xid: u32) -> Self { + Self(xid) + } + + pub const fn into_inner(self) -> u32 { + self.0 + } +} + +impl Default for TransactionId { + fn default() -> Self { + Self::INVALID + } +} + +impl From for TransactionId { + #[inline] + fn from(xid: u32) -> Self { + Self::from_inner(xid) + } +} + +impl From for u32 { + #[inline] + fn from(xid: TransactionId) -> Self { + xid.into_inner() + } +} + +impl From for crate::Datum { + fn from(xid: TransactionId) -> Self { + xid.into_inner().into() + } +} + +unsafe impl SqlTranslatable for TransactionId { + fn argument_sql() -> Result { + Ok(SqlMapping::literal("xid")) + } + + fn return_sql() -> Result { + Ok(Returns::One(SqlMapping::literal("xid"))) + } +} + +impl Debug for TransactionId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.0, f) + } +} + +impl Display for TransactionId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} diff --git a/pgrx-tests/src/tests/roundtrip_tests.rs b/pgrx-tests/src/tests/roundtrip_tests.rs index 264708675d..8dce10018e 100644 --- a/pgrx-tests/src/tests/roundtrip_tests.rs +++ b/pgrx-tests/src/tests/roundtrip_tests.rs @@ -125,6 +125,7 @@ mod tests { roundtrip!(rt_point, test_rt_point, pg_sys::Point, pg_sys::Point { x: 1.0, y: 2.0 }); roundtrip!(rt_string, test_rt_string, String, String::from("string")); roundtrip!(rt_oid, test_rt_oid, pg_sys::Oid, pg_sys::Oid::from(BuiltinOid::ANYOID)); + roundtrip!(rt_xid, test_rt_xid, pg_sys::TransactionId, pg_sys::TransactionId::FIRST_NORMAL); roundtrip!(rt_i16, test_rt_i16, i16, i16::MAX); roundtrip!(rt_f64, test_rt_f64, f64, f64::MAX); roundtrip!( diff --git a/pgrx-tests/src/tests/xid64_tests.rs b/pgrx-tests/src/tests/xid64_tests.rs index 3783c263e8..670a7e859c 100644 --- a/pgrx-tests/src/tests/xid64_tests.rs +++ b/pgrx-tests/src/tests/xid64_tests.rs @@ -21,7 +21,7 @@ mod tests { #[pg_test] fn test_convert_xid_to_u64() { - let xid = xid_to_64bit(32768); + let xid = xid_to_64bit(32768.into()); assert_eq!(xid, 32768) } } diff --git a/pgrx/src/callconv.rs b/pgrx/src/callconv.rs index 46733e0147..fb41904d7c 100644 --- a/pgrx/src/callconv.rs +++ b/pgrx/src/callconv.rs @@ -265,7 +265,7 @@ argue_from_datum! { 'fcx; i8, i16, i32, i64, f32, f64, bool, char, String, Vec, char, Json, JsonB, Inet, Uuid, AnyNumeric, AnyArray, AnyElement, Internal, Date, Interval, Time, TimeWithTimeZone, Timestamp, TimestampWithTimeZone, - pg_sys::BOX, pg_sys::ItemPointerData, pg_sys::Oid, pg_sys::Point + pg_sys::BOX, pg_sys::ItemPointerData, pg_sys::Oid, pg_sys::Point, pg_sys::TransactionId } unsafe impl BoxRet for Numeric { diff --git a/pgrx/src/datum/from.rs b/pgrx/src/datum/from.rs index a327bcdfae..ac995541fa 100644 --- a/pgrx/src/datum/from.rs +++ b/pgrx/src/datum/from.rs @@ -209,6 +209,21 @@ impl FromDatum for pg_sys::Oid { } } +impl FromDatum for pg_sys::TransactionId { + #[inline] + unsafe fn from_polymorphic_datum( + datum: pg_sys::Datum, + is_null: bool, + _typoid: pg_sys::Oid, + ) -> Option { + if is_null { + None + } else { + datum.value().try_into().ok().map(Self::from_inner) + } + } +} + /// for bool impl FromDatum for bool { #[inline] diff --git a/pgrx/src/datum/into.rs b/pgrx/src/datum/into.rs index 50f69111df..1d0010d799 100644 --- a/pgrx/src/datum/into.rs +++ b/pgrx/src/datum/into.rs @@ -236,6 +236,22 @@ impl IntoDatum for pg_sys::Oid { } } +impl IntoDatum for pg_sys::TransactionId { + #[inline] + fn into_datum(self) -> Option { + if self == Self::INVALID { + None + } else { + Some(self.into()) + } + } + + #[inline] + fn type_oid() -> pg_sys::Oid { + pg_sys::XIDOID + } +} + impl IntoDatum for PgOid { #[inline] fn into_datum(self) -> Option { diff --git a/pgrx/src/xid.rs b/pgrx/src/xid.rs index ae5646f74f..5557995a76 100644 --- a/pgrx/src/xid.rs +++ b/pgrx/src/xid.rs @@ -16,14 +16,18 @@ pub fn xid_to_64bit(xid: pg_sys::TransactionId) -> u64 { let last_xid = full_xid.value as u32; let epoch = (full_xid.value >> 32) as u32; - convert_xid_common(xid, last_xid, epoch) + convert_xid_common(xid, last_xid.into(), epoch) } #[inline] -fn convert_xid_common(xid: pg_sys::TransactionId, last_xid: u32, epoch: u32) -> u64 { +fn convert_xid_common( + xid: pg_sys::TransactionId, + last_xid: pgrx_pg_sys::TransactionId, + epoch: u32, +) -> u64 { /* return special xid's as-is */ if !pg_sys::TransactionIdIsNormal(xid) { - return xid as u64; + return xid.into_inner() as u64; } /* xid can be on either side when near wrap-around */ @@ -34,5 +38,5 @@ fn convert_xid_common(xid: pg_sys::TransactionId, last_xid: u32, epoch: u32) -> epoch += 1; } - (epoch << 32) | xid as u64 + (epoch << 32) | xid.into_inner() as u64 }