1use crate::{FlowError, Result, StoredRow};
2use std::collections::HashMap;
3use std::fs::{self, OpenOptions};
4use std::io::{BufRead, BufReader, Write};
5use std::path::{Path, PathBuf};
6
7pub trait Storage: Send + Sync {
14 fn append(&mut self, row: &StoredRow) -> Result<()>;
16
17 fn read_all(&self) -> Result<Vec<StoredRow>>;
19
20 fn checkpoint(&mut self, state: &CheckpointData) -> Result<()>;
22
23 fn read_checkpoint(&self) -> Result<Option<CheckpointData>>;
25
26 fn compact(&mut self) -> Result<()>;
28
29 fn storage_type(&self) -> &'static str;
31}
32
33#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
35pub struct CheckpointData {
36 pub rows: HashMap<String, HashMap<String, StoredRow>>,
38 pub key_counters: HashMap<String, u64>,
40 pub delta_operations: Vec<StoredRow>,
42}
43
44pub struct MemoryStorage {
46 operations: Vec<StoredRow>,
47 checkpoint: Option<CheckpointData>,
48}
49
50impl MemoryStorage {
51 pub fn new() -> Self {
52 Self {
53 operations: Vec::new(),
54 checkpoint: None,
55 }
56 }
57}
58
59impl Default for MemoryStorage {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl Storage for MemoryStorage {
66 fn append(&mut self, row: &StoredRow) -> Result<()> {
67 self.operations.push(row.clone());
68 Ok(())
69 }
70
71 fn read_all(&self) -> Result<Vec<StoredRow>> {
72 Ok(self.operations.clone())
73 }
74
75 fn checkpoint(&mut self, state: &CheckpointData) -> Result<()> {
76 self.checkpoint = Some(state.clone());
77 self.operations.clear();
78 Ok(())
79 }
80
81 fn read_checkpoint(&self) -> Result<Option<CheckpointData>> {
82 Ok(self.checkpoint.clone())
83 }
84
85 fn compact(&mut self) -> Result<()> {
86 if let Some(checkpoint) = &self.checkpoint {
88 self.operations = checkpoint.delta_operations.clone();
89 }
90 Ok(())
91 }
92
93 fn storage_type(&self) -> &'static str {
94 "memory"
95 }
96}
97
98pub struct FileStorage {
100 path: PathBuf,
101 checkpoint_path: PathBuf,
102}
103
104impl FileStorage {
105 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
106 let path = path.as_ref().to_path_buf();
107 if let Some(parent) = path.parent() {
108 fs::create_dir_all(parent)?;
109 }
110 if !path.exists() {
111 OpenOptions::new().create(true).append(true).open(&path)?;
112 }
113
114 let checkpoint_path = path.with_extension("checkpoint");
115 Ok(Self {
116 path,
117 checkpoint_path,
118 })
119 }
120}
121
122impl Storage for FileStorage {
123 fn append(&mut self, row: &StoredRow) -> Result<()> {
124 let mut file = OpenOptions::new()
125 .create(true)
126 .append(true)
127 .open(&self.path)?;
128 let mut line = serde_json::to_string(row)?;
129 line.push('\n');
130 file.write_all(line.as_bytes())?;
131 file.flush()?;
132 Ok(())
133 }
134
135 fn read_all(&self) -> Result<Vec<StoredRow>> {
136 let mut operations = Vec::new();
137
138 if let Ok(checkpoint_data) = self.read_checkpoint() {
140 if checkpoint_data.is_some() {
141 if let Some(cp) = checkpoint_data {
143 for bucket in cp.rows.values() {
144 for row in bucket.values() {
145 operations.push(row.clone());
146 }
147 }
148 operations.extend(cp.delta_operations.clone());
150 return Ok(operations);
151 }
152 }
153 }
154
155 if self.path.exists() {
157 let file = OpenOptions::new().read(true).open(&self.path)?;
158 let reader = BufReader::new(file);
159
160 for (index, line) in reader.lines().enumerate() {
161 let line = line?;
162 if line.trim().is_empty() {
163 continue;
164 }
165 let row: StoredRow = serde_json::from_str(&line).map_err(|_| {
166 FlowError::CorruptLogLine(Box::new(crate::LogCorruption {
167 line_number: index + 1,
168 byte_offset: 0,
169 reason: "not a durable record this build understands".to_string(),
170 excerpt: line.chars().take(120).collect(),
171 }))
172 })?;
173 operations.push(row);
174 }
175 }
176
177 Ok(operations)
178 }
179
180 fn checkpoint(&mut self, state: &CheckpointData) -> Result<()> {
181 let checkpoint_json = serde_json::to_string(state)?;
182 let mut file = OpenOptions::new()
183 .create(true)
184 .write(true)
185 .truncate(true)
186 .open(&self.checkpoint_path)?;
187 file.write_all(checkpoint_json.as_bytes())?;
188 file.flush()?;
189 Ok(())
190 }
191
192 fn read_checkpoint(&self) -> Result<Option<CheckpointData>> {
193 if !self.checkpoint_path.exists() {
194 return Ok(None);
195 }
196
197 let content = fs::read_to_string(&self.checkpoint_path)?;
198 match serde_json::from_str(&content) {
199 Ok(data) => Ok(Some(data)),
200 Err(_) => Ok(None),
201 }
202 }
203
204 fn compact(&mut self) -> Result<()> {
205 Ok(())
212 }
213
214 fn storage_type(&self) -> &'static str {
215 "file"
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn memory_storage_append_and_read() {
225 let mut storage = MemoryStorage::new();
226 let row = StoredRow {
227 capability: "test".to_string(),
228 key: "test:1".to_string(),
229 rust_type: "String".to_string(),
230 value: serde_json::json!("value"),
231 unix_ms: 1000,
232 content_hash: None,
233 flow_ref: None,
234 deleted: false,
235 operation: None,
236 };
237
238 storage.append(&row).expect("append should succeed");
239 let operations = storage.read_all().expect("read_all should succeed");
240 assert_eq!(operations.len(), 1);
241 assert_eq!(operations[0].key, "test:1");
242 }
243
244 #[test]
245 fn memory_storage_checkpoint() {
246 let mut storage = MemoryStorage::new();
247 let row = StoredRow {
248 capability: "test".to_string(),
249 key: "test:1".to_string(),
250 rust_type: "String".to_string(),
251 value: serde_json::json!("value"),
252 unix_ms: 1000,
253 content_hash: None,
254 flow_ref: None,
255 deleted: false,
256 operation: None,
257 };
258
259 storage.append(&row).expect("append");
260
261 let checkpoint = CheckpointData {
262 rows: Default::default(),
263 key_counters: Default::default(),
264 delta_operations: vec![row],
265 };
266
267 storage
268 .checkpoint(&checkpoint)
269 .expect("checkpoint should succeed");
270 let operations = storage.read_all().expect("read_all should succeed");
271 assert_eq!(operations.len(), 0); }
273}