Skip to content

feat(tracker-core): introduce sqlx schema migrations (subissue 1525-06)#1720

Merged
josecelano merged 8 commits intotorrust:developfrom
josecelano:1719-1525-06-introduce-schema-migrations
Apr 30, 2026
Merged

feat(tracker-core): introduce sqlx schema migrations (subissue 1525-06)#1720
josecelano merged 8 commits intotorrust:developfrom
josecelano:1719-1525-06-introduce-schema-migrations

Conversation

@josecelano
Copy link
Copy Markdown
Member

@josecelano josecelano commented Apr 30, 2026

Implements subissue 1525-06 of the persistence overhaul (#1525): adopt automatic schema migrations using sqlx::migrate!.

Spec: docs/issues/1719-1525-06-introduce-schema-migrations.md

Closes #1719.

Tasks

  • Task 1 — Fix the SQLite-only # comment in migration 1 so the embedded migrator can parse it.
  • Task 2 — Scaffolding: enable the sqlx macros feature, add per-driver MIGRATOR statics, add Error::MigrationError + From impl.
  • Task 3 — Fresh-install path: wire MIGRATOR.run() into create_database_tables(), switch drop_database_tables() to DROP TABLE IF EXISTS (incl. _sqlx_migrations), rewrite migrations/README.md, add idempotency tests.
  • Task 4 — Legacy bootstrap path: detect existing pre-v4 schemas without _sqlx_migrations and seed the migration history so the embedded migrator skips them on subsequent runs. Tests on both backends.

Both fresh installs and pre-v4 in-place upgrades work end-to-end. The legacy-bootstrap layer is documented as a single removable unit (see the rust-doc on bootstrap_legacy_schema in each driver) so it can be dropped cleanly when pre-v4 support is no longer required.

…ust#1719

Renames docs/issues/1525-06-introduce-schema-migrations.md to docs/issues/1719-1525-06-introduce-schema-migrations.md so the filename includes the GitHub issue number, matching the sibling-subissue naming pattern (<issue-number>-<spec-slug>.md) already used by 1717-1525-05-migrate-sqlite-and-mysql-to-sqlx.md.

Updates the three internal references to the renamed file in:

- docs/issues/1525-overhaul-persistence.md
- docs/issues/1525-07-align-rust-and-db-types.md (two references)
- docs/issues/1525-08-add-postgresql-driver.md

Documentation only; no code changes.
Add 11 findings (F1–F11) before the Tasks section reflecting the actual develop-branch state after subissue 1525-05, and rewrite spec passages and code blocks to match.

Highlights:

- Remove obsolete references to an ensure_schema() latch (1525-05 explicitly chose not to use one).

- Correct the false claim that drop_database_tables() omits torrent_aggregate_metrics; only _sqlx_migrations is the new drop.

- Align the proposed error API with the existing tuple-From pattern: introduce an Error::MigrationError variant and impl From<(MigrateError, Driver)> for Error instead of a free-standing Error::migration_error() constructor.

- Add 'notnull' to the cspell dictionary for the SQLite PRAGMA table_info reference.
…ases

Capture the implementer Q&A (Q1-Q7 plus follow-up Q1.5) inline in the
spec and rework the plan based on the answers:

- Reorganize Tasks into three commit-sized phases:
  1. Scaffolding (Tasks 1+2): add sqlx-migrate, embed migrations,
     and apply the SQLite-only # -> -- comment fix in
     migration 1. MySQL migration 1 is left untouched (Q1.5).
  2. Fresh-install path (Task 3): wire migrations into bootstrap for
     empty databases.
  3. Legacy bootstrap (Task 4): detect pre-v4 schemas and baseline
     them; precondition simplified to all four legacy tables
     present with no column-level checks (Q4).
- Rewrite Acceptance Criteria to match the new split: SQLite migration
  1 is the only file edit, both backends are exercised for
  fresh/idempotency/legacy-bootstrap/partial-migration (MySQL gated),
  and _sqlx_migrations is the only newly required drop (F2).
- Document upgrade-from-older-versions in the README only, instead of
  publishing a separate v4 changelog entry (Q5).
- Add Out-of-Scope items: defer INT(10) -> INT normalization
  to subissue 1525-07 (Q2) and defer the metrics ->
  torrent_metrics rename (Q1.5).
- Patch stale Findings: F5 no longer claims the MySQL migration must
  be edited, and F3 now points at Task 4 instead of Task 3.
- Minor: normalize behavioural -> behavioral to satisfy
  cspell.
Lays the groundwork for managing schema with sqlx migrations as part of
subissue 1525-06 (issue torrust#1719). No migrations are executed yet; wiring
into table creation will follow in the next phase.

Changes:

- Enable the sqlx `macros` feature so `sqlx::migrate!` can embed the
  per-driver migration directories at compile time.
- Embed per-driver `MIGRATOR` statics in the SQLite and MySQL driver
  modules, pointing at `migrations/sqlite` and `migrations/mysql`
  respectively. They are marked `#[allow(dead_code)]` until invoked.
- Add an `Error::MigrationError` variant and a
  `From<(MigrateError, Driver)>` impl, mirroring the existing tuple-From
  pattern used for `SqlxError` so call sites can use `?` uniformly.
- Replace the unsupported `#` comment with `--` on the first SQLite
  migration so the embedded migrator can parse it. The MySQL migration
  is intentionally left untouched: MySQL accepts `#` comments, and
  editing a published migration would break installer immutability.

Refs: subissue 1525-06 (torrust#1719)
Replace the hand-written CREATE TABLE statements in both database drivers
with the embedded `sqlx` migrator so schema bootstrap and future schema
evolution share a single, version-tracked code path.

- `create_database_tables()` on SQLite and MySQL now runs `MIGRATOR.run(&self.pool)`,
  applying every embedded migration idempotently and recording state in the
  `_sqlx_migrations` table.
- `drop_database_tables()` switches to `DROP TABLE IF EXISTS` and also drops
  `_sqlx_migrations`, so test teardown leaves a truly empty schema and the
  next `create_database_tables()` call re-applies every migration from a
  clean state.
- Removes the now-stale `AUTH_KEY_LENGTH` coupling from the MySQL driver;
  the `keys.key` column width lives in the migration file instead.
- Rewrites `packages/tracker-core/migrations/README.md` to document the new
  automatic-migrations behavior, the `_sqlx_migrations` tracking table, how
  to add new migrations, the immutability rule for applied migrations, and
  the pre-v4 upgrade path.
- Adds an idempotency test on SQLite (`create_database_tables_should_be_idempotent_on_a_fresh_database`,
  always on) and an idempotency assertion in the gated MySQL driver test
  (`TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true`).

Fresh installs now bootstrap end-to-end through the migrator. Pre-v4
installs are still expected to fail at startup because their schemas
predate `_sqlx_migrations`; that legacy-bootstrap path is addressed in
the next task of this subissue.

Refs subissue 1525-06 (torrust#1719) — fresh-install phase.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

❌ Patch coverage is 48.81890% with 130 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.89%. Comparing base (b6a545a) to head (d6c2f65).
⚠️ Report is 9 commits behind head on develop.

Files with missing lines Patch % Lines
...core/src/databases/driver/mysql/schema_migrator.rs 0.00% 63 Missing ⚠️
...ges/tracker-core/src/databases/driver/mysql/mod.rs 0.00% 49 Missing ⚠️
...ore/src/databases/driver/sqlite/schema_migrator.rs 91.05% 4 Missing and 7 partials ⚠️
packages/tracker-core/src/databases/error.rs 0.00% 7 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1720      +/-   ##
===========================================
- Coverage    81.16%   80.89%   -0.27%     
===========================================
  Files          348      348              
  Lines        24917    25162     +245     
  Branches     24917    25162     +245     
===========================================
+ Hits         20225    20356     +131     
- Misses        4471     4579     +108     
- Partials       221      227       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Adds per-driver `bootstrap_legacy_schema` free function (SQLite + MySQL) that
detects pre-v4 user-managed schemas and fake-applies the three legacy migrations
into `_sqlx_migrations` using the embedded checksums, so subsequent
`MIGRATOR.run()` is a no-op.

Adds `Error::LegacyDatabaseNotMigrated` to reject partial pre-v4 states (some
legacy tables missing) with an actionable message.

Wired into `SchemaMigrator::create_database_tables` for both drivers; runs once
before the embedded migrator.

SQLite: three unit tests covering fresh-noop, fully-migrated bootstrap, and
partial-state rejection. Extracted `create_legacy_pre_v4_schema` helper for the
legacy DDL.

MySQL: extends `run_mysql_driver_tests` (gated by `db-compatibility-tests`
feature + `TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true`) with legacy
bootstrap + partial-state assertions using the same helper pattern.

Added rust-doc on the legacy constants, free functions and test helpers
documenting that they form a single removable compatibility layer, with a 4-step
recipe for deleting it when pre-v4 support is dropped.

Refs torrust#1719 (subissue 1525-06).
@josecelano josecelano requested a review from Copilot April 30, 2026 18:20
@josecelano josecelano marked this pull request as ready for review April 30, 2026 18:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces sqlx::migrate!()-based embedded schema migrations for bittorrent-tracker-core’s SQLite and MySQL async drivers, replacing hardcoded DDL and adding a legacy bootstrap path for pre-v4 databases without _sqlx_migrations.

Changes:

  • Embed and run per-backend sqlx migrators on startup; switch drop paths to DROP TABLE IF EXISTS including _sqlx_migrations.
  • Add migration-focused error variants and conversions (MigrationError, plus a dedicated legacy-precondition error).
  • Update migration documentation/specs and add idempotency + legacy-bootstrap tests (SQLite always-on; MySQL in gated compatibility tests).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
project-words.txt Adds notnull to project dictionary.
packages/tracker-core/Cargo.toml Enables sqlx macros to support sqlx::migrate!().
packages/tracker-core/src/databases/error.rs Adds migration/legacy-bootstrap error variants and From<(MigrateError, Driver)>.
packages/tracker-core/src/databases/driver/sqlite/mod.rs Defines embedded SQLite MIGRATOR and adds idempotency test.
packages/tracker-core/src/databases/driver/sqlite/schema_migrator.rs Replaces raw DDL with migrator + legacy bootstrap; updates drop logic; adds legacy-bootstrap tests.
packages/tracker-core/src/databases/driver/mysql/mod.rs Defines embedded MySQL MIGRATOR and extends gated MySQL tests for idempotency/legacy bootstrap.
packages/tracker-core/src/databases/driver/mysql/schema_migrator.rs Replaces raw DDL with migrator + legacy bootstrap; updates drop logic.
packages/tracker-core/migrations/sqlite/20240730183000_torrust_tracker_create_all_tables.sql Fixes SQLite-incompatible # comment to -- so embedded migrator can parse it.
packages/tracker-core/migrations/README.md Updates migration docs for automatic embedded migrations and legacy upgrade expectations.
docs/issues/1719-1525-06-introduce-schema-migrations.md Adds the detailed spec document for subissue 1525-06 (as issue #1719).
docs/issues/1525-overhaul-persistence.md Updates spec reference to the new 1719-prefixed filename.
docs/issues/1525-08-add-postgresql-driver.md Updates spec reference to the new 1719-prefixed filename.
docs/issues/1525-07-align-rust-and-db-types.md Updates link to history-alignment section in the renamed spec doc.
docs/issues/1525-06-introduce-schema-migrations.md Removes the older spec file in favor of the new renamed document.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/issues/1719-1525-06-introduce-schema-migrations.md Outdated
Comment thread packages/tracker-core/src/databases/driver/sqlite/schema_migrator.rs Outdated
- Add the legacy-bootstrap timestamp cutoff (`> 20250527093000`) so contributors don't accidentally write a new migration that gets fake-applied on legacy databases.
- Note that `sqlx-cli` is an optional filename generator; manual creation is equally valid.
- State the forward-only convention (no `.up.sql`/`.down.sql` pairs).
- Generalise the comment-syntax note across both backends.
- Mention that a rebuild is required for new migrations to be embedded.

Refs torrust#1719 (subissue 1525-06).
- Fix stale "(both backends)" wording in the subissue 1525-06 spec — Task 1 only edits the SQLite migration; MySQL migration 1 must remain untouched per the immutability rule.

- Build the SQLite test pool through `SqliteConnectOptions::filename` (mirroring the production driver) so non-UTF-8 and Windows paths are handled correctly; drop the brittle `to_str().unwrap()` URL-formatting in the test helper.

Refs torrust#1719 (subissue 1525-06), PR torrust#1720.
@josecelano
Copy link
Copy Markdown
Member Author

ACK d6c2f65

@josecelano josecelano merged commit a370c3e into torrust:develop Apr 30, 2026
22 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1525-06: Introduce schema migrations

2 participants