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