Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ else
endif

EXTENSION = pg_net
EXTVERSION = 0.20.4
EXTVERSION = 0.21.0

DATA = $(wildcard sql/*--*.sql)

Expand Down
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,75 @@ FROM selected_row

---

## Callback requests (net.http_request)

### net.http_request function signature

```sql
net.http_request(
-- http method (GET, POST or DELETE)
method net.http_method,
-- url for the request
url text,
-- key/value pairs to be url encoded and appended to the `url`
params jsonb default '{}'::jsonb,
-- key/values to be included in request headers
headers jsonb default '{}'::jsonb,
-- optional body of the request
body jsonb default null,
-- the maximum number of milliseconds the request may take before being cancelled
timeout_milliseconds int default 5000,
-- SQL command executed when an HTTP response arrives (any status code).
-- Parameters: $1 = status_code int, $2 = headers jsonb, $3 = body text.
-- When null, the response is discarded (fire-and-forget).
on_success text default null,
-- SQL command executed when the request fails without an HTTP response
-- (timeout, connection error). Parameters: $1 = error message text,
-- $2 = timed_out bool. When null, the failure is only logged.
on_error text default null
)
returns void

volatile
parallel unsafe
language plpgsql
```

Unlike `http_get`/`http_post`/`http_delete`, requests made with `net.http_request` never touch the `net._http_response` table: the response is handed to the `on_success` callback, or discarded when no callback is given. This avoids the write-then-expire churn on `net._http_response` for callers that never read responses (see [#62](https://github.com/supabase/pg_net/issues/62)).

- Every HTTP response — including 4xx/5xx — goes through `on_success`, with the status code as `$1`. Only failures without a response (timeouts, connection errors) go through `on_error`.
- Callbacks are executed by the background worker **as the role that enqueued the request**, with `SET ROLE`/`SET SESSION AUTHORIZATION` blocked, so they can't do anything the enqueuing role couldn't.
- A callback that raises an error is rolled back and reported as a `WARNING` in the database logs; the batch keeps processing and the request is considered handled.
- Retry policies stay in user hands: an `on_error` (or `on_success` checking `$1`) callback can re-enqueue with `net.http_request(...)` again.

### Examples:

#### Fire-and-forget

```sql
SELECT net.http_request(
'POST',
'https://postman-echo.com/post',
headers := '{"Content-Type": "application/json"}'::JSONB,
body := '{"key": "value"}'::JSONB
);
```

No response row is stored, and the response body is not even buffered in memory.

#### Processing the response

```sql
CREATE TABLE webhook_results(status int, body jsonb);

SELECT net.http_request(
'GET',
'https://postman-echo.com/get',
on_success := 'insert into webhook_results values ($1, $3::jsonb)',
on_error := $$select pg_notify('webhook_failures', $1)$$
);
```

# Practical Examples

## Syncing data with an external data source using triggers
Expand Down
60 changes: 60 additions & 0 deletions sql/pg_net--0.20.4--0.21.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
alter table net.http_request_queue
add column use_callbacks bool not null default false,
add column on_success text,
add column on_error text,
add column calling_role text;

-- Interface to make an async request handled by callbacks instead of the
-- net._http_response table. See https://github.com/supabase/pg_net/issues/62
-- API: Public
create or replace function net.http_request(
-- http method (GET, POST or DELETE)
method net.http_method,
-- url for the request
url text,
-- key/value pairs to be url encoded and appended to the `url`
params jsonb default '{}'::jsonb,
-- key/values to be included in request headers
headers jsonb default '{}'::jsonb,
-- optional body of the request
body jsonb default null,
-- the maximum number of milliseconds the request may take before being cancelled
timeout_milliseconds int default 5000,
-- SQL command executed by the background worker when an HTTP response
-- arrives (any status code). Parameters: $1 = status_code int,
-- $2 = headers jsonb, $3 = body text. When null, the response is
-- discarded without being stored anywhere (fire-and-forget).
on_success text default null,
-- SQL command executed by the background worker when the request fails
-- without an HTTP response (timeout, connection error). Parameters:
-- $1 = error message text, $2 = timed_out bool. When null, the failure
-- is only reported in the database logs.
on_error text default null
)
returns void
language plpgsql
as $$
declare
params_array text[];
begin
select coalesce(array_agg(net._urlencode_string(key) || '=' || net._urlencode_string(value)), '{}')
into params_array
from jsonb_each_text(params);

-- Add to the request queue
insert into net.http_request_queue(method, url, headers, body, timeout_milliseconds, use_callbacks, on_success, on_error, calling_role)
values (
method,
net._encode_url_with_params_array(url, params_array),
headers,
convert_to(body::text, 'UTF8'),
timeout_milliseconds,
true,
on_success,
on_error,
current_user
);

perform net.wake();
end
$$;
66 changes: 65 additions & 1 deletion sql/pg_net.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ create unlogged table net.http_request_queue(
url text not null,
headers jsonb,
body bytea,
timeout_milliseconds int not null
timeout_milliseconds int not null,
-- when true, the response is handed to the callbacks below and never
-- stored in net._http_response
use_callbacks bool not null default false,
-- SQL command executed when an HTTP response arrives (any status code)
on_success text,
-- SQL command executed when the request fails without an HTTP response
on_error text,
-- role the callbacks are executed as
calling_role text
);

create or replace function net.check_worker_is_up() returns void as $$
Expand Down Expand Up @@ -261,6 +270,61 @@ begin
end
$$;

-- Interface to make an async request handled by callbacks instead of the
-- net._http_response table. See https://github.com/supabase/pg_net/issues/62
-- API: Public
create or replace function net.http_request(
-- http method (GET, POST or DELETE)
method net.http_method,
-- url for the request
url text,
-- key/value pairs to be url encoded and appended to the `url`
params jsonb default '{}'::jsonb,
-- key/values to be included in request headers
headers jsonb default '{}'::jsonb,
-- optional body of the request
body jsonb default null,
-- the maximum number of milliseconds the request may take before being cancelled
timeout_milliseconds int default 5000,
-- SQL command executed by the background worker when an HTTP response
-- arrives (any status code). Parameters: $1 = status_code int,
-- $2 = headers jsonb, $3 = body text. When null, the response is
-- discarded without being stored anywhere (fire-and-forget).
on_success text default null,
-- SQL command executed by the background worker when the request fails
-- without an HTTP response (timeout, connection error). Parameters:
-- $1 = error message text, $2 = timed_out bool. When null, the failure
-- is only reported in the database logs.
on_error text default null
)
returns void
language plpgsql
as $$
declare
params_array text[];
begin
select coalesce(array_agg(net._urlencode_string(key) || '=' || net._urlencode_string(value)), '{}')
into params_array
from jsonb_each_text(params);

-- Add to the request queue
insert into net.http_request_queue(method, url, headers, body, timeout_milliseconds, use_callbacks, on_success, on_error, calling_role)
values (
method,
net._encode_url_with_params_array(url, params_array),
headers,
convert_to(body::text, 'UTF8'),
timeout_milliseconds,
true,
on_success,
on_error,
current_user
);

perform net.wake();
end
$$;

-- Lifecycle states of a request (all protocols)
-- API: Public
create type net.request_status as enum ('PENDING', 'SUCCESS', 'ERROR');
Expand Down
Loading