Triggers

PL/Rust functions can be used to define trigger functions on data changes. A trigger function is created using the CREATE FUNCTION command, declaring it as a function with no arguments and a return type of trigger. Trigger variables are available from in trigger to describe the condition that triggered the call and the new and old rows.

PL/Rust trigger support options are documented on docs.rs and defined in the .rs files in the trigger_support directory.

These examples are an expansion of the code from plrust/plrust/src/tests.rs. The elaborations here illustrate additional functionality.

Table for Triggers

Create the plrust.dog table to allow us to keep track of our dogs, and how much attention they have received via a count of scritches.

CREATE TABLE plrust.dog (
    id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name TEXT,
    scritches INT NOT NULL DEFAULT 0,
    last_scritch TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

The name column in plrust.dog is the only column without a default value set. The scritches and last_scritch column both have defaults set. The goal of this design is to only have to define the name during INSERT. Subsequent UPDATE queries should only have to update the last_scritch column.

Trigger example

The following example creates a trigger function named plrust.dog_trigger(). The trigger will be used on INSERT and UPDATE with slightly different behavior depending on which operation is being used. This logic is based on the value of trigger.op()?, for INSERT the trigger.new object is used, for UPDATE the trigger.old object is used. This code is explained further after the code block.

CREATE OR REPLACE FUNCTION plrust.dog_trigger()
RETURNS trigger AS
$$
    let tg_op = trigger.op()?;

    let my_row = match tg_op {
        INSERT => trigger.new().unwrap(),
        _ => trigger.old().unwrap()
    };
    let mut my_row = my_row.into_owned();

    let counter_field = "scritches";
    match my_row.get_by_name::<i32>(counter_field)? {
        Some(val) => my_row.set_by_name(counter_field, val + 1)?,
        None => (),
    }

    Ok(Some(my_row))
$$
LANGUAGE plrust;


CREATE TRIGGER dog_trigger
    BEFORE INSERT OR UPDATE ON plrust.dog
    FOR EACH ROW
    EXECUTE FUNCTION plrust.dog_trigger();

The tg_op variable is available from the trigger.op() method and has values of INSERT, UPDATE, DELETE and TRUNCATE. See the definition of PgTriggerOperation for more. The tg_op value is used in a match to define the my_row variable.

#![allow(unused)]
fn main() {
let tg_op = trigger.op()?;

let my_row = match tg_op {
    INSERT => trigger.new().unwrap(),
    _ => trigger.old().unwrap()
};
let mut my_row = my_row.into_owned();
}

With the appropriate my_row identified, the next step is to increment the scritches column by 1. This is defined in the counter_field variable for easy reuse. The get_by_name and set_by_name functions are used for this operation.

#![allow(unused)]
fn main() {
let counter_field = "scritches";
match my_row.get_by_name::<i32>(counter_field)? {
    Some(val) => my_row.set_by_name(counter_field, val + 1)?,
    None => (),
}
}

Finally, the my_row is returned for the operation to proceed.

#![allow(unused)]
fn main() {
Ok(Some(my_row))
}

Next we INSERT a row and then query the table to observe the effects of the trigger.

INSERT INTO plrust.dog (name) VALUES ('Nami');
SELECT * FROM plrust.dog;

The results show that while the DEFAULT value for the scritches column is defined as 0 in the table, the initial value is 1 because trigger updated the value.

 id | name | scritches |         last_scritch          
----+------+-----------+-------------------------------
  1 | Nami |         1 | 2023-03-04 17:30:43.601525+00

If we update the record for Nami by setting the last_scritch value to NOW() the trigger will increment the scritches column value for us.

UPDATE plrust.dog
    SET last_scritch = NOW()
    WHERE id = 1;

SELECT * FROM plrust.dog;
 id | name | scritches |         last_scritch          
----+------+-----------+-------------------------------
  1 | Nami |         2 | 2023-03-04 17:35:05.320482+00

Not yet supported

Event Triggers and DO blocks are not (yet) supported by PL/Rust.