Add Manage Lightweight Schema Migrations With User Version as a SQLite TIL

This commit is contained in:
jbranchaud
2026-08-04 17:15:28 -05:00
parent 0866968be3
commit 4055ed7a7d
2 changed files with 64 additions and 1 deletions
@@ -0,0 +1,62 @@
# Manage Lightweight Schema Migrations With `user_version`
The [`user_version` pragma](https://sqlite.org/pragma.html#pragma_user_version)
in SQLite is a bit of persistent state built into the header of a SQLite
database file.
> The user_version pragma will get or set the value of the user-version integer
> at offset 60 in the database header. The user-version is an integer that is
> available to applications to use however they want. SQLite makes no use of the
> user-version itself.
One use case for `user_version` is as a schema version indicator. It starts at
`0` which can mean nothing has been migrated yet.
```sql
sqlite> pragma user_version;
0
```
Then for each individual migration that my application applies, I can increment
the value of `user_version`. At any point in the application lifecycle, it knows
up to what point schema migrations have been run. If the number of migrations is
greater than the value of `user_version`, then migrations need to be applied.
Here is some Python code (from
[`py-vmt`](https://github.com/jbranchaud/py-vmt/blob/e426b3fda92f59c116494a4a4ed992965bac1dba/src/py_vmt/db.py))
that uses this pattern:
```python
from sqlite3 import Connection
MIGRATIONS = [
# ...
]
# Using the `user_version` pragma in SQLite, this checks if there are any
# sets of statements in `MIGRATIONS` that have not been run yet. It then
# executes those and updates `user_version`.
def migrate(conn: Connection):
version = conn.execute("pragma user_version").fetchone()[0]
for i, statement in enumerate(MIGRATIONS[version:], start=version):
conn.executescript(statement)
conn.execute(f"pragma user_version = {i + 1}")
conn.commit()
```
Notice that `user_version` can be updated directly with an assignment:
```sql
sqlite> pragma user_version = 3;
sqlite> pragma user_version;
3
```
A `select` statement can also be used to access the `user_version` value like
so:
```sql
sqlite> select * from pragma_user_version;
3
```