feat(backend): graceful handling of DB schema version mismatch on downgrade#1342
Conversation
…ngrade When a user downgrades the app, the SQLite database may have a newer schema than the older app expects, causing a panic on startup. This adds a pre-flight schema compatibility check before Tauri starts, and shows a native dialog offering to reset the database, continue anyway, or exit — instead of crashing silently.
📝 WalkthroughWalkthroughAdds a database preflight compatibility check that compares the app's max migration version with the existing SQLite database at startup, plus a startup service to prompt the user to reset, continue, or exit; integrates the check into application initialization and exposes a helper to compute max migration version. Changes
Sequence DiagramsequenceDiagram
participant App as App Startup
participant Preflight as Preflight Check
participant DB as SQLite DB
participant Svc as Startup Service
participant UI as Dialog/UI
App->>Preflight: check_db_compatibility()
Preflight->>Preflight: get_max_migration_version()
Preflight->>DB: SELECT MAX(version) FROM _sqlx_migrations WHERE success = 1
DB-->>Preflight: db_max_version
alt db_max_version > app_max_version
Preflight-->>App: IncompatibleVersion
App->>Svc: prompt_startup_error(error)
Svc->>UI: show compatibility dialog (Reset / Continue / Exit)
UI-->>Svc: user choice
alt Reset
Svc->>DB: delete database files
Svc->>App: relaunch app (restart)
else Continue
Svc-->>App: Resume startup
else Exit
Svc->>App: exit(1)
end
else Compatible or first-run
Preflight-->>App: None
App->>App: proceed with initialization (register migrations, init controllers)
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Rust Backend Coverage ReportCoverage Details |
There was a problem hiding this comment.
Pull request overview
Adds a backend preflight check to detect SQLite schema version mismatches (e.g., after downgrading), and surfaces a native startup dialog to avoid crashing on launch.
Changes:
- Added DB schema compatibility preflight logic that inspects
_sqlx_migrationsvs the app’s max migration version. - Introduced a native startup dialog service with options to reset+restart, continue, or exit.
- Made SQL plugin registration and several startup routines conditional on DB compatibility.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src-tauri/src/services/mod.rs |
Exposes the new DB startup dialog service module. |
src-tauri/src/services/db_startup_service.rs |
Implements the native dialog + DB file deletion + restart behavior (with unit tests). |
src-tauri/src/lib.rs |
Runs the preflight check and conditionally initializes the SQL plugin and startup workers/UI. |
src-tauri/src/infrastructure/database/preflight.rs |
New preflight implementation that queries max successful migration version (with tests). |
src-tauri/src/infrastructure/database/mod.rs |
Re-exports the new preflight module. |
src-tauri/src/infrastructure/database/migration.rs |
Adds get_max_migration_version() helper and test. |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src-tauri/src/lib.rs (1)
138-208:⚠️ Potential issue | 🔴 Critical
ContinueAnywayis currently a dead-end startup path.At Lines 138-189 and 243-249, all window/event/worker initialization and SQL plugin registration are gated on
is_db_ok. When the dialog returnsContinueAnywayat Line 204, this branch does not resume any of that work or re-show the hidden"main"window, so the app keeps running in the background with no usable UI. Either remove this action or explicitly continue the non-DB startup flow from here.Also applies to: 243-249
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src-tauri/src/lib.rs` around lines 138 - 208, The ContinueAnyway path currently returns without resuming the startup sequence guarded by is_db_ok, leaving the app running headless; update the ContinueAnyway branch in the thread spawned for db_startup_service::prompt_startup_error so it re-runs the same post-DB-success initialization (re-show the "main" window and execute the code that runs when is_db_ok is true): call the UI init (commands::ui::init), mount events (builder.mount_events), register SQL plugin/attachments, set up SystemMonitorController::setup and HardwareArchiveController::setup and store them into app.state::<workers::WorkersState>() (monitor and hw_archive), and re-enable scheduled_data_deletion (tauri::async_runtime::spawn of batch_delete_old_data) — or refactor that whole block into a helper function (e.g., init_after_db_ok) and invoke it from both the is_db_ok branch and the ContinueAnyway arm to avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src-tauri/src/services/db_startup_service.rs`:
- Around line 70-76: The restart code currently calls std::env::current_exe()
and .spawn().expect(...) which will panic on failure; instead, change the
recover-and-restart path in db_startup_service.rs to handle errors from
current_exe and Command::new(...).spawn() gracefully: remove both expect()
calls, propagate or match on the Result from current_exe() and spawn(), and on
error log/report the failure and show the same user-facing actionable error flow
used by the delete-failure branch (e.g., show a dialog or return an Err) rather
than aborting; update references around exe_path, args, Command::new and spawn
to use non-panicking error handling and surface the error message to the user or
caller.
- Around line 1-7: The service currently performs UI dialogs and filesystem
cleanup directly (see imports of tauri_plugin_dialog and use of Path) which must
be moved to an infrastructure abstraction: extract the recovery I/O (dialog
display + cleanup operations) into a new infrastructure component (e.g.,
infrastructure::recovery::RecoveryIO) that implements a trait (e.g.,
RecoveryIoTrait) with methods like confirm_recovery_dialog(...) and
remove_recovery_files(path: &Path) and implement the platform-specific Tauri
dialog + fs logic there; then change db_startup_service.rs to depend on the
trait (injected via factory or constructor) and replace direct
tauri_plugin_dialog and Path-based cleanup calls with calls to the trait
methods, keeping DbStartupError usage unchanged so only the I/O implementation
moves to infrastructure.
---
Outside diff comments:
In `@src-tauri/src/lib.rs`:
- Around line 138-208: The ContinueAnyway path currently returns without
resuming the startup sequence guarded by is_db_ok, leaving the app running
headless; update the ContinueAnyway branch in the thread spawned for
db_startup_service::prompt_startup_error so it re-runs the same post-DB-success
initialization (re-show the "main" window and execute the code that runs when
is_db_ok is true): call the UI init (commands::ui::init), mount events
(builder.mount_events), register SQL plugin/attachments, set up
SystemMonitorController::setup and HardwareArchiveController::setup and store
them into app.state::<workers::WorkersState>() (monitor and hw_archive), and
re-enable scheduled_data_deletion (tauri::async_runtime::spawn of
batch_delete_old_data) — or refactor that whole block into a helper function
(e.g., init_after_db_ok) and invoke it from both the is_db_ok branch and the
ContinueAnyway arm to avoid duplication.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 16f03013-1008-4486-b11b-1d3ae4a6ab59
📒 Files selected for processing (6)
src-tauri/src/infrastructure/database/migration.rssrc-tauri/src/infrastructure/database/mod.rssrc-tauri/src/infrastructure/database/preflight.rssrc-tauri/src/lib.rssrc-tauri/src/services/db_startup_service.rssrc-tauri/src/services/mod.rs
- Move UI/monitor initialization outside the is_db_ok gate so real-time monitoring works even when DB is incompatible - Show the main window when user chooses "Continue Anyway" instead of leaving the app headless - Replace expect() with error dialogs in reset_database_and_restart to avoid panicking in the recovery path - Extract show_error_dialog helper to reduce duplication
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src-tauri/src/services/db_startup_service.rs (1)
1-6:⚠️ Potential issue | 🟠 MajorMove the recovery I/O out of
services/.This module still owns Tauri dialog calls, filesystem deletion, and process restart logic, so the service layer is doing UI/OS I/O instead of delegating it behind a platform/infrastructure abstraction. Please keep this layer focused on choosing the recovery action and push the actual dialog/fs/process work behind a trait/factory boundary.
As per coding guidelines, "Infrastructure code for OS APIs, database, and external I/O should be in
src-tauri/src/infrastructure/**" and "Services layer must implement business logic and use Factory pattern for platform abstraction".Also applies to: 23-39, 56-105
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src-tauri/src/services/db_startup_service.rs` around lines 1 - 6, The db_startup_service.rs currently performs UI and OS I/O (Tauri dialogs, filesystem deletion, and process restart) inside the service; refactor by extracting a platform/infrastructure trait (e.g., RecoveryPlatform or DbRecoveryHandler) with methods like show_recovery_dialog(...), delete_db_files(Path), and restart_process() implemented under src-tauri/src/infrastructure/**, then update the service (functions in db_startup_service.rs that determine recovery actions) to call only the trait methods to choose an action and return/dispatch the selected recovery enum instead of performing dialog/fs/process work directly; keep decision logic (choose recovery action) in the service and move all Tauri/Dialog/Path/Process calls into the new infrastructure implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src-tauri/src/services/db_startup_service.rs`:
- Around line 1-6: The db_startup_service.rs currently performs UI and OS I/O
(Tauri dialogs, filesystem deletion, and process restart) inside the service;
refactor by extracting a platform/infrastructure trait (e.g., RecoveryPlatform
or DbRecoveryHandler) with methods like show_recovery_dialog(...),
delete_db_files(Path), and restart_process() implemented under
src-tauri/src/infrastructure/**, then update the service (functions in
db_startup_service.rs that determine recovery actions) to call only the trait
methods to choose an action and return/dispatch the selected recovery enum
instead of performing dialog/fs/process work directly; keep decision logic
(choose recovery action) in the service and move all Tauri/Dialog/Path/Process
calls into the new infrastructure implementation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 0d512595-7173-4119-ac6c-0ace839e43f7
📒 Files selected for processing (2)
src-tauri/src/lib.rssrc-tauri/src/services/db_startup_service.rs
Summary
When a user downgrades the app, the SQLite database may have a newer schema than the older app expects, causing a panic on startup. This adds a pre-flight schema compatibility check before Tauri starts, and shows a native dialog offering to reset the database, continue anyway, or exit — instead of crashing silently.
Related Issues
Type of Change
fix/branch)feat/branch)refactor/branch)docs/branch)chore/branch)Screenshots / Videos
Test Plan
Checklist
npm run lint && npm run format/cargo tauri-lint && cargo tauri-fmt)npm test/cargo tauri-test)Summary by CodeRabbit
New Features
Bug Fixes
Tests