@@ -1429,6 +1429,197 @@ pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
14291429 item
14301430}
14311431
1432+ /**
1433+ Declare a function as a GUC hook. This takes one argument: `show`, `check`, or `assign`.
1434+
1435+ The first parameter of `check` and `assign` hooks must implement `pgrx::guc::GucValue`.
1436+
1437+ Examples:
1438+ ```rust,ignore
1439+ #[pg_guc_hook(show)]
1440+ fn my_show_hook() -> String {
1441+ "CUSTOM_VALUE".to_string()
1442+ }
1443+
1444+ #[pg_guc_hook(check)]
1445+ fn my_check_hook(newval: i32) -> Result<(), GucCheckError> {
1446+ // accept or reject newval
1447+ }
1448+
1449+ #[pg_guc_hook(assign)]
1450+ fn my_assign_hook(newval: i32) {
1451+ // do more now that every change is accepted
1452+ }
1453+ ```
1454+
1455+ # Check Hooks
1456+
1457+ This macro adapts check hooks of multiple forms.
1458+
1459+ The simplest of these takes a single argument and returns a `bool`.
1460+ - `true` means the value is valid, and Postgres will store that value in the GUC variable.
1461+ - `false` means the value is invalid, and Postgres will produce an error message automatically.
1462+
1463+ ```rust,ignore
1464+ #[pg_guc_hook(check)]
1465+ fn my_check_hook(newval: i32) -> bool { newval > 0 }
1466+ ```
1467+
1468+ To apply your own error message for an invalid value, return a `Result<(), GucCheckError>`.
1469+ - `Ok` means the value is valid.
1470+ - `Err` means the value is invalid, and `pgrx` will pass the contents of `GucCheckError` to Postgres.
1471+
1472+ ```rust,ignore
1473+ #[pg_guc_hook(check)]
1474+ fn my_check_hook(newval: i32) -> Result<(), GucCheckError> {
1475+ if newval > 0 {
1476+ Ok(())
1477+ } else {
1478+ Err(
1479+ GucCheckError::new("value cannot be negative")
1480+ .with_hint("to configure the opposite behavior, set other_param"),
1481+ )
1482+ }
1483+ }
1484+ ```
1485+
1486+ A check hook can also accept a second argument that indicates when the parameter is changing.
1487+ Like the examples above, this can return a bool or a Result.
1488+
1489+ ```rust,ignore
1490+ #[pg_guc_hook(check)]
1491+ fn my_check_hook(newval: i32, source: pg_sys::GucSource::Type) -> bool { true }
1492+ ```
1493+ */
1494+ #[ proc_macro_attribute]
1495+ pub fn pg_guc_hook ( attr : TokenStream , item : TokenStream ) -> TokenStream {
1496+ let hook_type = parse_macro_input ! ( attr as syn:: Ident ) ;
1497+ let mut func = parse_macro_input ! ( item as syn:: ItemFn ) ;
1498+
1499+ // Rename the original function and use its name.
1500+ let original_ident = func. sig . ident . clone ( ) ;
1501+ let inner_ident = format_ident ! ( "{}_INNER" , original_ident) ;
1502+ func. sig . ident = inner_ident. clone ( ) ;
1503+
1504+ // Remove the original visibility and use it.
1505+ let vis = & func. vis ;
1506+ let mut inner_func = func. clone ( ) ;
1507+ inner_func. vis = syn:: Visibility :: Inherited ;
1508+
1509+ let wrapper = match hook_type. to_string ( ) . as_str ( ) {
1510+ "show" => {
1511+ quote ! {
1512+ #[ :: pgrx:: pg_guard]
1513+ #vis unsafe extern "C-unwind" fn #original_ident( ) -> * const :: core:: ffi:: c_char {
1514+ #[ inline( always) ]
1515+ #inner_func
1516+
1517+ let show = #inner_ident( ) ;
1518+ :: pgrx:: pg_sys:: AsPgCStr :: as_pg_cstr( & show)
1519+ }
1520+ }
1521+ }
1522+ "check" => {
1523+ let invoke_inner = match func. sig . inputs . len ( ) {
1524+ 1 => quote ! { #inner_ident( value) } ,
1525+ 2 => quote ! { #inner_ident( value, source) } ,
1526+ _ => {
1527+ return syn:: Error :: new (
1528+ func. sig . span ( ) ,
1529+ "check hook must have one or two arguments" ,
1530+ )
1531+ . into_compile_error ( )
1532+ . into ( ) ;
1533+ }
1534+ } ;
1535+
1536+ let arg_type = match func. sig . inputs . first ( ) {
1537+ Some ( syn:: FnArg :: Typed ( pat_type) ) => & pat_type. ty ,
1538+ _ => {
1539+ return syn:: Error :: new (
1540+ func. sig . span ( ) ,
1541+ "check hook first argument must implement GucValue" ,
1542+ )
1543+ . into_compile_error ( )
1544+ . into ( ) ;
1545+ }
1546+ } ;
1547+
1548+ // The original function may return bool or Result. Pass bool through directly and apply
1549+ // any details present in GucCheckError.
1550+ let invoke_to_bool = if let syn:: ReturnType :: Type ( _, return_type) = & func. sig . output
1551+ && let syn:: Type :: Path ( type_path) = & * * return_type
1552+ && type_path. path . segments . last ( ) . map_or ( false , |seg| seg. ident == "bool" )
1553+ {
1554+ quote ! { #invoke_inner }
1555+ } else {
1556+ quote ! {
1557+ match #invoke_inner {
1558+ Ok ( ( ) ) => true ,
1559+ Err ( err) => {
1560+ unsafe { :: pgrx:: guc:: GucCheckError :: apply( err) } ;
1561+ false
1562+ }
1563+ }
1564+ }
1565+ } ;
1566+
1567+ quote ! {
1568+ #[ :: pgrx:: pg_guard]
1569+ #vis unsafe extern "C-unwind" fn #original_ident(
1570+ newval: * mut <#arg_type as :: pgrx:: guc:: GucValue >:: Raw ,
1571+ extra: * mut * mut :: core:: ffi:: c_void,
1572+ source: :: pgrx:: pg_sys:: GucSource :: Type ,
1573+ ) -> bool {
1574+ #[ inline( always) ]
1575+ #inner_func
1576+
1577+ debug_assert!( !newval. is_null( ) ) ;
1578+ let value = unsafe { <#arg_type as :: pgrx:: guc:: GucValue >:: from_raw( * newval) } ;
1579+ #invoke_to_bool
1580+ }
1581+ }
1582+ }
1583+ "assign" => {
1584+ let arg_type = match func. sig . inputs . first ( ) {
1585+ Some ( syn:: FnArg :: Typed ( pat_type) ) => & pat_type. ty ,
1586+ _ => {
1587+ return syn:: Error :: new (
1588+ func. sig . span ( ) ,
1589+ "assign hook first argument must implement GucValue" ,
1590+ )
1591+ . into_compile_error ( )
1592+ . into ( ) ;
1593+ }
1594+ } ;
1595+
1596+ quote ! {
1597+ #[ :: pgrx:: pg_guard]
1598+ #vis unsafe extern "C-unwind" fn #original_ident(
1599+ newval: <#arg_type as :: pgrx:: guc:: GucValue >:: Raw ,
1600+ extra: * mut :: core:: ffi:: c_void,
1601+ ) {
1602+ #[ inline( always) ]
1603+ #inner_func
1604+
1605+ let value = unsafe { <#arg_type as :: pgrx:: guc:: GucValue >:: from_raw( newval) } ;
1606+ #inner_ident( value) ;
1607+ }
1608+ }
1609+ }
1610+ _ => {
1611+ return syn:: Error :: new (
1612+ hook_type. span ( ) ,
1613+ "Unknown GUC hook type. Expected 'show', 'check', or 'assign'" ,
1614+ )
1615+ . into_compile_error ( )
1616+ . into ( ) ;
1617+ }
1618+ } ;
1619+
1620+ wrapper. into ( )
1621+ }
1622+
14321623/**
14331624Create a [PostgreSQL trigger function](https://www.postgresql.org/docs/current/plpgsql-trigger.html)
14341625
0 commit comments