Open-Source Wikis

/

Prisma

/

Features

/

Query execution

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:

  • ActionfindMany / findUnique / create / update / aggregate / etc.
  • Model — the table/collection name
  • Argswhere, include, select, orderBy, take, skip, data, …
  • Query modesingle or compacted (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 SQL
  • unique / aggregate / group-by — shaped wrappers
  • join / attach-children — relation hydration
  • transform — projection/typing
  • transaction — wrap a sub-plan in a transaction
  • concat / 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 unchanged
  • type: '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

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Query execution – Prisma wiki | Factory