fix: order no_phase operations after the objects they act on - #848
Merged
zachdaniel merged 2 commits intoSep 11, 2026
Conversation
A migration operation flagged `no_phase: true` carries no implicit ordering
and, without a `requires/1` clause, floats ahead of the phased operations that
build a table's structure. Two such operations render SQL against an object
another operation creates in the same batch, so floating puts them before that
object exists:
- AlterDeferrability (`ALTER CONSTRAINT ... DEFERRABLE`) runs against a
foreign key an AddAttribute carrying `references:` creates. A composite
`with:` key that pushes the foreign key into a later phase left the
deferrability alter running first, failing with "constraint does not
exist". Require table_columns_settled.
- AddPrimaryKey (`ADD PRIMARY KEY (keys)`) runs against key columns an
AddAttribute can add in the same batch. Widening a primary key to include
a newly added column (list-partitioning by that column) generated
`ADD PRIMARY KEY (id, cell_id)` before cell_id existed. Require column_ready
for each key.
Both requirements are vacuous when the object predates the batch, and neither
adds a cycle: each provides only table_structure_ready, which AddAttribute
never requires.
Document the class in the module doc so future no_phase operations are held to
it, and audit the rest: they act on objects that predate the batch (renames and
drops), render nothing in `up` (the `*Down` helpers), or are ordered by the
consumers that require their facts. Add reproducing tests for both fixes; each
fails without its requires clause.
zachdaniel
reviewed
Sep 10, 2026
| barrier would need it to run last — contradictory. Give each operation | ||
| type the ordering it needs via `requires/1` instead. | ||
|
|
||
| Operations flagged `no_phase: true` are not grouped into the create/alter |
Contributor
There was a problem hiding this comment.
I think a lot of the comments/docs in this PR can be trimmed down significantly. Would you mind making that change? Otherwise LGTM
zachdaniel
approved these changes
Sep 11, 2026
Contributor
|
🚀 Thank you for your contribution! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An operation flagged
no_phase: truecarries no implicit ordering, so without arequires/1clause it floats ahead of the phased operations that build a table's structure. Two such operations render SQL against an object another operation creates in the same batch, so floating puts them before that object exists:AlterDeferrability(ALTER CONSTRAINT ... DEFERRABLE) runs against a foreign key anAddAttributecarryingreferences:creates. When a compositewith:key pushes the foreign key into a later phase, the deferrability alter ran first and failed withconstraint ... does not exist.AddPrimaryKey(ADD PRIMARY KEY (keys)) runs against key columns anAddAttributecan add in the same batch. Widening a primary key to include a newly added column (list-partitioning by that column, for instance) generatedADD PRIMARY KEY (id, cell_id)beforecell_idexisted.Fix
Give each a
requires/1clause:AlterDeferrability{direction: :up}requirestable_columns_settled;AddPrimaryKeyrequirescolumn_readyfor each key column. Both requirements are vacuous when the object predates the batch (nothing in the batch provides the fact), and neither adds a cycle: each provides onlytable_structure_ready, whichAddAttributenever requires.This builds on the dependency-graph model from #798, and is a distinct case from the composite-primary-key work in #805, which addressed the
streamlinemerge and theDropForeignKey{direction: :down}phase split rather thanADD PRIMARY KEYover a newly added column.The class
The module doc now documents the whole class, so future
no_phaseoperations are held to it. The rest are already safe: they act on objects that predate the batch (RenameTable,MoveTableSchema,RemoveCustomIndex, and the other renames and drops), render nothing inup(the*Downhelpers), or are ordered by the consumers that require their facts.Tests
Reproducing tests for both fixes in
operation_deps_test.exs, at both therequires/provideslevel and thetoposort_operations/1level. Each fails without itsrequiresclause (verified by neutralizing the clause).mix ash_postgres.generate_migrations --checkis clean for both PG variants, so no checked-in migration reorders.