ff_backend_sqlite/queries/attempt.rs
1//! SQLite dialect-forked queries for the attempt hot path.
2//!
3//! Populated in Phase 2a.2 per RFC-023 §4.1. The SQL strings are
4//! module-level `const`s so `backend.rs` call sites reference them
5//! by name and cross-dialect review lines them up against the PG
6//! reference at `ff-backend-postgres/src/attempt.rs` statement-by-
7//! statement.
8//!
9//! # Dialect translation summary
10//!
11//! | PG pattern | SQLite fork |
12//! | ------------------------------- | ----------------------------------------------- |
13//! | `FOR UPDATE SKIP LOCKED` | `BEGIN IMMEDIATE` txn + plain SELECT (§4.1 A3) |
14//! | `text[] @> ARRAY[...]` | junction-table SELECT + Rust subset match (A4) |
15//! | `jsonb_build_object(...)` | `json_set(raw_fields, '$.field', ?)` |
16//! | `BIGSERIAL RETURNING` | `INTEGER PRIMARY KEY AUTOINCREMENT` + RETURNING |
17//! | `BYTEA` binds | `BLOB` binds (sqlx auto-encodes `&[u8]`) |
18//!
19//! # Fence-triple contract
20//!
21//! `BEGIN IMMEDIATE` on SQLite escalates the txn to RESERVED, so the
22//! single-writer invariant (§3.2) holds for the full read-modify-write
23//! window. Fence CAS is expressed as a plain SELECT of the attempt
24//! row's `lease_epoch` under the txn lock; a mismatch against the
25//! caller's handle surfaces as
26//! [`ff_core::engine_error::ContentionKind::LeaseConflict`] without
27//! retrying (it is a semantic conflict, not transient busy).
28
29/// Scan up to `?3` eligible rows in the partition/lane ordered by
30/// `(priority DESC, created_at_ms ASC)`. No `FOR UPDATE SKIP LOCKED`
31/// — the enclosing `BEGIN IMMEDIATE` already serializes writers per
32/// §4.1 A3.
33///
34/// The batch shape (vs. `LIMIT 1`) lets `claim_impl` walk past rows
35/// whose required capabilities the current worker lacks without
36/// starving lower-priority-but-eligible members of the same lane —
37/// caught in PR-375 review. Bounded budget keeps the lock window
38/// predictable even when the top-priority row has an exotic cap set.
39pub(crate) const SELECT_ELIGIBLE_EXEC_SQL: &str = r#"
40 SELECT execution_id, attempt_index
41 FROM ff_exec_core
42 WHERE partition_key = ?1
43 AND lane_id = ?2
44 AND lifecycle_phase = 'runnable'
45 AND eligibility_state = 'eligible_now'
46 ORDER BY priority DESC, created_at_ms ASC
47 LIMIT ?3
48"#;
49
50/// Fetch the capability tokens bound to an execution via the junction
51/// table (RFC-023 §4.1 A4). Caller collects the returned rows into a
52/// [`ff_core::caps::CapabilityRequirement`] and runs
53/// `caps::matches(&req, worker)` in Rust — same shape as the PG path
54/// (see `ff-backend-postgres/src/attempt.rs:170-182`), just reading
55/// from the junction instead of a PG `text[]` column.
56pub(crate) const SELECT_EXEC_CAPABILITIES_SQL: &str = r#"
57 SELECT capability
58 FROM ff_execution_capabilities
59 WHERE execution_id = ?1
60"#;
61
62/// UPSERT the attempt row on claim. On first claim the row is fresh
63/// (`lease_epoch = 1`); on a retry-attempt re-claim the PK matches
64/// the prior (attempt_index, execution_id) and we bump
65/// `lease_epoch = prior + 1`, rotate worker identity, clear the
66/// outcome. Mirror of the PG ON CONFLICT DO UPDATE at
67/// `ff-backend-postgres/src/attempt.rs:192-218`.
68pub(crate) const UPSERT_ATTEMPT_ON_CLAIM_SQL: &str = r#"
69 INSERT INTO ff_attempt (
70 partition_key, execution_id, attempt_index,
71 worker_id, worker_instance_id,
72 lease_epoch, lease_expires_at_ms, started_at_ms
73 ) VALUES (?1, ?2, ?3, ?4, ?5, 1, ?6, ?7)
74 ON CONFLICT (partition_key, execution_id, attempt_index)
75 DO UPDATE SET
76 worker_id = excluded.worker_id,
77 worker_instance_id = excluded.worker_instance_id,
78 lease_epoch = ff_attempt.lease_epoch + 1,
79 lease_expires_at_ms = excluded.lease_expires_at_ms,
80 started_at_ms = excluded.started_at_ms,
81 outcome = NULL
82 RETURNING lease_epoch
83"#;
84
85/// Fence check: read the attempt row's current `lease_epoch` so the
86/// caller can compare it against the handle-embedded epoch before any
87/// terminal write. Cheaper shape than PG's `SELECT ... FOR UPDATE` —
88/// SQLite's `BEGIN IMMEDIATE` already holds the RESERVED lock for the
89/// full txn, so a plain SELECT is sufficient.
90pub(crate) const SELECT_ATTEMPT_EPOCH_SQL: &str = r#"
91 SELECT lease_epoch
92 FROM ff_attempt
93 WHERE partition_key = ?1 AND execution_id = ?2 AND attempt_index = ?3
94"#;
95
96/// Mark the attempt row as terminal-success. Drops the lease by
97/// nulling `lease_expires_at_ms` so the scanner does not re-issue
98/// reclaim grants. Mirror of PG at
99/// `ff-backend-postgres/src/attempt.rs:538-552`.
100pub(crate) const UPDATE_ATTEMPT_COMPLETE_SQL: &str = r#"
101 UPDATE ff_attempt
102 SET terminal_at_ms = ?1,
103 outcome = 'success',
104 lease_expires_at_ms = NULL
105 WHERE partition_key = ?2 AND execution_id = ?3 AND attempt_index = ?4
106"#;
107
108/// Mark the attempt row as retry-scheduled. `outcome = 'retry'` is the
109/// PG/Valkey-parity token; the `exec_core` flip to runnable +
110/// attempt_index bump lives in `exec_core::UPDATE_EXEC_CORE_FAIL_RETRY_SQL`.
111/// Mirror of PG at `ff-backend-postgres/src/attempt.rs:630-645`.
112pub(crate) const UPDATE_ATTEMPT_FAIL_RETRY_SQL: &str = r#"
113 UPDATE ff_attempt
114 SET terminal_at_ms = ?1,
115 outcome = 'retry',
116 lease_expires_at_ms = NULL
117 WHERE partition_key = ?2 AND execution_id = ?3 AND attempt_index = ?4
118"#;
119
120/// Mark the attempt row as terminal-failed (retry budget exhausted or
121/// classification was permanent). Mirror of PG at
122/// `ff-backend-postgres/src/attempt.rs:684-698`.
123pub(crate) const UPDATE_ATTEMPT_FAIL_TERMINAL_SQL: &str = r#"
124 UPDATE ff_attempt
125 SET terminal_at_ms = ?1,
126 outcome = 'failed',
127 lease_expires_at_ms = NULL
128 WHERE partition_key = ?2 AND execution_id = ?3 AND attempt_index = ?4
129"#;
130
131/// Outbox write for the completion event. The AFTER-INSERT `pg_notify`
132/// trigger from the PG schema is intentionally dropped (RFC-023 §4.2 —
133/// broadcast moves into a Rust post-commit path); durable replay still
134/// rides `event_id > cursor` catch-up against this table, so the
135/// insert shape is identical to the PG statement at
136/// `ff-backend-postgres/src/attempt.rs:575-592` (and `:720-737` for
137/// the `failed` variant).
138pub(crate) const INSERT_COMPLETION_EVENT_SQL: &str = r#"
139 INSERT INTO ff_completion_event (
140 partition_key, execution_id, flow_id, outcome,
141 namespace, instance_tag, occurred_at_ms
142 )
143 SELECT partition_key, execution_id, flow_id, ?1,
144 json_extract(raw_fields, '$.namespace'),
145 json_extract(raw_fields, '$.tags."cairn.instance_id"'),
146 ?2
147 FROM ff_exec_core
148 WHERE partition_key = ?3 AND execution_id = ?4
149"#;