Skip to content

feat(store): improve SQL migrations - #2358

Merged
igamigo merged 10 commits into
nextfrom
jmunoz-sql-migrations
Aug 21, 2026
Merged

feat(store): improve SQL migrations#2358
igamigo merged 10 commits into
nextfrom
jmunoz-sql-migrations

Conversation

@juan518munoz

@juan518munoz juan518munoz commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Closes #2346

  • Froze store.sql as migrations/0001_init.sql. Schema changes are appended as new files, no longer patched into an existing one.
  • Pinned each migration's fingerprint in PINNED_SCHEMA_HASHES and made it the runtime check. The previous check is now test only.
  • Rewrote normalize_sql as a quote-aware scanner. Inline column comments are now free to edit, and three cases where different schemas shared a digest no longer do.
  • apply_migrations handles every SchemaVersion arm. A store from a newer client reports SchemaTooNew rather than an opaque DatabaseTooFarAhead.
  • A failed migration no longer prints the whole schema to the terminal.
  • Added scripts/check-migrations.sh and a CI job that rejects any edit to a released migration.

Notes

#2304 already moved the hash from the script's bytes to the materialized schema.

@igamigo igamigo changed the title chore: improve sql migrations feat(store): improve SQL migrations Aug 5, 2026
@igamigo
igamigo marked this pull request as ready for review August 5, 2026 22:18

@igamigo igamigo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good so far. One thing we should do before closing the issue is this point from the issue:

  • Back up the database before migration.

We should look into backing up the store, migrating it, and once we know it has passed (by checking the hashes), delete the backup. If anything errors in the middle, we roll back the upgrade and then report the error. We could decide to do this in a separate PR.

Another thing that would be good to validate is whether the first migration is applied on a fresh file and that there is no way for a sqlite database to be applied the 0001 migration

Comment on lines +43 to +52
/// Renders a migration failure without reproducing the migration script.
pub fn describe_migration_error(err: &MigrationError) -> String {
match err {
MigrationError::RusqliteError { err, .. } => describe_sqlite_error(err),
MigrationError::ForeignKeyCheck(violations) => {
format!("{} foreign key violation(s) after applying the migration", violations.len())
},
other => other.to_string(),
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: Let's just inline this function where it's called (it's only one place AFAICT)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 0e10af0

Comment on lines 173 to 175
/// Rewrites the SQL text stored for a schema object into a form that ignores differences `SQLite`
/// itself ignores, so cosmetic edits do not change the fingerprint.
fn normalize_sql(sql: &str) -> String {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's revert these changes. I don't think it's code worth maintaining as it becomes instantly quite more complicated. A solution could be to introduce a SQL linter so the normalization is even more trivial.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reverted

0e10af0

Comment on lines 120 to 131

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One thing I think we can do is verify that the hash and check that it's indeed as we expect

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We now follow node's procedure of checking migrations. At startup, the MigrationBuilder derives hashes by replaying the migrations, and checks them.

0e10af0

@igamigo
igamigo requested a review from gabrielbosio August 6, 2026 03:28
@juan518munoz

Copy link
Copy Markdown
Collaborator Author

We should look into backing up the store, migrating it, and once we know it has passed (by checking the hashes), delete the backup. If anything errors in the middle, we roll back the upgrade and then report the error. We could decide to do this in a separate PR.

Another thing that would be good to validate is whether the first migration is applied on a fresh file and that there is no way for a sqlite database to be applied the 0001 migration

This is now handled as of 0e10af0

Comment thread scripts/check-migrations.sh Outdated
exit 0
fi

>&2 echo "The following released migrations were modified, renamed or deleted:"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the diff is compared against the base branch then a migration the script is comparing to might not be released yet:

Suggested change
>&2 echo "The following released migrations were modified, renamed or deleted:"
>&2 echo "The following merged migrations were modified, renamed or deleted:"

@juan518munoz juan518munoz Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added suggestion in 4f4dd5d

Comment thread crates/sqlite-store/src/db_management/backup.rs Outdated
@gabrielbosio

gabrielbosio commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

If I am correct, merging this PR would cause any future change in SQL store database to force the addition of a migration script. Do we want this once we merge it to next, should we wait after the 0.16 release or should we disable the migration freeze for now? If we go with the latter approach, maybe it would not be even necessary to disable the entire migration freeze logic but to only disable the migration check job for now.

@gabrielbosio

gabrielbosio commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

If I am correct, merging this PR would cause any future change in SQL store database to force the addition of a migration script. Do we want this once we merge it to next, should we wait after the 0.16 release or should we disable the migration freeze for now? If we go with the latter approach, maybe it would not be even necessary to disable the entire migration freeze logic but to only disable the migration check job for now.

Let's leave this as it is right now and skip the migration check job in another PR with a new label (analogous to what no changelog is being used for). We can handle changes in the SQL store schema by changing the migration hash too for now.

@juan518munoz

Copy link
Copy Markdown
Collaborator Author

If I am correct, merging this PR would cause any future change in SQL store database to force the addition of a migration script. Do we want this once we merge it to next, should we wait after the 0.16 release or should we disable the migration freeze for now? If we go with the latter approach, maybe it would not be even necessary to disable the entire migration freeze logic but to only disable the migration check job for now.

Let's leave this as it is right now and skip the migration check job in another PR with a new label (analogous to what no changelog is being used for). We can handle changes in the SQL store schema by changing the migration hash too for now.

#2368

@igamigo

igamigo commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Overall the PR looks fine, but it relies on a lot of loose functionality. I think the PR would benefit from having some of that logic wrapped in structs (e.g., SqliteMigration, SqliteBackup) that makes this easier to reason about

@juan518munoz

Copy link
Copy Markdown
Collaborator Author

@igamigo abstracted the new functionality of this PR into specific structs

SqliteMigration: owns the migration scripts together with the fingerprint each version builds. It checks, applies and verifies migrations.

SchemaHash: owns the schema fingerprint, how it is derived, manages storing SQL in a normalised form before hashing, and how it renders.

SqliteBackup: owns the store's path, its copy's path, and the copy's lifecycle. It is only produced by taking a copy and only consumed by restoring or discarding one, so restoring a store that was never copied is no longer expressible.

5f0fabb

@igamigo igamigo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! I left one comment which I believe will be important, but it doesn't have to be done in this PR necessarily

Comment thread crates/sqlite-store/src/db_management/errors.rs
Comment on lines +150 to +152
fn migrations_from(scripts: &[&'static str]) -> Migrations<'static> {
Migrations::new(scripts.iter().map(|&script| M::up(script).foreign_key_check()).collect())
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This works, but we cannot easily do Rust hooks with this. We need to generalize this a bit more.
Basically I was expecting to have a single Migration (or SqliteMigration as this PR named it) that would have an associated .sql file and some optional Rust code (that takes a transaction and returns a Result<> or maybe just a bool). Then we could call M::up_with_hook and call the migration within the SQL transaction that would also run some Rust code. I think this would give us the most flexibility and is something that we will need.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@igamigo igamigo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! Thanks

@igamigo
igamigo merged commit c9ccc8b into next Aug 21, 2026
23 checks passed
@igamigo
igamigo deleted the jmunoz-sql-migrations branch August 21, 2026 19:48
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.

SQL store hardening - Migrations

3 participants