diff --git a/README.md b/README.md index e7eee6c..9bb36b7 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1849 TILs and counting..._ +_1850 TILs and counting..._ See some of the other learning resources I work on: @@ -1634,6 +1634,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Display Results In Readable Column Format](sqlite/display-results-in-readable-column-format.md) - [Explore The Database Schema](sqlite/explore-the-database-schema.md) +- [Manage Lightweight Schema Migrations With `user_version`](sqlite/manage-lightweight-schema-migrations-with-user-version.md) ### Streaming diff --git a/sqlite/manage-lightweight-schema-migrations-with-user-version.md b/sqlite/manage-lightweight-schema-migrations-with-user-version.md new file mode 100644 index 0000000..2c28a75 --- /dev/null +++ b/sqlite/manage-lightweight-schema-migrations-with-user-version.md @@ -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 +```