Some migrations can be completed without needing a transaction for
atomicity. We should avoid transactions for DDL as far as possible to
avoid interfering with other queries and holding locks too long. It's
possible that all actions could be completed without a transaction but
I'm not sure of that yet.
Some migrations also won't work with transactions. For example dropping
an index using `DROP INDEX CONCURRENTLY` doesn't work inside a
transation.
Next step is to perform the same change for aborts.
This builds on the previous commit to make the transactions even more
granular when completing a migration. Keeping the transaction span short
is important to avoid interfering with other queries.
We previously used one transaction across all the migrations being
completed, which is not ideal from a locking perspective. Preferably we
want to keep the transactions as short-lived as possible to avoid
interfering with other queries. The next step will be to use one
transaction for each action.
To achieve this, we need to introduce a new intermediate state called
`Completing` which tracks which migrations have been completed so far.
Changing the default value only affects the new schema, the old schema
will still use the existing default value. This is to make sure a new
default value doesn't break the old application.
The builder will help simplify the tests and avoid boilerplate updates
when need fields are added to the create_table migration. We might want
to add builders all migrations later.
This checks for dangling temporary columns, triggers and function which
were used by migrations but should be removed once the migration has
been completed or aborted.
Improved backtrace support for errors in Rust is currently in progress:
https://github.com/rust-lang/rust/issues/53487. Adding the feature lets
anyhow add backtraces already which greatly improves readability of
errors in tests.
Column tracking refers to how temporary columns for a column are
tracked. This changes the tracking to use a single vector, the first
element is the original column name and subsequent ones are temporary
columns.
Migrations shouldn't need to know about any temporary schema changes
as they should already have been applied once the migration is going
to be completed.
This state is set as soon as a migration is started, before any
migrations are run. If a migration unexpectedly fails and doesn't abort
properly (which should happen automatically), the state will be left
dangling in `Applying`. In that case, it's fine for the user to run
migrate again as all migrations are idempotent.
Previously the column was added with its final name directly. This could
cause trouble if there was a column with the same name removed during
the same migration.
The previous implementation Schema couldn't handle some sequence of
migrations. For example when a temporary column is introduced for a
column and that column is then renamed. This new implementation keeps
track of all intermediate (temporary) columns to handle this.
This commit also greatly simplifies the API for `Schema`, creating a
single method on `TableChanges` and `ColumnChanges` for each possible
change: renaming, changing the backing column and removing.
This makes no difference right now but will make it easier to add
cross-compilation later as the cargo Github action has built-in support
for `cross`: https://github.com/actions-rs/cargo.