Summary
The case-insensitive contains search on the description field uses a sequential scan. At scale this degrades to a full table scan on every search request. A PostgreSQL tsvector column with a GIN index would make text search performant and support prefix/stemming queries.
Location
prisma/schema.prisma — add a generated tsvector column with GIN index via a raw migration
src/modules/shipments/shipments.service.ts — switch findAll() search to use to_tsquery()
Expected Behaviour
- Add a migration that adds a
description_search tsvector GENERATED ALWAYS AS (to_tsvector('english', coalesce(description, ''))) STORED column to shipments and creates a CREATE INDEX CONCURRENTLY shipments_description_search_idx ON shipments USING GIN (description_search).
- In
findAll(), replace the { contains: search, mode: 'insensitive' } clause with a prisma.$queryRaw that uses description_search @@ plainto_tsquery('english', ${search}).
- Keep the
contains fallback for referenceNumber exact match.
Acceptance Criteria
- Text search uses the GIN index (confirmed via
EXPLAIN ANALYZE in migration tests)
- Migration runs without locking the table (uses
CONCURRENTLY)
- Search results are unchanged from the user's perspective
- The feature works with the existing
search query parameter
Summary
The case-insensitive
containssearch on the description field uses a sequential scan. At scale this degrades to a full table scan on every search request. A PostgreSQLtsvectorcolumn with a GIN index would make text search performant and support prefix/stemming queries.Location
prisma/schema.prisma— add a generatedtsvectorcolumn with GIN index via a raw migrationsrc/modules/shipments/shipments.service.ts— switchfindAll()search to useto_tsquery()Expected Behaviour
description_search tsvector GENERATED ALWAYS AS (to_tsvector('english', coalesce(description, ''))) STOREDcolumn toshipmentsand creates aCREATE INDEX CONCURRENTLY shipments_description_search_idx ON shipments USING GIN (description_search).findAll(), replace the{ contains: search, mode: 'insensitive' }clause with aprisma.$queryRawthat usesdescription_search @@ plainto_tsquery('english', ${search}).containsfallback for referenceNumber exact match.Acceptance Criteria
EXPLAIN ANALYZEin migration tests)CONCURRENTLY)searchquery parameter