diff --git a/Cargo.lock b/Cargo.lock index 768742ae11..e0b456bb6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,6 +1396,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pathsearch" version = "0.2.0" @@ -1450,6 +1456,7 @@ dependencies = [ "heapless", "libc", "once_cell", + "paste", "pgrx-macros", "pgrx-pg-sys", "pgrx-sql-entity-graph", diff --git a/pgrx-pg-sys/include/pg12.h b/pgrx-pg-sys/include/pg12.h index 08a482c619..140263ce54 100644 --- a/pgrx-pg-sys/include/pg12.h +++ b/pgrx-pg-sys/include/pg12.h @@ -50,6 +50,7 @@ #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" +#include "catalog/pg_index.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" diff --git a/pgrx-pg-sys/include/pg13.h b/pgrx-pg-sys/include/pg13.h index cf40fa4af7..8fba9fcd93 100644 --- a/pgrx-pg-sys/include/pg13.h +++ b/pgrx-pg-sys/include/pg13.h @@ -50,6 +50,7 @@ #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" +#include "catalog/pg_index.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" diff --git a/pgrx-pg-sys/include/pg14.h b/pgrx-pg-sys/include/pg14.h index a816ad148a..dd634e9477 100644 --- a/pgrx-pg-sys/include/pg14.h +++ b/pgrx-pg-sys/include/pg14.h @@ -50,6 +50,7 @@ #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" +#include "catalog/pg_index.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" diff --git a/pgrx-pg-sys/include/pg15.h b/pgrx-pg-sys/include/pg15.h index 7a2db34745..e8fa4b126d 100644 --- a/pgrx-pg-sys/include/pg15.h +++ b/pgrx-pg-sys/include/pg15.h @@ -51,6 +51,7 @@ #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" +#include "catalog/pg_index.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" diff --git a/pgrx-pg-sys/include/pg16.h b/pgrx-pg-sys/include/pg16.h index bf5a972784..601fc3b07b 100644 --- a/pgrx-pg-sys/include/pg16.h +++ b/pgrx-pg-sys/include/pg16.h @@ -52,6 +52,7 @@ #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" +#include "catalog/pg_index.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" diff --git a/pgrx-tests/src/tests/mod.rs b/pgrx-tests/src/tests/mod.rs index 1d372b6168..6a1d2ce1a0 100644 --- a/pgrx-tests/src/tests/mod.rs +++ b/pgrx-tests/src/tests/mod.rs @@ -38,6 +38,7 @@ mod memcxt_tests; mod name_tests; mod numeric_tests; mod pg_cast_tests; +mod pg_catalog_tests; mod pg_extern_tests; mod pg_guard_tests; mod pg_operator_tests; diff --git a/pgrx-tests/src/tests/pg_catalog_tests.rs b/pgrx-tests/src/tests/pg_catalog_tests.rs new file mode 100644 index 0000000000..c42e9a7873 --- /dev/null +++ b/pgrx-tests/src/tests/pg_catalog_tests.rs @@ -0,0 +1,135 @@ +use pgrx::prelude::*; + +#[cfg(any(test, feature = "pg_test"))] +#[pg_schema] +mod tests { + use pgrx::pg_sys::Oid; + use std::ffi::CString; + + #[allow(unused_imports)] + use crate as pgrx_tests; + use pgrx::prelude::*; + + #[pg_test] + fn test_pg_catalog_pg_proc_boolin() { + use pgrx::pg_catalog::*; + let proname = CString::new("boolin").unwrap(); + let proargtypes = /* cstring */ [pgrx::wrappers::regtypein("cstring")]; + let pronamespace = /* pg_catalog */ Oid::from(11); + // search + let pg_proc = PgProc::search_procnameargsnsp(&proname, &proargtypes, pronamespace).unwrap(); + let pg_proc = pg_proc.get().unwrap(); + // getstruct, name + assert_eq!(pg_proc.proname(), proname.as_c_str()); + // getstruct, primitive types + assert_eq!(pg_proc.pronamespace(), pronamespace); + assert_eq!(pg_proc.procost(), 1.0); + assert_eq!(pg_proc.prorows(), 0.0); + assert_eq!(pg_proc.provariadic(), Oid::INVALID); + // getstruct, regproc + assert_eq!(pg_proc.prosupport(), Oid::INVALID); + // getstruct, char + assert_eq!(pg_proc.prokind(), PgProcProkind::Function); + assert_eq!(pg_proc.prosecdef(), false); + assert_eq!(pg_proc.proleakproof(), false); + assert_eq!(pg_proc.proisstrict(), true); + assert_eq!(pg_proc.proretset(), false); + assert_eq!(pg_proc.provolatile(), PgProcProvolatile::Immutable); + assert_eq!(pg_proc.proparallel(), PgProcProparallel::Safe); + assert_eq!(pg_proc.pronargs(), 1); + assert_eq!(pg_proc.pronargdefaults(), 0); + assert_eq!(pg_proc.prorettype(), pgrx::pg_sys::BOOLOID); + // getstruct, oidvector + assert_eq!(pg_proc.proargtypes(), &proargtypes); + // getattr, null + assert!(pg_proc.proallargtypes().is_none()); + assert!(pg_proc.proargmodes().is_none()); + assert!(pg_proc.proargnames().is_none()); + assert!(pg_proc.protrftypes().is_none()); + // getattr, text + assert_eq!(pg_proc.prosrc(), "boolin"); + assert!(pg_proc.probin().is_none()); + assert!(pg_proc.proconfig().is_none()); + } + + #[pg_test] + fn test_pg_catalog_pg_proc_num_nulls() { + use pgrx::pg_catalog::*; + let proname = CString::new("num_nulls").unwrap(); + let proargtypes = [pgrx::pg_sys::ANYOID]; + let pronamespace = /* pg_catalog */ pgrx::pg_sys::Oid::from(11); + let pg_proc = PgProc::search_procnameargsnsp(&proname, &proargtypes, pronamespace).unwrap(); + let pg_proc = pg_proc.get().unwrap(); + assert_eq!(pg_proc.proname(), proname.as_c_str()); + assert_eq!(pg_proc.pronamespace(), pronamespace); + assert_eq!(pg_proc.procost(), 1.0); + assert_eq!(pg_proc.prorows(), 0.0); + assert_eq!(pg_proc.provariadic(), pgrx::pg_sys::ANYOID); + assert_eq!(pg_proc.prosupport(), Oid::INVALID); + assert_eq!(pg_proc.prokind(), PgProcProkind::Function); + assert_eq!(pg_proc.prosecdef(), false); + assert_eq!(pg_proc.proleakproof(), false); + assert_eq!(pg_proc.proisstrict(), false); + assert_eq!(pg_proc.proretset(), false); + assert_eq!(pg_proc.provolatile(), PgProcProvolatile::Immutable); + assert_eq!(pg_proc.proparallel(), PgProcProparallel::Safe); + assert_eq!(pg_proc.pronargs(), 1); + assert_eq!(pg_proc.pronargdefaults(), 0); + assert_eq!(pg_proc.prorettype(), pgrx::pg_sys::INT4OID); + assert_eq!(pg_proc.proargtypes(), &proargtypes); + // getattr, oid[] + assert_eq!( + pg_proc.proallargtypes().map(|v| v.iter().collect()), + Some(vec![Some(pgrx::pg_sys::ANYOID)]) + ); + // getattr, char[] + assert_eq!( + pg_proc.proargmodes().map(|v| v.iter().collect()), + Some(vec![Some(PgProcProargmodes::Variadic)]) + ); + assert!(pg_proc.proargnames().is_none()); + assert!(pg_proc.protrftypes().is_none()); + assert_eq!(pg_proc.prosrc(), "pg_num_nulls"); + assert!(pg_proc.probin().is_none()); + assert!(pg_proc.proconfig().is_none()); + } + + #[pg_test] + fn test_pg_catalog_pg_proc_gcd() { + // skip this test for pg12 + if pgrx::pg_sys::PG_VERSION_NUM < 130000 { + return; + } + use pgrx::pg_catalog::*; + let proname = CString::new("gcd").unwrap(); + // search_list + let pg_proc = PgProc::search_list_procnameargsnsp_1(&proname).unwrap(); + let mut int4gcd = false; + let mut int8gcd = false; + for i in 0..pg_proc.len() { + let pg_proc = pg_proc.get(i).unwrap(); + if pg_proc.prosrc() == "int4gcd" { + int4gcd = true; + } + if pg_proc.prosrc() == "int8gcd" { + int8gcd = true; + } + } + assert!(int4gcd); + assert!(int8gcd); + } + + #[pg_test] + fn test_pg_catalog_pg_class_pg_stats() { + use pgrx::pg_catalog::*; + let relname = CString::new("pg_stats").unwrap(); + let relnamespace = /* pg_catalog */ pgrx::pg_sys::Oid::from(11); + let pg_class = PgClass::search_relnamensp(&relname, relnamespace).unwrap(); + let pg_class = pg_class.get().unwrap(); + // getattr, text[] + assert_eq!( + pg_class.reloptions().map(|v| v.iter().collect()), + Some(vec![Some("security_barrier=true".to_string())]) + ); + } +} diff --git a/pgrx/Cargo.toml b/pgrx/Cargo.toml index 472c42da65..ed4ac6ae63 100644 --- a/pgrx/Cargo.toml +++ b/pgrx/Cargo.toml @@ -65,3 +65,4 @@ seahash = "4.1.0" # derive(PostgresHash) serde = { version = "1.0", features = [ "derive" ] } # impls on pub types serde_cbor = "0.11.2" # derive(PostgresType) serde_json = "1.0" # everything JSON +paste = "1.0.14" diff --git a/pgrx/src/enum_helper.rs b/pgrx/src/enum_helper.rs index c1f2db863f..032c9d3df1 100644 --- a/pgrx/src/enum_helper.rs +++ b/pgrx/src/enum_helper.rs @@ -9,45 +9,26 @@ //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. //! Helper functions for working with Postgres `enum` types -use crate::pg_sys::GETSTRUCT; +use crate::pg_catalog::PgEnum; use crate::{ereport, pg_sys, PgLogLevel, PgSqlErrorCode}; pub fn lookup_enum_by_oid(enumval: pg_sys::Oid) -> (String, pg_sys::Oid, f32) { - let tup = unsafe { - pg_sys::SearchSysCache( - pg_sys::SysCacheIdentifier_ENUMOID as i32, - pg_sys::Datum::from(enumval), - pg_sys::Datum::from(0), - pg_sys::Datum::from(0), - pg_sys::Datum::from(0), - ) - }; - if tup.is_null() { + let pg_enum = PgEnum::search_enumoid(enumval).unwrap(); + + let Some(pg_enum) = pg_enum.get() else { ereport!( PgLogLevel::ERROR, PgSqlErrorCode::ERRCODE_INVALID_BINARY_REPRESENTATION, format!("invalid internal value for enum: {enumval:?}") ); - } - - let en = unsafe { GETSTRUCT(tup) } as pg_sys::Form_pg_enum; - let en = unsafe { en.as_ref() }.unwrap(); - let result = ( - unsafe { - core::ffi::CStr::from_ptr(en.enumlabel.data.as_ptr() as *const std::os::raw::c_char) - } - .to_str() - .unwrap() - .to_string(), - en.enumtypid, - en.enumsortorder as f32, - ); - - unsafe { - pg_sys::ReleaseSysCache(tup); - } + unreachable!() + }; - result + ( + pg_enum.enumlabel().to_str().unwrap().to_string(), + pg_enum.enumtypid(), + pg_enum.enumsortorder() as f32, + ) } pub fn lookup_enum_by_label(typname: &str, label: &str) -> pg_sys::Datum { @@ -57,35 +38,13 @@ pub fn lookup_enum_by_label(typname: &str, label: &str) -> pg_sys::Datum { panic!("could not locate type oid for type: {typname}"); } - let tup = unsafe { - let label = - alloc::ffi::CString::new(label).expect("failed to convert enum typname to a CString"); - pg_sys::SearchSysCache( - pg_sys::SysCacheIdentifier_ENUMTYPOIDNAME as i32, - pg_sys::Datum::from(enumtypoid), - pg_sys::Datum::from(label.as_ptr()), - pg_sys::Datum::from(0usize), - pg_sys::Datum::from(0usize), - ) - }; - - if tup.is_null() { - panic!("could not find heap tuple for enum: {typname}.{label}, typoid={enumtypoid:?}"); - } + let label = std::ffi::CString::new(label).expect("failed to convert enum typname to a CString"); - // SAFETY: we know that `tup` is valid because we just got it from Postgres above - unsafe { - let oid = extract_enum_oid(tup); - pg_sys::ReleaseSysCache(tup); - pg_sys::Datum::from(oid) - } -} + let pg_enum = PgEnum::search_enumtypoidname(enumtypoid, &label).unwrap(); -unsafe fn extract_enum_oid(tup: *mut pg_sys::HeapTupleData) -> pg_sys::Oid { - let en = { - // SAFETY: the caller has assured us that `tup` is a valid HeapTupleData pointer - GETSTRUCT(tup) as pg_sys::Form_pg_enum + let Some(pg_enum) = pg_enum.get() else { + panic!("could not find heap tuple for enum: {typname}.{label:?}, typoid={enumtypoid:?}"); }; - let en = en.as_ref().unwrap(); - en.oid + + pg_sys::Datum::from(pg_enum.oid()) } diff --git a/pgrx/src/fn_call.rs b/pgrx/src/fn_call.rs index c5365ae3ff..241547512d 100644 --- a/pgrx/src/fn_call.rs +++ b/pgrx/src/fn_call.rs @@ -14,7 +14,8 @@ use pgrx_pg_sys::PgTryBuilder; use std::panic::AssertUnwindSafe; use crate::memcx; -use crate::pg_catalog::pg_proc::{PgProc, ProArgMode, ProKind}; +use crate::pg_catalog::PgProc; +use crate::pg_catalog::{PgProcProargmodes, PgProcProkind}; use crate::seal::Sealed; use crate::{ direct_function_call, is_a, list::List, pg_sys, pg_sys::AsPgCStr, Array, FromDatum, IntoDatum, @@ -205,18 +206,22 @@ pub fn fn_call_with_collation( let func_oid = lookup_fn(fname, args)?; // lookup the function's pg_proc entry and do some validation - let pg_proc = PgProc::new(func_oid).ok_or(FnCallError::UndefinedFunction)?; + let pg_proc = PgProc::search_procoid(func_oid).ok_or(FnCallError::UndefinedFunction)?; + let pg_proc = pg_proc.get().ok_or(FnCallError::UndefinedFunction)?; let retoid = pg_proc.prorettype(); // // do some validation to catch the cases we don't/can't directly call // - if !matches!(pg_proc.prokind(), ProKind::Function) { + if !matches!(pg_proc.prokind(), PgProcProkind::Function) { // It only makes sense to directly call regular functions. Calling aggregate or window // functions is nonsensical return Err(FnCallError::UnsupportedFunctionType); - } else if pg_proc.proargmodes().iter().any(|mode| *mode != ProArgMode::In) { + } else if pg_proc + .proargmodes() + .map_or(false, |x| x.iter_deny_null().any(|mode| mode != PgProcProargmodes::In)) + { // Right now we only know how to support arguments with the IN mode. Perhaps in the // future we can support IN_OUT and TABLE return types return Err(FnCallError::UnsupportedArgumentModes); @@ -240,7 +245,7 @@ pub fn fn_call_with_collation( .iter() .enumerate() .map(|(i, a)| a.as_datum(&pg_proc, i)) - .chain((args.len()..pg_proc.pronargs()).map(|i| create_default_value(&pg_proc, i))) + .chain((args.len()..pg_proc.pronargs() as usize).map(|i| create_default_value(&pg_proc, i))) .map(|datum| { null |= matches!(datum, Ok(None)); datum @@ -276,7 +281,7 @@ pub fn fn_call_with_collation( // // SAFETY: we allocate enough zeroed space for the base FunctionCallInfoBaseData *plus* the number of arguments // we have, and we've asserted that we have the correct number of arguments - assert_eq!(nargs, pg_proc.pronargs()); + assert_eq!(nargs, pg_proc.pronargs() as usize); let fcinfo = pg_sys::palloc0( std::mem::size_of::() + std::mem::size_of::() * nargs, @@ -433,7 +438,7 @@ fn parse_sql_ident(ident: &str) -> Result> { /// - [`FnCallError::NotDefaultArgument`] if the specified `argnum` does not have a `DEFAULT` clause /// - [`FnCallError::DefaultNotConstantExpression`] if the `DEFAULT` clause is one we cannot evaluate fn create_default_value(pg_proc: &PgProc, argnum: usize) -> Result> { - let non_default_args_cnt = pg_proc.pronargs() - pg_proc.pronargdefaults(); + let non_default_args_cnt = (pg_proc.pronargs() - pg_proc.pronargdefaults()) as usize; if argnum < non_default_args_cnt { return Err(FnCallError::NotDefaultArgument(argnum)); } diff --git a/pgrx/src/pg_catalog.rs b/pgrx/src/pg_catalog.rs new file mode 100644 index 0000000000..e0b1955a55 --- /dev/null +++ b/pgrx/src/pg_catalog.rs @@ -0,0 +1,866 @@ +//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 crate as pgrx; +use pgrx::datum::Array; +use pgrx::pg_sys::Oid; +use pgrx::pg_sys::Oid as Regproc; +use std::ffi::{c_char, CStr}; + +unsafe trait GetStruct { + unsafe fn get_struct(raw: *const T) -> Self; +} + +unsafe impl GetStruct for T { + unsafe fn get_struct(raw: *const T) -> Self { + unsafe { raw.read() } + } +} + +unsafe impl GetStruct for &CStr { + unsafe fn get_struct(raw: *const pgrx::pg_sys::nameData) -> Self { + unsafe { CStr::from_ptr(raw.cast::()) } + } +} + +unsafe impl GetStruct for &[i16] { + unsafe fn get_struct(raw: *const pgrx::pg_sys::int2vector) -> Self { + unsafe { (*raw).values.as_slice((*raw).dim1 as usize) } + } +} + +unsafe impl GetStruct for &[Oid] { + unsafe fn get_struct(raw: *const pgrx::pg_sys::oidvector) -> Self { + unsafe { (*raw).values.as_slice((*raw).dim1 as usize) } + } +} + +macro_rules! _macro_1 { + { + $table:ident, ($column:ident, character { $(($variant:ident, $value:literal))* } $($x:tt)*) + } => { + paste::paste! { + #[non_exhaustive] + #[repr(u8)] + #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub enum [<$table:camel $column:camel>] { + $($variant = $value),* + } + + impl [<$table:camel $column:camel>] { + fn from_c_char(value: c_char) -> Self { + match value as u8 { + $($value => Self::$variant,)* + + _ => panic!("unrecognized value: `{}`", value as u8 as char), + } + } + } + + impl pgrx::datum::FromDatum for [<$table:camel $column:camel>] { + unsafe fn from_polymorphic_datum( + datum: pgrx::pg_sys::Datum, + is_null: bool, + _: Oid, + ) -> Option + where + Self: Sized, + { + if is_null { + None + } else { + Some(Self::from_c_char(datum.value() as _)) + } + } + } + + impl pgrx::datum::IntoDatum for [<$table:camel $column:camel>] { + fn into_datum(self) -> std::option::Option { + Some(pgrx::pg_sys::Datum::from(self as i8)) + } + fn type_oid() -> pgrx_pg_sys::Oid { + pgrx::pg_sys::CHAROID + } + } + + unsafe impl pgrx::datum::UnboxDatum for [<$table:camel $column:camel>] { + type As<'src> = Self; + #[inline] + unsafe fn unbox<'src>(datum: pgrx::datum::Datum<'src>) -> Self::As<'src> + where + Self: 'src, + { + Self::from_c_char(datum.sans_lifetime().value() as c_char) + } + } + + unsafe impl GetStruct for [<$table:camel $column:camel>] { + unsafe fn get_struct(raw: *const c_char) -> Self { + unsafe { Self::from_c_char(raw.read()) } + } + } + } + }; +} + +macro_rules! _macro_0 { + { + $table:ident, $(#[$m_column:meta])* ($column:ident, c_char, character { $(($variant:ident, $value:literal))* } $($x:tt)*) + } => { + paste::paste! { + _macro_1! { $table, ($column, character { $(($variant, $value))* }) } + _macro_0! { $table, $(#[$m_column])* ($column, [<$table:camel $column:camel>] $($x)*) } + } + }; + { + $table:ident, $(#[$m_column:meta])* ($column:ident, Array, character { $(($variant:ident, $value:literal))* } $($x:tt)*) + } => { + paste::paste! { + _macro_1! { $table, ($column, character { $(($variant, $value))* }) } + _macro_0! { $table, $(#[$m_column])* ($column, Array<[<$table:camel $column:camel>]> $($x)*) } + } + }; + { + $table:ident, $(#[$m_column:meta])* ($column:ident, $type:ty, get_struct) + } => { + paste::paste! { + impl [<$table:camel>]<'_> { + $(#[$m_column])* + pub fn $column(&self) -> $type { + unsafe { + let start = self.inner.t_data.cast::(); + let offset = (*self.inner.t_data).t_hoff as usize; + let p = start.add(offset).cast::]>(); + GetStruct::get_struct(std::ptr::addr_of!((*p).$column)) + } + } + } + } + }; + { + $table:ident, $(#[$m_column:meta])* ($column:ident, $type:ty, get_attr) + } => { + paste::paste! { + impl [<$table:camel>]<'_> { + $(#[$m_column])* + pub fn $column(&self) -> Option<$type> { + self.get_attr::<$type>(pgrx::pg_sys::[]) + } + } + } + }; + { + $table:ident, $(#[$m_column:meta])* ($column:ident, $type:ty, get_attr, notnull) + } => { + paste::paste! { + impl [<$table:camel>]<'_> { + $(#[$m_column])* + pub fn $column(&self) -> $type { + self.get_attr::<$type>(pgrx::pg_sys::[]).unwrap() + } + } + } + }; +} + +macro_rules! define { + { + catalog ($table:ident) { + $($(#[$m_column:meta])* ($($x:tt)*))* + } + } => { + paste::paste!{ + pub struct [<$table:camel>]<'a> { + inner: &'a pgrx::pg_sys::HeapTupleData, + cache_id: i32, + } + + impl<'a> [<$table:camel>]<'a> { + #[inline] + #[allow(dead_code)] + fn get_attr(&self, attribute: u32) -> Option { + unsafe { + let mut is_null = false; + let datum = pgrx::pg_sys::SysCacheGetAttr( + self.cache_id, + std::ptr::addr_of!(*self.inner).cast_mut(), + attribute as _, + &mut is_null, + ); + T::from_datum(datum, is_null) + } + } + } + + pub struct [<$table:camel Search>] { + inner: Option>, + cache_id: i32, + } + + impl [<$table:camel Search>] { + pub fn is_empty(&self) -> bool { + self.inner.is_none() + } + pub fn get(&self) -> Option<[<$table:camel>]> { + unsafe { + Some([<$table:camel>] { + inner: self.inner?.as_ref(), + cache_id: self.cache_id, + }) + } + } + } + + impl Drop for [<$table:camel Search>] { + fn drop(&mut self) { + unsafe { + if let Some(inner) = self.inner { + pgrx::pg_sys::ReleaseSysCache(inner.as_ptr()); + } + } + } + } + + pub struct [<$table:camel SearchList>] { + inner: std::ptr::NonNull, + cache_id: i32, + } + + impl [<$table:camel SearchList>] { + pub fn len(&self) -> usize { + unsafe { + let inner = self.inner.as_ref(); + inner.n_members as usize + } + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, i: usize) -> Option<[<$table:camel>]> { + unsafe { + let inner = self.inner.as_ref(); + let slice = inner.members.as_slice(inner.n_members as usize); + let member = *slice.get(i)?; + let tuple = &(*member).tuple; + Some([<$table:camel>] { + inner: tuple, + cache_id: self.cache_id + }) + } + } + } + + impl Drop for [<$table:camel SearchList>] { + fn drop(&mut self) { + unsafe { pgrx::pg_sys::ReleaseCatCacheList(self.inner.as_ptr()) } + } + } + + $(_macro_0! { $table, $(#[$m_column])* ($($x)*) })* + } + }; + { + cache ($cache:ident, $catalog:ident) { + ($p0_name:ident, $p0_type:ty) + } + } => { + paste::paste!{ + impl<'a> [<$catalog:camel>]<'a> { + pub fn []($p0_name: $p0_type) -> Option<[<$catalog:camel Search>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCache1(cache_id, $p0_name.into_datum()?); + let inner = std::ptr::NonNull::new(entry); + Some([<$catalog:camel Search>] { inner, cache_id }) + } + } + } + } + }; + { + cache ($cache:ident, $catalog:ident) { + ($p0_name:ident, $p0_type:ty) + ($p1_name:ident, $p1_type:ty) + } + } => { + paste::paste!{ + impl<'a> [<$catalog:camel>]<'a> { + pub fn []($p0_name: $p0_type, $p1_name: $p1_type) -> Option<[<$catalog:camel Search>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCache2( + cache_id, + $p0_name.into_datum()?, + $p1_name.into_datum()?, + ); + let inner = std::ptr::NonNull::new(entry); + Some([<$catalog:camel Search>] { inner, cache_id }) + } + } + pub fn []($p0_name: $p0_type) -> Option<[<$catalog:camel SearchList>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCacheList( + cache_id, + 1, + $p0_name.into_datum()?, + 0.into(), + 0.into(), + ); + let inner = std::ptr::NonNull::new(entry).unwrap(); + Some([<$catalog:camel SearchList>] { inner, cache_id }) + } + } + } + } + }; + { + cache ($cache:ident, $catalog:ident) { + ($p0_name:ident, $p0_type:ty) + ($p1_name:ident, $p1_type:ty) + ($p2_name:ident, $p2_type:ty) + } + } => { + paste::paste!{ + impl<'a> [<$catalog:camel>]<'a> { + pub fn []($p0_name: $p0_type, $p1_name: $p1_type, $p2_name: $p2_type) -> Option<[<$catalog:camel Search>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCache3( + cache_id, + $p0_name.into_datum()?, + $p1_name.into_datum()?, + $p2_name.into_datum()?, + ); + let inner = std::ptr::NonNull::new(entry); + Some([<$catalog:camel Search>] { inner, cache_id }) + } + } + pub fn []($p0_name: $p0_type) -> Option<[<$catalog:camel SearchList>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCacheList( + cache_id, + 1, + $p0_name.into_datum()?, + 0.into(), + 0.into(), + ); + let inner = std::ptr::NonNull::new(entry).unwrap(); + Some([<$catalog:camel SearchList>] { inner, cache_id }) + } + } + pub fn []($p0_name: $p0_type, $p1_name: $p1_type) -> Option<[<$catalog:camel SearchList>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCacheList( + cache_id, + 2, + $p0_name.into_datum()?, + $p1_name.into_datum()?, + 0.into(), + ); + let inner = std::ptr::NonNull::new(entry).unwrap(); + Some([<$catalog:camel SearchList>] { inner, cache_id }) + } + } + } + } + }; + { + cache ($cache:ident, $catalog:ident) { + ($p0_name:ident, $p0_type:ty) + ($p1_name:ident, $p1_type:ty) + ($p2_name:ident, $p2_type:ty) + ($p3_name:ident, $p3_type:ty) + } + } => { + paste::paste!{ + impl<'a> [<$catalog:camel>]<'a> { + pub fn []($p0_name: $p0_type, $p1_name: $p1_type, $p2_name: $p2_type, $p3_name: $p3_type) -> Option<[<$catalog:camel Search>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCache4( + cache_id, + $p0_name.into_datum()?, + $p1_name.into_datum()?, + $p2_name.into_datum()?, + $p3_name.into_datum()?, + ); + let inner = std::ptr::NonNull::new(entry); + Some([<$catalog:camel Search>] { inner, cache_id }) + } + } + pub fn []($p0_name: $p0_type) -> Option<[<$catalog:camel SearchList>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCacheList( + cache_id, + 1, + $p0_name.into_datum()?, + 0.into(), + 0.into(), + ); + let inner = std::ptr::NonNull::new(entry).unwrap(); + Some([<$catalog:camel SearchList>] { inner, cache_id }) + } + } + pub fn []($p0_name: $p0_type, $p1_name: $p1_type) -> Option<[<$catalog:camel SearchList>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCacheList( + cache_id, + 2, + $p0_name.into_datum()?, + $p1_name.into_datum()?, + 0.into(), + ); + let inner = std::ptr::NonNull::new(entry).unwrap(); + Some([<$catalog:camel SearchList>] { inner, cache_id }) + } + } + pub fn []($p0_name: $p0_type, $p1_name: $p1_type, $p2_name: $p2_type) -> Option<[<$catalog:camel SearchList>]> { + unsafe { + use pgrx::datum::IntoDatum; + let cache_id = pgrx::pg_sys::[] as i32; + let entry = pgrx::pg_sys::SearchSysCacheList( + cache_id, + 3, + $p0_name.into_datum()?, + $p1_name.into_datum()?, + $p2_name.into_datum()?, + ); + let inner = std::ptr::NonNull::new(entry).unwrap(); + Some([<$catalog:camel SearchList>] { inner, cache_id }) + } + } + } + } + }; +} + +macro_rules! defines { + { + $($x:ident $y:tt $z:tt)* + } => { + $(define! { $x $y $z })* + } +} + +defines! { + catalog (pg_am) { + (oid, Oid, get_struct) + (amname, &CStr, get_struct) + (amhandler, Regproc, get_struct) + (amtype, c_char, character { + (Table, b't') + (Index, b'i') + }, get_struct) + } + catalog (pg_amop) { + (oid, Oid, get_struct) + (amopfamily, Oid, get_struct) + (amoplefttype, Oid, get_struct) + (amoprighttype, Oid, get_struct) + (amopstrategy, i16, get_struct) + (amoppurpose, c_char, character { + (Search, b's') + (Order, b'o') + }, get_struct) + (amopopr, Oid, get_struct) + (amopmethod, Oid, get_struct) + (amopsortfamily, Oid, get_struct) + } + catalog (pg_amproc) { + (oid, Oid, get_struct) + (amprocfamily, Oid, get_struct) + (amproclefttype, Oid, get_struct) + (amprocrighttype, Oid, get_struct) + (amprocnum, i16, get_struct) + (amproc, Regproc, get_struct) + } + catalog (pg_class) { + (oid, Oid, get_struct) + (relname, &CStr, get_struct) + (relnamespace, Oid, get_struct) + (reltype, Oid, get_struct) + (reloftype, Oid, get_struct) + (relowner, Oid, get_struct) + (relam, Oid, get_struct) + (relfilenode, Oid, get_struct) + (reltablespace, Oid, get_struct) + (relpages, i32, get_struct) + (reltuples, f32, get_struct) + (relallvisible, i32, get_struct) + (reltoastrelid, Oid, get_struct) + (relhasindex, bool, get_struct) + (relisshared, bool, get_struct) + (relpersistence, c_char, character { + (Permanent, b'p') + (Unlogged, b'u') + (Temp, b't') + }, get_struct) + (relkind, c_char, character { + (Relation, b'r') + (Index, b'i') + (Sequence, b'S') + (Toastvalue, b't') + (View, b'v') + (Matview, b'm') + (CompositeType, b'c') + (ForeignTable, b'f') + (PartitionedTable, b'p') + (PartitionedIndex, b'I') + }, get_struct) + (relnatts, i16, get_struct) + (relchecks, i16, get_struct) + (relhasrules, bool, get_struct) + (relhastriggers, bool, get_struct) + (relhassubclass, bool, get_struct) + (relrowsecurity, bool, get_struct) + (relforcerowsecurity, bool, get_struct) + (relispopulated, bool, get_struct) + (relreplident, c_char, character { + (DEFAULT, b'd') + (NOTHING, b'n') + (FULL, b'f') + (INDEX, b'i') + }, get_struct) + (relispartition, bool, get_struct) + (relrewrite, Oid, get_struct) + (relfrozenxid, u32, get_struct) + (relminmxid, u32, get_struct) + // (relacl, aclitem[], get_attr) + (reloptions, Array, get_attr) + // (relpartbound, pg_node_tree, get_attr) + } + catalog (pg_enum) { + (oid, Oid, get_struct) + (enumtypid, Oid, get_struct) + (enumsortorder, f32, get_struct) + (enumlabel, &CStr, get_struct) + } + catalog (pg_index) { + (indexrelid, Oid, get_struct) + (indrelid, Oid, get_struct) + (indnatts, i16, get_struct) + (indnkeyatts, i16, get_struct) + (indisunique, bool, get_struct) + #[cfg(not(any(feature = "pg12", feature = "pg13", feature = "pg14")))] + (indnullsnotdistinct, bool, get_struct) + (indisprimary, bool, get_struct) + (indisexclusion, bool, get_struct) + (indimmediate, bool, get_struct) + (indisclustered, bool, get_struct) + (indisvalid, bool, get_struct) + (indcheckxmin, bool, get_struct) + (indisready, bool, get_struct) + (indislive, bool, get_struct) + (indisreplident, bool, get_struct) + (indkey, &[i16], get_struct) + (indcollation, Array, get_attr, notnull) + (indclass, Array, get_attr, notnull) + (indoption, Array, get_attr, notnull) + // (indexprs, pg_node_tree, get_attr) + // (indpred, pg_node_tree, get_attr) + } + catalog (pg_namespace) { + (oid, Oid, get_struct) + (nspname, &CStr, get_struct) + (nspowner, Oid, get_struct) + // (nspacl, aclitem[], get_attr) + } + catalog (pg_opclass) { + (oid, Oid, get_struct) + (opcmethod, Oid, get_struct) + (opcname, &CStr, get_struct) + (opcnamespace, Oid, get_struct) + (opcowner, Oid, get_struct) + (opcfamily, Oid, get_struct) + (opcintype, Oid, get_struct) + (opcdefault, bool, get_struct) + (opckeytype, Oid, get_struct) + } + catalog (pg_operator) { + (oid, Oid, get_struct) + (oprname, &CStr, get_struct) + (oprnamespace, Oid, get_struct) + (oprowner, Oid, get_struct) + (oprkind, c_char, character { + (Prefix, b'l') + (Infix, b'b') + }, get_struct) + (oprcanmerge, bool, get_struct) + (oprcanhash, bool, get_struct) + (oprleft, Oid, get_struct) + (oprright, Oid, get_struct) + (oprresult, Oid, get_struct) + (oprcom, Oid, get_struct) + (oprnegate, Oid, get_struct) + (oprcode, Regproc, get_struct) + (oprrest, Regproc, get_struct) + (oprjoin, Regproc, get_struct) + } + catalog (pg_opfamily) { + (oid, Oid, get_struct) + (opfmethod, Oid, get_struct) + (opfname, &CStr, get_struct) + (opfnamespace, Oid, get_struct) + (opfowner, Oid, get_struct) + } + catalog (pg_proc) { + (oid, Oid, get_struct) + (proname, &CStr, get_struct) + (pronamespace, Oid, get_struct) + (proowner, Oid, get_struct) + (prolang, Oid, get_struct) + (procost, f32, get_struct) + (prorows, f32, get_struct) + (provariadic, Oid, get_struct) + (prosupport, Regproc, get_struct) + (prokind, c_char, character { + (Function, b'f') + (Procedure, b'p') + (Aggregate, b'a') + (Window, b'w') + }, get_struct) + (prosecdef, bool, get_struct) + (proleakproof, bool, get_struct) + (proisstrict, bool, get_struct) + (proretset, bool, get_struct) + (provolatile, c_char, character { + (Immutable, b'i') + (Stable, b's') + (Volatile, b'v') + }, get_struct) + (proparallel, c_char, character { + (Safe, b's') + (Restricted, b'r') + (Unsafe, b'u') + }, get_struct) + (pronargs, i16, get_struct) + (pronargdefaults, i16, get_struct) + (prorettype, Oid, get_struct) + (proargtypes, &[Oid], get_struct) + (proallargtypes, Array, get_attr) + (proargmodes, Array, character { + (In, b'i') + (Out, b'o') + (Inout, b'b') + (Variadic, b'v') + (Table, b't') + }, get_attr) + (proargnames, Array, get_attr) + // (proargdefaults, pg_node_tree, get_attr) + (protrftypes, Array, get_attr) + (prosrc, &str, get_attr, notnull) + (probin, &str, get_attr) + // (prosqlbody, pg_node_tree, get_attr) + (proconfig, Array, get_attr) + // (proacl, aclitem[], get_attr) + } + catalog (pg_type) { + (oid, Oid, get_struct) + (typname, &CStr, get_struct) + (typnamespace, Oid, get_struct) + (typowner, Oid, get_struct) + (typlen, i16, get_struct) + (typbyval, bool, get_struct) + (typtype, c_char, character { + (Base, b'b') + (Composite, b'c') + (Domain, b'd') + (Enum, b'e') + (Multirange, b'm') + (Pseudo, b'p') + (Range, b'r') + }, get_struct) + (typcategory, c_char, character { + (Array, b'A') + (Boolean, b'B') + (Composite, b'C') + (DateTime, b'D') + (Enum, b'E') + (Geometric, b'G') + (Network, b'I') + (Numeric, b'N') + (PseudoType, b'P') + (Range, b'R') + (String, b'S') + (TimeSpan, b'T') + (User, b'U') + (BitString, b'V') + (Unknown, b'X') + (Internal, b'Z') + }, get_struct) + (typispreferred, bool, get_struct) + (typisdefined, bool, get_struct) + (typdelim, c_char, get_struct) + (typrelid, Oid, get_struct) + #[cfg(not(any(feature = "pg12", feature = "pg13")))] + (typsubscript, Regproc, get_struct) + (typelem, Oid, get_struct) + (typarray, Oid, get_struct) + (typinput, Regproc, get_struct) + (typoutput, Regproc, get_struct) + (typreceive, Regproc, get_struct) + (typsend, Regproc, get_struct) + (typmodin, Regproc, get_struct) + (typmodout, Regproc, get_struct) + (typanalyze, Regproc, get_struct) + (typalign, c_char, character { + (Char, b'c') + (Short, b's') + (Int, b'i') + (Double, b'd') + }, get_struct) + (typstorage, c_char, character { + (Plain, b'p') + (External, b'e') + (Extended, b'x') + (Main, b'm') + }, get_struct) + (typnotnull, bool, get_struct) + (typbasetype, Oid, get_struct) + (typtypmod, i32, get_struct) + (typndims, i32, get_struct) + (typcollation, Oid, get_struct) + // (typdefaultbin, pg_node_tree, get_attr) + (typdefault, String, get_attr) + // (typacl, aclitem[], get_attr) + } + cache (amname, pg_am) { + (amname, &CStr) + } + cache (amoid, pg_am) { + (oid, Oid) + } + cache (amopopid, pg_amop) { + (amopopr, Oid) + (amoppurpose, PgAmopAmoppurpose) + (amopfamily, Oid) + } + cache (amopstrategy, pg_amop) { + (amopfamily, Oid) + (amoplefttype, Oid) + (amoprighttype, Oid) + (amopstrategy, i16) + } + cache (amprocnum, pg_amproc) { + (amprocfamily, Oid) + (amproclefttype, Oid) + (amprocrighttype, Oid) + (amprocnum, i16) + } + cache (claamnamensp, pg_opclass) { + (opcmethod, Oid) + (opcname, &CStr) + (opcnamespace, Oid) + } + cache (claoid, pg_opclass) { + (oid, Oid) + } + cache (enumoid, pg_enum) { + (oid, Oid) + } + cache (enumtypoidname, pg_enum) { + (enumtypid, Oid) + (enumlabel, &CStr) + } + cache (indexrelid, pg_index) { + (indexrelid, Oid) + } + cache (namespacename, pg_namespace) { + (nspname, &CStr) + } + cache (namespaceoid, pg_namespace) { + (oid, Oid) + } + cache (opernamensp, pg_operator) { + (oprname, &CStr) + (oprleft, Oid) + (oprright, Oid) + (oprnamespace, Oid) + } + cache (operoid, pg_operator) { + (oid, Oid) + } + cache (opfamilyamnamensp, pg_opfamily) { + (opfmethod, Oid) + (opfname, &CStr) + (opfnamespace, Oid) + } + cache (opfamilyoid, pg_opfamily) { + (oid, Oid) + } + cache (procnameargsnsp, pg_proc) { + (proname, &CStr) + (proargtypes, &[Oid]) + (pronamespace, Oid) + } + cache (procoid, pg_proc) { + (oid, Oid) + } + cache (relnamensp, pg_class) { + (relname, &CStr) + (relnamespace, Oid) + } + cache (reloid, pg_class) { + (oid, Oid) + } + cache (typenamensp, pg_type) { + (typname, &CStr) + (typnamespace, Oid) + } + cache (typeoid, pg_type) { + (oid, Oid) + } +} + +use pgrx::list::List; + +impl PgProc<'_> { + /// Expression trees for default values. This is a [`List`] with `pronargdefaults` elements, + /// corresponding to the last N input arguments (i.e., the last N proargtypes positions). + /// + /// If none of the arguments have defaults, this function returns [`Option::None`]. + pub fn proargdefaults<'cx>( + &self, + mcx: &'cx pgrx::memcx::MemCx<'_>, + ) -> Option> { + unsafe { + use crate::{pg_sys, FromDatum}; + use pgrx_pg_sys::AsPgCStr; + + let mut is_null = false; + let proargdefaults = pg_sys::SysCacheGetAttr( + pg_sys::SysCacheIdentifier_PROCOID as _, + std::ptr::addr_of!(*self.inner).cast_mut(), + pg_sys::Anum_pg_proc_proargdefaults as _, + &mut is_null, + ); + let proargdefaults = <&str>::from_datum(proargdefaults, is_null)?; + + let str = proargdefaults.as_pg_cstr(); + let argdefaults = mcx.exec_in(|| pg_sys::stringToNode(str)).cast::(); + pg_sys::pfree(str.cast()); + List::downcast_ptr_in_memcx(argdefaults, mcx) + } + } +} diff --git a/pgrx/src/pg_catalog/mod.rs b/pgrx/src/pg_catalog/mod.rs deleted file mode 100644 index 83afce4fad..0000000000 --- a/pgrx/src/pg_catalog/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//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. - -pub mod pg_proc; diff --git a/pgrx/src/pg_catalog/pg_proc.rs b/pgrx/src/pg_catalog/pg_proc.rs deleted file mode 100644 index 100920f6ba..0000000000 --- a/pgrx/src/pg_catalog/pg_proc.rs +++ /dev/null @@ -1,354 +0,0 @@ -//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 crate::list::List; -use crate::memcx::MemCx; -use crate::{pg_sys, FromDatum, IntoDatum}; -use std::ptr::NonNull; - -/// Provides a safe wrapper around a Postgres "SysCache" entry from `pg_catalog.pg_proc`. -pub struct PgProc { - inner: NonNull, - oid: pg_sys::Oid, -} - -#[non_exhaustive] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum ProArgMode { - In, - Out, - InOut, - Variadic, - Table, -} - -impl From for ProArgMode { - fn from(value: i8) -> Self { - match value as u8 { - b'i' => ProArgMode::In, - b'o' => ProArgMode::Out, - b'b' => ProArgMode::InOut, - b'v' => ProArgMode::Variadic, - b't' => ProArgMode::Table, - - // there's just no ability to move forward if given a value that we don't know about - _ => panic!("unrecognized `ProArgMode`: `{}`", value as u8 as char), - } - } -} - -#[non_exhaustive] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum ProKind { - Function, - Procedure, - Aggregate, - Window, -} - -impl From for ProKind { - fn from(value: i8) -> Self { - match value as u8 { - b'f' => ProKind::Function, - b'p' => ProKind::Procedure, - b'a' => ProKind::Aggregate, - b'w' => ProKind::Window, - - // there's just no ability to move forward if given a value that we don't know about - _ => panic!("unrecognized `ProKind`: `{}`", value as u8 as char), - } - } -} - -#[non_exhaustive] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum ProVolatile { - Immutable, - Stable, - Volatile, -} - -impl From for ProVolatile { - fn from(value: i8) -> Self { - match value as u8 { - b'i' => ProVolatile::Immutable, - b's' => ProVolatile::Stable, - b'v' => ProVolatile::Volatile, - - // there's just no ability to move forward if given a value that we don't know about - _ => panic!("unrecognized `ProVolatile`: `{}`", value as u8 as char), - } - } -} - -#[non_exhaustive] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum ProParallel { - Safe, - Restricted, - Unsafe, -} - -impl From for ProParallel { - fn from(value: i8) -> Self { - match value as u8 { - b's' => ProParallel::Safe, - b'r' => ProParallel::Restricted, - b'u' => ProParallel::Unsafe, - - // there's just no ability to move forward if given a value that we don't know about - _ => panic!("unrecognized `ProParallel`: `{}`", value as u8 as char), - } - } -} - -impl Drop for PgProc { - fn drop(&mut self) { - // SAFETY: We have a valid pointer and this just decrements the reference count. - // This will generally get resolved by the end of the transaction anyways, - // but Postgres strongly recommends you do not do that. - unsafe { pg_sys::ReleaseSysCache(self.inner.as_ptr()) } - } -} - -impl PgProc { - /// Construct a new [`PgProc`] from a known function [`pg_sys::Oid`]. If the specified oid is not - /// a function, we return [`None`]. - pub fn new(pg_proc_oid: pg_sys::Oid) -> Option { - unsafe { - // SAFETY: SearchSysCache1 will give us a valid HeapTuple or it'll return null. - // Either way, using NonNull::new()? will make the right decision for us - let entry = pg_sys::SearchSysCache1( - pg_sys::SysCacheIdentifier_PROCOID as _, - pg_proc_oid.into_datum()?, - ); - let inner = NonNull::new(entry)?; - Some(PgProc { inner, oid: pg_proc_oid }) - } - } - - /// Oid of the function - pub fn oid(&self) -> pg_sys::Oid { - self.oid - } - - /// Owner of the function - pub fn proowner(&self) -> pg_sys::Oid { - // won't panic because `proowner` has a NOT NULL constraint - self.get_attr(pg_sys::Anum_pg_proc_proowner).unwrap() - } - - /// Estimated execution cost (in units of cpu_operator_cost); if [`proretset()`][PgProc::proretset], - /// this is cost per row returned - pub fn procost(&self) -> f32 { - // won't panic because `procost` has a NOT NULL constraint - self.get_attr(pg_sys::Anum_pg_proc_procost).unwrap() - } - - /// Estimated number of result rows (zero if not [`proretset()`][PgProc::proretset]) - pub fn prorows(&self) -> f32 { - // won't panic because `prorows` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_prorows).unwrap() - } - - /// Data type of the variadic array parameter's elements, or [`None`] if the function does not have a variadic parameter - pub fn provariadic(&self) -> Option { - let oid = self.get_attr(pg_sys::Anum_pg_proc_provariadic).unwrap(); - if oid == pg_sys::InvalidOid { - None - } else { - Some(oid) - } - } - - /// Planner support function for this function (see Section 38.11), or zero if none - pub fn prosupport(&self) -> pg_sys::Oid { - // won't panic because `prosupport` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_prosupport).unwrap() - } - - /// The kind of function - pub fn prokind(&self) -> ProKind { - // won't panic because `prokind` has a NOT NULL constraint, so `.unwrap()` wont panic - ProKind::from(self.get_attr::(pg_sys::Anum_pg_proc_prokind).unwrap()) - } - - /// Returns true if the function is a security definer (i.e., a “setuid” function) - pub fn prosecdef(&self) -> bool { - // won't panic because `prosecdef` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_prosecdef).unwrap() - } - - /// The function has no side effects. No information about the arguments is conveyed except via - /// the return value. Any function that might throw an error depending on the values of its - /// arguments is not leak-proof. - pub fn proleakproof(&self) -> bool { - // won't panic because `proleakproof` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_proleakproof).unwrap() - } - - /// Implementation language or call interface of this function - pub fn prolang(&self) -> pg_sys::Oid { - // won't panic because `prolang` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_prolang).unwrap() - } - - /// This tells the function handler how to invoke the function. It might be the actual source - /// code of the function for interpreted languages, a link symbol, a file name, or just about - /// anything else, depending on the implementation language/call convention. - pub fn prosrc(&self) -> String { - // won't panic because `prosrc` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_prosrc).unwrap() - } - - /// Additional information about how to invoke the function. Again, the interpretation is - /// language-specific. - pub fn probin(&self) -> Option { - self.get_attr(pg_sys::Anum_pg_proc_probin) - } - - /// Function's local settings for run-time configuration variables - pub fn proconfig(&self) -> Option> { - self.get_attr(pg_sys::Anum_pg_proc_proconfig) - } - - /// From : - /// > An array of the modes of the function arguments, encoded as i for IN arguments, o for OUT - /// > arguments, b for INOUT arguments, v for VARIADIC arguments, t for TABLE arguments. If all - /// > the arguments are IN arguments, this field will be null. Note that subscripts correspond to - /// > positions of proallargtypes not proargtypes. - /// - /// In our case, if all the arguments are `IN` arguments, the returned Vec will have the - /// corresponding `ProArgModes::In` value in each element. - pub fn proargmodes(&self) -> Vec { - self.get_attr::>(pg_sys::Anum_pg_proc_proargmodes) - .unwrap_or_else(|| vec!['i' as i8; self.proargnames().len()]) - .into_iter() - .map(ProArgMode::from) - .collect::>() - } - - /// Number of input arguments - pub fn pronargs(&self) -> usize { - // won't panic because `pronargs` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr::(pg_sys::Anum_pg_proc_pronargs).unwrap() as usize - } - - /// Number of arguments that have defaults - pub fn pronargdefaults(&self) -> usize { - // won't panic because `pronargdefaults` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr::(pg_sys::Anum_pg_proc_pronargdefaults).unwrap() as usize - } - - /// An array of the names of the function arguments. Arguments without a name are set to empty - /// strings in the array. If none of the arguments have a name, this field will be null. Note - /// that subscripts correspond to positions of proallargtypes not proargtypes. - pub fn proargnames(&self) -> Vec> { - self.get_attr::>>(pg_sys::Anum_pg_proc_proargnames) - .unwrap_or_else(|| vec![None; self.pronargs()]) - } - - /// An array of the data types of the function arguments. This includes only input arguments - /// (including INOUT and VARIADIC arguments), and thus represents the call signature of the - /// function. - pub fn proargtypes(&self) -> Vec { - self.get_attr(pg_sys::Anum_pg_proc_proargtypes).unwrap_or_default() - } - - /// An array of the data types of the function arguments. This includes all arguments (including - /// OUT and INOUT arguments); however, if all the arguments are IN arguments, this field will be - /// null. Note that subscripting is 1-based, whereas for historical reasons proargtypes is - /// subscripted from 0. - pub fn proallargtypes(&self) -> Vec { - self.get_attr(pg_sys::Anum_pg_proc_proallargtypes).unwrap_or_else(|| self.proargtypes()) - } - - /// Data type of the return value - pub fn prorettype(&self) -> pg_sys::Oid { - // won't panic because `prorettype` has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_prorettype).unwrap() - } - - /// Function returns null if any call argument is null. In that case the function won't actually - /// be called at all. Functions that are not “strict” must be prepared to handle null inputs. - pub fn proisstrict(&self) -> bool { - // 'proisstrict' has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_proisstrict).unwrap() - } - - /// provolatile tells whether the function's result depends only on its input arguments, or is - /// affected by outside factors. It is i for “immutable” functions, which always deliver the - /// same result for the same inputs. It is s for “stable” functions, whose results (for fixed - /// inputs) do not change within a scan. It is v for “volatile” functions, whose results might - /// change at any time. (Use v also for functions with side-effects, so that calls to them - /// cannot get optimized away.) - pub fn provolatile(&self) -> ProVolatile { - // 'provolatile' has a NOT NULL constraint, so `.unwrap()` wont panic - ProVolatile::from(self.get_attr::(pg_sys::Anum_pg_proc_provolatile).unwrap()) - } - - /// proparallel tells whether the function can be safely run in parallel mode. It is s for - /// functions which are safe to run in parallel mode without restriction. It is r for functions - /// which can be run in parallel mode, but their execution is restricted to the parallel group - /// leader; parallel worker processes cannot invoke these functions. It is u for functions which - /// are unsafe in parallel mode; the presence of such a function forces a serial execution plan. - pub fn proparallel(&self) -> ProParallel { - // 'proparallel' has a NOT NULL constraint, so `.unwrap()` wont panic - ProParallel::from(self.get_attr::(pg_sys::Anum_pg_proc_proparallel).unwrap()) - } - - /// Function returns a set (i.e., multiple values of the specified data type) - pub fn proretset(&self) -> bool { - // 'proretset' has a NOT NULL constraint, so `.unwrap()` wont panic - self.get_attr(pg_sys::Anum_pg_proc_proretset).unwrap() - } - - /// Expression trees for default values. This is a [`List`] with `pronargdefaults` elements, - /// corresponding to the last N input arguments (i.e., the last N proargtypes positions). - /// - /// If none of the arguments have defaults, this function returns [`Option::None`]. - pub fn proargdefaults<'cx>( - &self, - mcx: &'cx MemCx<'_>, - ) -> Option> { - unsafe { - use pgrx_pg_sys::AsPgCStr; - - let mut is_null = false; - let proargdefaults = pg_sys::SysCacheGetAttr( - pg_sys::SysCacheIdentifier_PROCOID as _, - self.inner.as_ptr(), - pg_sys::Anum_pg_proc_proargdefaults as _, - &mut is_null, - ); - let proargdefaults = <&str>::from_datum(proargdefaults, is_null)?; - - let str = proargdefaults.as_pg_cstr(); - let argdefaults = mcx.exec_in(|| pg_sys::stringToNode(str)).cast::(); - pg_sys::pfree(str.cast()); - List::downcast_ptr_in_memcx(argdefaults, mcx) - } - } - - #[inline] - fn get_attr(&self, attribute: u32) -> Option { - unsafe { - // SAFETY: SysCacheGetAttr will give us what we need to create a Datum of type T, - // and this PgProc type ensures we have a valid "arg_tup" pointer for the cache entry - let mut is_null = false; - let datum = pg_sys::SysCacheGetAttr( - pg_sys::SysCacheIdentifier_PROCOID as _, - self.inner.as_ptr(), - attribute as _, - &mut is_null, - ); - T::from_datum(datum, is_null) - } - } -}