|
| 1 | +-- Modify "step_states" table |
| 2 | +ALTER TABLE "pgflow"."step_states" DROP CONSTRAINT "step_states_remaining_tasks_check", ADD CONSTRAINT "remaining_tasks_state_consistency" CHECK ((remaining_tasks IS NULL) OR (status <> 'created'::text)), ADD CONSTRAINT "step_states_initial_tasks_check" CHECK (initial_tasks >= 0), ALTER COLUMN "remaining_tasks" DROP NOT NULL, ALTER COLUMN "remaining_tasks" DROP DEFAULT, ADD COLUMN "initial_tasks" integer NULL DEFAULT 1; |
| 3 | +-- Modify "step_tasks" table |
| 4 | +ALTER TABLE "pgflow"."step_tasks" DROP CONSTRAINT "only_single_task_per_step"; |
| 5 | +-- Modify "steps" table |
| 6 | +ALTER TABLE "pgflow"."steps" DROP CONSTRAINT "steps_step_type_check", ADD CONSTRAINT "steps_step_type_check" CHECK (step_type = ANY (ARRAY['single'::text, 'map'::text])); |
| 7 | +-- Modify "start_ready_steps" function |
| 8 | +CREATE OR REPLACE FUNCTION "pgflow"."start_ready_steps" ("run_id" uuid) RETURNS void LANGUAGE sql SET "search_path" = '' AS $$ |
| 9 | +WITH ready_steps AS ( |
| 10 | + SELECT * |
| 11 | + FROM pgflow.step_states AS step_state |
| 12 | + WHERE step_state.run_id = start_ready_steps.run_id |
| 13 | + AND step_state.status = 'created' |
| 14 | + AND step_state.remaining_deps = 0 |
| 15 | + ORDER BY step_state.step_slug |
| 16 | + FOR UPDATE |
| 17 | +), |
| 18 | +started_step_states AS ( |
| 19 | + UPDATE pgflow.step_states |
| 20 | + SET status = 'started', |
| 21 | + started_at = now(), |
| 22 | + remaining_tasks = ready_steps.initial_tasks -- Copy initial_tasks to remaining_tasks when starting |
| 23 | + FROM ready_steps |
| 24 | + WHERE pgflow.step_states.run_id = start_ready_steps.run_id |
| 25 | + AND pgflow.step_states.step_slug = ready_steps.step_slug |
| 26 | + RETURNING pgflow.step_states.* |
| 27 | +), |
| 28 | +sent_messages AS ( |
| 29 | + SELECT |
| 30 | + started_step.flow_slug, |
| 31 | + started_step.run_id, |
| 32 | + started_step.step_slug, |
| 33 | + pgmq.send( |
| 34 | + started_step.flow_slug, |
| 35 | + jsonb_build_object( |
| 36 | + 'flow_slug', started_step.flow_slug, |
| 37 | + 'run_id', started_step.run_id, |
| 38 | + 'step_slug', started_step.step_slug, |
| 39 | + 'task_index', 0 |
| 40 | + ), |
| 41 | + COALESCE(step.opt_start_delay, 0) |
| 42 | + ) AS msg_id |
| 43 | + FROM started_step_states AS started_step |
| 44 | + JOIN pgflow.steps AS step |
| 45 | + ON step.flow_slug = started_step.flow_slug |
| 46 | + AND step.step_slug = started_step.step_slug |
| 47 | +), |
| 48 | +broadcast_events AS ( |
| 49 | + SELECT |
| 50 | + realtime.send( |
| 51 | + jsonb_build_object( |
| 52 | + 'event_type', 'step:started', |
| 53 | + 'run_id', started_step.run_id, |
| 54 | + 'step_slug', started_step.step_slug, |
| 55 | + 'status', 'started', |
| 56 | + 'started_at', started_step.started_at, |
| 57 | + 'remaining_tasks', started_step.remaining_tasks, |
| 58 | + 'remaining_deps', started_step.remaining_deps |
| 59 | + ), |
| 60 | + concat('step:', started_step.step_slug, ':started'), |
| 61 | + concat('pgflow:run:', started_step.run_id), |
| 62 | + false |
| 63 | + ) |
| 64 | + FROM started_step_states AS started_step |
| 65 | +) |
| 66 | +INSERT INTO pgflow.step_tasks (flow_slug, run_id, step_slug, message_id) |
| 67 | +SELECT |
| 68 | + sent_messages.flow_slug, |
| 69 | + sent_messages.run_id, |
| 70 | + sent_messages.step_slug, |
| 71 | + sent_messages.msg_id |
| 72 | +FROM sent_messages; |
| 73 | +$$; |
| 74 | +-- Modify "start_flow" function |
| 75 | +CREATE OR REPLACE FUNCTION "pgflow"."start_flow" ("flow_slug" text, "input" jsonb, "run_id" uuid DEFAULT NULL::uuid) RETURNS SETOF "pgflow"."runs" LANGUAGE plpgsql SET "search_path" = '' AS $$ |
| 76 | +declare |
| 77 | + v_created_run pgflow.runs%ROWTYPE; |
| 78 | +begin |
| 79 | + |
| 80 | +WITH |
| 81 | + flow_steps AS ( |
| 82 | + SELECT steps.flow_slug, steps.step_slug, steps.deps_count |
| 83 | + FROM pgflow.steps |
| 84 | + WHERE steps.flow_slug = start_flow.flow_slug |
| 85 | + ), |
| 86 | + created_run AS ( |
| 87 | + INSERT INTO pgflow.runs (run_id, flow_slug, input, remaining_steps) |
| 88 | + VALUES ( |
| 89 | + COALESCE(start_flow.run_id, gen_random_uuid()), |
| 90 | + start_flow.flow_slug, |
| 91 | + start_flow.input, |
| 92 | + (SELECT count(*) FROM flow_steps) |
| 93 | + ) |
| 94 | + RETURNING * |
| 95 | + ), |
| 96 | + created_step_states AS ( |
| 97 | + INSERT INTO pgflow.step_states (flow_slug, run_id, step_slug, remaining_deps, initial_tasks) |
| 98 | + SELECT |
| 99 | + fs.flow_slug, |
| 100 | + (SELECT created_run.run_id FROM created_run), |
| 101 | + fs.step_slug, |
| 102 | + fs.deps_count, |
| 103 | + 1 -- For now, all steps get initial_tasks = 1 (single steps) |
| 104 | + FROM flow_steps fs |
| 105 | + ) |
| 106 | +SELECT * FROM created_run INTO v_created_run; |
| 107 | + |
| 108 | +-- Send broadcast event for run started |
| 109 | +PERFORM realtime.send( |
| 110 | + jsonb_build_object( |
| 111 | + 'event_type', 'run:started', |
| 112 | + 'run_id', v_created_run.run_id, |
| 113 | + 'flow_slug', v_created_run.flow_slug, |
| 114 | + 'input', v_created_run.input, |
| 115 | + 'status', 'started', |
| 116 | + 'remaining_steps', v_created_run.remaining_steps, |
| 117 | + 'started_at', v_created_run.started_at |
| 118 | + ), |
| 119 | + 'run:started', |
| 120 | + concat('pgflow:run:', v_created_run.run_id), |
| 121 | + false |
| 122 | +); |
| 123 | + |
| 124 | +PERFORM pgflow.start_ready_steps(v_created_run.run_id); |
| 125 | + |
| 126 | +RETURN QUERY SELECT * FROM pgflow.runs where pgflow.runs.run_id = v_created_run.run_id; |
| 127 | + |
| 128 | +end; |
| 129 | +$$; |
| 130 | +-- Create "add_step" function |
| 131 | +CREATE FUNCTION "pgflow"."add_step" ("flow_slug" text, "step_slug" text, "deps_slugs" text[] DEFAULT '{}', "max_attempts" integer DEFAULT NULL::integer, "base_delay" integer DEFAULT NULL::integer, "timeout" integer DEFAULT NULL::integer, "start_delay" integer DEFAULT NULL::integer, "step_type" text DEFAULT 'single') RETURNS "pgflow"."steps" LANGUAGE plpgsql SET "search_path" = '' AS $$ |
| 132 | +DECLARE |
| 133 | + result_step pgflow.steps; |
| 134 | + next_idx int; |
| 135 | +BEGIN |
| 136 | + -- Validate map step constraints |
| 137 | + -- Map steps can have either: |
| 138 | + -- 0 dependencies (root map - maps over flow input array) |
| 139 | + -- 1 dependency (dependent map - maps over dependency output array) |
| 140 | + IF COALESCE(add_step.step_type, 'single') = 'map' AND COALESCE(array_length(add_step.deps_slugs, 1), 0) > 1 THEN |
| 141 | + RAISE EXCEPTION 'Map step "%" can have at most one dependency, but % were provided: %', |
| 142 | + add_step.step_slug, |
| 143 | + COALESCE(array_length(add_step.deps_slugs, 1), 0), |
| 144 | + array_to_string(add_step.deps_slugs, ', '); |
| 145 | + END IF; |
| 146 | + |
| 147 | + -- Get next step index |
| 148 | + SELECT COALESCE(MAX(s.step_index) + 1, 0) INTO next_idx |
| 149 | + FROM pgflow.steps s |
| 150 | + WHERE s.flow_slug = add_step.flow_slug; |
| 151 | + |
| 152 | + -- Create the step |
| 153 | + INSERT INTO pgflow.steps ( |
| 154 | + flow_slug, step_slug, step_type, step_index, deps_count, |
| 155 | + opt_max_attempts, opt_base_delay, opt_timeout, opt_start_delay |
| 156 | + ) |
| 157 | + VALUES ( |
| 158 | + add_step.flow_slug, |
| 159 | + add_step.step_slug, |
| 160 | + COALESCE(add_step.step_type, 'single'), |
| 161 | + next_idx, |
| 162 | + COALESCE(array_length(add_step.deps_slugs, 1), 0), |
| 163 | + add_step.max_attempts, |
| 164 | + add_step.base_delay, |
| 165 | + add_step.timeout, |
| 166 | + add_step.start_delay |
| 167 | + ) |
| 168 | + ON CONFLICT ON CONSTRAINT steps_pkey |
| 169 | + DO UPDATE SET step_slug = EXCLUDED.step_slug |
| 170 | + RETURNING * INTO result_step; |
| 171 | + |
| 172 | + -- Insert dependencies |
| 173 | + INSERT INTO pgflow.deps (flow_slug, dep_slug, step_slug) |
| 174 | + SELECT add_step.flow_slug, d.dep_slug, add_step.step_slug |
| 175 | + FROM unnest(COALESCE(add_step.deps_slugs, '{}')) AS d(dep_slug) |
| 176 | + WHERE add_step.deps_slugs IS NOT NULL AND array_length(add_step.deps_slugs, 1) > 0 |
| 177 | + ON CONFLICT ON CONSTRAINT deps_pkey DO NOTHING; |
| 178 | + |
| 179 | + RETURN result_step; |
| 180 | +END; |
| 181 | +$$; |
| 182 | +-- Drop "add_step" function |
| 183 | +DROP FUNCTION "pgflow"."add_step" (text, text, integer, integer, integer, integer); |
| 184 | +-- Drop "add_step" function |
| 185 | +DROP FUNCTION "pgflow"."add_step" (text, text, text[], integer, integer, integer, integer); |
0 commit comments