Upsert a checkpoint row (in-memory + redb). Idempotent: if the exact `(migration_id, phase, attempt)` tuple is already stored, this is a no-op.
(&mut self, row: PersistedMigrationCheckpoint)
| 244 | /// Idempotent: if the exact `(migration_id, phase, attempt)` tuple |
| 245 | /// is already stored, this is a no-op. |
| 246 | pub fn upsert(&mut self, row: PersistedMigrationCheckpoint) -> Result<()> { |
| 247 | let key = row.migration_id.clone(); |
| 248 | // Idempotency: skip if identical (migration_id, phase_tag, attempt) already stored. |
| 249 | if let Some(existing) = self.mem.get(&key) |
| 250 | && existing.payload.phase_tag() == row.payload.phase_tag() |
| 251 | && existing.attempt == row.attempt |
| 252 | { |
| 253 | return Ok(()); |
| 254 | } |
| 255 | // Encode and persist. |
| 256 | let bytes = zerompk::to_msgpack_vec(&row).map_err(|e| ClusterError::Codec { |
| 257 | detail: format!("migration_state encode: {e}"), |
| 258 | })?; |
| 259 | let txn = self.db.begin_write().map_err(|e| ClusterError::Storage { |
| 260 | detail: format!("migration_state begin_write: {e}"), |
| 261 | })?; |
| 262 | { |
| 263 | let mut table = txn |
| 264 | .open_table(Self::TABLE) |
| 265 | .map_err(|e| ClusterError::Storage { |
| 266 | detail: format!("migration_state open_table: {e}"), |
| 267 | })?; |
| 268 | table |
| 269 | .insert(key.as_str(), bytes.as_slice()) |
| 270 | .map_err(|e| ClusterError::Storage { |
| 271 | detail: format!("migration_state insert: {e}"), |
| 272 | })?; |
| 273 | } |
| 274 | txn.commit().map_err(|e| ClusterError::Storage { |
| 275 | detail: format!("migration_state commit: {e}"), |
| 276 | })?; |
| 277 | self.mem.insert(key, row); |
| 278 | Ok(()) |
| 279 | } |
| 280 | |
| 281 | /// Remove a checkpoint row (in-memory + redb). No-op if not present. |
| 282 | pub fn remove(&mut self, migration_id: &MigrationId) -> Result<()> { |