docs: add architecture research for schema-first multi-dialect TypeBox/Drizzle bridge
This commit is contained in:
440
docs/research/dizzle-column-diffs.md
Normal file
440
docs/research/dizzle-column-diffs.md
Normal file
@@ -0,0 +1,440 @@
|
||||
# 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:
|
||||
|
||||
```ts
|
||||
{
|
||||
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):
|
||||
|
||||
```ts
|
||||
{
|
||||
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**: The `integer()` 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' }`. With `mode: 'json'`, it returns `SQLiteTextJsonBuilder` (dataType `'json'`), which has `mapFromDriverValue`/`mapToDriverValue` for JSON serialization.
|
||||
- **`blob()` modes**: `blob()` accepts `{ mode: 'buffer' | 'json' | 'bigint' }`. Default is `'json'` (not `'buffer'`!).
|
||||
- **No native boolean/date types**: SQLite uses `integer` with mode overrides instead of dedicated boolean or date column classes.
|
||||
- **`primaryKey()` on integer**: SQLite's `primaryKey()` on `integer` implies auto-increment (ROWID). The `SQLiteBaseIntegerBuilder` has a `PrimaryKeyConfig` with `autoIncrement?: boolean` and `onConflict`.
|
||||
- **`generatedAlwaysAs`**: Accepts `{ mode?: 'virtual' | 'stored' }` config for generated columns.
|
||||
- **Enum handling**: Enums are just `text({ enum: [...] })` — a constraint on `text`, not a separate type.
|
||||
|
||||
### 4.3 PostgreSQL-Specific
|
||||
|
||||
- **`pgEnum`**: Enums are a top-level declaration, not a column config option. You call `pgEnum('name', ['val1', 'val2'])` at module scope to create a `PgEnum` object, then use it as a column: `myEnum()`. This is fundamentally different from SQLite/MySQL enum handling.
|
||||
- **Array support**: `PgColumnBuilder` has an `.array(size?)` method that returns a `PgArrayBuilder`. 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' }`. With `mode: 'string'`, returns `PgTimestampStringBuilder` (dataType `'string'`).
|
||||
- **`jsonb`**: PG-specific JSON storage type distinct from `json`.
|
||||
- **`uuid`**: Has `.defaultRandom()` method shortcut for `defaultRANDOM()`.
|
||||
- **Identity columns**: PG supports `generatedAlwaysAsIdentity()`/`generatedByDefaultAsIdentity()` (not just `generatedAlwaysAs`), using PG sequences.
|
||||
- **Index operator classes**: `ExtraConfigColumn` on 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 from `MySqlColumnBuilderWithAutoIncrement` instead of plain `MySqlColumnBuilder`. This adds an `.autoincrement()` method. `serial()` is shorthand for `int().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's `pgEnum`), 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 from `timestamp`). 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:
|
||||
|
||||
```ts
|
||||
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**: `pgTable` extra config gets `BuildExtraConfigColumns` (columns get `ExtraConfigColumn` with `.asc()`, `.desc()`, `.nullsFirst()`, `.nullsLast()`, `.op()` for index op classes). Also supports `.enableRLS()`.
|
||||
- **SQLite/MySQL**: Extra config gets `BuildColumns` without the `ExtraConfigColumn` wrapper.
|
||||
- **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:
|
||||
|
||||
```ts
|
||||
// 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:
|
||||
```ts
|
||||
'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 (check `enumValues`)
|
||||
- `'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 }` (on `SQLiteBaseInteger`)
|
||||
- **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:**
|
||||
|
||||
1. **Use `dataType` as primary classification** — it's the cross-dialect enum that maps naturally to TypeBox schema types.
|
||||
2. **Use `columnType` for dialect-specific dispatch** — when you need to handle a column differently based on its SQL type (e.g., `PgJsonb` vs `MySqlJson`).
|
||||
3. **Check `enumValues`** to detect enum columns regardless of how they were declared.
|
||||
4. **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).
|
||||
5. **Mode detection**: For columns with mode variants, the `dataType` already reflects the mode (e.g., `integer({ mode: 'boolean' })` produces `dataType: 'boolean'`), so you don't need to check mode separately for most schema generation.
|
||||
6. **Handle PG arrays specially**: PG's `.array()` wraps any column type into an array type with `dataType: 'array'`. The base column type info is preserved in `config.baseBuilder`.
|
||||
7. **PG identity columns**: PG has `generatedIdentity` on columns (via `generatedAlwaysAsIdentity()`/`generatedByDefaultAsIdentity()`), which is separate from regular defaults. Check `column.generatedIdentity` or `builder._.identity`.
|
||||
|
||||
### 6.7 Column Introspection at Runtime
|
||||
|
||||
At runtime, all built columns extend `Column` which has these useful properties:
|
||||
|
||||
```ts
|
||||
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` |
|
||||
Reference in New Issue
Block a user