feat: Migrate Prisma from 6.14.0 to 7.7.0 with driver adapters#3391
feat: Migrate Prisma from 6.14.0 to 7.7.0 with driver adapters#3391devin-ai-integration[bot] wants to merge 9 commits intomainfrom
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Local Runtime Testing ResultsRan the webapp locally against Postgres with the PrismaPg driver adapter. Devin session Results
Evidence: Dashboard pages load with real DB data
Evidence: Server logs confirm PrismaPg adapter (no Rust engine)Evidence: /metrics and process checks$ curl -s http://localhost:3030/metrics | grep -i prisma
# (no output — zero prisma metrics)
$ ps aux | grep -v grep | grep query-engine
# (no output — no Rust engine process)Not tested (recommend staging verification)
|
- Bump prisma, @prisma/client to 7.7.0, add @prisma/adapter-pg - Switch to engine-less client (engineType = 'client') with PrismaPg adapter - Remove binaryTargets and metrics preview feature from schema.prisma - Remove url/directUrl from datasource block (Prisma 7 requirement) - Create prisma.config.ts for CLI tools (migrations) - Rewrite db.server.ts to use PrismaPg adapter for writer + replica clients - Drop $metrics: remove from metrics.ts, delete configurePrismaMetrics from tracer.server.ts - Update PrismaClientKnownRequestError import path (runtime/library -> runtime/client) - Update all PrismaClient instantiation sites to use adapter pattern: testcontainers, tests/utils.ts, scripts, benchmark producer - Exclude prisma.config.ts from TypeScript build Co-Authored-By: Eric Allam <eallam@icloud.com>
- Bump @prisma/instrumentation from ^6.14.0 to ^7.7.0 for Prisma 7 compatibility
- Fix DATABASE_POOL_TIMEOUT incorrectly mapped to idleTimeoutMillis (semantic mismatch)
- pool_timeout was a connection acquisition timeout, idleTimeoutMillis is idle eviction
- Use DATABASE_CONNECTION_TIMEOUT for idleTimeoutMillis instead (pg Pool has no
direct acquisition timeout equivalent)
Co-Authored-By: Eric Allam <eallam@icloud.com>
The references/prisma-7 project had @prisma/client@6.20.0-integration-next.8 which caused @prisma/client-runtime-utils@6.20.0 to be hoisted to root instead of @7.7.0 needed by the generated Prisma 7 client. This caused TypeError: isObjectEnumValue is not a function at runtime. Co-Authored-By: Eric Allam <eallam@icloud.com>
…ma 7) Prisma 7 removed the --skip-generate flag from 'prisma db push'. This caused the testcontainers migration command to fail silently (tinyexec swallows the error), resulting in empty databases and 'table does not exist' errors in tests. Also added --url flag to pass the connection string directly to the CLI, ensuring the correct URL is used regardless of config file resolution. Co-Authored-By: Eric Allam <eallam@icloud.com>
… binary copy) Co-Authored-By: Eric Allam <eallam@icloud.com>
With Prisma 7's PrismaPg driver adapter, write conflicts (PostgreSQL 40001) surface as DriverAdapterError with message 'TransactionWriteConflict' instead of PrismaClientKnownRequestError with code P2034. Without this fix, the $transaction retry logic silently stops retrying on serialization failures. Added isDriverAdapterTransactionWriteConflict() check to isPrismaRetriableError() and updated $transaction catch block to use the unified retriable check. Co-Authored-By: Eric Allam <eallam@icloud.com>
With Prisma 7's PrismaPg driver adapter, each query goes through the pg Pool + adapter layer, adding ~5-10ms overhead per query vs the old in-process Rust engine. The continueRunIfUnblocked worker job executes 5+ DB queries, so the previous 200ms window was too tight for CI runners. - Increase setTimeout from 200ms to 1000ms for all completeWaitpoint -> continueRunIfUnblocked wait patterns - Increase idempotencyKeyExpiresAt from 200ms to 60s to prevent expiry during test execution - Increase getSnapshotsSince pre-complete wait from 200ms to 500ms Co-Authored-By: Eric Allam <eallam@icloud.com>
…t race condition With PrismaPg adapter overhead, each trigger() call takes longer than with the old Rust engine. A 50ms processWorkerQueueDebounceMs causes the background processQueueForWorkerQueue job to fire between individual triggers, moving items to the worker queue one-by-one in arrival (FIFO) order instead of waiting for all items to be in the master queue and moving them collectively in priority order. Increase to 10s so the test's manual processMasterQueueForEnvironment call controls the ordering. Co-Authored-By: Eric Allam <eallam@icloud.com>
Add deterministic prepared statement names (SHA-256 hash of SQL) to both writer and replica PrismaPg adapters. This lets PostgreSQL reuse cached query plans instead of parsing and planning every query from scratch. The old Rust engine did this automatically; the driver adapter requires explicit opt-in via the statementNameGenerator option (added in v7.6.0). Co-Authored-By: Eric Allam <eallam@icloud.com>
c1180e9 to
ee1351e
Compare
Summary
Upgrades Prisma from 6.14.0 to 7.7.0, replacing the Rust binary query engine with the new TypeScript/WASM engine-less client using
@prisma/adapter-pg. This eliminates the Rust↔JS serialization overhead and the binary engine process.What changed:
prisma,@prisma/client→ 7.7.0; added@prisma/adapter-pg@7.7.0;@prisma/instrumentation→^7.7.0schema.prisma: removedurl/directUrlfrom datasource, removedbinaryTargets, removedpreviewFeatures = ["metrics"], addedengineType = "client"prisma.config.tsfor Prisma CLI (migrations) — usesengine: "classic"so the schema engine binary still works for migrations while the app uses the new client engine. Excluded from TS build viatsconfig.json.db.server.ts: both writer and replicaPrismaClientinstances now usePrismaPgadapter with pool config passed directly to the constructor instead of URL query params. TheextendQueryParams()helper is removed.statementNameGeneratoron both writer and replica adapters — SHA-256 hash of SQL string generates deterministic names so PostgreSQL reuses cached query plans (restores behavior the old Rust engine had automatically)$metricsfully dropped: removedprisma.$metrics.prometheus()from/metricsroute, deleted entireconfigurePrismaMetrics()(~200 LOC of OTel gauges) fromtracer.server.tsPrismaClientKnownRequestErrorimport path updated:@prisma/client/runtime/library→@prisma/client/runtime/clientPrismaClientinstantiation sites updated to adapter pattern:testcontainers,tests/utils.ts,scripts/recover-stuck-runs.ts, benchmark producerreferences/prisma-7updated from6.20.0-integration-next.8→7.7.0to fix a pnpm hoisting conflictprisma db pushcommand updated for Prisma 7 CLI changes$transactionretry logic for Prisma 7 write conflictsPrepared statement caching (
statementNameGenerator)The old Rust engine automatically used deterministic prepared statement names, allowing PostgreSQL to reuse cached query plans. The
PrismaPgdriver adapter does not do this by default — every query uses an anonymous prepared statement that PostgreSQL must parse and plan from scratch.Added a
statementNameGenerator(available since@prisma/adapter-pg@7.6.0) to both writer and replica adapters indb.server.ts. It generates a deterministic name by SHA-256 hashing the SQL string (p_<first 16 hex chars>). This restores the plan-reuse behavior of the old engine — PostgreSQL skips parsing and planning for repeated query shapes, reducing per-query overhead. Usesnode:crypto'screateHash("sha256")which is synchronous but fast (~1-2µs per call).$transactionretry logic fix forDriverAdapterErrorLocal integration testing revealed that with Prisma 7's
PrismaPgdriver adapter, write conflicts (PostgreSQL40001serialization failures) surface as aDriverAdapterErrorwithmessage: "TransactionWriteConflict"— not asPrismaClientKnownRequestErrorwith codeP2034. This meant the existingisPrismaRetriableError()check silently failed to match, and the$transactionwrapper would not retry on write conflicts.Fix in
transaction.ts:isDriverAdapterTransactionWriteConflict()— duck-type check forerror.name === "DriverAdapterError" && error.message === "TransactionWriteConflict"isPrismaRetriableError()to catch both error shapes$transactioncatch block to use the unified checkLocal integration test results (all pass):
$on("query")events fire with all required fieldsPrismaClientKnownRequestErrorDriverAdapterError: TransactionWriteConflictisPrismaRetriableError()catches both P2028 and DriverAdapterError$transactionwrapper retries on DriverAdapterError then succeeds$transactionwrapper does NOT retry on P2002$transactionwrapper exhausts maxRetries then throws$transactionerror code analysisTransactionManagertimeout/closed/rollback errors map herePrismaClientKnownRequestError@prisma/adapter-pgmaps PG40001→DriverAdapterError: TransactionWriteConflictinsteadDriverAdapterErrorpg.Poolhas no acquisition timeout; its errors surface as rawErrorRun-engine test timing fixes (test-only changes)
waitpoints.test.ts: PrismaPg adds ~5-10ms overhead per query vs the old Rust engine. ThecontinueRunIfUnblockedworker job executes 5+ DB queries, so the previous 200mssetTimeoutbudget was too tight on CI runners.setTimeoutfrom 200ms → 1000ms (4 locations)idempotencyKeyExpiresAtfrom 200ms → 60s (2 locations)priority.test.ts:processWorkerQueueDebounceMs: 50caused the backgroundprocessQueueForWorkerQueuejob to fire between individualtrigger()calls (which are slower with PrismaPg). This moved runs from the priority-sorted master queue to the FIFO worker queue one-by-one in arrival order instead of collectively in priority order.processWorkerQueueDebounceMsfrom 50 → 10,000 so the test's manualprocessMasterQueueForEnvironment()controls orderingDocker changes
Dockerfile: Updatedprisma generatefromprisma@6.14.0→prisma@7.7.0, addedCOPYforprisma.config.tsintodev-depsstageentrypoint.sh: Removedcp node_modules/@prisma/engines/*.node apps/webapp/prisma/— Prisma 7 uses WASM bundled in the generated client instead of.nodebinary enginesOther fixes
@prisma/client-runtime-utilshoisting conflict:references/prisma-7pinned@prisma/client@6.20.0-integration-next.8, causing pnpm to hoist the wrong version of@prisma/client-runtime-utils. Updated to 7.7.0.prisma db push: Prisma 7 removed--skip-generateflag. The old command failed silently (tinyexecswallows non-zero exits), creating test databases without tables. Fixed by removing--skip-generateand adding--url.Review & Testing Checklist for Human
DATABASE_POOL_TIMEOUT(default 60s) is now unused. The old code used it as Prisma's connection acquisition timeout.pg.Poolhas no equivalent — under high load, requests queue indefinitely instead of failing after 60s. P2024 retries are dead code. Decide if this is acceptable or if a wrapper is needed.DriverAdapterErrordetection is duck-typed: Matches onerror.name === "DriverAdapterError" && error.message === "TransactionWriteConflict". If Prisma changes these strings, retry logic silently breaks. Also,DriverAdapterErrorwrite conflicts do NOT flow through theprismaErrorcallback — error reporting for write conflicts will differ from P2028 timeouts.statementNameGeneratorshould help (PG skips parse+plan), but verify net impact on hot paths (triggerTask) in staging.pg_prepared_statementsin staging for long-lived pool connections with many distinct query shapes. (Same behavior as old Rust engine, but worth confirming.)prisma_*Prometheus metrics and 18 OTel gauges are gone. Verify Grafana dashboards and alerting are updated. Consider addingpg.Poolstats in a follow-up.Recommended test plan: Deploy to staging with a representative workload. Local integration tests confirmed: query events fire, pool handles concurrent transactions, retries work for both P2028 and DriverAdapterError. Staging should verify: connection pool behavior under production-scale load, query latency on hot paths (should benefit from statement caching), and OTel span completeness.
Notes
prisma.config.tsis excluded from the TypeScript build (tsconfig.json) — only consumed by Prisma CLI.DATABASE_POOL_TIMEOUTenv var is now a no-op. Needs a follow-up to document the change or implement acquisition timeout at a different layer.tinyexeccall does not throw on non-zero exit codes — consider adding error handling in a follow-up.performIOonPgTransactiontriggers apgdeprecation warning on writes with multipleincluderelations. Will become a hard error inpg@9.0. Not introduced by this PR.Link to Devin session: https://app.devin.ai/sessions/fe7341a644774ff9acda74a2d35fb54c
Requested by: @ericallam