Function Arguments
PL/Rust function arguments are mapped in the same order declared by the CREATE FUNCTION
statement. They'll have the
same names and the types will be mapped following the supported data type mappings. Note that the
STRICT
function property impacts the actual type. This is described below.
Naming
The basic rules for naming are:
- Argument names are case-sensitive.
- Argument names must also be valid Rust identifiers. It's best to stick with lowercase ASCII in the set
[a-z0-9_]
. - Anonymous argument names are not supported. Procedural Languages such as
sql
andplpgsql
support anonymous arguments where they can be referenced as$1
,$2
, etc. PL/Rust does not.
Argument Ownership
Except in the case of SQL the TEXT/VARCHAR
and BYTEA
types, all argument datums are passed to the PL/Rust function
as owned, immutable instances.
Quick Code Example
Given a LANGUAGE plrust
function like this:
CREATE OR REPLACE FUNCTION lots_of_args(a TEXT, b INT, c BOOL[], d JSON) RETURNS INT STRICT LANGUAGE plrust AS $$
// ... code goes here ...
$$;
PL/Rust essentially generates a wrapper Rust function like this:
#![allow(unused)] fn main() { use pgrx::prelude::*; fn lots_of_args(a: &str, b: i32, c: Vec<Option<bool>>, d: Json) -> Result<Option<i32>, Box<dyn std::error::Error + Send + Sync + 'static>> { // ... code goes here ... } }
It is the developer's responsibility to fully implement this function, including returning the proper value.
Note that the above is just an abridged example. The anatomy section describes in detail what really happens.
The section below describes how the STRICT
keyword impacts the actual function signature, specifically each argument type.
STRICT
and NULL
PL/Rust uses Rust's Option<T>
type to represent arguments that might be NULL, plus all return types. A Postgres UDF
that is not declared as STRICT
means that any of its arguments might be NULL, and PL/Rust is required to account
for this at compile time. This means that the actual PL/Rust function argument type is context dependent.
As a Postgres refresher, declaring a function as STRICT
(which is not the default) means that if any argument
value is NULL
then the return value is also NULL
. In this case, Postgres elides calling the function.
This distinction allows PL/Rust to optimize a bit. STRICT
functions have Rust argument types of T
whereas non-STRICT
functions have argument types of Option<T>
.
Here is the "same" function, the first declared as STRICT
, the second not:
CREATE OR REPLACE FUNCTION lcase(s TEXT) RETURNS TEXT STRICT LANGUAGE plrust AS $$
let lcase = s.to_lowercase(); // `s` is a `&str`
Ok(Some(lcase))
$$;
# SELECT lcase('HELLO WORLD'), lcase(NULL) IS NULL AS is_null;
lcase | is_null
-------------+---------
hello world | t
CREATE OR REPLACE FUNCTION lcase(s TEXT) RETURNS TEXT LANGUAGE plrust AS $$
let unwrapped_s = s.unwrap(); // `s` is an `Option<&str>` and will panic if `s` IS NULL
let lcase = unwrapped_s.to_lowercase();
Ok(Some(lcase))
$$;
# SELECT lcase('HELLO WORLD'), lcase(NULL) IS NULL AS is_null;
ERROR: called `Option::unwrap()` on a `None` value
Rust programmers likely recognize this error message. When a function is not declared as STRICT
, it is the programmer's
responsibility to properly handle the possibility of an argument being Option::None
.
STRICT
is an Immutable Property
PL/Rust requires that a LANGUAGE plrust
function's STRICT
property be immutable. As such, PL/Rust prohibits
ALTERing the STRICT
property:
ALTER FUNCTION lcase STRICT;
ERROR: plrust functions cannot have their STRICT property altered
DETAIL: Use 'CREATE OR REPLACE FUNCTION' to alter the STRICT-ness of an existing plrust function
Instead, you must CREATE OR REPLACE
the function. The reason for this is that the underlying Rust wrapper function's
signature will be different and this will require that the code be changed to account for the new argument type
(Option<T>
or T
).