1mod postgres;
16mod sqlite;
17
18use crate::model::{ExecutionState, HistoryEvent};
19use orbit_ir::WorkflowIr;
20use serde::Serialize;
21use std::path::Path;
22
23pub(crate) use self::postgres::{session_client, CLUSTER_SCHEMA};
24pub use self::postgres::{PgMode, PgStore};
25pub(crate) use self::postgres::{SharedAppend, TaskOffer, UnitSync, NOW_MS, SHARED_SCHEMA};
26pub use self::sqlite::SqliteStore;
27
28#[derive(Debug)]
29pub enum PutDefinition {
30 Created,
31 Unchanged,
32 Conflict { existing_hash: String },
33}
34
35#[derive(Debug, Clone, Serialize)]
36pub struct ExecutionSummary {
37 pub execution_id: String,
38 pub workflow: String,
39 pub version: String,
40 pub status: String,
41 pub created_at_ms: u64,
42 pub updated_at_ms: u64,
43}
44
45#[derive(Debug, Clone, Serialize)]
46pub struct WorkerRecord {
47 pub worker_id: String,
48 pub runtime: String,
49 pub sdk: Option<String>,
50 pub contracts: serde_json::Value,
51 pub last_seen_ms: u64,
52 pub registered_at_ms: u64,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct StoreError(pub String);
58
59impl std::fmt::Display for StoreError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.write_str(&self.0)
62 }
63}
64
65impl std::error::Error for StoreError {}
66
67impl From<rusqlite::Error> for StoreError {
68 fn from(e: rusqlite::Error) -> Self {
69 StoreError(format!("sqlite: {e}"))
70 }
71}
72
73impl From<::postgres::Error> for StoreError {
74 fn from(e: ::postgres::Error) -> Self {
75 StoreError(format!("postgres: {e}"))
76 }
77}
78
79pub type Result<T> = std::result::Result<T, StoreError>;
80
81pub(crate) const SCHEMA: &str = r#"
83CREATE TABLE IF NOT EXISTS schema_version (version BIGINT NOT NULL);
84CREATE TABLE IF NOT EXISTS workflow_definitions (
85 name TEXT NOT NULL,
86 version TEXT NOT NULL,
87 definition_hash TEXT NOT NULL,
88 ir TEXT NOT NULL,
89 created_at_ms BIGINT NOT NULL,
90 PRIMARY KEY (name, version)
91);
92CREATE TABLE IF NOT EXISTS workflow_executions (
93 execution_id TEXT PRIMARY KEY,
94 workflow TEXT NOT NULL,
95 version TEXT NOT NULL,
96 definition_hash TEXT NOT NULL,
97 status TEXT NOT NULL,
98 created_at_ms BIGINT NOT NULL,
99 updated_at_ms BIGINT NOT NULL,
100 last_seq BIGINT NOT NULL
101);
102CREATE INDEX IF NOT EXISTS idx_executions_status ON workflow_executions(status);
103CREATE TABLE IF NOT EXISTS execution_events (
104 execution_id TEXT NOT NULL,
105 seq BIGINT NOT NULL,
106 event_type TEXT NOT NULL,
107 step TEXT,
108 payload TEXT NOT NULL,
109 timestamp_ms BIGINT NOT NULL,
110 PRIMARY KEY (execution_id, seq)
111);
112CREATE TABLE IF NOT EXISTS worker_registry (
113 worker_id TEXT PRIMARY KEY,
114 runtime TEXT NOT NULL,
115 sdk TEXT,
116 contracts TEXT NOT NULL,
117 registered_at_ms BIGINT NOT NULL,
118 last_seen_ms BIGINT NOT NULL
119);
120"#;
121
122pub enum Store {
124 Sqlite(SqliteStore),
125 Postgres(PgStore),
126}
127
128macro_rules! dispatch {
129 ($self:ident, $s:ident => $call:expr) => {
130 match $self {
131 Store::Sqlite($s) => $call,
132 Store::Postgres($s) => $call,
133 }
134 };
135}
136
137impl Store {
138 pub fn connect(location: &str) -> Result<Store> {
141 if location.starts_with("postgres://") || location.starts_with("postgresql://") {
142 Ok(Store::Postgres(PgStore::connect(location)?))
143 } else {
144 Store::open(Path::new(location))
145 }
146 }
147
148 pub fn open(path: &Path) -> Result<Store> {
150 Ok(Store::Sqlite(SqliteStore::open(path)?))
151 }
152
153 pub fn in_memory() -> Result<Store> {
154 Ok(Store::Sqlite(SqliteStore::in_memory()?))
155 }
156
157 pub fn backend(&self) -> &'static str {
158 match self {
159 Store::Sqlite(_) => "sqlite",
160 Store::Postgres(_) => "postgres",
161 }
162 }
163
164 pub fn put_definition(&self, ir: &WorkflowIr, now: u64) -> Result<PutDefinition> {
165 dispatch!(self, s => s.put_definition(ir, now))
166 }
167
168 pub fn definitions(&self) -> Result<Vec<WorkflowIr>> {
169 dispatch!(self, s => s.definitions())
170 }
171
172 pub fn execution_exists(&self, id: &str) -> Result<bool> {
173 dispatch!(self, s => s.execution_exists(id))
174 }
175
176 pub fn append(&mut self, state: &ExecutionState, events: &[HistoryEvent]) -> Result<()> {
178 dispatch!(self, s => s.append(state, events))
179 }
180
181 pub fn history(&self, id: &str) -> Result<Vec<HistoryEvent>> {
182 dispatch!(self, s => s.history(id))
183 }
184
185 pub fn active_execution_ids(&self) -> Result<Vec<String>> {
187 dispatch!(self, s => s.active_execution_ids())
188 }
189
190 pub fn list_executions(
191 &self,
192 status: Option<&str>,
193 workflow: Option<&str>,
194 limit: u32,
195 ) -> Result<Vec<ExecutionSummary>> {
196 dispatch!(self, s => s.list_executions(status, workflow, limit))
197 }
198
199 pub fn count_by_status(&self) -> Result<Vec<(String, u64)>> {
200 dispatch!(self, s => s.count_by_status())
201 }
202
203 pub fn upsert_worker(&self, w: &WorkerRecord) -> Result<()> {
204 dispatch!(self, s => s.upsert_worker(w))
205 }
206
207 pub fn touch_worker(&self, worker_id: &str, now: u64) -> Result<()> {
208 dispatch!(self, s => s.touch_worker(worker_id, now))
209 }
210
211 pub fn workers(&self) -> Result<Vec<WorkerRecord>> {
212 dispatch!(self, s => s.workers())
213 }
214}