Skip to content

Functions & Triggers

Requires the dbwarden-pgsql-extensions plugin: dbwarden plugin add dbwarden-pgsql-extensions. The plugin owns the pg_functions and pg_triggers config keys and their object handlers, so declaring them without it installed raises DBWardenConfigError at config load.

Functions and triggers are config-driven objects processed during the PREAMBLE phase (before table diffing). They are declared in database_config(...).

Functions

Declared via pg_functions. Each function definition includes the body, language, and options.

pg_functions=[
    {
        "name": "update_timestamp",
        "language": "plpgsql",
        "body": """
            BEGIN
                NEW.updated_at = NOW();
                RETURN NEW;
            END;
        """,
        "returns": "trigger",
    },
    {
        "name": "count_users",
        "language": "sql",
        "body": "SELECT count(*) FROM users",
        "returns": "bigint",
        "volatility": "STABLE",
    },
]

Lifecycle

Operation DDL
Create CREATE FUNCTION name (...args...) RETURNS type AS $$ body $$ LANGUAGE lang;
Drop DROP FUNCTION IF EXISTS name (...args...) CASCADE;

Changes are detected as drop-then-create. There is no CREATE OR REPLACE for changed bodies.

Options

Config Key SQL
volatility VOLATILE / STABLE / IMMUTABLE
security_definer SECURITY DEFINER
leakproof LEAKPROOF
parallel PARALLEL UNSAFE / RESTRICTED / SAFE
cost COST n
rows ROWS n

Volatility semantics:

Volatility Behaviour Optimizer Assumptions
VOLATILE Can return different results on each call (default) No optimizations
STABLE Same results within same statement Can be used in index scan conditions
IMMUTABLE Same results for same arguments always Can be pre-evaluated, used in expression indexes, partition pruning

Return Types

Returns Description
trigger Trigger function (returns TRIGGER)
void No return value
table (...) Set-returning function (use rows for estimate)
setof type Set-returning function (alternative syntax)
Any PG type Scalar return

Arguments

{
    "name": "add_user",
    "args": [
        {"name": "p_name", "type": "text"},
        {"name": "p_email", "type": "text"},
    ],
    "returns": "int",
    "language": "sql",
    "body": "INSERT INTO users (name, email) VALUES (p_name, p_email) RETURNING id",
}

Parameter Modes

Mode Description
IN Default: input parameter
OUT Output parameter, acts as return value
INOUT Input-output parameter (both accepts and returns)
VARIADIC Variable number of arguments (last parameter only)
{
    "name": "get_user_stats",
    "args": [
        {"name": "p_user_id", "type": "int", "mode": "IN"},
        {"name": "out_count", "type": "bigint", "mode": "OUT"},
    ],
    "returns": "bigint",
    "language": "sql",
    "body": "SELECT count(*) FROM orders WHERE user_id = p_user_id",
}

Function Overloading

PostgreSQL supports multiple functions with the same name but different argument types. DBWarden tracks the full argument signature. Two functions with the same name but different args are treated as distinct objects.

WINDOW Functions

{
    "name": "rank_per_category",
    "returns": "int",
    "language": "sql",
    "body": "SELECT rank() OVER (PARTITION BY category ORDER BY score DESC)",
    "args": [{"name": "category", "type": "text"}],
}

Set RETURNS TABLE (...) or RETURNS SETOF with rows estimate.

Procedures

PostgreSQL 11+ supports CREATE PROCEDURE, distinct from functions in that procedures can use transaction control (COMMIT / ROLLBACK).

pg_functions=[
    {
        "name": "transfer_funds",
        "language": "plpgsql",
        "body": """
            BEGIN
                UPDATE accounts SET balance = balance - amount WHERE id = from_id;
                UPDATE accounts SET balance = balance + amount WHERE id = to_id;
                COMMIT;
            END;
        """,
        "returns": "void",
        "kind": "procedure",
    },
]

Key differences from functions:

Aspect Function Procedure
Transaction control No Yes (COMMIT/ROLLBACK)
Called via SELECT func() CALL proc()
Return value Required (can be void) No return value
kind field Omit or "function" "procedure"

Triggers

Declared via pg_triggers. Each trigger binds a function to a table event.

pg_triggers=[
    {
        "name": "trg_users_updated_at",
        "table": "users",
        "function": "update_timestamp",
        "timing": "BEFORE",
        "events": ["UPDATE"],
        "for_each": "ROW",
    },
]

Lifecycle

Operation DDL
Create CREATE TRIGGER name timing event ON table FOR EACH ROW EXECUTE FUNCTION func();
Alter ALTER TRIGGER name ON table RENAME TO new_name;
Drop DROP TRIGGER IF EXISTS name ON table;

Events

One or more of: INSERT, UPDATE, DELETE, TRUNCATE.

Timing

Timing Description
BEFORE Fires before the event
AFTER Fires after the event
INSTEAD OF Replaces the event (views only)

UPDATE OF Columns

Fire the trigger only when specific columns are updated:

pg_triggers=[{
    "name": "trg_user_email",
    "table": "users",
    "function": "send_email_verification",
    "timing": "AFTER",
    "events": ["UPDATE OF email"],
    "for_each": "ROW",
}]

Condition

Use condition to add a WHEN clause:

pg_triggers=[{
    "name": "trg_prevent_delete",
    "table": "users",
    "function": "prevent_delete",
    "timing": "BEFORE",
    "events": ["DELETE"],
    "for_each": "ROW",
    "condition": "OLD.is_protected",
}]

Trigger Function Context

Trigger functions access row data through special variables:

Variable Type Description
NEW RECORD New row for INSERT/UPDATE (NULL for DELETE)
OLD RECORD Old row for UPDATE/DELETE (NULL for INSERT)
TG_OP text Operation: INSERT, UPDATE, DELETE, TRUNCATE
TG_TABLE_NAME text Table that fired the trigger
TG_TABLE_SCHEMA text Schema of the table
TG_NAME text Trigger name
TG_WHEN text Timing: BEFORE, AFTER, or INSTEAD OF
TG_LEVEL text ROW or STATEMENT
TG_NARGS int Number of arguments passed to the trigger
TG_ARGV text[] Arguments passed to the trigger

Constraint Triggers

Constraint triggers are deferred triggers that fire at transaction end. Declared separately from regular triggers:

pg_triggers=[{
    "name": "trg_check_balance",
    "table": "accounts",
    "function": "verify_balance",
    "timing": "AFTER",
    "events": ["UPDATE"],
    "for_each": "ROW",
    "constraint": True,
    "deferrable": True,
    "initially": "DEFERRED",
}]

Generated DDL:

CREATE CONSTRAINT TRIGGER trg_check_balance
AFTER UPDATE ON accounts
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE FUNCTION verify_balance();

Trigger Arguments

pg_triggers=[{
    "name": "trg_log_changes",
    "table": "users",
    "function": "log_changes",
    "timing": "AFTER",
    "events": ["UPDATE"],
    "for_each": "ROW",
    "args": ["user_audit", "ignore_columns:password_hash"],
}]

Arguments are passed as TG_ARGV[0], TG_ARGV[1], etc. in the trigger function.