Runs all pending migrations up to `LATEST_VERSION`. Acquires an EXCLUSIVE transaction to prevent concurrent writers from interleaving schema changes. Each migration is applied and the version is bumped inside the same transaction. Returns `true` if any migrations were applied, `false` if already up-to-date.
(conn: &Connection)
| 285 | /// is bumped inside the same transaction. |
| 286 | /// Returns `true` if any migrations were applied, `false` if already up-to-date. |
| 287 | pub async fn migrate(conn: &Connection) -> Result<bool> { |
| 288 | let current = get_version(conn).await?; |
| 289 | debug_assert!( |
| 290 | current <= LATEST_VERSION, |
| 291 | "database version {current} is ahead of code version {LATEST_VERSION}" |
| 292 | ); |
| 293 | if current >= LATEST_VERSION { |
| 294 | enable_incremental_auto_vacuum(conn, "migrate", true).await?; |
| 295 | return Ok(false); |
| 296 | } |
| 297 | |
| 298 | eprintln!("[tracedecay] migrating database schema v{current} → v{LATEST_VERSION}…"); |
| 299 | |
| 300 | // BEGIN EXCLUSIVE blocks other writers (including other MCP servers or |
| 301 | // post-commit hooks) until we COMMIT. Readers using WAL mode are not |
| 302 | // blocked. |
| 303 | conn.execute("BEGIN EXCLUSIVE", ()) |
| 304 | .await |
| 305 | .map_err(|e| TraceDecayError::Database { |
| 306 | message: format!("failed to acquire exclusive lock: {e}"), |
| 307 | operation: "migrate".to_string(), |
| 308 | })?; |
| 309 | |
| 310 | // Re-read inside the lock in case another process migrated between our |
| 311 | // check and the lock acquisition. |
| 312 | let current = get_version(conn).await?; |
| 313 | |
| 314 | let result = run_migrations(conn, current).await; |
| 315 | |
| 316 | match result { |
| 317 | Ok(()) => { |
| 318 | conn.execute("COMMIT", ()) |
| 319 | .await |
| 320 | .map_err(|e| TraceDecayError::Database { |
| 321 | message: format!("failed to commit migrations: {e}"), |
| 322 | operation: "migrate".to_string(), |
| 323 | })?; |
| 324 | enable_incremental_auto_vacuum(conn, "migrate", true).await?; |
| 325 | Ok(true) |
| 326 | } |
| 327 | Err(e) => { |
| 328 | let _ = conn.execute("ROLLBACK", ()).await; |
| 329 | Err(e) |
| 330 | } |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | /// Applies migrations sequentially from `current` up to `LATEST_VERSION`. |
| 335 | async fn run_migrations(conn: &Connection, current: u32) -> Result<()> { |