Yes, let an AI agent write your database migrations. The agent is good at the part that is tedious: turning a model change into SQL, remembering the index, getting the syntax right for your engine. What it cannot do is decide what should happen to the data that is already there, so the review has to move from the SQL text to the change the file makes to the schema.

That is a different review from the one most teams run. A migration is usually approved the way code is approved: someone reads the diff, sees nothing strange, and CI is green. For application code that works well enough, because a wrong line can be reverted. A migration runs once against data that has no other copy, and the line that destroys a column is one of the shortest lines in the file.

Why a green migration can still lose data

The most common way it happens is a rename. The ORM or the agent compares the old model with the new one, sees a field that disappeared and a field that appeared, and has to guess whether they are the same field. Prisma guesses no. A user reported it in Prisma issue 4694 in November 2020: “After renaming a column in schema.prisma, Prisma Migrate executes the following steps: 1 - ALTER TABLE … DROP COLUMN 2 - ADD COLUMN … It loses the data.” The documented fix is still manual: create the migration with --create-only and edit the SQL before it runs.

Django asks instead of guessing. makemigrations prints “Was customer.email renamed to customer.email_address (a CharField)? [y/N]” and waits. The default is N, and in a non-interactive run the question is never shown: Django’s NonInteractiveMigrationQuestioner inherits a False answer to every rename question. An agent running makemigrations --noinput, or running it without a terminal attached, gets RemoveField and AddField, and the SQL behind them is a DROP COLUMN and an ADD COLUMN.

Nothing in that output fails. The syntax is valid, the model and the database agree afterwards, the tests that create a fresh database pass, and the new column is simply empty. On PostgreSQL the drop is also instant, because DROP COLUMN only marks the column as dropped in the catalog, so there is no slow statement to make anyone suspicious.

Undoing it is harder than it looks. In one public repository, a Django migration co-authored with Cursor removed six columns from clinics_clinic, including opening hours and therapist counts. The pull request that added it was then reverted, which deleted the migration file but not its effect: the columns were already gone from the deployed database, and the next deploy failed with column clinics_clinic.number_of_therapists does not exist. The fix was yet another migration, written only to recreate the six columns. Reverting code does not revert a schema.

Should you let an AI agent write your database migrations?

Yes, with the review moved to where the risk is. The questions worth a human’s attention are not about SQL style. They are about the schema before and after:

  • Which tables and columns does this drop? Every drop should match something you meant to delete.
  • Is anything that looks like a replacement actually a rename? A dropped column and an added column of the same type on the same table is the pattern to stop on.
  • How much data goes with each drop? A drop of an empty column and a drop of a column with a million rows read identically in the file.
  • What depends on what changes? Views, functions, triggers and foreign keys that use a dropped or renamed column, and the other tables a change to a key reaches.
  • Will it fail on the data that exists? A new NOT NULL column with no default, a unique constraint over duplicates, a foreign key over orphans.

None of these are answered by reading the SQL more carefully. They are answered by knowing the schema the file runs against, which is exactly what a diff view of the migration does not show. A migration linter reads the statements in one file and is good at locks and rewrites; it cannot tell you that the column being dropped holds the only copy of every customer’s email.

RENAME COLUMN vs DROP COLUMN and ADD COLUMN

The two ways of changing a column’s name look similar in a model file and do very different things in PostgreSQL:

ALTER TABLE ... RENAME COLUMNDROP COLUMN + ADD COLUMN
Existing dataKeptDeleted; the new column is NULL or its default
LockACCESS EXCLUSIVE, for a catalog-only changeACCESS EXCLUSIVE, plus a rewrite if the new column’s default is volatile
Views, indexes, foreign keysFollow the new nameThe drop fails while a view depends on the column, unless CASCADE drops the view too; indexes and foreign keys on it are dropped
Queries using the old nameBreak at onceBreak at once
In a text diffOne lineTwo lines, often far apart in a long file
ReversibleRename it backOnly from a backup

The last two rows are the reason this belongs in a review process and not in a style guide. Neither version is hard to write. The destructive one is simply the one a generator produces when it is unsure, and it is the one that is harder to spot.

The rename is not always the right answer either. On a busy table served by several application instances, the instances still running old code will query the old name the moment it disappears. That is where the expand-and-contract sequence earns its extra steps: add the new column, write to both, backfill, switch reads, and drop the old column in a later release. That is a decision about your deploy process, and it is one a person should make, not one a generator should make by default.

How Schemity shows an agent’s migration before it runs

Schemity is database design software that connects to your database, and its MCP server is how an agent working in your terminal reaches it. When the agent has written a migration file, it can ask Schemity to draw it. The file is replayed statement by statement against the schema of the connected database, without executing any of it, and the result opens as a change preview: a throwaway, read-only canvas showing the tables the migration touches and the tables one relation away from them.

A change preview is a read-only picture of a schema change that is never saved into the diagram. Tables and columns the migration creates, drops, renames or alters are marked, a dropped table carries a red border, and relation lines are coloured by the table they disappear or arrive with, so a foreign key that goes away with a dropped table is visible as a line, not as a missing one. Search covers only the preview’s tables, and Save, History and undo are blocked while it is open; Close or Escape returns to the diagram.

This is where the rename trap stops being subtle. The preview’s Findings drawer opens with Planned changes, the migration in words. A migration that uses RENAME COLUMN is listed there as a renamed column. A migration that drops and re-adds is listed as two entries, a dropped column and an added one, and on the canvas the same table shows the old column tinted red next to the new one tinted green. Below that, the drawer carries the impact analysis findings for the file, so the drop is listed under Loses data with an estimate of the rows it destroys, and a view or function that uses the column is listed as a dependent that breaks. Impact findings name the tables a change reaches and the context views they sit in, so a change to a key shared by two modules says which modules.

The same picture works without an agent. Editing a connected diagram builds a pending migration, and Preview changes in the Impact drawer draws it the same way before you apply it, so the migration SQL diff comes with a view of what it does to the model. A migration file you paste or open in the SQL migration drawer (Shift+F7) has the same button.

Getting an agent there takes one command. schemity --mcp runs a live MCP server that launches Schemity when it is not already open, and the MCP drawer carries setup snippets for Claude Code, Claude Desktop, Cursor, Codex and OpenCode.

What it does not do matters as much. Schemity does not approve or block the migration, does not run it, and does not write the diagram file; the agent’s file stays the artifact, and you run it with your own tooling. It does not estimate how long a lock is held, which is still a job for a migration linter. And the replay covers DDL and data-changing statements; anything it cannot classify, such as a DO block, is listed as not analysed rather than guessed at.

Reviewing agent-written migrations as schema changes

The shift is small to describe and large in practice. Let the agent write the SQL, and review the migration as a change to the schema: what goes, what arrives, what was renamed and what only looks renamed, and what else depends on it. A text diff of a migration was never a good picture of that, and a file that an agent writes in seconds is exactly the one nobody will read twice.

If the agent is also reading your schema to write that file, it helps to keep the schema on your own machine and to have the domain structure drawn before the agent starts. And if a migration is adding the foreign keys your schema never had, the orphan rows are the thing to count first.

Frequently asked questions

Why did my migration drop a column instead of renaming it?

Because the tool that generated it compared two versions of the model and could not tell a renamed field from one field removed and another added. Prisma Migrate emits DROP COLUMN and ADD COLUMN for a rename unless you edit the file, and Django only generates a rename when you answer yes to its prompt, which is skipped entirely in non-interactive runs. Check the generated SQL for a DROP COLUMN next to an ADD COLUMN of the same type on the same table.

Is ALTER TABLE RENAME COLUMN safe in PostgreSQL?

It keeps the data and is a catalog-only change, so it finishes quickly, but it takes an ACCESS EXCLUSIVE lock on the table for that moment and it breaks every query still using the old name. Views, indexes and foreign keys follow the rename automatically. On a busy table with several application instances, teams add the new column, write to both, backfill, switch reads, and drop the old column later instead.

How do I review an AI-generated migration before running it?

Read what it does, not how it is written: which tables and columns it creates, drops, renames and alters, how many rows each drop destroys, and which views, functions and foreign keys depend on what changes. Search the file for DROP, then check every drop against something you meant to delete. Run it against a copy of production data before it reaches production.