27 KiB
Drizzle ORM Column Builder Differences: SQLite vs PostgreSQL vs MySQL
Research based on drizzle-orm@0.38.4 source in node_modules/drizzle-orm.
1. Column Types by Dialect
SQLite (drizzle-orm/sqlite-core/columns/)
| Function | columnType | dataType | Notes |
|---|---|---|---|
integer() |
SQLiteInteger |
number |
mode: 'number' (default) |
integer() |
SQLiteTimestamp |
date |
mode: 'timestamp' or 'timestamp_ms' |
integer() |
SQLiteBoolean |
boolean |
mode: 'boolean' |
text() |
SQLiteText |
string |
mode: 'text' (default), supports enum, length |
text() |
SQLiteTextJson |
json |
mode: 'json' |
real() |
SQLiteReal |
number |
- |
blob() |
SQLiteBlobJson |
json |
mode: 'json' (default) |
blob() |
SQLiteBlobBuffer |
buffer |
mode: 'buffer' |
blob() |
SQLiteBigInt |
bigint |
mode: 'bigint' |
numeric() |
SQLiteNumeric |
string |
- |
customType() |
SQLiteCustomColumn |
custom |
- |
PostgreSQL (drizzle-orm/pg-core/columns/)
| Function | columnType | dataType | Notes |
|---|---|---|---|
integer() |
PgInteger |
number |
- |
smallint() |
PgSmallInt |
number |
- |
bigint() |
PgBigInt |
bigint |
|
serial() |
PgSerial |
number |
notNull + hasDefault built-in |
smallserial() |
PgSmallSerial |
number |
notNull + hasDefault built-in |
bigserial() |
PgBigSerial |
bigint |
notNull + hasDefault built-in |
boolean() |
PgBoolean |
boolean |
- |
text() |
PgText |
string |
supports enum |
varchar() |
PgVarchar |
string |
supports enum, length |
char() |
PgChar |
string |
supports length, enum |
numeric() |
PgNumeric |
string |
precision/scale config |
real() |
PgReal |
number |
- |
doublePrecision() |
PgDoublePrecision |
number |
- |
json() |
PgJson |
json |
- |
jsonb() |
PgJsonb |
json |
PG-specific |
uuid() |
PgUUID |
string |
has .defaultRandom() |
date() |
PgDate/PgDateString |
date/string |
mode toggle |
timestamp() |
PgTimestamp/PgTimestampString |
date/string |
mode, withTimezone, precision |
time() |
PgTime/PgTimeString |
string |
precision, withTimezone |
interval() |
PgInterval |
string |
PG-specific |
inet() |
PgInet |
string |
PG network type |
cidr() |
PgCidr |
string |
PG network type |
macaddr() |
PgMacaddr |
string |
PG network type |
macaddr8() |
PgMacaddr8 |
string |
PG network type |
point() |
PgPoint |
string |
PG geometric type |
line() |
PgLine |
string |
PG geometric type |
pgEnum() |
PgEnumColumn |
string |
Requires pre-declared enum |
customType() |
PgCustomColumn |
custom |
- |
MySQL (drizzle-orm/mysql-core/columns/)
| Function | columnType | dataType | Notes |
|---|---|---|---|
int() |
MySqlInt |
number |
unsigned config, .autoincrement() |
smallint() |
MySqlSmallInt |
number |
unsigned, .autoincrement() |
mediumint() |
MySqlMediumInt |
number |
unsigned, .autoincrement() |
tinyint() |
MySqlTinyInt |
number |
unsigned, .autoincrement() |
bigint() |
MySqlBigInt |
bigint |
unsigned, mode toggle |
serial() |
MySqlSerial |
number |
autoIncrement+primaryKey+notNull+default |
boolean() |
MySqlBoolean |
boolean |
- |
float() |
MySqlFloat |
number |
precision config |
double() |
MySqlDouble |
number |
precision config |
decimal() |
MySqlDecimal |
string |
precision/scale config |
real() |
MySqlReal |
number |
- |
text() |
MySqlText |
string |
supports enum, textType variants |
tinytext() |
MySqlText |
string |
textType: 'tinytext' |
mediumtext() |
MySqlText |
string |
textType: 'mediumtext' |
longtext() |
MySqlText |
string |
textType: 'longtext' |
varchar() |
MySqlVarChar |
string |
length, enum |
char() |
MySqlChar |
string |
length, enum |
json() |
MySqlJson |
json |
- |
date() |
MySqlDate/MySqlDateString |
date/string |
mode toggle |
datetime() |
MySqlDateTime/MySqlDateTimeString |
date/string |
mode, fsp |
timestamp() |
MySqlTimestamp/MySqlTimestampString |
date/string |
mode, fsp |
time() |
MySqlTime/MySqlTimeString |
string |
fsp |
binary() |
MySqlBinary |
buffer |
length config |
varbinary() |
MySqlVarBinary |
buffer |
length config |
year() |
MySqlYear |
number |
- |
mysqlEnum() |
MySqlEnumColumn |
string |
inline enum values |
customType() |
MySqlCustomColumn |
custom |
- |
2. Builder API Naming Differences
Factory Function Names
The same concept has different factory function names per dialect. A tool like drizzlebox that must handle all three cannot assume a universal name space.
| Concept | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
| Integer | integer |
integer |
int |
| Small int | — | smallint |
smallint |
| Big int | — | bigint |
bigint |
| Auto-increment | .primaryKey() on integer |
serial/bigserial/smallserial |
.autoincrement() on int etc., or serial |
| Boolean | integer({ mode: 'boolean' }) |
boolean |
boolean / tinyint |
| Text/varchar | text |
text / varchar |
text / varchar |
| JSON | text({ mode: 'json' }) / blob({ mode: 'json' }) |
json / jsonb |
json |
| Enum | text({ enum: [...] }) |
pgEnum (separate declaration) |
mysqlEnum |
| Timestamp | integer({ mode: 'timestamp' }) |
timestamp |
timestamp / datetime |
| UUID | text() (manual) |
uuid |
— |
| Numeric/decimal | numeric |
numeric / decimal |
decimal |
| Real/float | real |
real |
float / double / real |
Builder Class Names
All builder classes are dialect-prefixed:
- SQLite:
SQLiteTextBuilder,SQLiteIntegerBuilder,SQLiteColumnBuilder(base) - PG:
PgTextBuilder,PgIntegerBuilder,PgColumnBuilder(base) - MySQL:
MySqlTextBuilder,MySqlIntBuilder,MySqlColumnBuilder(base),MySqlColumnBuilderWithAutoIncrement
Column Class Names (the columnType field)
All column classes are also dialect-prefixed:
- SQLite:
SQLiteText,SQLiteInteger,SQLiteTimestamp,SQLiteBoolean,SQLiteTextJson,SQLiteReal,SQLiteNumeric,SQLiteBlobJson,SQLiteBlobBuffer,SQLiteBigInt,SQLiteCustomColumn - PG:
PgText,PgInteger,PgBoolean,PgJson,PgJsonb,PgUUID,PgEnumColumn,PgNumeric,PgVarchar,PgSerial, etc. - MySQL:
MySqlText,MySqlInt,MySqlBoolean,MySqlJson,MySqlEnumColumn,MySqlDecimal,MySqlVarChar,MySqlSerial, etc.
3. Shared Column Builder API (from base ColumnBuilder)
All three dialect builder hierarchies share a common base class ColumnBuilder (in drizzle-orm/column-builder.d.ts) that provides these universal methods:
| Method | Description |
|---|---|
.notNull() |
Makes column not null |
.default(value) |
Set a default value |
.$defaultFn(fn) / .$default |
Dynamic runtime default |
.$onUpdateFn(fn) / .$onUpdate |
Dynamic runtime update value |
.primaryKey() |
Makes column a primary key (implies notNull) |
.$type<T>() |
Override the TypeScript type |
.generatedAlwaysAs(as, config?) |
Generated column (overridden per-dialect) |
The ColumnBuilderRuntimeConfig shared by all dialects:
{
name: string;
keyAsName: boolean;
notNull: boolean;
default: TData | SQL | undefined;
defaultFn: (() => TData | SQL) | undefined;
onUpdateFn: (() => TData | SQL) | undefined;
hasDefault: boolean;
primaryKey: boolean;
isUnique: boolean;
uniqueName: string | undefined;
uniqueType: string | undefined;
dataType: string;
columnType: string;
generated: GeneratedColumnConfig<TData> | undefined;
generatedIdentity: GeneratedIdentityConfig | undefined;
}
The ColumnBaseConfig (shared, on the Column side):
{
name: string;
tableName: string;
dataType: ColumnDataType; // 'string' | 'number' | 'boolean' | 'array' | 'json' | 'date' | 'bigint' | 'custom' | 'buffer'
columnType: string; // Dialect-specific e.g. 'PgText', 'SQLiteInteger'
data: unknown;
driverParam: unknown;
notNull: boolean;
hasDefault: boolean;
isPrimaryKey: boolean;
isAutoincrement: boolean;
hasRuntimeDefault: boolean;
enumValues: string[] | undefined;
}
4. Dialect-Specific Differences
4.1 Dialect Discriminator
Every builder and column carries a dialect type tag in its TTypeConfig:
- SQLite:
{ dialect: 'sqlite' } - PG:
{ dialect: 'pg' } - MySQL:
{ dialect: 'mysql' }
This is used in the BuildColumn conditional type in column-builder.d.ts to route to the correct column class.
4.2 SQLite-Specific
integer()modes: Theinteger()function accepts{ mode: 'number' | 'timestamp' | 'timestamp_ms' | 'boolean' }. Based on mode, it returns different builder classes (SQLiteIntegerBuilder,SQLiteTimestampBuilder,SQLiteBooleanBuilder). This is a compile-time type-level dispatch, not a runtime polymorphic thing.text()modes:text()accepts{ mode: 'text' | 'json' }. Withmode: 'json', it returnsSQLiteTextJsonBuilder(dataType'json'), which hasmapFromDriverValue/mapToDriverValuefor JSON serialization.blob()modes:blob()accepts{ mode: 'buffer' | 'json' | 'bigint' }. Default is'json'(not'buffer'!).- No native boolean/date types: SQLite uses
integerwith mode overrides instead of dedicated boolean or date column classes. primaryKey()on integer: SQLite'sprimaryKey()onintegerimplies auto-increment (ROWID). TheSQLiteBaseIntegerBuilderhas aPrimaryKeyConfigwithautoIncrement?: booleanandonConflict.generatedAlwaysAs: Accepts{ mode?: 'virtual' | 'stored' }config for generated columns.- Enum handling: Enums are just
text({ enum: [...] })— a constraint ontext, not a separate type.
4.3 PostgreSQL-Specific
pgEnum: Enums are a top-level declaration, not a column config option. You callpgEnum('name', ['val1', 'val2'])at module scope to create aPgEnumobject, then use it as a column:myEnum(). This is fundamentally different from SQLite/MySQL enum handling.- Array support:
PgColumnBuilderhas an.array(size?)method that returns aPgArrayBuilder. No other dialect has this. .unique()with nulls: PG's.unique()accepts{ nulls: 'distinct' | 'not distinct' }— a PG-specific option.timestamp/timestamptz:timestamp()has{ withTimezone?: boolean, precision?: number, mode?: 'date' | 'string' }. Withmode: 'string', returnsPgTimestampStringBuilder(dataType'string').jsonb: PG-specific JSON storage type distinct fromjson.uuid: Has.defaultRandom()method shortcut fordefaultRANDOM().- Identity columns: PG supports
generatedAlwaysAsIdentity()/generatedByDefaultAsIdentity()(not justgeneratedAlwaysAs), using PG sequences. - Index operator classes:
ExtraConfigColumnon PG columns supports.asc(),.desc(),.nullsFirst(),.nullsLast(),.op(opClass)for index definitions. - Network/geometry types:
inet,cidr,macaddr,macaddr8,point,line— all PG-only. interval: PG-specific date/time interval type.date(): Has{ mode: 'date' | 'string' }similar to timestamp.
4.4 MySQL-Specific
.autoincrement(): MySQL int types inherit fromMySqlColumnBuilderWithAutoIncrementinstead of plainMySqlColumnBuilder. This adds an.autoincrement()method.serial()is shorthand forint().primaryKey().notNull().default(autoincrement).unsigned: MySQL int types (int,smallint,mediumint,tinyint,bigint) accept{ unsigned?: boolean }.generatedAlwaysAs: Accepts{ mode?: 'virtual' | 'stored' }like SQLite.- Enum handling:
mysqlEnum(values)is a standalone column factory (like PG'spgEnum), but takes inline values instead of a pre-declared enum object. - Text variants:
tinytext(),mediumtext(),longtext()are MySQL-specific shorthands. - Datetime types:
datetime()is MySQL-specific (distinct fromtimestamp). Both have{ mode?: 'date' | 'string', fsp?: number }. year(): MySQL-specific.- Binary types:
binary(),varbinary()with length — MySQL-specific. - No array type: MySQL has no
.array()method.
4.5 Column Builder Inheritance Differences
ColumnBuilder (base, drizzle-orm/column-builder.js)
├── SQLiteColumnBuilder (sqlite-core/columns/common.js)
│ ├── SQLiteTextBuilder
│ ├── SQLiteTextJsonBuilder
│ ├── SQLiteBaseIntegerBuilder (adds .primaryKey() with autoIncrement config)
│ │ ├── SQLiteIntegerBuilder
│ │ ├── SQLiteTimestampBuilder
│ │ └── SQLiteBooleanBuilder
│ ├── SQLiteRealBuilder
│ ├── SQLiteNumericBuilder
│ ├── SQLiteBlobBufferBuilder / SQLiteBlobJsonBuilder / SQLiteBigIntBuilder
│ └── SQLiteCustomColumnBuilder
│
├── PgColumnBuilder (pg-core/columns/common.js, adds .array(), .unique() with nulls)
│ ├── PgTextBuilder, PgVarcharBuilder, PgCharBuilder
│ ├── PgIntegerBuilder, PgSmallIntBuilder, etc.
│ ├── PgBooleanBuilder
│ ├── PgJsonBuilder, PgJsonbBuilder
│ ├── PgUUIDBuilder (adds .defaultRandom())
│ ├── PgEnumColumnBuilder
│ ├── PgDateColumnBaseBuilder (adds .defaultNow())
│ │ ├── PgTimestampBuilder
│ │ └── PgDateBuilder
│ ├── PgNumericBuilder
│ ├── PgCustomColumnBuilder
│ └── ...network/geometry types
│
└── MySqlColumnBuilder (mysql-core/columns/common.js)
├── MySqlColumnBuilderWithAutoIncrement (adds .autoincrement())
│ ├── MySqlIntBuilder
│ ├── MySqlSmallIntBuilder
│ ├── MySqlSerialBuilder
│ └── ...other int types
├── MySqlTextBuilder
├── MySqlVarCharBuilder
├── MySqlBooleanBuilder
├── MySqlJsonBuilder
├── MySqlEnumColumnBuilder
├── MySqlDateColumnBaseBuilder (adds .defaultNow())
│ ├── MySqlTimestampBuilder
│ ├── MySqlDateTimeBuilder
│ └── MySqlDateBuilder
├── MySqlDecimalBuilder
├── MySqlCustomColumnBuilder
└── ...MySQL-specific types
5. Table Creation Functions
Common Pattern
All three dialects use the same general pattern:
dialectTable(name, columns, extraConfig?)
dialectTable(name, (columnTypes) => columns, extraConfig?)
| Dialect | Table Function | Schema Variant |
|---|---|---|
| SQLite | sqliteTable |
sqliteTableCreator(fn) |
| PG | pgTable |
pgTableCreator(fn) |
| MySQL | mysqlTable |
mysqlTableCreator(fn) |
Key differences:
- PG:
pgTableextra config getsBuildExtraConfigColumns(columns getExtraConfigColumnwith.asc(),.desc(),.nullsFirst(),.nullsLast(),.op()for index op classes). Also supports.enableRLS(). - SQLite/MySQL: Extra config gets
BuildColumnswithout theExtraConfigColumnwrapper. - SQLite: Extra config types include
IndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder | UniqueConstraintBuilder. - PG: Extra config types include
AnyIndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder | UniqueConstraintBuilder | PgPolicy. - MySQL: Extra config types include
AnyIndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder | UniqueConstraintBuilder.
The column builder callback receives dialect-specific column builders:
// SQLite
sqliteTable('users', (t) => ({ id: t.integer().primaryKey() }))
// PG
pgTable('users', (t) => ({ id: t.serial().primaryKey() }))
// MySQL
mysqlTable('users', (t) => ({ id: t.serial() }))
6. Key Takeaways for a Transform Registry / DbType Approach
6.1 The columnType String is the Key Discriminator
Each column has a unique columnType string (e.g., 'SQLiteText', 'PgJsonb', 'MySqlInt'). This is the most reliable way to identify a column's dialect and specific type at runtime. The dataType field ('string' | 'number' | 'boolean' | 'date' | 'json' | 'bigint' | 'array' | 'buffer' | 'custom') is shared across dialects but too coarse for schema generation.
6.2 The dataType Enum is the Universal Type Categories
The ColumnDataType union is:
'string' | 'number' | 'boolean' | 'array' | 'json' | 'date' | 'bigint' | 'custom' | 'buffer'
This can serve as a common "logical type" for cross-dialect schema generation, but you need dialect-specific handling for:
'string'columns that are enums (checkenumValues)'date'columns (timestamp vs date, timezone handling)'json'columns (json vs jsonb, mode-based detection)'number'columns (integer vs float vs decimal semantics)
6.3 Enum Handling is Radically Different
| Aspect | SQLite | PG | MySQL |
|---|---|---|---|
| Declaration | text({ enum: [...] }) |
pgEnum() at module scope |
mysqlEnum(values) inline |
| Column builder | SQLiteTextBuilder |
PgEnumColumnBuilder |
MySqlEnumColumnBuilder |
| columnType | SQLiteText |
PgEnumColumn |
MySqlEnumColumn |
| Storage | TEXT with enum constraint | Native CREATE TYPE ... AS ENUM |
ENUM column type |
enumValues |
Set on builder config | Set via PgEnum instance |
Set on builder constructor |
For a schema generator, all three surfaces the enumValues field on the built column, so you can reliably extract enum values regardless of dialect. But the declaration style varies, and PG requires a pre-declared enum type.
6.4 Mode-Based Polymorphism
Several column types use a mode config to change the TypeScript type at compile time:
| Dialect | Column | Modes | Effect on dataType |
|---|---|---|---|
| SQLite | integer() |
'number' / 'timestamp' / 'timestamp_ms' / 'boolean' |
number / date / boolean |
| SQLite | text() |
'text' / 'json' |
string / json |
| SQLite | blob() |
'buffer' / 'json' / 'bigint' |
buffer / json / bigint |
| PG | timestamp() |
'date' / 'string' |
date / string |
| PG | date() |
'date' / 'string' |
date / string |
| PG | time() |
'string' (default) / 'date'? |
string / date |
| MySQL | timestamp() |
'date' / 'string' |
date / string |
| MySQL | datetime() |
'date' / 'string' |
date / string |
| MySQL | date() |
'date' / 'string' |
date / string |
| MySQL | bigint() |
'number' / 'bigint' |
number / bigint |
The mode is not stored in the dataType field on the column config directly — it's stored in the dialect-specific runtime config. The dataType on the built column does reflect the mode correctly (because each mode produces a different builder class with a different dataType).
6.5 Runtime Config Differences
Every column builder's config object extends ColumnBuilderRuntimeConfig with dialect-specific fields:
- SQLite integer:
{ autoIncrement: boolean }(onSQLiteBaseInteger) - SQLite text:
{ length?: number, enumValues?: string[] } - PG varchar:
{ length?: number, enumValues?: string[] } - PG timestamp:
{ withTimezone: boolean, precision?: number } - PG numeric:
{ precision?: number, scale?: number } - MySQL int:
{ unsigned?: boolean }plus{ autoIncrement: boolean } - MySQL text:
{ textType: 'tinytext' | 'text' | 'mediumtext' | 'longtext', enumValues?: string[] } - MySQL timestamp/datetime:
{ fsp?: number }
6.6 Implications for a DbType / Transform Registry
Recommended approach:
- Use
dataTypeas primary classification — it's the cross-dialect enum that maps naturally to TypeBox schema types. - Use
columnTypefor dialect-specific dispatch — when you need to handle a column differently based on its SQL type (e.g.,PgJsonbvsMySqlJson). - Check
enumValuesto detect enum columns regardless of how they were declared. - Don't rely on builder class hierarchy — the builders are compile-time types that don't exist at runtime in a useful way for introspection. Instead, inspect the built column's properties (
dataType,columnType,enumValues, runtime config). - Mode detection: For columns with mode variants, the
dataTypealready reflects the mode (e.g.,integer({ mode: 'boolean' })producesdataType: 'boolean'), so you don't need to check mode separately for most schema generation. - Handle PG arrays specially: PG's
.array()wraps any column type into an array type withdataType: 'array'. The base column type info is preserved inconfig.baseBuilder. - PG identity columns: PG has
generatedIdentityon columns (viageneratedAlwaysAsIdentity()/generatedByDefaultAsIdentity()), which is separate from regular defaults. Checkcolumn.generatedIdentityorbuilder._.identity.
6.7 Column Introspection at Runtime
At runtime, all built columns extend Column which has these useful properties:
column.name // Column name
column.dataType // ColumnDataType: 'string' | 'number' | ...
column.columnType // Dialect-specific string: 'PgJsonb', 'SQLiteText', etc.
column.notNull // boolean
column.hasDefault // boolean
column.primary // boolean (isPrimaryKey)
column.default // Default value or SQL
column.enumValues // string[] | undefined
column.isUnique // boolean
column.uniqueName // string | undefined
column.generated // GeneratedColumnConfig | undefined
Plus dialect-specific properties accessible via the column class (e.g., column.withTimezone on PgTimestamp, column.mode on SQLiteTimestamp, etc.).
6.8 The entityKind Discriminator
Every builder and column class has a static [entityKind] string that uniquely identifies the class. This can be used for runtime type checking:
'SQLiteTextBuilder' / 'SQLiteText'
'PgTextBuilder' / 'PgText'
'MySqlTextBuilder' / 'MySqlText'
'PgColumnBuilder' (base)
'SQLiteColumnBuilder' (base)
'MySqlColumnBuilder' (base)
// etc.
This is set via entityKind symbol from drizzle-orm/entity.js.
7. Summary Table: Cross-Dialect Type Mapping
| Logical Type | SQLite Factory | PG Factory | MySQL Factory | dataType |
|---|---|---|---|---|
| String | text() |
text() / varchar() |
text() / varchar() |
string |
| Enum | text({ enum: [...] }) |
pgEnum()() |
mysqlEnum()() |
string |
| JSON | text({ mode: 'json' }) |
json() / jsonb() |
json() |
json |
| Number | integer() / real() |
integer() / real() |
int() / float() |
number |
| BigInt | blob({ mode: 'bigint' }) |
bigint() |
bigint() |
bigint |
| Boolean | integer({ mode: 'boolean' }) |
boolean() |
boolean() / tinyint() |
boolean |
| Date | integer({ mode: 'timestamp' }) |
timestamp() |
timestamp() / datetime() |
date |
| UUID | text() (manual) |
uuid() |
— | string |
| Decimal | numeric() |
numeric() / decimal() |
decimal() |
string |
| Buffer | blob({ mode: 'buffer' }) |
— | binary() / varbinary() |
buffer |
| Array | — | .array() on any col |
— | array |