Files
supabase/apps/docs/content/guides/database/debugging-functions.mdx
T
Miranda Limonczenko a91ef3fe10 docs(database): regroup the database functions guide and split out debugging
Moves only. No claim changes and no reworded prose beyond the connective text
the new shape needs.

Groups the four procedures under one heading, promotes the Edge Functions
comparison ahead of them, and folds Deep dive into Resources. Eight top-level
headings become five.

Debugging moves to its own page. It carried three subsections and shared only
the word "function" with the rest of the guide. Every inbound anchor is
repointed and the page is registered in the Database sidebar.

Heading text that other pages link to is unchanged: Quick demo, Security
definer vs invoker, and General logging.
2026-09-30 14:36:22 -07:00

174 lines
4.2 KiB
Plaintext

---
id: 'debugging-functions'
title: 'Debugging database functions'
description: 'Logging and error handling inside Postgres functions.'
---
Add logs and error handling to a database function so you can see what it does at runtime. Logs matter most in a complex function.
For how to write and call a function, see [Database functions](/docs/guides/database/functions).
Good targets to log include:
- Values of (non-sensitive) variables
- Returned results from queries
## General logging
Use the `raise` keyword to write custom logs to the [Postgres logs](/dashboard/project/_/logs/postgres-logs) in the Dashboard. Three severity levels appear by default:
- `log`
- `warning`
- `exception` (error level)
```sql
create function logging_example(
log_message text,
warning_message text,
error_message text
)
returns void
language plpgsql
as $$
begin
raise log 'logging message: %', log_message;
raise warning 'logging warning: %', warning_message;
-- immediately ends function and reverts transaction
raise exception 'logging error: %', error_message;
end;
$$;
select logging_example('LOGGED MESSAGE', 'WARNING MESSAGE', 'ERROR MESSAGE');
```
## Error handling
You can create custom errors with the `raise exception` keywords.
A common pattern is to throw an error when a variable doesn't meet a condition:
```sql
create or replace function error_if_null(some_val text)
returns text
language plpgsql
as $$
begin
-- error if some_val is null
if some_val is null then
raise exception 'some_val should not be NULL';
end if;
-- return some_val if it is not null
return some_val;
end;
$$;
select error_if_null(null);
```
Value checking is common, so Postgres provides the `assert` keyword as a shorthand. It takes the following format:
```sql
-- throw error when condition is false
assert <some condition>, 'message';
```
For example:
```sql
create function assert_example(name text)
returns uuid
language plpgsql
as $$
declare
student_id uuid;
begin
-- save a user's id into the user_id variable
select
id into student_id
from attendance_table
where student = name;
-- throw an error if the student_id is null
assert student_id is not null, 'assert_example() ERROR: student not found';
-- otherwise, return the user's id
return student_id;
end;
$$;
select assert_example('Harry Potter');
```
You can also capture and modify an error message with the `exception` keyword:
```sql
create function error_example()
returns void
language plpgsql
as $$
begin
-- fails: cannot read from nonexistent table
select * from table_that_does_not_exist;
exception
when others then
raise exception 'An error occurred in function <function name>: %', sqlerrm;
end;
$$;
```
## Advanced logging
For a more complex function, or for harder debugging, log the following:
- Formatted variables
- Individual rows
- Start and end of function calls
```sql
create or replace function advanced_example(num int default 10)
returns text
language plpgsql
as $$
declare
var1 int := 20;
var2 text;
begin
-- Logging start of function
raise log 'logging start of function call: (%)', (select now());
-- Logging a variable from a SELECT query
select
col_1 into var1
from some_table
limit 1;
raise log 'logging a variable (%)', var1;
-- It is also possible to avoid using variables, by returning the values of your query to the log
raise log 'logging a query with a single return value(%)', (select col_1 from some_table limit 1);
-- If necessary, you can even log an entire row as JSON
raise log 'logging an entire row as JSON (%)', (select to_jsonb(some_table.*) from some_table limit 1);
-- When using INSERT or UPDATE, the new value(s) can be returned
-- into a variable.
-- When using DELETE, the deleted value(s) can be returned.
-- All three operations use "RETURNING value(s) INTO variable(s)" syntax
insert into some_table (col_2)
values ('new val')
returning col_2 into var2;
raise log 'logging a value from an INSERT (%)', var2;
return var1 || ',' || var2;
exception
-- Handle exceptions here if needed
when others then
raise exception 'An error occurred in function <advanced_example>: %', sqlerrm;
end;
$$;
select advanced_example();
```