Getting Started
Database Drivers
Schema Integrations
Seed Generators
Utilities and Testing
The following files were used as context for generating this wiki page:
SQL Expressions are the fundamental building blocks of Drizzle ORM's query-building system. They provide a unified, type-safe abstraction for constructing SQL queries across different database dialects while maintaining portability and expressive power. By encapsulating raw query parts into composable objects, the library allows developers to write SQL using familiar TypeScript syntax that safely handles parameter binding and escaping without sacrificing direct control over the generated query structure.
The system is designed around the SQL class, which serves as the core container for query fragments (chunks). These chunks can be strings, column references, table references, nested SQL expressions, or user-provided parameters. This architecture treats a query as an abstract syntax tree of fragments, which is only flattened into a final string and a set of parameters at the very last moment by a dialect-specific engine. This deferred execution ensures that the driver handles parameter binding consistently and securely.
Beyond simple composition, the SQL Expression system acts as the bridge between TypeScript's type system and the database's execution layer. It manages driver-specific value encoding and decoding, identifier escaping, and placeholder transformation. Because the system is built recursively—where expressions can contain sub-expressions—it enables the creation of complex, modular queries that are easy to maintain, test, and adapt to different database backends (Gel, PostgreSQL, SQLite, SingleStore).
SQL ContainerThe SQL class is the central orchestrator for query fragment management. It accepts an array of SQLChunk objects and maintains a list of usedTables for internal tracking. The primary responsibility of this class is to serve as a builder that can aggregate query fragments via the append method, allowing for dynamic query construction.
Crucially, SQL instances are immutable in their definition, but the builder functions like sql.join or the sql template literal create new instances, ensuring that queries are composed safely. The SQL class exposes the getSQL() method, which fulfills the SQLWrapper interface, ensuring that any expression (a table, a column, or a subquery) can be treated uniformly by the query builder.
Sources: drizzle-orm/src/sql/sql.ts:103-130
The sql tagged template literal is the primary entry point for developers to write SQL queries. When invoked, it tokenizes the input template and wraps variables in appropriate SQLChunk types. This transformation is not merely syntactic sugar; it ensures that every interpolated value is treated as part of the query structure, which helps the system distinguish between query keywords (strings) and dynamic content (parameters, identifiers, or other expressions).
The mechanism works by alternating between StringChunk instances (for raw text) and the interpolated expressions passed as arguments. This sequence is then fed into a new SQL instance.
// Example of how the SQL expression system builds fragments
const query = sql`SELECT * FROM ${users} WHERE ${eq(users.id, 1)}`;In this example, the system tracks the users table reference separately from the condition eq(...), allowing the dialect to later perform dialect-specific identifier escaping for both.
Sources: drizzle-orm/src/sql/sql.ts:485-495
The compilation process, or sqlToQuery, is where the abstract SQL container is flattened into a runnable format for a specific database driver. The SQL.toQuery method is responsible for iterating over queryChunks and executing transformations based on a BuildQueryConfig.
The dialect provides specific implementation logic for escapeName, escapeParam, and escapeString. This ensures that, for instance, a MySQL/SingleStore backend uses backticks while a PostgreSQL/Gel backend uses double quotes for identifiers.
Sources: drizzle-orm/src/sql/sql.ts:137-146, drizzle-orm/src/pg-core/dialect.ts:609-618
Parameter binding is managed by the Param class and the DriverValueEncoder interface. When a parameter is encountered during query compilation, the system invokes encoder.mapToDriverValue(). This is a critical architectural step: it allows the ORM to convert complex TypeScript types (like Date, JSON objects, or custom classes) into formats the underlying database driver understands.
Note
If a value is wrapped in Param, the system checks if it is a Placeholder. If it is, the system defers binding until the final query execution time, which allows for prepared statements.
Sources: drizzle-orm/src/sql/sql.ts:432-450
Conditions (like eq, lt, gt) are implemented as wrappers around the sql template literal. These functions take arguments, apply bindIfParam to ensure they are properly encoded, and return a SQL object that can be further composed in a where() clause.
The bindIfParam function acts as a guard that prevents double-wrapping of parameters if they are already identified as an SQLWrapper or a Column.
// The implementation of eq is a straightforward wrapper
export const eq: BinaryOperator = (left: SQLWrapper, right: unknown): SQL => {
return sql`${left} = ${bindIfParam(right, left)}`;
};Sources: drizzle-orm/src/sql/expressions/conditions.ts:17-64
The SQL Expression architecture prioritizes flexibility and cross-dialect portability over absolute runtime performance. The decision to use a recursive SQL structure introduces minor overhead due to tree traversal during query serialization, but it buys significant architectural benefits.
Sources: drizzle-orm/src/sql/sql.ts:103-372