Relevant source files
The following files were used as context for generating this wiki page:
- drizzle-orm/type-tests/geldb/tables.ts
- drizzle-orm/type-tests/mysql/tables.ts
- drizzle-orm/type-tests/geldb/no-strict-null-checks/test.ts
- drizzle-orm/type-tests/geldb/select.ts
- drizzle-orm/type-tests/singlestore/tables.ts
- drizzle-orm/type-tests/sqlite/tables.ts
- drizzle-orm/type-tests/pg/select.ts
- drizzle-orm/type-tests/mysql/db-rel.ts
- drizzle-orm/type-tests/singlestore/select.ts
- drizzle-orm/type-tests/pg/no-strict-null-checks/test.ts
- drizzle-orm/type-tests/sqlite/select.ts
- drizzle-orm/type-tests/singlestore/no-strict-null-checks/test.ts
- drizzle-orm/type-tests/mysql/select.ts
- drizzle-orm/type-tests/mysql/no-strict-null-checks/test.ts
- drizzle-orm/type-tests/mysql/1000columns.ts
- drizzle-orm/type-tests/sqlite/no-strict-null-checks/test.ts
- drizzle-orm/type-tests/pg/db-rel.ts
- drizzle-orm/type-tests/mysql/subquery.ts
- integration-tests/type-tests/join-nodenext/singlestore.ts
- drizzle-orm/type-tests/common/aliased-table.ts
- drizzle-orm/type-tests/pg/subquery.ts
- drizzle-orm/type-tests/geldb/db-rel.ts
- integration-tests/type-tests/join-nodenext/gel.ts
- drizzle-orm/type-tests/mysql/db.ts
- drizzle-orm/type-tests/singlestore/set-operators.ts
- drizzle-orm/type-tests/singlestore/1000columns.ts
- drizzle-orm/type-tests/geldb/subquery.ts
- drizzle-orm/type-tests/knex/index.ts
- drizzle-orm/type-tests/geldb/db.ts
- drizzle-orm/type-tests/pg/db.ts
Overview
Type system verification validates compile-time correctness across Drizzle's table definitions, query builders, and database drivers. It uses assertion utilities such as Expect and Equal to verify that inferred types match expected shapes for PostgreSQL, MySQL, SQLite, SingleStore, and Gel databases.
Sources: drizzle-orm/type-tests/mysql/db-rel.ts:2-2, drizzle-orm/type-tests/pg/db-rel.ts:2-2, drizzle-orm/type-tests/geldb/db-rel.ts:2-2, drizzle-orm/type-tests/singlestore/set-operators.ts:1-2, drizzle-orm/type-tests/common/aliased-table.ts:1-1
Type Testing Assertions and Harness
Test Harness Mechanics and Assertion Utilities
Type tests across dialects utilize the Expect and Equal utility types imported from type-tests/utils.ts to enforce precise type checking at compile time. These assertions validate query execution results, relational queries, and schema projections without emitting runtime code.
import { type Equal, Expect } from 'type-tests/utils.ts';Sources: drizzle-orm/type-tests/mysql/db-rel.ts:2-2, drizzle-orm/type-tests/pg/db-rel.ts:2-2, drizzle-orm/type-tests/geldb/db-rel.ts:2-2, drizzle-orm/type-tests/common/aliased-table.ts:1-1
Query results and builder inferences are wrapped inside block scopes to isolate variable declarations across different test cases. Each test block executes a database query or schema operation using dialect-specific drivers and schema modules.
{
const res = db('test').select();
Expect<Equal<PromiseOf<typeof res>, typeof test.$inferSelect[]>>;
}Sources: drizzle-orm/type-tests/knex/index.ts:20-23
Note
Type test files do not execute queries against a live database during standard type-checking passes; instead, drivers are frequently instantiated using .mock() or connection pool stubs to verify AST builder output and inference boundaries.
Sources: drizzle-orm/type-tests/common/aliased-table.ts:16-18
Cross-Dialect Test Structure
The type-testing harness organizes suites by database dialect and common utility modules. Each directory contains dedicated schema definitions and query verification scripts that test relational finders, table aliases, view integration, and third-party query builder typings like Knex.
| Dialect / Module | Source File Path | Primary Verification Target |
|---|---|---|
| PostgreSQL Relational | drizzle-orm/type-tests/pg/db-rel.ts | Relational query builder, findMany, with, columns, and extras inference |
| MySQL Relational | drizzle-orm/type-tests/mysql/db-rel.ts | Cross-dialect driver integration, placeholder typing, and nested relations |
| Gel Database | drizzle-orm/type-tests/geldb/db-rel.ts | Gel client driver initialization and relational schema projections |
| Common Aliases | drizzle-orm/type-tests/common/aliased-table.ts | Table aliases, view joins, and multi-dialect select structure validation |
| Knex Integration | drizzle-orm/type-tests/knex/index.ts | Knexify table declaration augmentation and PromiseOf resolution |
Sources: drizzle-orm/type-tests/mysql/db-rel.ts:1-104, drizzle-orm/type-tests/pg/db-rel.ts:1-121, drizzle-orm/type-tests/common/aliased-table.ts:1-232, drizzle-orm/type-tests/geldb/db-rel.ts:1-118, drizzle-orm/type-tests/knex/index.ts:1-23
Column and Table Schema Verification
Builder Column Configurations and Defaults
Compile-time verification ensures that table column builders properly enforce type safety across scalar types, default values, and custom type definitions. In MySQL, SQLite, Gel, and SingleStore type tests, column defaults accept static values or callbacks via $default and $defaultFn, rejecting mismatched value types at compile time.
{
mysqlTable('test', {
id: int('id').$default(() => 1),
id2: int('id').$defaultFn(() => 1),
// @ts-expect-error - should be number
id3: int('id').$default(() => '1'),
// @ts-expect-error - should be number
id4: int('id').$defaultFn(() => '1'),
});
}Sources: drizzle-orm/type-tests/mysql/tables.ts:746-754, drizzle-orm/type-tests/sqlite/tables.ts:425-433, drizzle-orm/type-tests/geldb/tables.ts:853-861
Caution
Using $default with a factory function that returns a type conflicting with the underlying column data type bypasses runtime type guards and triggers @ts-expect-error compiler errors during schema declaration tests.
Sources: drizzle-orm/type-tests/mysql/tables.ts:749-752, drizzle-orm/type-tests/geldb/tables.ts:856-859
Custom column configurations validate required and optional parameters through generic constraints on customType. When configRequired: true is specified, the builder requires configuration arguments to be supplied.
{
const customRequiredConfig = customType<{
data: string;
driverData: string;
config: { length: number };
configRequired: true;
}>({
dataType(config) {
Expect<Equal<{ length: number }, typeof config>>;
return `varchar(${config.length})`;
},
toDriver(value) {
Expect<Equal<string, typeof value>>();
return value;
},
fromDriver(value) {
Expect<Equal<string, typeof value>>();
return value;
},
});
customRequiredConfig('t', { length: 10 });
customRequiredConfig({ length: 10 });
// @ts-expect-error - config is required
customRequiredConfig('t');
// @ts-expect-error - config is required
customRequiredConfig();
}Sources: drizzle-orm/type-tests/mysql/tables.ts:803-831, drizzle-orm/type-tests/singlestore/tables.ts:792-837, drizzle-orm/type-tests/sqlite/tables.ts:476-506
Enum Array and Scalar Type Inference
Column definitions containing enumerated values verify tuple inference for both standard and as const assertions. In SQLite and SingleStore table tests, enum types correctly surface their allowed string literals on .enumValues.
{
const test = sqliteTable('test', {
test1: text('test', { length: 255, enum: ['a', 'b', 'c'] as const }).notNull(),
test2: text('test', { length: 255, enum: ['a', 'b', 'c'] }).notNull(),
test3: text('test', { enum: ['a', 'b', 'c'] as const }).notNull(),
test4: text('test', { enum: ['a', 'b', 'c'] }).notNull(),
});
Expect<Equal<['a', 'b', 'c'], typeof test.test1.enumValues>>;
Expect<Equal<['a', 'b', 'c'], typeof test.test2.enumValues>>;
Expect<Equal<['a', 'b', 'c'], typeof test.test3.enumValues>>;
Expect<Equal<['a', 'b', 'c'], typeof test.test4.enumValues>>;
}Sources: drizzle-orm/type-tests/sqlite/tables.ts:450-460, drizzle-orm/type-tests/singlestore/tables.ts:633-633
Tip
When defining columns without explicitly providing column names as the first argument, Drizzle infers the table property key as the database column name, verified via typeof table['id']['_']['name'].
Sources: drizzle-orm/type-tests/geldb/tables.ts:901-908, drizzle-orm/type-tests/singlestore/tables.ts:930-937
Query Result Inference and Nullability
Projection Shapes and Operator Mappings
Type inference validation for SELECT projection shapes ensures that raw SQL expressions, column selections, and operator results resolve to exact TypeScript types across all database dialects. When executing projections with db.select(), expressions mapped via template literals or custom mapping functions propagate their types directly to the resulting tuple array. For instance, sql templates combined with .mapWith() map raw driver values into structured objects or specific scalar types such as numbers, booleans, or custom record shapes.
const allOperators = await db
.select({
col2: sql`5 - ${users.id} + 1`, // unknown
col3: sql<number>`${users.id} + 1`, // number
col33: sql`${users.id} + 1`.mapWith(users.id), // number
col34: sql`${users.id} + 1`.mapWith(mapFunkyFuncResult), // number
col4: sql<string | number>`one_or_another(${users.id}, ${users.class})`, // string | number
col5: sql`true`, // unknown
col6: sql<boolean>`true`, // boolean
col7: sql<number>`random()`, // number
col8: sql`some_funky_func(${users.id})`.mapWith(mapFunkyFuncResult), // { foo: string }
col9: sql`greatest(${users.createdAt}, ${sql.param(new Date(), users.createdAt)})`, // unknown
col10: sql<Date | boolean>`date_or_false(${users.createdAt}, ${
sql.param(
new Date(),
users.createdAt,
)
})`, // Date | boolean
col11: sql`${users.age1} + ${age}`, // unknown
col12: sql`${users.age1} + ${sql.param(age, users.age1)}`, // unknown
col13: sql`lower(${users.class})`, // unknown
col14: sql<number>`length(${users.class})`, // number
count: sql<number>`count(*)::int`, // number
})
.from(users)Sources: drizzle-orm/type-tests/geldb/select.ts:412-436, drizzle-orm/type-tests/pg/select.ts:411-435, drizzle-orm/type-tests/singlestore/select.ts:354-373
Note
Unannotated sql`` template expressions without explicit type generics or .mapWith()calls resolve tounknownrather than defaulting toany` or primitive types.
Sources: drizzle-orm/type-tests/geldb/select.ts:414-414, drizzle-orm/type-tests/pg/select.ts:413-413, drizzle-orm/type-tests/sqlite/select.ts:367-367
Conditional SPREAD Properties and Optional Fields
Conditional object spreading inside select projections correctly introduces optional property modifiers into the resulting row type. When an object property is conditionally included using a boolean flag, the resulting type infers an optional field modifier rather than an explicit undefined union type.
{
const authenticated = false as boolean;
const result = await db
.select({
id: users.id,
...(authenticated ? { city: users.homeCity } : {}),
})
.from(users);
Expect<
Equal<
{
id: number;
city?: number;
}[],
typeof result
>
>;
}Sources: drizzle-orm/type-tests/mysql/select.ts:526-545
Warning
Choking query modifiers such as .where() or .for() enforce strict AST builder constraints: invoking .where() multiple times or chaining configuration options like .for('share', { noWait: true, skipLocked: true }) triggers compilation errors via @ts-expect-error.
Sources: drizzle-orm/type-tests/mysql/select.ts:547-608
Relational Joins and Aliased Scopes
Subquery Type Inference and Constraint Validation
Type definitions for derived tables and subqueries enforce strict structural verification across dialect implementations, ensuring that joined scopes, set operators, and inline selections preserve correct nullability and projection shapes. When queries are aliased via .as('aliasName'), their exported shape maps precisely to the columns selected within the builder block. For example, subqueries built with explicit grouping or aggregation correctly propagate column types and handle nullable projections resulting from outer or left join operations.
const n1 = db
.select({
id: users.id,
name: users.name,
authorId: users.managerId,
count1: sql<number>`count(1)::int`.as('count1'),
})
.from(users)
.groupBy(users.id, users.name, users.managerId)
.as('n1');Sources: drizzle-orm/type-tests/mysql/subquery.ts:21-30, drizzle-orm/type-tests/pg/subquery.ts:21-30
Warning
Attempting to project multiple columns inside a scalar subquery passed to .select() triggers a compile-time DrizzleTypeError containing the message 'You can only select one column in the subquery', preventing invalid multi-column scalar expansions.
Sources: drizzle-orm/type-tests/mysql/subquery.ts:118-124, drizzle-orm/type-tests/pg/subquery.ts:118-124
Aliased Table Scopes and NodeNext Join Resolution
Aliasing tables using the dialect-specific alias() helper enables self-joins and complex graph relationships while maintaining distinct object keys in result tuples. Under NodeNext module resolution and mock configurations across SingleStore, Gel, PostgreSQL, MySQL, and SQLite, table joins against aliased references correctly namespace the resulting row objects.
| Dialect Module | Alias Helper Import Path | View Constructor | Mock Driver Method |
|---|---|---|---|
| PostgreSQL | ~/pg-core/alias.ts | pgView('name') | pgd.mock() |
| MySQL / SingleStore | ~/mysql-core/alias.ts | mysqlView('name') | mysqld.mock() |
| SQLite | ~/sqlite-core/alias.ts | sqliteView('name') | sqlited.mock() |
| Gel / GelDB | ~/gel-core/index.ts | — | drizzle.mock() |
Sources: drizzle-orm/type-tests/common/aliased-table.ts:1-31, integration-tests/type-tests/join-nodenext/gel.ts:1-21, integration-tests/type-tests/join-nodenext/singlestore.ts:1-20
When performing an innerJoin or leftJoin between a primary table and an aliased table or view, the inferred result structure indexes both the base table and the alias or view identifier as separate properties, with left-joined secondary relations correctly typed as nullable unions (T | null).
const pgRes = await pg.select().from(pgUsers).leftJoin(pgAlias, eq(pgAlias.id, pgUsers.id));Sources: drizzle-orm/type-tests/common/aliased-table.ts:32-32
Note
View definitions created via .as((qb) => qb.select().from(...)) inherit the complete column schema of their underlying source tables, allowing view aliases to participate in joins identically to base table aliases.
Sources: drizzle-orm/type-tests/common/aliased-table.ts:20-22, drizzle-orm/type-tests/common/aliased-table.ts:36-38
Strict Null Check Compiler Compatibility
Column Builder Schema under Relaxed Null Settings
To verify type correctness when TypeScript's strictNullChecks flag is disabled, the test suite defines standard table structures across PostgreSQL, MySQL, SQLite, SingleStore, and GelDB. Each table specification includes primary keys, generated columns, default functions, not-null constraints, nullable descriptions, and database default values.
| Column Name | Data Type | Constraint / Modifier |
|---|---|---|
id | text | Primary Key, Generated Always (.generatedAlwaysAs('genstr')) |
intId | integer | Primary Key, Generated Identity (.generatedAlwaysAsIdentity()) |
int2Id | integer | Generated Default Identity (.generatedByDefaultAsIdentity()) |
name | text | Default Function (.$defaultFn(() => '' as string)) |
title | text | Not Null (.notNull()) |
description | text | Nullable (implicit) |
dbdef | text | Database Default (.default('dbdefval')) |
Sources: drizzle-orm/type-tests/geldb/no-strict-null-checks/test.ts:4-19, drizzle-orm/type-tests/pg/no-strict-null-checks/test.ts:4-19, drizzle-orm/type-tests/mysql/no-strict-null-checks/test.ts:4-15, drizzle-orm/type-tests/singlestore/no-strict-null-checks/test.ts:4-15, drizzle-orm/type-tests/sqlite/no-strict-null-checks/test.ts:4-15
Warning
Attempting to pass explicit values to generated columns such as id or intId during update and insert operations correctly triggers compiler errors guarded by // @ts-expect-error, verifying that database-generated fields remain protected even when strict null checks are turned off.
Sources: drizzle-orm/type-tests/geldb/no-strict-null-checks/test.ts:23-28, drizzle-orm/type-tests/pg/no-strict-null-checks/test.ts:33-41, drizzle-orm/type-tests/mysql/no-strict-null-checks/test.ts:37-44, drizzle-orm/type-tests/sqlite/no-strict-null-checks/test.ts:37-44
Mutation Validation and Partial Inserts
Mock database instances created via drizzle.mock() evaluate insert and update payloads against the column definitions. Omitting optional fields, nullable attributes, and default-backed properties is permitted across all supported dialects.
db.insert(test).values({
title: 'title',
});Sources: drizzle-orm/type-tests/geldb/no-strict-null-checks/test.ts:104-105, drizzle-orm/type-tests/pg/no-strict-null-checks/test.ts:104-105, drizzle-orm/type-tests/mysql/no-strict-null-checks/test.ts:64-65, drizzle-orm/type-tests/singlestore/no-strict-null-checks/test.ts:66-67, drizzle-orm/type-tests/sqlite/no-strict-null-checks/test.ts:64-65
Compiler Stress and Set Operators
Large-Scale Schema Inference
Type checking scalability under extreme column counts is verified using generated schemas containing up to 1,000 columns per table. Both MySQL and SingleStore definitions declare extensive numeric and identifier fields with primary keys, autoincrement flags, and default values to validate compiler overhead and memory consumption during type instantiation.
mysqlTable('test', {
col0: double('col1').primaryKey().autoincrement().default(0),
col1: double('col1').primaryKey().autoincrement().default(0),
col2: double('col1').primaryKey().autoincrement().default(0),
col3: double('col1').primaryKey().autoincrement().default(0),
col4: double('col1').primaryKey().autoincrement().default(0),
col5: double('col1').primaryKey().autoincrement().default(0),
col6: double('col1').primaryKey().autoincrement().default(0),
col8: double('col1').primaryKey().autoincrement().default(0),
col9: double('col1').primaryKey().autoincrement().default(0),
col10: double('col1').primaryKey().autoincrement().default(0),
col11: double('col1').primaryKey().autoincrement().default(0),
col12: double('col1').primaryKey().autoincrement().default(0),
col13: double('col1').primaryKey().autoincrement().default(0),
col14: double('col1').primaryKey().autoincrement().default(0),
col15: double('col1').primaryKey().autoincrement().default(0),
col16: double('col1').primaryKey().autoincrement().default(0),
col18: double('col1').primaryKey().autoincrement().default(0),
col19: double('col1').primaryKey().autoincrement().default(0),
col20: double('col1').primaryKey().autoincrement().default(0),
col21: double('col1').primaryKey().autoincrement().default(0),Sources: drizzle-orm/type-tests/mysql/1000columns.ts:3-23
singlestoreTable('test', {
col0: double('col1').primaryKey().autoincrement().default(0),
col1: double('col1').primaryKey().autoincrement().default(0),
col2: double('col1').primaryKey().autoincrement().default(0),
col3: double('col1').primaryKey().autoincrement().default(0),
col4: double('col1').primaryKey().autoincrement().default(0),
col5: double('col1').primaryKey().autoincrement().default(0),
col6: double('col1').primaryKey().autoincrement().default(0),
col8: double('col1').primaryKey().autoincrement().default(0),
col9: double('col1').primaryKey().autoincrement().default(0),
col10: double('col1').primaryKey().autoincrement().default(0),
col11: double('col1').primaryKey().autoincrement().default(0),
col12: double('col1').primaryKey().autoincrement().default(0),
col13: double('col1').primaryKey().autoincrement().default(0),
col14: double('col1').primaryKey().autoincrement().default(0),
col15: double('col1').primaryKey().autoincrement().default(0),
col16: double('col1').primaryKey().autoincrement().default(0),
col18: double('col1').primaryKey().autoincrement().default(0),
col19: double('col1').primaryKey().autoincrement().default(0),
col20: double('col1').primaryKey().autoincrement().default(0),
col21: double('col1').primaryKey().autoincrement().default(0),Sources: drizzle-orm/type-tests/singlestore/1000columns.ts:3-23
Note
These stress files do not execute runtime queries; their sole purpose is to push the TypeScript type engine to its limits when evaluating massive object types and union structures generated by dialect builders.
Sources: drizzle-orm/type-tests/mysql/1000columns.ts:1-23, drizzle-orm/type-tests/singlestore/1000columns.ts:1-23
Set Operator Combinations and Type Safety
SingleStore set operators (union, unionAll, intersect, except, and minus) enforce strict compile-time validation of column counts and compatible data types across combining statements. Mismatched selections are intercepted by // @ts-expect-error assertions.
| Set Operator Method / Function | Associated Type Constraint | Error Handling for Column / Type Mismatches |
|---|---|---|
.union() / union() | Equivalent projection count and compatible types | Intercepted via // @ts-expect-error |
.unionAll() / unionAll() | Equivalent projection count and compatible types | Intercepted via // @ts-expect-error |
.intersect() / intersect() | Equivalent projection count and compatible types | Intercepted via // @ts-expect-error |
.except() / .minus() | Equivalent projection count and compatible types | Intercepted via // @ts-expect-error |
Sources: drizzle-orm/type-tests/singlestore/set-operators.ts:8-66, drizzle-orm/type-tests/singlestore/set-operators.ts:159-234
Warning
Combining statements that alter column order or omit fields between queries will fail type verification; the engine requires identical tuple shapes or explicitly compatible mapped types across every branch of a set operation.
Sources: drizzle-orm/type-tests/singlestore/set-operators.ts:162-181, drizzle-orm/type-tests/singlestore/set-operators.ts:201-234
Related
On This Page
OverviewType Testing Assertions and HarnessTest Harness Mechanics and Assertion UtilitiesCross-Dialect Test StructureColumn and Table Schema VerificationBuilder Column Configurations and DefaultsEnum Array and Scalar Type InferenceQuery Result Inference and NullabilityProjection Shapes and Operator MappingsConditional SPREAD Properties and Optional FieldsRelational Joins and Aliased ScopesSubquery Type Inference and Constraint ValidationAliased Table Scopes and NodeNext Join ResolutionStrict Null Check Compiler CompatibilityColumn Builder Schema under Relaxed Null SettingsMutation Validation and Partial InsertsCompiler Stress and Set OperatorsLarge-Scale Schema InferenceSet Operator Combinations and Type SafetyRelated