Skip to main content

ff_backend_sqlite/queries/
exec_core.rs

1//! SQLite dialect-forked queries for execution core (create / claim /
2//! complete / fail).
3//!
4//! Populated in Phase 2a.2 per RFC-023 §4.1. The SQL strings are kept
5//! as module-level `const`s so `backend.rs` call sites reference them
6//! by name and migration-parity review can line them up against the
7//! PG reference statement-by-statement.
8
9/// Flip exec_core to `active/leased/running` on a successful claim
10/// (mirror of PG's claim-path UPDATE at
11/// `ff-backend-postgres/src/attempt.rs:236-250`).
12///
13/// `public_state = 'running'` mirrors the Postgres resume-claim write
14/// at `ff-backend-postgres/src/suspend_ops.rs:958-960` and normalises
15/// to [`ff_core::types::PublicState::Active`] via
16/// [`crate::reads::normalise_public_state`]. Without this write the
17/// column remained at its create-time `'waiting'` literal on SQLite
18/// while Spine-B consumers (and any direct `SELECT public_state`)
19/// expected the claimed-execution literal.
20pub(crate) const UPDATE_EXEC_CORE_CLAIM_SQL: &str = r#"
21    UPDATE ff_exec_core
22       SET lifecycle_phase = 'active',
23           ownership_state = 'leased',
24           eligibility_state = 'not_applicable',
25           public_state = 'running',
26           attempt_state = 'running_attempt',
27           started_at_ms = COALESCE(started_at_ms, ?3)
28     WHERE partition_key = ?1 AND execution_id = ?2
29"#;
30
31/// Flip exec_core to terminal success, recording the result payload
32/// (mirror of `ff-backend-postgres/src/attempt.rs:554-572`).
33pub(crate) const UPDATE_EXEC_CORE_COMPLETE_SQL: &str = r#"
34    UPDATE ff_exec_core
35       SET lifecycle_phase = 'terminal',
36           ownership_state = 'unowned',
37           eligibility_state = 'not_applicable',
38           attempt_state = 'attempt_terminal',
39           terminal_at_ms = ?1,
40           result = ?2
41     WHERE partition_key = ?3 AND execution_id = ?4
42"#;
43
44/// Re-enqueue the execution for a retry attempt: flip lifecycle back to
45/// runnable, bump attempt_index, stash the last failure message in the
46/// TEXT-encoded `raw_fields` JSON document via SQLite's `json_set` (JSON1).
47/// Mirrors PG's `jsonb_build_object(...)` concat at
48/// `ff-backend-postgres/src/attempt.rs:647-664`.
49pub(crate) const UPDATE_EXEC_CORE_FAIL_RETRY_SQL: &str = r#"
50    UPDATE ff_exec_core
51       SET lifecycle_phase = 'runnable',
52           ownership_state = 'unowned',
53           eligibility_state = 'eligible_now',
54           attempt_state = 'pending_retry_attempt',
55           attempt_index = attempt_index + 1,
56           raw_fields = json_set(raw_fields, '$.last_failure_message', ?1)
57     WHERE partition_key = ?2 AND execution_id = ?3
58"#;
59
60/// Merge `progress_pct` + `progress_message` into `ff_exec_core.raw_fields`
61/// (TEXT-encoded JSON). NULL binds must leave the corresponding field
62/// untouched, INCLUDING when the JSON path is currently absent — a naive
63/// `json_set(x, '$.k', coalesce(NULL, json_extract(absent))) =
64/// json_set(x, '$.k', NULL)` materializes an explicit JSON `null`,
65/// which diverges from the PG `raw_fields ||` no-op semantics for an
66/// empty patch (PR #376 Copilot review).
67///
68/// The `CASE WHEN ? IS NULL THEN <inner> ELSE json_set(<inner>, '$.k', ?)`
69/// shape skips the `json_set` call entirely when the bind is NULL,
70/// preserving absent fields AND preserving prior non-NULL values.
71/// Mirror of PG at `ff-backend-postgres/src/attempt.rs:498-518`; PG
72/// uses `raw_fields ||` on a jsonb object, we re-express the same
73/// observable write shape via conditional `json_set` calls over a
74/// TEXT document.
75///
76/// Binds: `?1 = pct (nullable INT)`, `?2 = message (nullable TEXT)`,
77/// `?3 = partition_key`, `?4 = execution_id`.
78pub(crate) const UPDATE_EXEC_CORE_PROGRESS_SQL: &str = r#"
79    UPDATE ff_exec_core
80       SET raw_fields = CASE
81               WHEN ?2 IS NULL THEN
82                   CASE
83                       WHEN ?1 IS NULL THEN raw_fields
84                       ELSE json_set(raw_fields, '$.progress_pct', ?1)
85                   END
86               ELSE json_set(
87                   CASE
88                       WHEN ?1 IS NULL THEN raw_fields
89                       ELSE json_set(raw_fields, '$.progress_pct', ?1)
90                   END,
91                   '$.progress_message',
92                   ?2
93               )
94           END
95     WHERE partition_key = ?3 AND execution_id = ?4
96"#;
97
98/// Flip exec_core to terminal failed — retry budget exhausted or
99/// classification was permanent. Mirror of PG at
100/// `ff-backend-postgres/src/attempt.rs:700-718`.
101pub(crate) const UPDATE_EXEC_CORE_FAIL_TERMINAL_SQL: &str = r#"
102    UPDATE ff_exec_core
103       SET lifecycle_phase = 'terminal',
104           ownership_state = 'unowned',
105           eligibility_state = 'not_applicable',
106           attempt_state = 'attempt_terminal',
107           terminal_at_ms = ?1,
108           raw_fields = json_set(raw_fields, '$.last_failure_message', ?2)
109     WHERE partition_key = ?3 AND execution_id = ?4
110"#;
111
112// ── Phase 2b.1 producer-side: create_execution inserts ──────────────────
113
114/// Idempotent insert of one `ff_exec_core` row. `ON CONFLICT DO NOTHING`
115/// mirrors the PG path (`ff-backend-postgres/src/exec_core.rs:157-188`)
116/// and the Valkey `ff_create_execution` FCALL's `Duplicate` outcome —
117/// caller detects duplicate via post-insert changes() == 0.
118///
119/// Binds (in order):
120///   1. partition_key (i64)
121///   2. execution_id (Uuid)
122///   3. lane_id (TEXT)
123///   4. priority (i64)
124///   5. created_at_ms (i64)
125///   6. deadline_at_ms (Option<i64>)
126///   7. payload (Option<Vec<u8>>)
127///   8. policy (Option<TEXT JSON>)
128///   9. raw_fields (TEXT JSON)
129pub(crate) const INSERT_EXEC_CORE_SQL: &str = r#"
130    INSERT INTO ff_exec_core (
131        partition_key, execution_id, flow_id, lane_id,
132        attempt_index,
133        lifecycle_phase, ownership_state, eligibility_state,
134        public_state, attempt_state,
135        priority, created_at_ms, deadline_at_ms,
136        payload, policy, raw_fields
137    ) VALUES (
138        ?1, ?2, NULL, ?3,
139        0,
140        'submitted', 'unowned', 'eligible_now',
141        'waiting', 'pending',
142        ?4, ?5, ?6,
143        ?7, ?8, ?9
144    )
145    ON CONFLICT (partition_key, execution_id) DO NOTHING
146"#;
147
148/// Idempotent insert into the normalized capability junction
149/// (RFC-023 §4.1 A4 — replaces PG's `text[]` + GIN). One row per
150/// `(execution_id, capability)` pair; junction rows persist as long
151/// as their parent `ff_exec_core` row does.
152pub(crate) const INSERT_EXEC_CAPABILITY_SQL: &str = r#"
153    INSERT INTO ff_execution_capabilities (execution_id, capability)
154    VALUES (?1, ?2)
155    ON CONFLICT (execution_id, capability) DO NOTHING
156"#;
157
158/// Idempotent seed of the lane registry (Q6 — dynamic lanes seed
159/// here on first use). Mirror of PG at
160/// `ff-backend-postgres/src/exec_core.rs:191-203`.
161pub(crate) const INSERT_LANE_REGISTRY_SQL: &str = r#"
162    INSERT INTO ff_lane_registry (lane_id, registered_at_ms, registered_by)
163    VALUES (?1, ?2, 'create_execution')
164    ON CONFLICT (lane_id) DO NOTHING
165"#;