Repository navigation
Store postgres snapshots as deltas in format 9 and check migration branches for conflicts - #6503
dankochetov wants to merge 55 commits into
Conversation
# Conflicts: # drizzle-kit/tests/postgres/pg-columns.test.ts # drizzle-kit/tests/postgres/pg-enums.test.ts # drizzle-kit/tests/postgres/pg-tables.test.ts # drizzle-kit/tests/postgres/pull.test.ts # drizzle-orm/tests/sql-builder.test.ts # integration-tests/tests/bun/effect-sqlite-bun.test.ts # integration-tests/tests/cockroach/common.ts # integration-tests/tests/mssql/mssql.test.ts # integration-tests/tests/mysql/mysql-common-8.ts # integration-tests/tests/pg/common-cache.ts # integration-tests/tests/pg/common-pt1.ts # integration-tests/tests/pg/common-pt2.ts # integration-tests/tests/pg/common-rqb.ts # integration-tests/tests/sqlite/sqlite-common.ts
Postgres snapshots move to format 9. The first snapshot in a migrations folder is a base snapshot with the full state. Every later snapshot is a diff snapshot that holds only the deltas of its own migration: keyed creates, drops, alters, renames and relocates. Commands rebuild a state from the base and the deltas. A merge snapshot starts from the combined state of its parents, which every command computes the same way. - ddlDiff returns the deltas next to its statements, and `update` returns a change log that the deltas come from. - generate writes base and diff snapshots, lists every leaf of a merge with the newest folder first, and names a new folder after its newest parent when the local clock is behind. The folder prefix now uses the UTC year for every dialect, like the rest of the prefix. - check compares every pair of leaf branches statement by statement with the facet rules, so a change and its later reversal no longer hide a conflict. - The folder loader stops postgres commands on a missing parent, a missing base, an unreadable snapshot or a folder that sorts before its parent, and the message says how to fix it. - `drizzle-kit up` converts format 8 and older postgres folders to format 9 and writes no snapshot if any rebuilt state differs from the original. - The programmatic API returns format 9 from `generateDrizzleJson` and `up`, accepts older snapshots in `generateMigration`, and exports `reconstruct`. `up` returned format 8 before. - The commutativity matrix is committed as data with a contract test, and a PGlite test checks every matrix cell with the SQL that drizzle-kit emits. Privilege statements now need their grantee role. - ddlDiff finds a view that moves to another schema and changes its definition, and it drops a recreated view after its rename or move. - Property tests cover delta replay, histories with forks and merges, and up. - The mysql and sqlite commutativity tests are back. - The JSON contract, the skills and the utils readme describe format 9. The pull skill also corrects what `--init` does.
- `migrateToFoldersV3` compared the length of a file name instead of the number of matching SQL files, so any single prefix match threw a conflict error. It now moves the one matching file and throws only when several files match. - Postgres pull checks the migrations folder before it connects or writes a file, so a broken folder leaves no schema.ts or relations.ts behind. - MCP.md, SDK.md and the MCP tool description say what `pull --init` does: it records the pulled migration as applied in the migrations table and does not run its SQL.
Postgres generate no longer writes a merge over conflicting branches. `drizzle-kit generate --fix-conflicts` deletes the migrations that hold the conflicting statements that check reports, with all their children, and checks again until no conflict is left. It then generates one migration from the rest of the history to the schema. - On a conflict, check, generate and migrate explain the fix: the folders that it deletes, the custom migrations among them, and that every database that ran a deleted migration must be reset. In interactive mode, generate offers to apply the fix. The JSON error lists the folders in `fix`, and the generate result lists `deleted_migrations`. - The fix deletes nothing while generate still needs hints, and nothing in explain mode. - Postgres rejects `--ignore-conflicts`, and the other dialects reject `--fix-conflicts`. This is a breaking change for postgres users of `--ignore-conflicts`. mysql, sqlite and the other dialects keep `--ignore-conflicts` as it was. - The MCP generate tool takes `fixConflicts` and signals the deletion in `_meta`. - A merge that would replay a checkpoint stops with an error. The replay added the rows of a checkpoint as creates, which brought back rows that the first parent had dropped. - The conflict report names the snapshot of each conflicting statement. - `upToV9` moves to `src/dialects/postgres/versions-v9.ts`. - The folder flow harness and the fuzz tests resolve conflicts with the fix, and the docs and skills describe it.
Deltas must describe the same change as the SQL of their migration, because check verifies that the SQL of two branches does not conflict. - `up` no longer puts back the name of a primary key that has no explicit name and has the Postgres default name `<table>_pkey`. Postgres keeps that name when a table is renamed, and drizzle-kit 0.x wrote no SQL for the key, but its files, converted to format 8, name the key after the new table. The state now keeps the name that the database has, so a later change of the key migrates. `up` ignores such key names when it compares a rebuilt state with the old file. - Check derives statements without keeping generated names. The drop and create deltas that `up` writes for SQL from drizzle-kit 0.x are now a drop and a create statement. So check finds the conflict when two open branches rename the two ends of a foreign key.
- The MCP server gets a `fix_conflicts` tool with `destructiveHint: true`. The `generate` tool loses `fixConflicts` and its `_meta` signal, so the static annotation of each tool holds for every call. - `checkSnapshots` returns a typed report and fix plan, and only `checkHandler` throws the conflict error. `planConflictFix` reuses the first report, so one `generate --fix-conflicts` runs detection twice instead of four times. generate no longer catches the error to read the plan. - Postgres `prepareSnapshot` computes the leaves from the history that it loads and no longer takes the check result. - The DDL records deltas only after `recordDeltas`, so the shared `update` tracks no changes for the other dialects. This removes `ChangeLogEntry` and the `addToLog` wrappers in postgres ddlDiff. - `AbstractCommutativity` is split into `FacetCommutativity` for postgres and `ConflictListCommutativity` for mysql and sqlite. The defaults that threw, `getCommutativityModel` and the postgres overrides that nothing called are removed. `getReasonsFromStatements` loses a parameter that had no effect. - The fuzz harness keeps the rename-hint fallback only for renames whose entity is missing from the state that generate starts from. A fuzz property calls `snapshotStatements` directly instead of a spy, and the `up` property compares default primary key names as `up` does. - Test helpers lose duplicated code, and comments match the code. - JSON_CONTRACT.md, MCP.md, OUTPUT_MODES.md, README.md and the skills describe the `up` folder layout, the SQL lookup, the scope of `malformed`, `deleted_migrations`, the postgres-only clock fallback and the conflict prompt as the code does.
- `generate` rejects `--explain` together with `--custom` before it loads the config. A custom migration has no SQL to explain, and every dialect wrote the file anyway. The error is `unsupported_command_dialect_error` with the meta `option: 'explain'` and `conflictsWith: 'custom'`. - `generate --explain --fix-conflicts` shows the migrations that the fix would delete: the fix text in text mode, and `fix` in the JSON `ok` and `no_changes` results. When the rest of the history already matches the schema, the text says that the fix writes no migration. - tsconfig.json excludes `tests/**/tmp`, so tsc does not fail on temp files that a running test deletes. - The MCP stdio smoke test fails with a clear message when the local `dist` build is older than `src`. - Two check-handler tests assert the leaves and conflicts that postgres reads. - OUTPUT_MODES.md names the prompts that hints do not answer, SDK.md documents `fixConflicts` and `ignoreConflicts`, and the umbrella skill names the MCP `fix_conflicts` tool.
- The check header says "Conflicting migrations detected", and the `--ignore-conflicts` help text says "conflict checks". - The fix text says that drizzle-kit deletes the conflicting migration branches, gives the number of migrations in total, and says that it generates one new migration from the schema. - The fix text drops the sentence about the migrator skipping a deleted migration. - The `drizzle-kit generate --fix-conflicts` command is on its own line in the fix text and in the errors that suggest it. - The docs, the migrations skill and the MCP tool description use the same wording.
The function that builds the claims read every statement through a cast to `any`, so the compiler did not see which fields a rule reads. Each case now reads the statement through its narrowed type, and the helpers take typed arguments. The rules give the same claims as before.
…s SDK A postgres migrations folder now holds base and diff snapshots only. A snapshot with the type `checkpoint` is malformed, and so is a base snapshot that lists a parent. The merge rule that refused a checkpoint is gone with the type. `generateDrizzleJson`, `up` and `reconstruct` return a schema state: a full state with an id and parents, and no `type` field. It is not a snapshot, and a migrations folder never holds one. `generateMigration` reads only the rows of its arguments, so it takes schema states and states in formats 5 to 8 in either position. The check fixtures build their snapshots from a real diff, because a full state with a parent is no longer valid.
…history `up` now converts every snapshot of an older postgres folder in memory, checks the result, and only then moves and writes files. A failure leaves every file where it was. The move out of a journal folder is split into a plan and an apply step for this. `up` refuses a folder whose history it cannot convert, with the new error kind `history`: a parent is missing, or a journal entry has no SQL file. The message names the migration and the fix, and links to the troubleshooting page. A folder that sorts before its parent now gives `check_error` with the kind `order`. The message is the same as before. `up` no longer validates a folder that holds only format 9 snapshots. `check` does that.
`rename_constraint` now holds the constraint kind in `entityType`. A primary key rename claims the key of its table, so it conflicts with an add, a drop and a change of that key on another branch. Before, check passed such a pair, and the migration failed in the database. A unique, check or foreign key rename claims only names of its own kind. The statement matrix has one row for each constraint kind, and a test checks that every rename that the diff writes has a matrix row with claims on the same kinds of nodes.
`create_table` now reads the enum types of its columns, so a new table with a column of an enum type conflicts with a rename, a move and a drop of that enum. Before, check passed such a pair, and the SQL failed in one order. `recreate_enum` now writes the existence of the enum, and it reads the table and the name of every column that it converts. Its SQL drops and creates the type and alters each of these columns. The statement kind `alter_type_drop_value` is deleted. The diff never wrote it, and no SQL writer handled it.
`alter_column` had one claim for the whole column, so two alters of different properties conflicted, and `generate --fix-conflicts` deleted migrations whose SQL runs in either order. The statement now reads the name of the column and writes one node for each property that its SQL changes or needs. NOT NULL commutes with a change of the type, the default or the generation expression. A type change also claims the default, and an identity change also claims the default and NOT NULL, because postgres fails or gives another result for these pairs. `alter_column` reads the enum name only when it changes the type or the default, because only SET DATA TYPE and SET DEFAULT name the type. A privilege statement claims its table, its grantee and its privilege type, and a grant reads the grantor that GRANTED BY names. The tests that used NOT NULL against a type change as their conflict now use two type changes of one column.
…names Check missed three kinds of conflicts, and the SQL then failed in one order. A create, alter or recreate of a policy now reads its roles, except the built-in role names, so it conflicts with a rename or a drop of such a role. A new or recreated foreign key now reads the unique key on the columns that it references. A primary key, a unique constraint and a unique index provide that key. The engine has a new claim list, `provides`, for this: it conflicts with a statement that needs the node, and not with another statement that provides it, so a primary key and a unique constraint on the same columns still commute. Names are now also claimed in the namespaces that postgres uses: relations (tables, views, sequences, indexes) and types (tables, views, enums) in a schema, and constraints in a table. A primary key or a unique constraint also takes the relation name of its index.
Every format 9 snapshot now has a required `statements` field with the statements of its own migration, in the order of the SQL. `generate` writes them for base and diff snapshots, `pull` writes the create statements of the state that it read, and `up` computes them for every snapshot that it converts. A custom migration stores an empty list. A snapshot without the field, or with a statement of an unknown kind, is malformed. `check` now takes its conflicts only from the stored statements. It no longer rebuilds the history and runs the diff for each snapshot, so its result for an old migration does not change with a newer drizzle-kit. Two independent first migrations are compared through the statements of their base snapshots. In this step the stored form is the full statement as the diff builds it. The diff helper of the postgres tests now asserts that the SQL from the stored statements equals the SQL of the diff.
…nerate` The code that rebuilds a state from snapshots is part of the format. A change to it could give another state for an existing folder with no error. Two guards now catch this. Every format 9 snapshot stores a required `stateHash`: the SHA-256 hash of the state after the snapshot. The hash reads a canonical form: rows in the order of their keys, fields in the order of their names, both by code unit, and no null fields. `generate`, `pull` and `up` write it. A rebuild compares it for the requested snapshot and for the first parent of every merge on the way. `check` rebuilds every leaf once for this comparison and reports a mismatch as `check_error` with the kind `state_hash`. Its conflicts still come only from the stored statements. Before `generate` writes a diff snapshot, it applies the new deltas to the starting state and compares the result with the state that the diff used. When they differ, it writes nothing.
A rename changes other rows too, such as the columns of a renamed table or the column list of a key after a column rename. A snapshot stored each changed row as its own keyed delta, so a merge of two branches could lose such a change: the delta of one branch named a row that the other branch had renamed, and the merge replay skipped it. The combined state then kept an old name. A rename now stores instructions. An instruction selects the rows of one entity type by field values and writes the new name into them. The three ops are `update`, `replace_in_list` and `update_list_items`. `generate` derives them from the reference rules: one for every rule that points at the renamed entity type, and for every rule that points at a type whose keys change as a result, whether a row matches or not. The replay runs each instruction on the rows that the state has at that moment and reads no reference rules. While the diff records deltas, it now changes its starting state only through the recorded deltas, so the diff continues with the state that a replay gives. The relocate op is removed. A rename or an instruction that moves a row onto a taken key throws an error that names both rows. `up` writes instructions for converted folders and refuses an old merge whose branches gave one name to two entities.
A snapshot stored each statement as the diff builds it, with fields that only the diff needs. A stored statement now holds what the SQL writer and the claim rule of its kind read, and nothing else. Every statement kind has one function that makes its stored form. It sits next to the SQL writer of the kind, and the SQL is written from the stored form only. The claim rules are typed on the stored form too, so a writer or a rule that reads a field which is not stored does not compile. A schema for each stored form gives its type and its strict validator: a missing field or an unknown field makes the snapshot malformed, and a row inside a statement is checked like a row of a snapshot. This step covers `create_table`, `drop_table`, `rename_table`, `move_table`, `add_column`, `drop_column`, `rename_column`, `alter_column` and `recreate_column`. The other kinds keep the full statement for now. Three statement interfaces that nothing used are removed.
The stored forms of `create_index`, `drop_index`, `rename_index`, `recreate_index`, `add_pk`, `drop_pk`, `alter_pk`, `create_fk`, `drop_fk`, `recreate_fk`, `add_unique`, `drop_unique`, `alter_unique`, `add_check`, `drop_check`, `alter_check` and `rename_constraint` now hold what their SQL writer and their claim rule read, and nothing else. A statement that creates an index or a constraint stores its full row. A drop stores its names and columns, and a dropped index also stores what decides whether it gave a unique key. A statement that replaces an index or a key stores the old columns next to the new row.
… form A snapshot now stores, for each of these 19 statement kinds, only the fields that the SQL writer and the claim rule of the kind read. Each kind has a strict schema, so a missing field or an unknown field makes the snapshot malformed. An `alter_enum` statement stores the added values with the value that each one goes in front of. A `recreate_enum` statement stores the full enum and, for each column of the type, the names, the type and both defaults.
A snapshot now stores, for each of these 13 statement kinds, only the fields that the SQL writer and the claim rule of the kind read. Each kind has a strict schema, so a missing field or an unknown field makes the snapshot malformed. A `revoke_privilege` statement stores no grantor, because REVOKE does not name it. An `alter_policy` statement stores the names, the roles and both expressions of the policy. Every statement kind now has a stored form of its own, so the function that stored a statement as the diff builds it is removed.
Every statement kind has a strict schema now, so the fallbacks are removed. A statement kind without a schema does not compile. The validator checks every stored statement against the schema of its kind, so a statement with an unknown kind, a missing field or an unknown field makes the snapshot malformed. The JSON contract says that a stored statement holds the data of its SQL statement, and that the SQL of a migration can be rebuilt from the statements of its snapshot.
Each row of the table is a base schema and two or three branches, where at least one branch renames or moves an entity. The test runs every row with each branch as the first parent of the merge, through real commands on a temporary folder and a PGlite database. For branches whose SQL runs in either order, the test asserts that `check` passes, that the merge migration holds only its own change, and that the database equals the schema after `migrate`. For the other branches, it asserts that `check` reports a conflict. The table holds 52 rows of the first kind and 15 rows of the second kind. The rename merge cases of the merge flow test move into it.
The replay is part of the snapshot format: a change to it can give another state for a folder that already exists. It lived in code that every dialect and `generate` share. `replay-v9.ts` now holds the delta types, the code that runs keyed deltas and instructions, the rebuild of a state from a history and the merge replay. The module imports no other module. It gets the rows and the key functions as data, and the caller gives it the entity definitions and the state hash check. A test fails if the module gets an import. The engine applies the deltas that it records through the same code. The behavior does not change. The developer notes say that a change to the replay needs a new snapshot format with its own module.
No reference rule pointed from the roles of a policy, or from the grantee and the grantor of a privilege, to roles. So after a role rename, the migration also revoked the privilege from the old role name, which no longer exists at that point, granted it again and altered the policy. Three rules now point at roles. A role rename changes those rows in the state, the migration holds only `ALTER ROLE ... RENAME TO`, and the snapshot stores one instruction for each rule. A drop of a role does not drop the rows that name it. The roles of a policy are a sorted list, and a rename can leave them in another order. The diff no longer writes `ALTER POLICY` for a change of the order only.
The key of an index held its table. After a table rename on one branch, a keyed index delta of another branch named the old table, and the merge replay skipped it. `check` hid this with a conflict that postgres does not have: `DROP INDEX` and `ALTER INDEX` do not name the table. An index is now keyed by schema and name, as postgres keeps index names unique in a schema. A table rename changes the `table` field of its indexes and leaves their keys unchanged. The claim rules of `drop_index` and `rename_index` no longer read the name of the table. They read that the table exists in its schema, which a drop of the table and a move to another schema both end. The merge table has rows for a table rename and for a table move against a drop and a rename of an index. The two known exceptions of the ground truth test are removed.
A claim says which object a statement changes, and not what it changes the object to. So two branches that make the same change were reported as a conflict, also when the SQL can run a second time with the same result. `check` now treats two equal stored statements on two branches as one change when the kind is repeatable: the alter and recreate kinds, RLS, and the privilege kinds. Two different changes of one object still conflict, and so do two equal statements of any other kind. Some statements of a repeatable kind hold SQL that fails on the second run, and they stay conflicts: an `alter_column` that adds or drops an identity, drops a generated expression or changes a serial column, a `recreate_index` without its drop, and an `alter_pk` without its drop. The ground truth test confirms the column cases on PGlite. The 23 known exceptions of the ground truth test for the same change on both branches are removed. The merge table has a row where both branches set NOT NULL on one column. The policy role comparison in the diff no longer uses `toSorted`.
… renames by table in postgres A migration that moved a table to another schema wrote the move after the statements that already named the table in its new schema: a policy drop, a foreign key drop and the row level security statement. That SQL failed. The move now runs right after the table renames. A migration that dropped a role wrote `DROP ROLE` before the policy changes that take the role out of a policy, and postgres refuses such a drop. A role drop now runs after the policy statements. A rename decision for a check, a foreign key, a policy, a primary key or a unique constraint selected rows of the old state by schema and name only. So it also renamed an object with the same name on another table, and a change of that other object got no SQL while the snapshot recorded it. The rename now selects by the table too.
`check` validated only the migrations folder. After a git merge of two branches, the database can differ from the merged schema until `generate` writes a merge migration, and nothing said so. For postgresql, `check` now compares the schema with the folder when it has a schema path, from the config file or from the new `--schema` option. The schema equals the folder when `generate` would write no SQL. With one leaf, a difference means that the schema has a change that no migration holds. With several leaves, it means that a merge migration is needed. Each case is a warning: text mode prints it in place of the success line, the `ok` envelope has it in `warnings`, and the exit code stays 0. A schema that cannot be compared is a warning too: no file matches the path, the schema fails to load, or it has errors. The comparison writes nothing.
…k` warnings a kind
A table move takes the indexes and constraints of the table into the
new schema. When the same migration drops a table that still holds one
of those names there, the move must run after the drop. The table
drops run before the moves again, and only the statements about a
moved table run after them.
A warning of `check` is now `{ kind, message }`. The kinds
`schema_changed` and `merge_needed` mean that `generate` would
write a migration, and `not_compared` means that the schema could not
be loaded or compared. The messages no longer say that the schema is
ahead of the migrations, because the difference can go in either
direction, and they name `generate --explain` first.
An exception anywhere in the comparison is a `not_compared` warning
now. Before, only a failed schema load was one, and another exception
failed `check` on a valid folder.
generate, export and check share one conversion of a schema into a
state.
…and show `check` warnings in the GitHub action Postgres diff: - A sequence or a view that moves to another schema and gets a new name in one rename decision gets a move and a rename statement. Before, the rename of a sequence was lost, and the move of a view named a view that did not exist. - A view that moves and changes between plain and materialized is moved with its old kind. - A column whose enum is created again in another schema gets `SET DATA TYPE`. A change of only the schema of the type counts when both states name a schema, because a folder of an older format can store none. - The recreate of a generated column creates the foreign keys from that column again. - Renames of unique constraints, and renames of primary keys, run in an order that frees a name before another rename takes it. - A table that moves and gets a new name moves first when the old schema has a table with the new name and the old name is free in the new schema. Cockroach diff: - `DROP ROLE` runs after the policy statements. - Table moves run directly after table renames, so a statement about a moved or renamed table finds the table. - Renames of checks, primary keys, foreign keys and policies select their row by table too. `check` prepares the migrations folder one time for the conflict check and the schema comparison. The GitHub action lists the warnings of `check` in its comment.
…es and cockroach diffs, and two `check` verdicts Postgres diff: - A sequence, a view or a table that moves to another schema and gets a new name takes the other order of its two statements when a relation holds the name on the way. - A rename of an index, a primary key or a unique constraint runs after the rename of another kind that frees its name. - The recreate of a generated column handles a new, a renamed and a shared foreign key, and creates an index or a unique constraint of the column one time. - A change between two serial types writes the integer type and keeps the sequence. - A type change of a generated column has no USING clause, and the expression is dropped before a type change. - The recreate of an enum finds its columns with the schema file when the old state stores no type schema. Array dimensions, postgres and cockroach: - A change of a column between a scalar type and an array type writes SET DATA TYPE with a plain cast. Most types have no such cast, so the user writes the conversion into the USING clause. - In postgres, generate writes the same statement for a change between two array dimension counts, and push writes nothing for it. - The schema reader counts the brackets of a custom type, and the postgres database reader reports a dimension only for a type whose text ends with brackets. Postgres `check`: - A removed enum value conflicts with a type change of a column of that enum. - The same change between two serial types on two branches is one change. The same change between a scalar type and an array type is reported. Cockroach diff: - The postgres fixes for sequences, views, enum type schemas and generated columns are ported. - A check rename decision writes RENAME CONSTRAINT, after the statement that frees its new name. - An index rename or drop names its table when another table of the schema holds the same index name. - A view that reads a renamed or moved table is dropped before the table statements and created after them. - The data loss queries of push name a moved or renamed table as the database has it.
…ine, and remove the state hash Snapshot format 9 changes its stored shape before its first release, so the format number stays 9. Snapshots: - A diff snapshot stores one delta for each changed entity, and a rename is one `rename` delta. The entities that reference the renamed entity get no delta. - The operations `update`, `replace_in_list` and `update_list_items` leave the delta type and the validator, so a snapshot that holds one of them is malformed. - No snapshot stores `stateHash`. The validator ignores the field in a snapshot that still holds it. Reconstruct: - The code that applies deltas is in `src/dialects/postgres/reconstruct.ts`, and it calls the DDL engine for each delta. A rename goes through `update`, which follows the edges and writes the new name into the referencing entities. - One set of rules covers a delta that does not fit the state, in a merge and outside a merge. A drop, an alter or a rename of a missing entity is skipped. A create onto a taken key does nothing when the entity there is equal, and it throws when a field differs. A rename that moves an entity onto a taken key throws. DDL engine: - While the postgres diff records deltas, `update` follows the edges on the old state and records one delta for each changed entity. - `update` copies the entities only when a key changes. - `update` and `delete` accept the key of one entity in `where`. Commands: - `check` and `migrate` reconstruct no state. `generate` reconstructs every leaf before it writes. A leaf or a combined state that does not reconstruct stops it with a `check_error` of kind `malformed`, and `check` with a schema path reports it as a warning. - A migrations folder with a second base snapshot is refused. The error carries both paths in `snapshot` and `baseSnapshot`. `up` refuses an older folder with two first migrations. - `up` refuses an old merge whose branches created different entities under one key. - The error kind `state_hash` is removed, and `generate` no longer applies its new deltas to the parent state before it writes. Tests: - The committed format 9 history is deleted. - New folder flow tests cover a rename of each entity type, the rules for a delta that does not fit, and a merge in which one branch stores a delta with no statement. - Eight date-gated postgres tests move their date from 2026-10-03 to 2026-11-08.
…ix to the API `reconstruct` error Skills: - The eight installable skills become one `drizzle` skill. The seven per-command bodies are topic files beside `SKILL.md`. They have no frontmatter, and they reference each other with links inside the folder. - `SKILL.md` gets a description that covers every topic, and a routing table over the topic files. Both also carry the triggers of the migrations folder: migrations from several git branches, a broken migrations folder, branch conflicts and `drizzle-kit up`. - `drizzle-kit skills` removes the seven superseded skills after a successful add. It removes them in project scope only and without a prompt. It prints nothing when no superseded skill is installed, and it prints the manual command when the removal fails. - `metadata.revision` stays 14. API: - `reconstruct` from `drizzle-kit/api-postgres` reports a state that does not reconstruct with the same message as `generate`. The message names the migration and the fix, and one function builds it for both. Tests: - `skills-bundle.test.ts` covers the shape of the bundle, the links of the routing table, and the arguments and the scope of the removal.
…and the fix of `generate` `reconstruct` from `drizzle-kit/api-postgres` added the folder name and the fix only to a key collision. A base snapshot that lists one entity twice gave an error with no folder name and no fix. The API now wraps every error of a snapshot that the folder holds, as `generate` does for a leaf. The error for an id that the folder does not hold keeps its own text, which already says what to do.
…t lists of `check`, and shrink its tests Source: - `src/dialects/postgres/folder.ts` is the one loaded model of a postgres migrations folder. It reads each `snapshot.json` one time, runs the history rules and the status check, and gives the snapshots by id, their files and the ids of the leaves. `generate`, `check`, `pull` and the API `reconstruct` use it. One `generate` or `check` run now parses each file one time, where it parsed each file three times. - `prepareOutFolder` only lists the snapshot files, as on `rc5`. A command of another dialect no longer runs the postgres history rules on a folder that holds postgres snapshots. - The API `reconstruct` follows the rule of `generate` for a folder in which two snapshot files hold one id: an error names the last folder with that id, and the leaf list counts the parents of the last file of each id. - Postgres `check` no longer holds the conflict lists of the older model, which it did not read. One function gives the target of a statement for the report. The report text and the JSON output are unchanged. - The claim rule of `alter_column` reads the enum name only when the default, the type, the type schema or the dimensions change. A `DROP EXPRESSION` on a generated column of an enum type no longer conflicts with a rename or a move of that enum. - The MCP description of `up` says that postgres converts and checks every snapshot before it moves or writes a file. Docs: - The hints topic file and `HINTS.md` no longer list the `privilege` hint kind, which the code rejects. - The developer note describes the folder module and the new layout of the matrix file. Tests: - `commutativity-statements.ts` is the one catalog of statement fixtures, and the invariants suite covers all 74 rows. - `commutativity-matrix.json` holds only `types`, `matrix` and `impossible`, with one string for each row. It has 230 lines, down from 15,492, and every cell is unchanged. - `forkFlow` in `folder-flow.ts` sets up a fork of two branches for the flow tests. Five tests of `check-branches.test.ts` are rows of the merge pairs table now, so each of them runs with each branch as the first parent. - `cli-skills.test.ts` replaces `skills-bundle.test.ts` and tests only the arguments of the removal of the superseded skills. The tests of the skill markdown are removed. - Every test date gate moves to 2026-11-08.
…statement names, and simplify `up` and `check` Diff: - The diffs of postgres, mysql and sqlite apply a table rename and a column rename to the new state again. Drizzle Studio builds the new state from the form of one table and copies the other tables from the old state, so its new state can still hold old names in constraints, indexes, policies and the foreign keys of other tables. Without the update, the diff wrote statements that name a table or a column that the rename removed. A new state from schema files never holds the old name, so `generate` and `push` write the same SQL as before. - The postgres diff returns the stored statements, and its three callers no longer build them a second time. - `renameResolvers` accepts only an entity type of the postgres DDL. - Cockroach writes a view that it must drop and create again as `drop_view` with a `cause` plus `create_view`, as postgres does. The SQL is unchanged. `--explain` shows such a view one time, and the statement `recreate_view` is removed. Stored statements of postgres snapshot format 9: - Four stored fields get new names: `indexNames` of `move_table`, `becomesSerial` of `alter_column`, `from` and `to` in the column defaults of `recreate_enum`, and `shouldDrop` of `alter_pk`, which holds the opposite value of the field `deleted` that it replaces. - A stored `move_enum` or `move_sequence` requires a schema name. `check`: - The claim rule of `recreate_column` reads the enum type of its column, so a new expression of a generated enum column conflicts with a rename, a move or a drop of that enum. - The node kinds of the claim rules are a union type, the privilege types come from the DDL definition, the statements of a branch are read without a promise cache, and postgres has no dry snapshot. - The warnings about a schema that differs from the migrations move to `src/cli/commands/check-postgres.ts`. `up` and the API: - `up` parses each file one time. For a folder with a journal, its errors name the journal entries, because the migration folders do not exist yet, and a wrong order has its own message with the fix. - The refusal of an old merge that cannot be converted says which migrations to delete. - Messages and docs say "reconstruct" where they said "rebuild". Engine: - `splitDeltaKey` throws for a key that has no entity type. - Ten short private names in `dialect.ts` are full words, `hasDiff` uses `isChangePair`, and the comments that named removed code are corrected. Skills command and MCP: - The removal of the superseded skills uses `spawnSync`, one constant for its arguments, and the words "remove" and "superseded". - The MCP tools `generate` and `fix_conflicts` share one handler. Docs: - The changelog names the removed postgres flag `--ignore-conflicts` and the single `drizzle` skill with the removal of the older skills. Tests: - `tests/other/studio-diff.test.ts` calls the Studio entry points of the three dialects with new states that hold old names. - The matrix has three new rows, for `recreate_column` of an enum column and for a foreign key against a dropped unique constraint. The cells of the 74 older rows are unchanged. - `flow.generate` fails when it writes no migration, and `flow.tryGenerate` returns the raw result. Helpers build the error envelope of `up` and a format 8 folder. - The tests of the state hash, the instruction operations and the checkpoints are deleted, together with a repeated rename test, the squash test and `commutativity-renames.pglite.test.ts`.
…r and remove leftovers of earlier snapshot designs The DDL engine recorded every `update` call so that the postgres diff could read rename deltas back. The diff now makes each rename or move decision through `renameEntity`, which applies the rename to the old state and returns its delta. The recorder is gone from the engine. Removed, because nothing reads it any more: - `renames` in the result of the postgres `ddlDiff` and `ddlDiffDry`. The fuzz test of `up` builds the format 8 rename strings itself. - `renames.ts`. Its function is `resolversOfRenameDeltas` in `versions-v9.ts`, next to `format8RenameResolver`. - `listSnapshots`. `prepareOutFolder` lists the snapshots for the postgres folder and for `up`. - `isPK` in the stored `add_column` statement. The primary key of a new column has its own statement. - The copy of the previous state in `generate`, the `prevIds` parameter and the `async` of `prepareSnapshot`, and the list of snapshot paths. - The second loop for an enum that moves to another schema, in postgres and cockroach. One loop applies the rename and the move. The test helpers read `snapshotStatements` from the diff result.
Conflicts: - The date gates of skipped tests take the values of `rc5`. - `push-cockroach.ts` takes the `identifier` closure of `rc5` for the data loss checks. It replaces the `tableInDatabase` helper of this branch, which did the same lookup. - `mysql/diff.ts` and `sqlite/diff.ts` keep the rename block of this branch. It already builds a new object for a renamed index column, which is the only change of `rc5` in that block. - `ext/api-postgres.ts` has the `hints` option and `runWithApiHints` of `rc5`, with the snapshot types, `up` and `reconstruct` of this branch. The `ddlDiff` of this branch has no privilege resolver. - The changelog has the lines of both branches. - The skills revision stays 14. - `cockroach/tables.test.ts` and `pg-tables.test.ts` keep the tests of both branches. Four CockroachDB tests of this branch expect the warning text that `rc5` uses now.
| const result = snapshots.reduce( | ||
| (accum, it) => { | ||
| const raw = JSON.parse(readFileSync(`./${it}`).toString()); | ||
| const raw = JSON.parse(readFileSync(it).toString()); |
There was a problem hiding this comment.
The old line read readFileSync(`./${it}`). That breaks for an absolute migrations folder.
prepareOutFolder builds each path with join(out, subdir, 'snapshot.json'). When out is absolute, the old line made .//abs/drizzle/0001/snapshot.json, and Node resolves that against the working directory: <cwd>/abs/drizzle/0001/snapshot.json. The file is then not found. Without the ./ prefix, a relative path and an absolute path both work.
The change came with 25ff4f9e2 ("Store postgres snapshots as deltas in format 9"). At that commit the postgres up read its folder through validateWithReport, and the new up-format9.test.ts runs up on absolute temporary folders. The postgres up no longer calls this function, but the up of mysql, sqlite, mssql and singlestore still does, so they keep the fix for an absolute out.
…branches, and remove code for unreported cases The `check` comparison of the schema with the migrations, the CockroachDB diff fixes and the postgres diff fixes that format 9 does not need move to their own pull requests on top of this branch. - Remove `check-postgres.ts`, the `--schema` option of `check`, its `warnings` and their docs, tests and GitHub action output. - Restore the CockroachDB diff, convertor, statements and their tests to rc5. Restore the postgres statement order around table moves, the drop of recreated views, `DROP ROLE`, `DROP EXPRESSION`, the recreate of generated columns and the dimensions of custom types to rc5. Keep the array, enum schema, serial and sequence move fixes, because the merge tests of format 9 need them. - Remove `inOtherOrder` and the ordering of renames that take a freed name, with their tests. - Restore `lowestCommonAncestor`, the member order of `ConflictListCommutativity` and the merge block of the commutativity engine. - Remove guards and tests for states that no caller produces: repeated history checks, a delta key without an entity type, an unknown delta operation, a cycle of first parents, the interactive prompt of the conflict fix and several parents in `generateDrizzleJson`. - Delete tests that repeat other tests or cannot fail, and the committed page of the commutativity matrix. - Store `toSerial` and `default` of `alter_column` with their statement names, and name the indexes of a moved table `indexNames` in both forms. - Add `rename` and `clone` to every DDL object and `parseEntityKey` to the engine, give `Field` and `Store` named types, let `validate` return a type predicate so that the snapshot validators need no explicit type arguments, and rename `toJsonSnapshot` to `toJsonBaseSnapshot`. - Reject an entity type or a field that only an inherited property name matches, such as `constructor` or `__proto__`. - Rewrite the comments of the branch so that each one starts with the purpose of its code, and fix wrong or unclear comments and docs.
| return composed; | ||
| } | ||
|
|
||
| export abstract class AbstractCommutativity< |
There was a problem hiding this comment.
Postgres snapshots store the statements of their migration, so postgres check needs no conflict lists, no diffSnapshots and no dry snapshot. With one class and the getCommutativityModel switch of 55db3bc, postgres still had to implement those abstract members, and nothing called them. AbstractCommutativity now holds what both models use: the footprint match, branchOf and buildSnapshotGraph. FacetCommutativity adds the facet footprints and detectBranchConflicts. ConflictListCommutativity holds the rc5 code, so mysql and sqlite change only the name of their base class and give the same results.
|
|
||
| // Tells whether two statements of two branches are one change, so that they do not conflict. A footprint | ||
| // names only the object that a statement changes, so two equal statements always match. | ||
| private isSameChange(a: TStatement, b: TStatement): boolean { |
There was a problem hiding this comment.
A footprint names the object that a statement changes and not the new value, so two equal statements on two branches always conflict. When the SQL gives the same result on a second run, for example SET NOT NULL on one column, this conflict is a false positive, and generate --fix-conflicts deletes a migration without need. isRepeatable is false in the base class, so mysql and sqlite keep their results. findFootprintIntersections tries the last statement of a hash first, which is the statement that the map of rc5 kept. The diagonal cells of rules agree with postgres on every possible cell run each catalog statement twice on PGlite, and the same change on two branches, against a real database covers the alter_column cases whose SQL fails on a second run.
|
|
||
| // Returns the target that the report and the JSON output of `check` show for a statement. A constraint, a | ||
| // policy and a privilege show their table. A role and a renamed index have the kind 'table' with their own name. | ||
| function targetOf(statement: SnapshotStatement): ConflictTarget { |
There was a problem hiding this comment.
In 55db3bc the conflict lists served only the report text, because matching used only buildFacetPaths. The lists also read JsonStatement, and check now gets the stored form SnapshotStatement. targetOf and describe give the same text and the same JSON target as check on rc5, so a role and a renamed index keep the kind 'table'. check-handler.test.ts expects drop_column: text on groups table, and check-branches.test.ts expects rename_constraint on users table.
| // kinds with one name conflict. In a schema, `rel` holds tables, views, sequences and indexes, and `type` | ||
| // holds tables, views and enums. In a table, `con` holds constraints. A primary key or a unique constraint | ||
| // also has an index with its name. | ||
| const sharedNames = (k: string, p: string[]): string[] => { |
There was a problem hiding this comment.
An index tags on one branch and a table tags on the other branch fail in one order, because Postgres keeps both names in the relation namespace of the schema. In 55db3bc each kind had its own name node, so check passed this pair. The test an index and a table that take one name conflict in check-branches.test.ts runs that migration and gets the error on CREATE TABLE. A primary key and a unique constraint also give their name to an index, so pkNames and create_table write that name too.
| // Returns what DROP INDEX and ALTER INDEX read of the table of the index. Their SQL names the index only by | ||
| // its schema and name, so a rename of the table does not break them. A drop or a move of the table also | ||
| // drops or moves the index, so they read that the table exists in its schema. | ||
| const indexHome = (schema: string, table: string) => [eref('tbl', schema, table), nref('sch', schema)]; |
There was a problem hiding this comment.
In 55db3bc, drop_index and rename_index read the table name with underTable, so a table rename on another branch conflicted with them, although both orders run. indexHome reads only that the table exists in its schema. A table move takes the indexes to the other schema, so move_table now writes that existence and the index names in both schemas. In merge-pairs.test.ts, a table rename against a drop of an index of that table merges, and a table move to another schema against a drop of an index of that table is reported.
| db?: Awaited<ReturnType<typeof preparePostgresDB>>, | ||
| ) => { | ||
| const entries = readEntries(out); | ||
| assertHistory(entries); |
There was a problem hiding this comment.
pull now checks the history of the folder before it connects, like generate, check and migrate, so a broken postgres folder stops it with the same check_error. pull reads only the number of snapshots and the newest path, so a broken history does not change what it writes. Without the check, it would still write schema.ts and relations.ts into a folder that the next generate refuses. The rule is in skills/drizzle/migrations.md, and two tests in folder-integrity.test.ts assert that pull throws.
|
|
||
| In a postgres folder that it converts, `up` also returns `check_error` for a `snapshot.json` that is not valid JSON (`kind: "malformed"`) and for a folder that sorts before one of its parents (`kind: "order"`). See [`check` integrity errors](#check-integrity-errors). In a folder with `meta/_journal.json`, the migration folders do not exist yet. The `malformed` message names the file under `meta/`. The `order` message and the messages of the conversion errors name the journal entries, for example `0001_posts`. The `order` error then means that the time in `when` of a journal entry is not at least one second after the time of its parent, and the fix is to correct `when` in `meta/_journal.json`. | ||
|
|
||
| The move out of `meta/_journal.json` can also fail. It returns `migration_snapshot_not_found_error` when the journal lists a migration whose `meta/<n>_snapshot.json` is missing. If `<tag>.sql` is missing, `up` moves the one SQL file whose name starts with the `<NNNN>_` prefix of the tag. It returns `migration_sql_files_conflict_error` (meta `prefix`) when several SQL files have that prefix. If no SQL file has that prefix, postgres returns the `history` error above. The other dialects skip the migration, and its snapshot is deleted with the `meta/` folder. |
There was a problem hiding this comment.
This sentence describes the move for mysql, sqlite, turso and singlestore, which this PR does not change. applyFoldersV3 gives an entry without an SQL file no folder, so its snapshot is deleted with meta/. Postgres refuses such an entry with a history error before it moves a file, because the next snapshot would lose its parent. The test up refuses a journal entry that has no SQL file and changes no file in up-format9.test.ts covers the postgres side.
| if (existsSync(sqlPath)) return { tag: entry.tag, folder, snapshotPath, sqlPath }; | ||
| // When `entry.tag` names no SQL file, for example after a rename by hand, the SQL file is found by | ||
| // `snapshotPrefix`. | ||
| const sqlFileNames = sqlFiles.filter((file) => file.startsWith(`${snapshotPrefix}_`) && file.endsWith('.sql')); |
There was a problem hiding this comment.
On rc5 the lookup by prefix could not succeed: sqlFileName?.length > 1 compared the length of a file name with 1, so a single match threw MigrationSqlFilesConflictCliError. After a hand rename of an SQL file, postgres up would report a conflict for one file instead of moving it. The lookup now matches <prefix>_*.sql and throws only for several matches. The fix reaches every dialect, because they all run planFoldersV3, and no test covers the prefix lookup.
| return lines.join('\n'); | ||
| }; | ||
|
|
||
| export const assertConflictOptions = ( |
There was a problem hiding this comment.
Postgres rejects --ignore-conflicts in generate, check and migrate, because a format 9 merge over conflicting branches can fail to reconstruct. When two branches create different entities under one key, the combined state throws, and every later generate on the folder stops, which a create of a different entity onto a taken key stops generate, and the error names the migration of that delta in delta-rules.test.ts shows. generate --fix-conflicts replaces the flag, and the changelog names this breaking change for postgres. postgresql rejects ignore-conflicts in cli-json.test.ts replaces the rc5 tests ignore-conflicts emits an ok envelope despite conflicts in cli-json.test.ts and ignoreConflicts keeps all open leaf ids for conflicting branches in check-handler.test.ts. The other dialects keep the flag, and ignoreConflicts keeps both leaf ids for conflicting branches in tests/sqlite/generate-flow.test.ts covers it for sqlite.
| key: (type: string, row: Row) => string; | ||
| // Renames the entity `from` and returns the delta of that change. The result is null when the state does | ||
| // not change: no field of `set` differs, the state does not hold `from`, or the new key is taken. | ||
| rename: (from: Row, set: Row) => Delta | null; |
There was a problem hiding this comment.
These are readability changes to the engine after 55db3bc. None of them changes behavior.
renameandcloneare methods of every DDL object.ddlDiffrecords each rename withddl1.rename(from, set), andupcopies a state withstart.clone().splitQualisparseEntityKey, because it splits the key of a delta into the entity type and the identity.compileEdgeuses full names (ListEdge,identityOf,source,elements), andreadListis inline.FieldandStorehave named types (EnumField,ObjectField,ObjectArrayField,EntityType,Identity) in place of trailing comments.validatereturns a type predicate, andarrayhas an overload that takes one. So the snapshot validators of all dialects need no explicit type arguments.- The postgres
toJsonSnapshotistoJsonBaseSnapshot, because it writes only the base snapshot.
… moves, renames, roles and generated columns Postgres: - Count the brackets of a custom type's SQL type as array dimensions. - Drop and create the foreign keys of a recreated generated column one time each, and create its indexes and unique constraints one time. - Rename a view in its old schema before it moves, and move it with its old kind. - Drop a recreated view after its rename or move. - Run the policy drops, foreign key drops and row level security statements of a moved table after `SET SCHEMA`. - Run `DROP ROLE` after the policy statements. - Run `DROP EXPRESSION` first and write no `USING` clause for a generated column. - Find the enum columns that a format 7 snapshot stores without a type schema when the enum is recreated, with the s06 tests and fixtures. CockroachDB: - Drop a view that reads a renamed or moved table before the table statement and create it after it. Replace `recreate_view` with a `drop_view` that has a `cause`, in the diff, in push and in `cockroachExplain`. - Write both statements for a view or a sequence that moves and gets a new name, and look up the old view of a moved view that is recreated. - Name the table in an index rename or drop when two tables hold the index name. - Write `RENAME CONSTRAINT` for a check rename. - Select the row of the renamed check, index, primary key, foreign key or policy by its table. - Change the column type when only the type schema or the array dimensions change, and count the brackets of a custom type's SQL type. - Run table renames and moves before the statements that name the table in its new place. - Create the foreign keys of a recreated generated column again. - Run `DROP ROLE` after the policy statements.
…rifies The helper files of the tests get a header that says what they provide and which test files use them. Where a file already had a comment that described the whole file, that comment is part of the header now.
…reating an add of a unique guarantee as a conflict `owns` and `destroys` behaved the same in the engine, so they are one list, `writes`. `needs` is `reads`, and `provides` is `sharedWrites`. The node for the uniqueness of a set of columns is `uniqueness`. Only a drop of a primary key, a unique constraint or a unique index writes it, because an add cannot break a foreign key that references the columns. So an add on one branch and a new foreign key on the other no longer conflict. A drop still conflicts with a new foreign key, also when another guarantee of the same columns remains, because the rules cannot count the guarantees.
| * | ||
| * Keys are opaque strings minted by the dialect; the engine only compares them. | ||
| */ | ||
| export type FacetPaths = { |
There was a problem hiding this comment.
The facet paths now say what the engine does with them. owns and destroys behaved the same: both put key#w into both lists. So they are one list, writes. needs is reads, and provides is sharedWrites.
sharedWrites is for a fact that several objects guarantee. Its only node is the uniqueness of a set of columns (e|uniqueness|schema.table.columns), which a new foreign key reads. Only a drop of a primary key, a unique constraint or a unique index writes it, because an add cannot break a foreign key. Two drops share the write, because each one alone leaves the uniqueness to the other guarantees.
The rules cannot count the guarantees. So a drop still conflicts with a new foreign key on the other branch when another guarantee of the same columns remains, although both orders run then. The curated cases in commutativity.facet.test.ts cover an add against a new foreign key (no conflict) and two drops (no conflict).
| // `ddl2` holds no column with the old name in this table, so every such name is stale. | ||
| const newNameOf = (column: string) => column === rename.from.name ? rename.to.name : column; | ||
| const inTable = { table: rename.to.table }; | ||
| ddl2.indexes.update({ |
There was a problem hiding this comment.
this is supposedly redundant, we need to check
cc: @RomanNabukhotnyi @dankochetov
| table: rename.from.name, | ||
| schema: rename.from.schema, | ||
| }, | ||
| // Applies the rename to the rows of `ddl2` that still name the old table. Drizzle Studio builds the new |
| // Applies the rename to the indexes and constraints of `ddl2` that still name the old column. The form | ||
| // of Drizzle Studio renames a column only in the column list. A new state from schema files has no such | ||
| // rows. | ||
| const newNameOf = (column: string) => column === rename.from.name ? rename.to.name : column; |
| // Defines how a snapshot stores one statement kind and how that kind becomes SQL. `store` picks the fields | ||
| // that the snapshot keeps. `convert` gets only these fields, so the SQL of a migration can be built again | ||
| // from its snapshot, and a field that `store` does not keep is a type error in `convert`. | ||
| export const convertor = <TType extends StatementType>( |
There was a problem hiding this comment.
I think we shall just have a:
const storedFor = (statement: JsonStatement) => {
// long if else or switch with exhaustive check
// return row type derived from ddl entry update type too with all fields required, so that whenever we change ddl entries - it's an exhaustive compile error here
}that way we will not overload json statement to sql convertor with anything related to storing metadata for commutativity
cc: @dankochetov
There was a problem hiding this comment.
@RomanNabukhotnyi please check if that's still relevant
Fix postgres and CockroachDB diff cases that write failing SQL around moves, renames, roles and generated columns
Postgres migrations get snapshot format 9 and a conflict check for migrations from two git branches. The work builds on the reworked DDL engine and the commutativity engine of #6046. This PR replaces #6046.
Snapshot format 9 (postgres only)
ddl.renamecall.reconstructbuilds the state of a migration from the base snapshot and the deltas. A merge snapshot stores its own deltas, relative to the combined state of its parents.upconverts a folder of format 8 or older to format 9.Conflicts between branches
checkcompares the stored statements of the migrations on two branches with the commutativity engine. It reports each pair of statements that does not commute.generate --fix-conflictsdeletes the conflicting migrations and their children, then generates one new migration. The MCP server has the same fix as thefix_conflictstool.Breaking change
--ignore-conflictsingenerate,migrateandcheck. Format 9 checks conflicts from the stored statements, so a conflict can no longer be ignored. Usegenerate --fix-conflicts, or merge the branches by hand.Diff fixes that format 9 needs
The merge tests of format 9 (
merge-pairs.test.ts) found postgres diff cases wheregeneratewrote no migration or wrote SQL that fails: a change of array dimensions, a column that switches to an enum with the same name in another schema, a change between two serial types, and a sequence that moves to another schema with a new name. These fixes stay in this PR. Review comments on the lines explain each one.Other changes
drizzleskill.Follow-up pull requests
These target this branch and move to rc5 after this PR merges:
checkand returnwarnings#6510:checkcompares the schema with the migrations and reports warnings.The rename-order cases that the new tests found are tracked in #6509.