@pgflow/core 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -19
- package/dist/ATLAS.md +32 -0
- package/dist/CHANGELOG.md +16 -0
- package/dist/README.md +31 -19
- package/dist/database-types.d.ts +116 -45
- package/dist/database-types.d.ts.map +1 -1
- package/dist/database-types.js +8 -1
- package/dist/package.json +2 -2
- package/dist/supabase/migrations/20250429164909_pgflow_initial.sql +579 -0
- package/package.json +3 -3
- package/dist/supabase/migrations/000000_schema.sql +0 -149
- package/dist/supabase/migrations/000005_create_flow.sql +0 -29
- package/dist/supabase/migrations/000010_add_step.sql +0 -48
- package/dist/supabase/migrations/000015_start_ready_steps.sql +0 -45
- package/dist/supabase/migrations/000020_start_flow.sql +0 -46
- package/dist/supabase/migrations/000030_read_with_poll_backport.sql +0 -70
- package/dist/supabase/migrations/000040_poll_for_tasks.sql +0 -100
- package/dist/supabase/migrations/000045_maybe_complete_run.sql +0 -30
- package/dist/supabase/migrations/000050_complete_task.sql +0 -98
- package/dist/supabase/migrations/000055_calculate_retry_delay.sql +0 -11
- package/dist/supabase/migrations/000060_fail_task.sql +0 -124
- package/dist/supabase/migrations/000_edge_worker_initial.sql +0 -86
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
create extension if not exists pgmq version '1.4.4';
|
|
2
|
-
|
|
3
|
-
create schema if not exists pgflow;
|
|
4
|
-
|
|
5
|
-
--------------------------------------------------------------------------
|
|
6
|
-
------------------ TODO: fix me, UNSECURE --------------------------------
|
|
7
|
-
--------------------------------------------------------------------------
|
|
8
|
-
grant usage on schema pgflow to anon, authenticated, service_role;
|
|
9
|
-
grant all on all tables in schema pgflow to anon, authenticated, service_role;
|
|
10
|
-
grant all on all routines in schema pgflow to anon, authenticated, service_role;
|
|
11
|
-
grant all on all sequences in schema pgflow to anon,
|
|
12
|
-
authenticated,
|
|
13
|
-
service_role;
|
|
14
|
-
alter default privileges for role postgres in schema pgflow
|
|
15
|
-
grant all on tables to anon, authenticated, service_role;
|
|
16
|
-
alter default privileges for role postgres in schema pgflow
|
|
17
|
-
grant all on routines to anon, authenticated, service_role;
|
|
18
|
-
alter default privileges for role postgres in schema pgflow
|
|
19
|
-
grant all on sequences to anon, authenticated, service_role;
|
|
20
|
-
|
|
21
|
-
------------------------------------------
|
|
22
|
-
-- Core flow definition tables
|
|
23
|
-
------------------------------------------
|
|
24
|
-
|
|
25
|
-
----- check constraint helper function -------
|
|
26
|
-
create or replace function pgflow.is_valid_slug(
|
|
27
|
-
slug text
|
|
28
|
-
)
|
|
29
|
-
returns boolean
|
|
30
|
-
language plpgsql
|
|
31
|
-
immutable
|
|
32
|
-
as $$
|
|
33
|
-
begin
|
|
34
|
-
return
|
|
35
|
-
slug is not null
|
|
36
|
-
and slug <> ''
|
|
37
|
-
and length(slug) <= 128
|
|
38
|
-
and slug ~ '^[a-zA-Z_][a-zA-Z0-9_]*$'
|
|
39
|
-
and slug NOT IN ('run'); -- reserved words
|
|
40
|
-
end;
|
|
41
|
-
$$;
|
|
42
|
-
|
|
43
|
-
-- Flows table - stores flow definitions
|
|
44
|
-
create table pgflow.flows (
|
|
45
|
-
flow_slug text primary key not null, -- Unique identifier for the flow
|
|
46
|
-
opt_max_attempts int not null default 3,
|
|
47
|
-
opt_base_delay int not null default 1,
|
|
48
|
-
opt_timeout int not null default 60,
|
|
49
|
-
constraint slug_is_valid check (pgflow.is_valid_slug(flow_slug)),
|
|
50
|
-
constraint opt_max_attempts_is_nonnegative check (opt_max_attempts >= 0),
|
|
51
|
-
constraint opt_base_delay_is_nonnegative check (opt_base_delay >= 0),
|
|
52
|
-
constraint opt_timeout_is_positive check (opt_timeout > 0)
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
-- Steps table - stores individual steps within flows
|
|
56
|
-
create table pgflow.steps (
|
|
57
|
-
flow_slug text not null references pgflow.flows (flow_slug),
|
|
58
|
-
step_slug text not null,
|
|
59
|
-
step_type text not null default 'single',
|
|
60
|
-
deps_count int not null default 0 check (deps_count >= 0),
|
|
61
|
-
opt_max_attempts int,
|
|
62
|
-
opt_base_delay int,
|
|
63
|
-
opt_timeout int,
|
|
64
|
-
primary key (flow_slug, step_slug),
|
|
65
|
-
check (pgflow.is_valid_slug(step_slug)),
|
|
66
|
-
check (step_type in ('single')),
|
|
67
|
-
constraint opt_max_attempts_is_nonnegative check (opt_max_attempts is null or opt_max_attempts >= 0),
|
|
68
|
-
constraint opt_base_delay_is_nonnegative check (opt_base_delay is null or opt_base_delay >= 0),
|
|
69
|
-
constraint opt_timeout_is_positive check (opt_timeout is null or opt_timeout > 0)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
-- Dependencies table - stores relationships between steps
|
|
73
|
-
create table pgflow.deps (
|
|
74
|
-
flow_slug text not null references pgflow.flows (flow_slug),
|
|
75
|
-
dep_slug text not null, -- slug of the dependency
|
|
76
|
-
step_slug text not null, -- slug of the dependent
|
|
77
|
-
primary key (flow_slug, dep_slug, step_slug),
|
|
78
|
-
foreign key (flow_slug, dep_slug)
|
|
79
|
-
references pgflow.steps (flow_slug, step_slug),
|
|
80
|
-
foreign key (flow_slug, step_slug)
|
|
81
|
-
references pgflow.steps (flow_slug, step_slug),
|
|
82
|
-
check (dep_slug != step_slug) -- Prevent self-dependencies
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
------------------------------------------
|
|
86
|
-
-- Runtime State Tables
|
|
87
|
-
------------------------------------------
|
|
88
|
-
|
|
89
|
-
-- Runs table - tracks flow execution instances
|
|
90
|
-
create table pgflow.runs (
|
|
91
|
-
run_id uuid primary key not null default gen_random_uuid(),
|
|
92
|
-
flow_slug text not null references pgflow.flows (flow_slug), -- denormalized
|
|
93
|
-
status text not null default 'started',
|
|
94
|
-
input jsonb not null,
|
|
95
|
-
output jsonb,
|
|
96
|
-
remaining_steps int not null default 0 check (remaining_steps >= 0),
|
|
97
|
-
check (status in ('started', 'failed', 'completed'))
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
-- Step states table - tracks the state of individual steps within a run
|
|
101
|
-
create table pgflow.step_states (
|
|
102
|
-
flow_slug text not null references pgflow.flows (flow_slug),
|
|
103
|
-
run_id uuid not null references pgflow.runs (run_id),
|
|
104
|
-
step_slug text not null,
|
|
105
|
-
status text not null default 'created',
|
|
106
|
-
remaining_tasks int not null default 1 check (remaining_tasks >= 0),
|
|
107
|
-
remaining_deps int not null default 0 check (remaining_deps >= 0),
|
|
108
|
-
primary key (run_id, step_slug),
|
|
109
|
-
foreign key (flow_slug, step_slug)
|
|
110
|
-
references pgflow.steps (flow_slug, step_slug),
|
|
111
|
-
check (status in ('created', 'started', 'completed', 'failed')),
|
|
112
|
-
check (status != 'completed' or remaining_tasks = 0)
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
-- Step tasks table - tracks units of work for step
|
|
116
|
-
create table pgflow.step_tasks (
|
|
117
|
-
flow_slug text not null references pgflow.flows (flow_slug),
|
|
118
|
-
run_id uuid not null references pgflow.runs (run_id),
|
|
119
|
-
step_slug text not null,
|
|
120
|
-
message_id bigint,
|
|
121
|
-
task_index int not null default 0,
|
|
122
|
-
status text not null default 'queued',
|
|
123
|
-
attempts_count int not null default 0,
|
|
124
|
-
error_message text,
|
|
125
|
-
output jsonb,
|
|
126
|
-
constraint step_tasks_pkey primary key (run_id, step_slug, task_index),
|
|
127
|
-
foreign key (run_id, step_slug)
|
|
128
|
-
references pgflow.step_states (run_id, step_slug),
|
|
129
|
-
constraint valid_status check (
|
|
130
|
-
status in ('queued', 'completed', 'failed')
|
|
131
|
-
),
|
|
132
|
-
constraint output_valid_only_for_completed check (
|
|
133
|
-
output is null or status = 'completed'
|
|
134
|
-
),
|
|
135
|
-
constraint only_single_task_per_step check (task_index = 0),
|
|
136
|
-
constraint attempts_count_nonnegative check (attempts_count >= 0)
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
------------------------------------------
|
|
140
|
-
-- Types
|
|
141
|
-
------------------------------------------
|
|
142
|
-
|
|
143
|
-
create type pgflow.step_task_record as (
|
|
144
|
-
flow_slug text,
|
|
145
|
-
run_id uuid,
|
|
146
|
-
step_slug text,
|
|
147
|
-
input jsonb,
|
|
148
|
-
msg_id bigint
|
|
149
|
-
);
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
create or replace function pgflow.create_flow(
|
|
2
|
-
flow_slug text,
|
|
3
|
-
max_attempts int default 3,
|
|
4
|
-
base_delay int default 5,
|
|
5
|
-
timeout int default 60
|
|
6
|
-
)
|
|
7
|
-
returns pgflow.flows
|
|
8
|
-
language sql
|
|
9
|
-
set search_path to ''
|
|
10
|
-
volatile
|
|
11
|
-
as $$
|
|
12
|
-
WITH
|
|
13
|
-
flow_upsert AS (
|
|
14
|
-
INSERT INTO pgflow.flows (flow_slug, opt_max_attempts, opt_base_delay, opt_timeout)
|
|
15
|
-
VALUES (flow_slug, max_attempts, base_delay, timeout)
|
|
16
|
-
ON CONFLICT (flow_slug) DO UPDATE
|
|
17
|
-
SET flow_slug = pgflow.flows.flow_slug -- Dummy update
|
|
18
|
-
RETURNING *
|
|
19
|
-
),
|
|
20
|
-
ensure_queue AS (
|
|
21
|
-
SELECT pgmq.create(flow_slug)
|
|
22
|
-
WHERE NOT EXISTS (
|
|
23
|
-
SELECT 1 FROM pgmq.list_queues() WHERE queue_name = flow_slug
|
|
24
|
-
)
|
|
25
|
-
)
|
|
26
|
-
SELECT f.*
|
|
27
|
-
FROM flow_upsert f
|
|
28
|
-
LEFT JOIN (SELECT 1 FROM ensure_queue) _dummy ON true; -- Left join ensures flow is returned
|
|
29
|
-
$$;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
create or replace function pgflow.add_step(
|
|
2
|
-
flow_slug text,
|
|
3
|
-
step_slug text,
|
|
4
|
-
deps_slugs text [],
|
|
5
|
-
max_attempts int default null,
|
|
6
|
-
base_delay int default null,
|
|
7
|
-
timeout int default null
|
|
8
|
-
)
|
|
9
|
-
returns pgflow.steps
|
|
10
|
-
language sql
|
|
11
|
-
set search_path to ''
|
|
12
|
-
volatile
|
|
13
|
-
as $$
|
|
14
|
-
WITH
|
|
15
|
-
create_step AS (
|
|
16
|
-
INSERT INTO pgflow.steps (flow_slug, step_slug, deps_count, opt_max_attempts, opt_base_delay, opt_timeout)
|
|
17
|
-
VALUES (flow_slug, step_slug, COALESCE(array_length(deps_slugs, 1), 0), max_attempts, base_delay, timeout)
|
|
18
|
-
ON CONFLICT (flow_slug, step_slug)
|
|
19
|
-
DO UPDATE SET step_slug = pgflow.steps.step_slug
|
|
20
|
-
RETURNING *
|
|
21
|
-
),
|
|
22
|
-
insert_deps AS (
|
|
23
|
-
INSERT INTO pgflow.deps (flow_slug, dep_slug, step_slug)
|
|
24
|
-
SELECT add_step.flow_slug, d.dep_slug, add_step.step_slug
|
|
25
|
-
FROM unnest(deps_slugs) AS d(dep_slug)
|
|
26
|
-
ON CONFLICT (flow_slug, dep_slug, step_slug) DO NOTHING
|
|
27
|
-
RETURNING 1
|
|
28
|
-
)
|
|
29
|
-
-- Return the created step
|
|
30
|
-
SELECT * FROM create_step;
|
|
31
|
-
$$;
|
|
32
|
-
|
|
33
|
-
-- New overloaded function without deps_slugs parameter
|
|
34
|
-
create or replace function pgflow.add_step(
|
|
35
|
-
flow_slug text,
|
|
36
|
-
step_slug text,
|
|
37
|
-
max_attempts int default null,
|
|
38
|
-
base_delay int default null,
|
|
39
|
-
timeout int default null
|
|
40
|
-
)
|
|
41
|
-
returns pgflow.steps
|
|
42
|
-
language sql
|
|
43
|
-
set search_path to ''
|
|
44
|
-
volatile
|
|
45
|
-
as $$
|
|
46
|
-
-- Call the original function with an empty array
|
|
47
|
-
SELECT * FROM pgflow.add_step(flow_slug, step_slug, ARRAY[]::text[], max_attempts, base_delay, timeout);
|
|
48
|
-
$$;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
create or replace function pgflow.start_ready_steps(run_id uuid)
|
|
2
|
-
returns void
|
|
3
|
-
language sql
|
|
4
|
-
set search_path to ''
|
|
5
|
-
as $$
|
|
6
|
-
|
|
7
|
-
WITH ready_steps AS (
|
|
8
|
-
SELECT *
|
|
9
|
-
FROM pgflow.step_states AS step_state
|
|
10
|
-
WHERE step_state.run_id = start_ready_steps.run_id
|
|
11
|
-
AND step_state.status = 'created'
|
|
12
|
-
AND step_state.remaining_deps = 0
|
|
13
|
-
ORDER BY step_state.step_slug
|
|
14
|
-
FOR UPDATE
|
|
15
|
-
),
|
|
16
|
-
started_step_states AS (
|
|
17
|
-
UPDATE pgflow.step_states
|
|
18
|
-
SET status = 'started'
|
|
19
|
-
FROM ready_steps
|
|
20
|
-
WHERE pgflow.step_states.run_id = start_ready_steps.run_id
|
|
21
|
-
AND pgflow.step_states.step_slug = ready_steps.step_slug
|
|
22
|
-
RETURNING pgflow.step_states.*
|
|
23
|
-
),
|
|
24
|
-
sent_messages AS (
|
|
25
|
-
SELECT
|
|
26
|
-
started_step.flow_slug,
|
|
27
|
-
started_step.run_id,
|
|
28
|
-
started_step.step_slug,
|
|
29
|
-
pgmq.send(started_step.flow_slug, jsonb_build_object(
|
|
30
|
-
'flow_slug', started_step.flow_slug,
|
|
31
|
-
'run_id', started_step.run_id,
|
|
32
|
-
'step_slug', started_step.step_slug,
|
|
33
|
-
'task_index', 0
|
|
34
|
-
)) AS msg_id
|
|
35
|
-
FROM started_step_states AS started_step
|
|
36
|
-
)
|
|
37
|
-
INSERT INTO pgflow.step_tasks (flow_slug, run_id, step_slug, message_id)
|
|
38
|
-
SELECT
|
|
39
|
-
sent_messages.flow_slug,
|
|
40
|
-
sent_messages.run_id,
|
|
41
|
-
sent_messages.step_slug,
|
|
42
|
-
sent_messages.msg_id
|
|
43
|
-
FROM sent_messages;
|
|
44
|
-
|
|
45
|
-
$$;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
-- drop function if exists pgflow.start_flow;
|
|
2
|
-
create or replace function pgflow.start_flow(
|
|
3
|
-
flow_slug TEXT,
|
|
4
|
-
input JSONB
|
|
5
|
-
)
|
|
6
|
-
returns setof PGFLOW.RUNS
|
|
7
|
-
language plpgsql
|
|
8
|
-
set search_path to ''
|
|
9
|
-
volatile
|
|
10
|
-
as $$
|
|
11
|
-
declare
|
|
12
|
-
v_created_run pgflow.runs%ROWTYPE;
|
|
13
|
-
begin
|
|
14
|
-
|
|
15
|
-
WITH
|
|
16
|
-
flow_steps AS (
|
|
17
|
-
SELECT steps.flow_slug, steps.step_slug, steps.deps_count
|
|
18
|
-
FROM pgflow.steps
|
|
19
|
-
WHERE steps.flow_slug = start_flow.flow_slug
|
|
20
|
-
),
|
|
21
|
-
created_run AS (
|
|
22
|
-
INSERT INTO pgflow.runs (flow_slug, input, remaining_steps)
|
|
23
|
-
VALUES (
|
|
24
|
-
start_flow.flow_slug,
|
|
25
|
-
start_flow.input,
|
|
26
|
-
(SELECT count(*) FROM flow_steps)
|
|
27
|
-
)
|
|
28
|
-
RETURNING *
|
|
29
|
-
),
|
|
30
|
-
created_step_states AS (
|
|
31
|
-
INSERT INTO pgflow.step_states (flow_slug, run_id, step_slug, remaining_deps)
|
|
32
|
-
SELECT
|
|
33
|
-
fs.flow_slug,
|
|
34
|
-
(SELECT run_id FROM created_run),
|
|
35
|
-
fs.step_slug,
|
|
36
|
-
fs.deps_count
|
|
37
|
-
FROM flow_steps fs
|
|
38
|
-
)
|
|
39
|
-
SELECT * FROM created_run INTO v_created_run;
|
|
40
|
-
|
|
41
|
-
PERFORM pgflow.start_ready_steps(v_created_run.run_id);
|
|
42
|
-
|
|
43
|
-
RETURN QUERY SELECT * FROM pgflow.runs where run_id = v_created_run.run_id;
|
|
44
|
-
|
|
45
|
-
end;
|
|
46
|
-
$$;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
--------------------------------------------------------------------------------
|
|
2
|
-
-- Read With Poll --------------------------------------------------------------
|
|
3
|
-
-- --
|
|
4
|
-
-- This is a backport of the pgmq.read_with_poll function from version 1.5.0 --
|
|
5
|
-
-- It is required because it fixes a bug with high CPU usage and Supabase --
|
|
6
|
-
-- is still using version 1.4.4. --
|
|
7
|
-
-- --
|
|
8
|
-
-- It is slightly modified (removed headers which are not available in 1.4.1) --
|
|
9
|
-
-- --
|
|
10
|
-
-- This will be removed once Supabase upgrades to 1.5.0 or higher. --
|
|
11
|
-
--------------------------------------------------------------------------------
|
|
12
|
-
create function pgflow.read_with_poll(
|
|
13
|
-
queue_name TEXT,
|
|
14
|
-
vt INTEGER,
|
|
15
|
-
qty INTEGER,
|
|
16
|
-
max_poll_seconds INTEGER default 5,
|
|
17
|
-
poll_interval_ms INTEGER default 100,
|
|
18
|
-
conditional JSONB default '{}'
|
|
19
|
-
)
|
|
20
|
-
returns setof PGMQ.MESSAGE_RECORD as $$
|
|
21
|
-
DECLARE
|
|
22
|
-
r pgmq.message_record;
|
|
23
|
-
stop_at TIMESTAMP;
|
|
24
|
-
sql TEXT;
|
|
25
|
-
qtable TEXT := pgmq.format_table_name(queue_name, 'q');
|
|
26
|
-
BEGIN
|
|
27
|
-
stop_at := clock_timestamp() + make_interval(secs => max_poll_seconds);
|
|
28
|
-
LOOP
|
|
29
|
-
IF (SELECT clock_timestamp() >= stop_at) THEN
|
|
30
|
-
RETURN;
|
|
31
|
-
END IF;
|
|
32
|
-
|
|
33
|
-
sql := FORMAT(
|
|
34
|
-
$QUERY$
|
|
35
|
-
WITH cte AS
|
|
36
|
-
(
|
|
37
|
-
SELECT msg_id
|
|
38
|
-
FROM pgmq.%I
|
|
39
|
-
WHERE vt <= clock_timestamp() AND CASE
|
|
40
|
-
WHEN %L != '{}'::jsonb THEN (message @> %2$L)::integer
|
|
41
|
-
ELSE 1
|
|
42
|
-
END = 1
|
|
43
|
-
ORDER BY msg_id ASC
|
|
44
|
-
LIMIT $1
|
|
45
|
-
FOR UPDATE SKIP LOCKED
|
|
46
|
-
)
|
|
47
|
-
UPDATE pgmq.%I m
|
|
48
|
-
SET
|
|
49
|
-
vt = clock_timestamp() + %L,
|
|
50
|
-
read_ct = read_ct + 1
|
|
51
|
-
FROM cte
|
|
52
|
-
WHERE m.msg_id = cte.msg_id
|
|
53
|
-
RETURNING m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message;
|
|
54
|
-
$QUERY$,
|
|
55
|
-
qtable, conditional, qtable, make_interval(secs => vt)
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
FOR r IN
|
|
59
|
-
EXECUTE sql USING qty
|
|
60
|
-
LOOP
|
|
61
|
-
RETURN NEXT r;
|
|
62
|
-
END LOOP;
|
|
63
|
-
IF FOUND THEN
|
|
64
|
-
RETURN;
|
|
65
|
-
ELSE
|
|
66
|
-
PERFORM pg_sleep(poll_interval_ms::numeric / 1000);
|
|
67
|
-
END IF;
|
|
68
|
-
END LOOP;
|
|
69
|
-
END;
|
|
70
|
-
$$ language plpgsql;
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
create or replace function pgflow.poll_for_tasks(
|
|
2
|
-
queue_name text,
|
|
3
|
-
vt integer,
|
|
4
|
-
qty integer,
|
|
5
|
-
max_poll_seconds integer default 5,
|
|
6
|
-
poll_interval_ms integer default 100
|
|
7
|
-
)
|
|
8
|
-
returns setof pgflow.step_task_record
|
|
9
|
-
volatile
|
|
10
|
-
set search_path to ''
|
|
11
|
-
as $$
|
|
12
|
-
|
|
13
|
-
with read_messages as (
|
|
14
|
-
select *
|
|
15
|
-
from pgflow.read_with_poll(
|
|
16
|
-
queue_name,
|
|
17
|
-
vt,
|
|
18
|
-
qty,
|
|
19
|
-
max_poll_seconds,
|
|
20
|
-
poll_interval_ms
|
|
21
|
-
)
|
|
22
|
-
),
|
|
23
|
-
tasks as (
|
|
24
|
-
select
|
|
25
|
-
task.flow_slug,
|
|
26
|
-
task.run_id,
|
|
27
|
-
task.step_slug,
|
|
28
|
-
task.task_index,
|
|
29
|
-
task.message_id
|
|
30
|
-
from pgflow.step_tasks as task
|
|
31
|
-
join read_messages as message on message.msg_id = task.message_id
|
|
32
|
-
where task.message_id = message.msg_id
|
|
33
|
-
and task.status = 'queued'
|
|
34
|
-
),
|
|
35
|
-
increment_attempts as (
|
|
36
|
-
update pgflow.step_tasks
|
|
37
|
-
set attempts_count = attempts_count + 1
|
|
38
|
-
from tasks
|
|
39
|
-
where step_tasks.message_id = tasks.message_id
|
|
40
|
-
and status = 'queued'
|
|
41
|
-
),
|
|
42
|
-
runs as (
|
|
43
|
-
select
|
|
44
|
-
r.run_id,
|
|
45
|
-
r.input
|
|
46
|
-
from pgflow.runs r
|
|
47
|
-
where r.run_id in (select run_id from tasks)
|
|
48
|
-
),
|
|
49
|
-
deps as (
|
|
50
|
-
select
|
|
51
|
-
st.run_id,
|
|
52
|
-
st.step_slug,
|
|
53
|
-
dep.dep_slug,
|
|
54
|
-
dep_task.output as dep_output
|
|
55
|
-
from tasks st
|
|
56
|
-
join pgflow.deps dep on dep.flow_slug = st.flow_slug and dep.step_slug = st.step_slug
|
|
57
|
-
join pgflow.step_tasks dep_task on
|
|
58
|
-
dep_task.run_id = st.run_id and
|
|
59
|
-
dep_task.step_slug = dep.dep_slug and
|
|
60
|
-
dep_task.status = 'completed'
|
|
61
|
-
),
|
|
62
|
-
deps_outputs as (
|
|
63
|
-
select
|
|
64
|
-
d.run_id,
|
|
65
|
-
d.step_slug,
|
|
66
|
-
jsonb_object_agg(d.dep_slug, d.dep_output) as deps_output
|
|
67
|
-
from deps d
|
|
68
|
-
group by d.run_id, d.step_slug
|
|
69
|
-
),
|
|
70
|
-
timeouts as (
|
|
71
|
-
select
|
|
72
|
-
task.message_id,
|
|
73
|
-
coalesce(step.opt_timeout, flow.opt_timeout) + 2 as vt_delay
|
|
74
|
-
from tasks task
|
|
75
|
-
join pgflow.flows flow on flow.flow_slug = task.flow_slug
|
|
76
|
-
join pgflow.steps step on step.flow_slug = task.flow_slug and step.step_slug = task.step_slug
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
select
|
|
80
|
-
st.flow_slug,
|
|
81
|
-
st.run_id,
|
|
82
|
-
st.step_slug,
|
|
83
|
-
jsonb_build_object('run', r.input) ||
|
|
84
|
-
coalesce(dep_out.deps_output, '{}'::jsonb) as input,
|
|
85
|
-
st.message_id as msg_id
|
|
86
|
-
from tasks st
|
|
87
|
-
join runs r on st.run_id = r.run_id
|
|
88
|
-
left join deps_outputs dep_out on
|
|
89
|
-
dep_out.run_id = st.run_id and
|
|
90
|
-
dep_out.step_slug = st.step_slug
|
|
91
|
-
cross join lateral (
|
|
92
|
-
-- TODO: this is slow because it calls set_vt for each row, and set_vt
|
|
93
|
-
-- builds dynamic query from string every time it is called
|
|
94
|
-
-- implement set_vt_batch(msgs_ids bigint[], vt_delays int[])
|
|
95
|
-
select pgmq.set_vt(queue_name, st.message_id,
|
|
96
|
-
(select t.vt_delay from timeouts t where t.message_id = st.message_id)
|
|
97
|
-
)
|
|
98
|
-
) set_vt;
|
|
99
|
-
|
|
100
|
-
$$ language sql;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
create or replace function pgflow.maybe_complete_run(run_id uuid)
|
|
2
|
-
returns void
|
|
3
|
-
language sql
|
|
4
|
-
volatile
|
|
5
|
-
set search_path to ''
|
|
6
|
-
as $$
|
|
7
|
-
-- Update run status to completed and set output when there are no remaining steps
|
|
8
|
-
-- All done in a single declarative SQL statement
|
|
9
|
-
UPDATE pgflow.runs
|
|
10
|
-
SET
|
|
11
|
-
status = 'completed',
|
|
12
|
-
output = (
|
|
13
|
-
-- Get outputs from final steps (steps that are not dependencies for other steps)
|
|
14
|
-
SELECT jsonb_object_agg(st.step_slug, st.output)
|
|
15
|
-
FROM pgflow.step_tasks st
|
|
16
|
-
JOIN pgflow.step_states ss ON ss.run_id = st.run_id AND ss.step_slug = st.step_slug
|
|
17
|
-
JOIN pgflow.runs r ON r.run_id = ss.run_id AND r.flow_slug = ss.flow_slug
|
|
18
|
-
WHERE st.run_id = maybe_complete_run.run_id
|
|
19
|
-
AND st.status = 'completed'
|
|
20
|
-
AND NOT EXISTS (
|
|
21
|
-
SELECT 1
|
|
22
|
-
FROM pgflow.deps d
|
|
23
|
-
WHERE d.flow_slug = ss.flow_slug
|
|
24
|
-
AND d.dep_slug = ss.step_slug
|
|
25
|
-
)
|
|
26
|
-
)
|
|
27
|
-
WHERE pgflow.runs.run_id = maybe_complete_run.run_id
|
|
28
|
-
AND pgflow.runs.remaining_steps = 0
|
|
29
|
-
AND pgflow.runs.status != 'completed';
|
|
30
|
-
$$;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
-- drop function if exists pgflow.complete_task(uuid, text, int, jsonb);
|
|
2
|
-
create or replace function pgflow.complete_task(
|
|
3
|
-
run_id uuid,
|
|
4
|
-
step_slug text,
|
|
5
|
-
task_index int,
|
|
6
|
-
output jsonb
|
|
7
|
-
)
|
|
8
|
-
returns setof pgflow.step_tasks
|
|
9
|
-
language plpgsql
|
|
10
|
-
volatile
|
|
11
|
-
set search_path to ''
|
|
12
|
-
as $$
|
|
13
|
-
begin
|
|
14
|
-
|
|
15
|
-
WITH run_lock AS (
|
|
16
|
-
SELECT * FROM pgflow.runs
|
|
17
|
-
WHERE pgflow.runs.run_id = complete_task.run_id
|
|
18
|
-
FOR UPDATE
|
|
19
|
-
),
|
|
20
|
-
step_lock AS (
|
|
21
|
-
SELECT * FROM pgflow.step_states
|
|
22
|
-
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
23
|
-
AND pgflow.step_states.step_slug = complete_task.step_slug
|
|
24
|
-
FOR UPDATE
|
|
25
|
-
),
|
|
26
|
-
task AS (
|
|
27
|
-
UPDATE pgflow.step_tasks
|
|
28
|
-
SET
|
|
29
|
-
status = 'completed',
|
|
30
|
-
output = complete_task.output
|
|
31
|
-
WHERE pgflow.step_tasks.run_id = complete_task.run_id
|
|
32
|
-
AND pgflow.step_tasks.step_slug = complete_task.step_slug
|
|
33
|
-
AND pgflow.step_tasks.task_index = complete_task.task_index
|
|
34
|
-
RETURNING *
|
|
35
|
-
),
|
|
36
|
-
step_state AS (
|
|
37
|
-
UPDATE pgflow.step_states
|
|
38
|
-
SET
|
|
39
|
-
status = CASE
|
|
40
|
-
WHEN pgflow.step_states.remaining_tasks = 1 THEN 'completed' -- Will be 0 after decrement
|
|
41
|
-
ELSE 'started'
|
|
42
|
-
END,
|
|
43
|
-
remaining_tasks = pgflow.step_states.remaining_tasks - 1
|
|
44
|
-
FROM task
|
|
45
|
-
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
46
|
-
AND pgflow.step_states.step_slug = complete_task.step_slug
|
|
47
|
-
RETURNING pgflow.step_states.*
|
|
48
|
-
),
|
|
49
|
-
-- Find all dependent steps if the current step was completed
|
|
50
|
-
dependent_steps AS (
|
|
51
|
-
SELECT d.step_slug AS dependent_step_slug
|
|
52
|
-
FROM pgflow.deps d
|
|
53
|
-
JOIN step_state s ON s.status = 'completed' AND d.flow_slug = s.flow_slug
|
|
54
|
-
WHERE d.dep_slug = complete_task.step_slug
|
|
55
|
-
ORDER BY d.step_slug -- Ensure consistent ordering
|
|
56
|
-
),
|
|
57
|
-
-- Lock dependent steps before updating
|
|
58
|
-
dependent_steps_lock AS (
|
|
59
|
-
SELECT * FROM pgflow.step_states
|
|
60
|
-
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
61
|
-
AND pgflow.step_states.step_slug IN (SELECT dependent_step_slug FROM dependent_steps)
|
|
62
|
-
FOR UPDATE
|
|
63
|
-
),
|
|
64
|
-
-- Update all dependent steps
|
|
65
|
-
dependent_steps_update AS (
|
|
66
|
-
UPDATE pgflow.step_states
|
|
67
|
-
SET remaining_deps = pgflow.step_states.remaining_deps - 1
|
|
68
|
-
FROM dependent_steps
|
|
69
|
-
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
70
|
-
AND pgflow.step_states.step_slug = dependent_steps.dependent_step_slug
|
|
71
|
-
)
|
|
72
|
-
-- Only decrement remaining_steps, don't update status
|
|
73
|
-
UPDATE pgflow.runs
|
|
74
|
-
SET remaining_steps = pgflow.runs.remaining_steps - 1
|
|
75
|
-
FROM step_state
|
|
76
|
-
WHERE pgflow.runs.run_id = complete_task.run_id
|
|
77
|
-
AND step_state.status = 'completed';
|
|
78
|
-
|
|
79
|
-
PERFORM pgmq.archive(
|
|
80
|
-
queue_name => (SELECT run.flow_slug FROM pgflow.runs AS run WHERE run.run_id = complete_task.run_id),
|
|
81
|
-
msg_id => (SELECT message_id FROM pgflow.step_tasks AS step_task
|
|
82
|
-
WHERE step_task.run_id = complete_task.run_id
|
|
83
|
-
AND step_task.step_slug = complete_task.step_slug
|
|
84
|
-
AND step_task.task_index = complete_task.task_index)
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
PERFORM pgflow.start_ready_steps(complete_task.run_id);
|
|
88
|
-
|
|
89
|
-
PERFORM pgflow.maybe_complete_run(complete_task.run_id);
|
|
90
|
-
|
|
91
|
-
RETURN QUERY SELECT *
|
|
92
|
-
FROM pgflow.step_tasks AS step_task
|
|
93
|
-
WHERE step_task.run_id = complete_task.run_id
|
|
94
|
-
AND step_task.step_slug = complete_task.step_slug
|
|
95
|
-
AND step_task.task_index = complete_task.task_index;
|
|
96
|
-
|
|
97
|
-
end;
|
|
98
|
-
$$;
|