Migrations¶
Run any pending migrations against the database, and validate previously applied migrations against the current migration source to detect accidental changes in previously applied migrations.
val res: Migrator.Results = db.migrate(
path = "./db/migrations",
table = "_sqlx4k_migrations",
afterFileMigration = { m, d -> println("Migration of file: $m, took $d") }
).getOrThrow()
println("Migration completed. $res")
The applied migrations are recorded in a table, _sqlx4k_migrations by default, which is created when missing. The
result reports the total number of files, how many were applied and validated, and the executionTime. For more
information, take a look at the examples.
The same migration files can be used to validate your @Query SQL against the schema at compile time: see
SQL schema validation.
Migration files¶
A migration is a .sql file named <version>_<description>.sql, e.g. 1_create_users.sql, 2_add_email.sql. A file
can hold several statements.
- The files run in the order of their versions, which must be consecutive (
1,2,3, …) and unique. - Each file runs in a single transaction: if one of its statements fails, the whole file is rolled back. On MySQL
and MariaDB, DDL statements (
CREATE,ALTER,DROP, …) commit implicitly and cannot be rolled back. - A file already applied is not run again; its checksum is compared with the one recorded when it was applied, and the migration fails if the file was changed since. Add a new file to change the schema further.
A failure (a gap or a duplicate in the versions, a changed or empty file, a failed statement) fails the migration
with an SQLError whose code is Migrate or Database.
Options¶
| Parameter | Default | What it does |
|---|---|---|
path |
"./db/migrations" |
The directory of the migration files. |
table |
"_sqlx4k_migrations" |
The table that records the applied migrations. |
schema |
the default schema | The schema of the migrations table, in which the files run too (PostgreSQL's search_path, MySQL's USE). Not supported on SQLite. |
createSchema |
false |
Creates the schema when it does not exist. |
afterStatementExecution |
no-op | Called after each statement, with the Statement and the time it took. |
afterFileMigration |
no-op | Called after each file, with the recorded Migration and the time it took. |
Migration files from code¶
Instead of a directory, migrate also takes the migration files themselves, as a list of MigrationFile (or a
function that supplies it), e.g. when there is no file system to read them from, as on mobile:
val files = listOf(
MigrationFile(name = "1_create_users.sql", content = "create table users(id bigint primary key, name text);"),
MigrationFile(name = "2_add_email.sql", content = "alter table users add column email text;"),
)
db.migrate(files = files).getOrThrow()
On the JVM, readMigrationFilesFromResources reads them from the classpath, also from inside a JAR:
SQLite and foreign keys
Each migration file runs in a single transaction. On SQLite, where every target enforces foreign keys,
PRAGMA foreign_keys has no effect inside a transaction, so a migration cannot turn them off: rebuilding a table
that other tables reference (create a new table, copy the rows, drop the old one, rename the new one, as SQLite's
documentation describes) deletes the referencing rows (ON DELETE CASCADE) or fails (NO ACTION / RESTRICT)
when the old table is dropped. Drop or rebuild the referencing tables in the same migration, or alter the table in
place (ALTER TABLE ... ADD / RENAME / DROP COLUMN) where SQLite allows it.