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