# Postgres-only migrations (apply post-cutover) Phase 3 locked in the full schema shape on SQLite. When the datasource flips to `postgresql` we apply a follow-up migration that upgrades a few columns to Postgres-native types and adds the Full-Text Search column required by the plan. ## 1. Part full-text search (tsvector + GIN) ```sql ALTER TABLE "Part" ADD COLUMN "searchVector" tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('simple', coalesce("serialNumber", '')), 'A') || setweight(to_tsvector('simple', coalesce("mpn", '')), 'B') || setweight(to_tsvector('english', coalesce("notes", '')), 'C') ) STORED; CREATE INDEX "Part_searchVector_idx" ON "Part" USING GIN ("searchVector"); ``` Query shape the API will use for the `q=` filter on `/api/parts`: ```sql SELECT * FROM "Part" WHERE "searchVector" @@ plainto_tsquery('simple', $1) ORDER BY ts_rank("searchVector", plainto_tsquery('simple', $1)) DESC LIMIT $2 OFFSET $3; ``` Expose on the Prisma model with `@ignore` (Prisma can't represent `GENERATED` columns) and read via `prisma.$queryRaw` inside the parts service. ## 2. Convert JSON-string columns to native Postgres types These were stored as `String` for SQLite portability. ```sql ALTER TABLE "WebhookSubscription" ALTER COLUMN "events" TYPE text[] USING string_to_array( trim(both '[]' from "events"), ',' ); ALTER TABLE "SavedView" ALTER COLUMN "filterJson" TYPE jsonb USING "filterJson"::jsonb; ALTER TABLE "CsvImportJob" ALTER COLUMN "errors" TYPE jsonb USING "errors"::jsonb; ``` After the DDL change, update `packages/db/prisma/schema.prisma`: ```prisma model WebhookSubscription { // ... events String[] } model SavedView { // ... filterJson Json } model CsvImportJob { // ... errors Json? } ``` Regenerate the Prisma client and tighten the zod → Prisma marshaling in the service layer (drop the `JSON.stringify` / `JSON.parse` bridges). ## Verification checklist (P3) - `prisma migrate deploy` applies cleanly against a fresh Postgres snapshot. - `EXPLAIN ANALYZE` on the FTS query shows a `Bitmap Index Scan` on `Part_searchVector_idx` and returns ranked results in <50ms at 100k rows. - Integration test inserts 3 parts with overlapping serial/mpn tokens, queries `q=...`, and asserts order-by-rank.