prisma/prisma
Query execution
The end-to-end flow from prisma.user.findMany({ ... }) to a typed array of rows. Spans @prisma/client, @prisma/client-engine-runtime, the Wasm query compiler, and a driver adapter.
Components
| Layer | Package | Responsibility |
|---|---|---|
| User-facing API | @prisma/client |
The proxied PrismaClient instance, model methods, fluent API |
| Action handling | @prisma/client core/model/ |
applyModel / applyFluent translate method calls into request descriptions |
| Request lifecycle | @prisma/client RequestHandler.ts |
Issues engine requests, batches via DataLoader, unpacks responses |
| Engine orchestration | @prisma/client core/engines/client/ClientEngine.ts |
Loads Wasm compiler, dispatches to executor |
| Plan compilation | @prisma/query-compiler-wasm (external Rust artifact) |
JSON request → query plan |
| Plan execution (in-process) | @prisma/client-engine-runtime |
QueryInterpreter, TransactionManager, error mapping |
| Plan execution (remote) | @prisma/query-plan-executor (separate service) |
Same QueryInterpreter, behind HTTPS |
| Database I/O | @prisma/adapter-* |
Wraps a JS driver, exposes SqlQueryable |
End-to-end sequence
sequenceDiagram
participant App
participant Proxy as PrismaClient (compositeProxy)
participant Apply as applyModel/applyFluent
participant RH as RequestHandler
participant DL as DataLoader
participant Engine as ClientEngine
participant QC as query-compiler-wasm
participant Exec as LocalExecutor
participant QI as QueryInterpreter
participant Adapter
participant DB as Database
App->>Proxy: prisma.user.findMany({where:{...}})
Proxy->>Apply: dispatch findMany
Apply->>RH: handleRequest(action, args, dataPath)
RH->>DL: enqueue(jsonProtocol)
Note over DL: Microtask boundary — coalesce siblings
DL->>Engine: request(merged)
Engine->>QC: compile to plan
QC-->>Engine: query plan
Engine->>Exec: execute(plan, ctx)
Exec->>QI: run(plan, adapter)
loop For each plan step
QI->>Adapter: queryRaw(SQL, params)
Adapter->>DB: SQL
DB-->>Adapter: rows
Adapter-->>QI: typed rows
end
QI-->>Exec: shaped JSON
Exec-->>Engine: response
Engine-->>DL: response
DL-->>RH: response
RH-->>Apply: shaped object
Apply-->>Proxy: typed result
Proxy-->>App: User[]What the JSON protocol carries
Defined in packages/client-engine-runtime/src/json-protocol.ts. A single request includes:
- Action —
findMany/findUnique/create/update/aggregate/ etc. - Model — the table/collection name
- Args —
where,include,select,orderBy,take,skip,data, … - Query mode —
singleorcompacted(batched) - Comments context — passed to SQL Commenter plugins
- Tracing context — OpenTelemetry baggage if set
- Transaction context — interactive transaction ID if inside
$transaction
What the query plan looks like
Defined in packages/client-engine-runtime/src/query-plan.ts. The plan is a discriminated union of node kinds, e.g.:
query— a literal SQL execution (parameterized)executeRaw/queryRaw— user-supplied raw SQLunique/aggregate/group-by— shaped wrappersjoin/attach-children— relation hydrationtransform— projection/typingtransaction— wrap a sub-plan in a transactionconcat/union— combine results
QueryInterpreter walks this tree, executing query/executeRaw/queryRaw against the adapter and applying transforms in TypeScript.
Per-request batching
DataLoader (packages/client/src/runtime/DataLoader.ts) is a microtask-bound batcher. Concurrent loads issued in the same tick get coalesced into a single engine request when possible. This is what makes include-heavy queries efficient — relation loads piggy-back on each other.
SqlCommenterQueryInfo distinguishes:
type: 'single'— a single user query, sent through unchangedtype: 'compacted'— multiple queries that DataLoader merged into one SQL statement; SQL Commenter plugins see metadata about all of them
Result unpacking and dataPath
RequestHandler.ts reads dataPath (a chain of select/include segments accumulated during the fluent walk) to extract the user-visible shape from the engine's response. Caveat: see @prisma/client runtime for the subtle rule about dataPath interpretation in extension contexts.
Local vs remote execution
graph LR
Engine[ClientEngine] --> Exec{adapter or accelerateUrl?}
Exec -->|adapter| Local[LocalExecutor]
Exec -->|accelerateUrl| Remote[RemoteExecutor]
Local --> QI[QueryInterpreter]
QI --> Adapter[Driver adapter]
Adapter --> DB1[(Direct DB)]
Remote --> HTTP[HTTPS to Accelerate]
HTTP --> QPE[query-plan-executor]
QPE --> QI2[QueryInterpreter]
QI2 --> Adapter2[Driver adapter on server]
Adapter2 --> DB2[(Pooled DB)]The branch is in ClientEngine. RemoteExecutor is identical in semantics — it just shifts execution to Accelerate's servers, where the same QueryInterpreter runs.
Performance tracking
End-to-end query benchmarks live in packages/client/src/__tests__/benchmarks/query-performance/. Compilation alone is benchmarked in compilation.bench.ts. Interpreter throughput is benchmarked in packages/client-engine-runtime/bench/interpreter.bench.ts. CodSpeed (CI: benchmark.yml) tracks all of them.
See also
- Driver adapters
- Transactions for what
$transactionadds to the picture @prisma/clientruntime for the in-process side@prisma/client-engine-runtimefor the interpreter@prisma/query-plan-executorfor Accelerate-side execution
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.