mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
Part 3 of 4 in stack #50823. This PR carries **technical revision only**: claims that produce a wrong outcome for a reader. ## Problem This PR came from running a technical assessment using `/test-the-docs`. A reader pastes the guide top to bottom. Two snippets fail. The `planets` table uses a `serial` primary key, then the seed sets ids explicitly. Explicit ids don't advance the sequence, so it stays at 0. The `add_planet('Jakku')` example then draws id 1, which the seed already used: ``` ERROR: duplicate key value violates unique constraint "planets_pkey" DETAIL: Key (id)=(1) already exists. ``` The `security definer` example re-creates `hello_world` with `create` rather than `create or replace`, so it collides with the function from Basic functions: ``` ERROR: function "hello_world" already exists with same argument types ``` The Data tab spells the planet Tatooine. The SQL tab spells it Tattoine. Two debugging snippets read `attendance_table` and `some_table`. No fence creates either, and neither is marked as omitted. ## Solution - **Seeds `planets` and `people` without explicit ids.** The sequence advances, so `add_planet` succeeds. This also settles Tattoine against Tatooine. - **Uses `create or replace` in the definer example**, so it no longer collides. - **Marks the two assumed tables** in the debugging snippets with a comment. - **Points the CREATE FUNCTION link at the current Postgres docs.** It pointed at 9.1, while the intro already links the current version of the same page. **Verification.** I ran every `sql` fence from the guide in document order against Postgres 15 in a throwaway container, with `anon` and `authenticated` created first. Before these changes, two fences errored. After them, the sequence runs clean. ## Manual testing 1. Start a throwaway Postgres: `docker run --rm -d --name pgcheck -e POSTGRES_PASSWORD=pw postgres:15`. 2. Create the Supabase roles the guide references: `docker exec -i pgcheck psql -U postgres -c "create role anon; create role authenticated;"`. 3. Paste every `sql` block from the guide, in page order, into `docker exec -i pgcheck psql -U postgres`. No statement errors. 4. Run `select * from planets;`. Tatooine, Alderaan, Kashyyyk, and Jakku, with sequential ids. 5. Remove it: `docker rm -f pgcheck`. ## Preview links | Site | Live | Preview | Search for | | --- | --- | --- | --- | | Docs | [/docs/guides/database/functions](https://supabase.com/docs/guides/database/functions) | [/docs/guides/database/functions](https://docs-git-docs-functions-technical-supabase.vercel.app/docs/guides/database/functions) | `('Tatooine')` | | Docs | New page, 404 in production | [/docs/guides/database/debugging-functions](https://docs-git-docs-functions-technical-supabase.vercel.app/docs/guides/database/debugging-functions) | `assumes an attendance_table` | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Documentation** - Clarified the required column types in database function examples. - Expanded guidance on function return values, including `INSERT`, `UPDATE`, and `DELETE` statements with `RETURNING` clauses. - Updated SQL examples to show table creation and automatically generated IDs, corrected the spelling of “Tatooine,” and refreshed the PostgreSQL reference link. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
175 lines
4.3 KiB
Plaintext
175 lines
4.3 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:
|
|
|
|
```text
|
|
assert <some condition>, 'message';
|
|
```
|
|
|
|
For example:
|
|
|
|
```sql
|
|
-- assumes an attendance_table with an id uuid column and a student text column
|
|
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
|
|
-- assumes a some_table with a col_1 int column and a col_2 text column
|
|
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();
|
|
```
|