Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

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:

  • read and write execute adapter-built SQL fragments;
  • guard fails when a required database premise no longer holds;
  • branch chooses a declared step sequence from a prior read;
  • failure represents 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.

Was this page helpful?