Drizzle ORM 自定义类型开发指南
前言
在现代应用开发中,数据库类型系统往往比编程语言的类型系统更加丰富。Drizzle ORM 作为一个类型安全的 ORM 框架,虽然已经内置了大量数据库类型支持,但在实际业务场景中,开发者仍然可能遇到需要自定义类型的情况。本文将深入讲解如何在 Drizzle ORM 中创建和使用自定义类型。
核心概念
在 Drizzle ORM 中,每个自定义类型的定义都涉及两个核心类:
- ColumnBuilder - 负责构建列定义,生成创建列所需的所有字段
- Column - 表示列本身,用于查询生成、迁移映射等操作
不同数据库模块有各自的实现类:
- PostgreSQL:
PgColumnBuilder
和PgColumn
- MySQL:
MySqlColumnBuilder
和MySqlColumn
- SQLite:
SQLiteColumnBuilder
和SQLiteColumn
构建器类详解
以 PostgreSQL 的 text 类型为例,构建器类需要:
- 指定 TypeScript 返回类型
- 重写 build 方法返回可用的列实例
export class PgTextBuilder<TData extends string = string>
extends PgColumnBuilder<
ColumnBuilderConfig<{ data: TData; driverParam: string }>
> {
protected $pgColumnBuilderBrand = 'PgTextBuilder';
build<TTableName extends string>(
table: AnyPgTable<{ name: TTableName }>,
): PgText<TTableName, TData> {
return new PgText(table, this.config);
}
}
关键点:
TData
泛型参数定义了列在 TypeScript 中的类型$pgColumnBuilderBrand
必须设置为构建器类名
列类详解
列类需要实现几个关键方法:
export class PgText<TTableName extends string, TData extends string>
extends PgColumn<ColumnConfig<{ tableName: TTableName; data: TData; driverParam: string }>> {
protected $pgColumnBrand = 'PgText';
constructor(table: AnyPgTable<{ name: TTableName }>, builder: PgTextBuilder<TData>['config']) {
super(table, builder);
}
getSQLType(): string {
return 'text';
}
override mapFromDriverValue(value: string): TData {
return value as TData;
}
override mapToDriverValue(value: TData): string {
return value;
}
}
关键方法:
getSQLType()
: 定义数据库中的实际类型名称mapFromDriverValue()
: 从数据库值到 TypeScript 值的转换mapToDriverValue()
: 从 TypeScript 值到数据库值的转换
完整示例:PostgreSQL 的 text 类型
import { ColumnConfig, ColumnBuilderConfig } from 'drizzle-orm';
import { AnyPgTable } from 'drizzle-orm/pg-core';
import { PgColumn, PgColumnBuilder } from './common';
export class PgTextBuilder<TData extends string = string>
extends PgColumnBuilder<
ColumnBuilderConfig<{ data: TData; driverParam: string }>
> {
// ...构建器实现
}
export class PgText<TTableName extends string, TData extends string>
extends PgColumn<
ColumnConfig<{ tableName: TTableName; data: TData; driverParam: string }>
> {
// ...列实现
}
export function text<T extends string = string>(
name: string,
): PgTextBuilder<T> {
return new PgTextBuilder(name);
}
实战案例:CITEXT 类型
CITEXT 是 PostgreSQL 的一个扩展类型,用于不区分大小写的文本比较。下面展示如何为其创建自定义类型:
export class PgCITextBuilder<TData extends string = string>
extends PgColumnBuilder<
PgColumnBuilderHKT,
ColumnBuilderConfig<{ data: TData; driverParam: string }>
> {
protected $pgColumnBuilderBrand = 'PgCITextBuilder';
build<TTableName extends string>(
table: AnyPgTable<{ name: TTableName }>
): PgCIText<TTableName, TData> {
return new PgCIText(table, this.config);
}
}
export class PgCIText<TTableName extends string, TData extends string>
extends PgColumn<
PgColumnHKT,
ColumnConfig<{ tableName: TTableName; data: TData; driverParam: string }>
> {
protected $pgColumnBrand = 'PgCIText';
getSQLType(): string {
return 'citext';
}
}
export function citext<T extends string = string>(name: string): PgCITextBuilder<T> {
return new PgCITextBuilder(name);
}
使用示例:
const users = pgTable('users', {
id: integer('id').primaryKey(),
username: citext('username') // 不区分大小写的用户名
});
最佳实践
- 类型安全:始终为自定义类型定义明确的 TypeScript 类型
- 命名规范:遵循
Pg[TypeName]Builder
和Pg[TypeName]
的命名约定 - 品牌标记:不要忘记设置
$pgColumnBuilderBrand
和$pgColumnBrand
- 值转换:在
mapFromDriverValue
和mapToDriverValue
中实现必要的类型转换
总结
通过 Drizzle ORM 的自定义类型机制,开发者可以轻松扩展 ORM 的类型系统,满足各种特殊业务需求。无论是 PostgreSQL 的扩展类型,还是业务特定的数据类型,都可以通过本文介绍的模式进行集成。这种设计既保持了核心库的精简,又提供了足够的扩展性,是 Drizzle ORM 强大而灵活的重要体现。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考