L6 - Query Engine
Compile one portable operation program and execute it consistently on every supported database
Location: src/query-engine/
Purpose
The query engine owns database-agnostic query meaning. Every accepted client
operation compiles to one declarative OperationProgram; adapters express its
SQL and drivers execute it. PostgreSQL, MySQL, SQLite, LibSQL, and PGlite may use
different syntax or execution capabilities, but portable operations retain the
same accepted inputs, atomic effects, results, and failures.
Ownership Graph
QueryEngine
└── PendingOperation
├── OperationCompiler → OperationProgram → SQL builders → adapter
├── OperationRuntime → OperationProgram → driver
└── OperationResults → result shape → strict parsers
| Owner | Responsibility |
|---|---|
QueryEngine |
Client-scoped driver, registry, instrumentation, client identity, and transaction scope |
PendingOperation |
The sole lazy and Promise-like operation lifecycle |
OperationCompiler |
Exhaustive read/write dispatch and program construction |
WriteOperations |
Every write semantic, including relation mutations |
OperationProgram |
Data-only steps, dependencies, guards, branches, produced values, atomicity, and result declaration |
OperationRuntime |
Select statement, transaction, or atomic-batch execution |
OperationResults |
Attribute provider outputs and produce the declared public result |
QueryScope |
Adapter, current model, alias allocation, root alias, and mutation target for SQL construction only |
QueryEngine is a real owner rather than a forwarding shell. A transaction-
bound engine preserves its originating client identity and receives a new scope
identity.
One Program Vocabulary
A simple read or write is a one-step program. Multi-step non-RETURNING
emulation, bulk operations, relation mutations, deep returns, and dynamic
branches compose from the same vocabulary:
readandwriteexecute adapter-built SQL fragments;guardfails when a required database premise no longer holds;branchchooses a declared step sequence from a prior read;failurerepresents an explicit typed error;- produced values reference earlier outputs without runtime callbacks.
This is an operation program, not a second SQL AST. Programs contain no callbacks, relation semantics, adapters, drivers, or arbitrary context bags.
Execution
client call
→ PendingOperation (lazy)
→ validation
→ OperationCompiler
→ OperationProgram
→ OperationRuntime
├── direct statement
├── interactive transaction
└── atomic driver batch
→ OperationResults
→ typed value
Runtime modules do not import relation semantics. Transaction and batch paths specialize the same program: one may observe uncommitted writes directly, while the other lowers references and guards into an ordered atomic batch.
Relation Writes
Nested writes remain a public feature, but there is no nested-write engine.
Relation mutation semantics live under WriteOperations, compile into ordinary
program steps, and use the same runtime as every other operation.
This keeps create, connect, connect-or-create, update, upsert, set, disconnect, delete, and many-to-many membership behavior equivalent across providers.
SQL and Adapter Boundary
The golden rule remains:
The query engine decides what to query. The adapter decides how the database expresses it.
// Wrong: PostgreSQL syntax in the query engine
sql`COALESCE(json_agg(...), '[]'::json)`;
// Right: adapter-owned syntax
scope.adapter.json.agg(expression);
SQL construction remains in builders/, grouped by real concerns: where,
relation filters, selections, includes, ordering, aggregation, mutation values,
row shapes, and many-to-many junctions. Builders return parameterized Sql
fragments and never interpolate user values into SQL strings.
Strict Results
OperationResults validates program result sources and owns one parser identity
per execution driver. Provider middleware runs in this order:
driver parser → adapter parser → default strict parser
Row, relation, aggregate, count, scalar, and expected-shape parsing remain
separate cohesive concerns under result/. Missing or malformed provider data
raises typed errors; parsing never replaces it with a plausible default.
SQL Inspection
QueryEngine.build() is deliberately limited to programs containing exactly
one executable SQL step. It throws for multi-step programs instead of pretending
that an atomic operation is one SQL statement. General operations use
prepare() or execute the returned PendingOperation.
Compatibility
PendingOperation is the sole deferred-operation class. The deprecated
QueryMetadata<T> export is only a type alias to PendingOperation<T> through
the next published compatibility release; no runtime metadata object exists.
Architecture Gates
pnpm test:gates enforces lifecycle ownership, compiler/runtime/result import
boundaries, the narrow QueryScope, removal of the retired subsystem, an
acyclic query-engine runtime import graph, and — outside the engine — that every
concrete error class reaches all four surfaces of the taxonomy (the driver-failure
union, classifyFailure, the Prisma-code map, and the published errors page).
Shared provider suites then prove the same portable behavior on PostgreSQL,
MySQL, SQLite, LibSQL, and PGlite.