Architecture
The Query Compiler architecture consists of three main layers:1
Query Graph Building
GraphQL-like queries are parsed and transformed into an internal query graph representation. This graph captures the logical structure of the query including relations, filters, and data dependencies.
2
Query Planning (Rust)
The query graph is translated into an optimized expression tree (query plan) by the
query-compiler crate. This happens entirely in Rust and produces a serializable plan.3
Query Execution (TypeScript)
The expression tree is interpreted and executed in Prisma Client using driver adapters. The interpreter has no knowledge of connection strings or database specifics.
Key Components
Query Graph
The query graph is an intermediate representation that captures:- Nodes: Queries, computations, control flow (if/else, return)
- Edges: Data dependencies, execution order, conditional branches
- Dependencies: Parent-child relationships and data flow
Expression Tree
The expression tree is the final output of compilation. It’s a serializable representation that can be executed by the TypeScript interpreter.Compilation Pipeline
The compilation process follows these stages:1
Parse Query Document
Parse the incoming GraphQL-like query into a
QueryDocument structure.2
Build Query Graph
Transform the query document into a query graph that represents dependencies and execution order.
3
Translate to Expression Tree
Walk the query graph and generate an optimized expression tree.
4
Simplify and Optimize
Apply simplification rules to reduce redundant expressions.
5
Serialize to JSON
Convert the expression tree to JSON for transmission to TypeScript.
Main Entry Point
The primary compilation function is straightforward:Dependencies
Key crates used by the query compiler:
From
query-compiler/Cargo.toml:
Expression Simplification
The compiler applies several optimization passes:- Single-element sequences:
Seq([expr])becomesexpr - Identity let bindings:
let x = e in xbecomese - Single-element aggregates:
Concat([expr])becomesexpr - Nested simplification: Recursively simplifies all sub-expressions
Error Handling
Compilation can fail at multiple stages:Design Principles
Separation of Concerns
Planning happens in Rust for performance and type safety. Execution happens in TypeScript for flexibility and ecosystem integration.
Database Agnostic Planning
The expression tree is database-agnostic. Database-specific details are handled during execution via driver adapters.
Serializable Plans
Expression trees serialize to JSON, enabling cross-language execution and potential caching.
Composable Expressions
Expressions compose naturally, enabling complex queries through simple building blocks.
Next Steps
Driver Adapters
Learn about the driver adapter system and TypeScript integration
Query Planning
Deep dive into how query planning works
WASM Build
Build the WebAssembly module for browser/edge environments