35 database and prisma review
Skill FluxonLab/Skillry/plugins/database-and-data/skills/35-database-and-prisma-review
Installable, permission-bounded, multi-platform agent skills & subagents for Claude Code, Codex, Copilot & Gemini/Antigravity — 125 skills + 73 subagents across 18 departments, with a validation harness, native plugin marketplace, and full upstream attribution. by FluxonLab.
npx -y skills add FluxonLab/Skillry --skill 35-database-and-prisma-reviewAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when you need to inspect schema.prisma, migrations, seeds, generated clients, database safety, and persistence changes.
SKILL.md
11.1 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it
Database And Prisma Review
Purpose
Inspect schema.prisma, migration history, seed scripts, the generated client, and the data-access code that depends on them, then issue a migration-safety verdict. Destructive changes — dropped columns, narrowed types, non-nullable columns added without a backfill, unbounded queries, and reset commands aimed at a shared database — are flagged as blocking before they reach staging or production. The review is local-only by default and produces concrete fixes plus the next safe command, never a destructive action.
When to use
- A schema change was made and needs a safety review before
prisma migrate deployruns against staging or production. - N+1 queries or unbounded
findMany()calls are suspected after a new data-fetching feature. - A seed or fixture script must be confirmed idempotent before it runs against a shared development database.
- Migration drift is suspected —
prisma migrate statushas not been checked and it is unclear whether applied migrations match the schema. - A pull request edits
schema.prisma, a file underprisma/migrations/, or the seed script.
When not to use
- The task is unrelated to database / persistence work.
- The work requires production deploys, destructive data actions, or secret disclosure (this skill reviews; it never runs the destructive command).
- The database is not Prisma-managed and a Prisma-specific lens does not apply — use the relevant SQL/ORM review instead.
- A narrower skill (query-performance, migration-safety) already covers the exact concern.
Procedure
- Read
schema.prisma. Inventory models, fields, relations, and attributes:@id,@unique,@@index,@@unique,@relationwithonDelete/onUpdate, enums, and@default. Note missing indexes on foreign keys and on frequently filtered columns. - Review migration history. Migrations must be additive and append-only; never edit a migration that has already been applied. Run
prisma migrate statusto detect drift between schema, the migrations folder, and the live database. - Separate local vs production flow.
migrate devcreates and applies (development only);migrate deployapplies existing migrations (production). Flag anydb push,migrate reset, or--force-resetaimed at anything other than a local throwaway database. - Scan for destructive changes. Dropped tables/columns, type narrowing, a non-nullable column added with no
@defaultor backfill step, and column renames (Prisma sees a rename as drop + add → data loss). Each is blocking until proven safe. - Review data access for N+1 and over-fetching. Loops issuing one query per item, missing
include/select, and unboundedfindManywith notake/cursor on a growable table. - Review seeds/fixtures. Confirm idempotency (
upsertovercreate) and that no seed performs a reset on a shared database. - Verify upsert preconditions. Every
upsertandconnectOrCreatemust target a field backed by@unique/@@unique; without it the operation inserts duplicates under concurrency. - Check backfill isolation. Any column-add that ends in a constraint must split the backfill into its own batched step so a large table is not locked during the migration.
- Confirm the generated client is regenerated, not hand-edited — never patch files under
node_modules/.prismaor@prisma/client.
Concrete checks
- Foreign-key columns lacking
@@index(Postgres does not auto-index FKs) → slow joins. - A required column added to a populated table without
@defaultor a backfill step → migration fails / data loss. onDelete: Cascadethat could silently wipe related rows, or a missing cascade leaving orphans.migrate reset,db push --accept-data-loss, or--force-resetnear a shared/production database → block.- N+1:
for (...) { await prisma.x.findUnique(...) }; fix withinclude,where: { id: { in } }, or one grouped query. - Unbounded queries:
findMany()with notake/cursor on a table that grows. - Seeds using
create(which duplicates on re-run) instead ofupsert. - Edits to generated output under
node_modules/.prismaor@prisma/client. - A column rename expressed as drop + add with no data-preserving migration.
- A backfill bundled into the same migration as a
NOT NULL/constraint change (lock + blocked writes on a large table). - An
upsertwhose target field has no@unique/@@uniqueconstraint (duplicate inserts under concurrency). - An enum member renamed by value rather than added-then-migrated (breaks existing rows in Postgres).
Commands
# Schema validity and canonical formatting
npx prisma validate
npx prisma format
# Drift / pending / failed migrations (run this first on any schema PR)
npx prisma migrate status
# Preview the SQL a schema change WOULD generate, without applying it
npx prisma migrate diff \
--from-schema-datasource prisma/schema.prisma \
--to-schema-datamodel prisma/schema.prisma --script
# Inspect the latest committed migration for destructive statements
ls -t prisma/migrations/*/migration.sql | head -1 | xargs rg -n "DROP|ALTER COLUMN|NOT NULL|RENAME"
# N+1 and unbounded-query smells in application code
rg -n "for\s*\(|\.map\(|\.forEach\(" src | rg "prisma\."
rg -n "findMany\(\)" src
rg -n "findMany\(" src | rg -v "take:|cursor:"
# FK columns vs declared indexes (spot missing @@index)
rg -n "@relation" prisma/schema.prisma ; rg -n "@@index" prisma/schema.prisma
# Confirm no one edited the generated client
git diff --name-only | rg "node_modules/.prisma|@prisma/client" && echo "BLOCK: generated client edited"
# Scan the pending migration SQL for every destructive verb
rg -n "DROP TABLE|DROP COLUMN|ALTER COLUMN .* TYPE|SET NOT NULL|RENAME (COLUMN|TO)|TRUNCATE" \
prisma/migrations/*/migration.sql
# Seeds: create (duplicates on re-run) vs upsert (idempotent)
rg -n "\.create\(|\.createMany\(" prisma/seed.* ; rg -n "\.upsert\(" prisma/seed.*
# Upserts whose target field lacks a unique constraint (duplicate risk)
rg -n "\.upsert\(|connectOrCreate" src/ prisma/
rg -n "@unique|@@unique" prisma/schema.prisma
# Raw SQL bypassing Prisma's parameterization (injection + drift risk)
rg -n "\$queryRawUnsafe|\$executeRawUnsafe|\$queryRaw\`.*\$\{" src/
# Confirm DATABASE_URL points at a local DB before any migrate command
rg -n "DATABASE_URL" .env* | sed -E 's#(://[^:]+:)[^@]+@#\1****@#' # redact the password
Safe column-add (expand / migrate / contract)
-- Step 1 (expand): add the column nullable, no default backfill yet
ALTER TABLE "Order" ADD COLUMN "currency" TEXT;
-- Step 2 (migrate): backfill in batches to avoid a long table lock
UPDATE "Order" SET "currency" = 'USD' WHERE "currency" IS NULL; -- batch by id range in prod
-- Step 3 (contract, a LATER migration): enforce the constraint once data is clean
ALTER TABLE "Order" ALTER COLUMN "currency" SET NOT NULL;
N+1 fix patterns (Prisma)
// WRONG: one query per order (N+1)
const orders = await prisma.order.findMany();
for (const o of orders) o.user = await prisma.user.findUnique({ where: { id: o.userId } });
// RIGHT: a single relational fetch
const orders = await prisma.order.findMany({ include: { user: true } });
// WRONG: unbounded — loads the whole table into memory
const all = await prisma.event.findMany();
// RIGHT: paginate with a cursor
const page = await prisma.event.findMany({ take: 50, cursor: { id: lastId }, skip: 1 });
Destructive-operation classification
| Operation | Risk | Safe alternative |
|---|---|---|
DROP COLUMN | data loss | deprecate, stop writing, drop in a later release |
add NOT NULL to populated table | migration fails | nullable → backfill → set not null |
| column rename | drop + add = data loss | hand-written RENAME COLUMN migration |
migrate reset / db push --accept-data-loss | wipes data | restrict to local disposable DB only |
onDelete: Cascade added | silent bulk delete | confirm intent; consider Restrict/SetNull |
Common issues & anti-patterns
- Editing an already-applied migration. Changing a committed
migration.sqldesyncs the migration checksum from the database;migrate deploythen fails on every environment. Add a new migration instead. - Required column on a populated table.
String(non-nullable) added without@defaultor a backfill fails the moment it runs against real data. Add nullable → backfill → enforce non-null in a later migration. - Column rename = data loss. Renaming a field in
schema.prismamakes Prisma drop the old column and add a new empty one. Use a hand-written rename migration to preserve data. migrate resetin a script. A seed or CI step that resets the database is catastrophic if it ever points at a shared environment. Restrict reset to a clearly local, disposable database.- N+1 in a list endpoint. A loop calling
findUniqueper row turns one request into hundreds of queries; replace withincludeor a singlewhere: { id: { in: [...] } }. - Patching the generated client. Hand-edits under
@prisma/clientvanish on the nextprisma generate. Change the schema and regenerate. - Backfill inside the same migration as the constraint. A single migration that adds the column, backfills, and sets
NOT NULLholds a long lock on a big table and blocks writes. Split backfill from the constraint and batch it. - Missing unique constraint behind an upsert.
upsertrelies on a unique field; without the@unique/@@unique, it inserts duplicates under concurrency. Confirm the constraint exists. includeeverything everywhere. Eagerly including deep relations on a list endpoint over-fetches and can be its own performance problem. Select only the fields the caller needs.- Enum changed by value, not name. Renaming an enum member is a destructive change in Postgres; existing rows holding the old value break. Add the new value, migrate rows, then remove the old.
Required output
Return: schema findings (missing indexes, risky relations); a migration-safety verdict (additive vs destructive, drift status from migrate status); explicit flags for any destructive or reset command; N+1 / over-fetch findings with the fix; seed idempotency status; and the safe next command. Mark anything that could lose data as blocking and pair it with a non-destructive alternative.
Safety
- Local-only by default; never run
migrate deploy,migrate reset,db push, or seeds against a shared or production database. - Back up before any destructive local migration; prefer additive migration + backfill over drop/recreate.
- Do not edit applied migrations or generated client code; regenerate with
prisma generate. - Redact credentials in
DATABASE_URLwhenever it appears in output.
Completion criteria
Done means schema, migrations, data-access patterns, and seeds were reviewed from evidence, migrate status was run, every destructive or drift risk is flagged as blocking with a safe alternative, N+1 and index issues have concrete fixes, and the next safe command is named.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.