Architecture
The Query Compiler architecture consists of three main layers: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.
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.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:Build Query Graph
Transform the query document into a query graph that represents dependencies and execution order.
Main Entry Point
The primary compilation function is straightforward:Dependencies
Key crates used by the query compiler:| Crate | Purpose |
|---|---|
psl | Prisma Schema Language parser and validator |
query-structure | Data structures for queries and schema |
query-builder | SQL query building abstractions |
query-core | Core query graph and document types |
sql-query-builder | Database-specific SQL generation |
quaint | Database connectivity and query execution |
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