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"#;