Drizzle ORM Schema定义与数据建模

【免费下载链接】drizzle-orm drizzle-team/drizzle-orm: 是一个基于 C++ 的 ORM(对象关系映射)库,支持 MySQL 和 SQLite 数据库。适合对 C++、数据库开发以及想要使用轻量级 ORM 的开发者。 【免费下载链接】drizzle-orm 项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm

本文全面介绍了Drizzle ORM的表结构定义与数据建模能力,重点阐述了其强大的类型系统架构、多数据库方言支持(PostgreSQL、MySQL、SQLite)以及丰富的数据类型映射。详细解析了主键、外键与索引的配置方法,包括单列主键、复合主键、级联操作和各类索引策略。深入探讨了关系建模与关联查询的实现机制,展示了一对一、一对多关系的定义方式以及使用with语法进行多层嵌套查询的方法。最后,系统介绍了自定义类型与扩展机制,包括如何创建自定义数据类型、值转换机制以及对PostGIS等数据库扩展的支持。

表结构定义与数据类型系统

Drizzle ORM 提供了一个强大而灵活的类型系统,让开发者能够以类型安全的方式定义数据库表结构。该系统支持多种数据库方言(PostgreSQL、MySQL、SQLite),并为每种数据库提供了专门优化的数据类型实现。

核心数据类型架构

Drizzle ORM 的数据类型系统建立在统一的抽象层之上,通过 ColumnBuilderBaseConfigColumnBaseConfig 接口定义了数据类型的基本结构:

export type ColumnDataType =
  | 'string'
  | 'number'
  | 'boolean'
  | 'array'
  | 'json'
  | 'date'
  | 'bigint'
  | 'custom'
  | 'buffer'
  | 'dateDuration'
  | 'duration'
  | 'relDuration'
  | 'localTime'
  | 'localDate'
  | 'localDateTime';

每个数据类型都遵循 Builder 模式,通过链式调用方法配置列的属性:

mermaid

多数据库方言支持

Drizzle ORM 为不同的数据库方言提供了专门的数据类型实现:

PostgreSQL 数据类型

PostgreSQL 提供了丰富的数据类型支持,包括:

// 基本数值类型
const users = pgTable('users', {
  id: integer('id').primaryKey(),
  age: smallint('age'),
  price: numeric('price', { precision: 10, scale: 2 }),
  balance: decimal('balance'),
});

// 字符串和文本类型
const posts = pgTable('posts', {
  title: varchar('title', { length: 255 }),
  content: text('content'),
  category: char('category', { length: 1 }),
});

// 特殊类型
const profiles = pgTable('profiles', {
  uuid: uuid('uuid').default(sql`gen_random_uuid()`),
  metadata: jsonb('metadata'),
  ipAddress: inet('ip_address'),
  createdAt: timestamp('created_at').defaultNow(),
});
MySQL 数据类型

MySQL 的数据类型系统针对 MySQL 特有的功能进行了优化:

const products = mysqlTable('products', {
  id: int('id').primaryKey().autoincrement(),
  name: varchar('name', { length: 100 }),
  price: decimal('price', { precision: 10, scale: 2 }),
  description: text('description'),
  isActive: boolean('is_active').default(true),
  tags: json('tags'),
  created: datetime('created').default(sql`CURRENT_TIMESTAMP`),
});

// 支持无符号整数
const statistics = mysqlTable('statistics', {
  views: int('views', { unsigned: true }),
  clicks: bigint('clicks', { unsigned: true }),
});
SQLite 数据类型

SQLite 使用灵活的类型系统,Drizzle 提供了相应的抽象:

const notes = sqliteTable('notes', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  isPinned: integer('is_pinned', { mode: 'boolean' }),
  createdAt: integer('created_at', { mode: 'timestamp' }).defaultNow(),
  updatedAt: integer('updated_at', { mode: 'timestamp_ms' }),
});

数据类型映射表

下表展示了 Drizzle ORM 数据类型到不同数据库方言的映射关系:

Drizzle 类型 PostgreSQL MySQL SQLite TypeScript 类型
integer() integer int integer number
bigint() bigint bigint integer bigint
varchar() varchar(n) varchar(n) text string
text() text text text string
boolean() boolean tinyint(1) integer boolean
json() json json text any
jsonb() jsonb - - any
timestamp() timestamp timestamp integer Date
date() date date text Date
uuid() uuid char(36) text string

高级类型特性

枚举类型支持

Drizzle ORM 提供了强大的枚举类型支持,可以在编译时确保类型安全:

// PostgreSQL 枚举
const statusEnum = pgEnum('status', ['active', 'inactive', 'pending']);

const users = pgTable('users', {
  status: statusEnum('status').default('pending'),
});

// MySQL 枚举
const userStatus = mysqlEnum('user_status', ['active', 'inactive']);

const mysqlUsers = mysqlTable('users', {
  status: userStatus('status'),
});
自定义类型

开发者可以创建自定义数据类型来处理特定的业务需求:

const emailType = customType<{ data: string; driverData: string }>({
  dataType: () => 'varchar(255)',
  toDriver: (value) => value.toLowerCase(),
  fromDriver: (value) => value,
});

const users = pgTable('users', {
  email: emailType('email').notNull().unique(),
});
数组类型

支持数组类型的列定义:

const posts = pgTable('posts', {
  tags: text('tags').array(),
  ratings: integer('ratings').array(),
  metadata: jsonb('metadata').array(),
});

类型安全的链式配置

Drizzle ORM 的链式配置方法提供了完整的类型安全:

const products = pgTable('products', {
  id: integer('id')
    .primaryKey()
    .$type<ProductID>(),  // 自定义类型标记
  
  name: varchar('name', { length: 100 })
    .notNull()
    .default('Unnamed Product'),
  
  price: numeric('price', { precision: 10, scale: 2 })
    .notNull()
    .check(sql`price >= 0`),
  
  createdAt: timestamp('created_at')
    .notNull()
    .defaultNow(),
  
  updatedAt: timestamp('updated_at')
    .notNull()
    .defaultNow()
    .$onUpdateFn(() => new Date()),
});

数据库特定的类型特性

PostgreSQL 特有功能
// 序列支持
const users = pgTable('users', {
  id: serial('id').primaryKey(),
  bigId: bigserial('big_id'),
});

// 几何类型
const locations = pgTable('locations', {
  point: point('coordinates'),
  line: line('path'),
});

// 网络地址类型
const devices = pgTable('devices', {
  mac: macaddr('mac_address'),
  ip: inet('ip_address'),
});
MySQL 特有功能
// 无符号整数
const metrics = mysqlTable('metrics', {
  value: int('value', { unsigned: true }),
});

// 年类型
const events = mysqlTable('events', {
  year: year('year'),
});

// 设置类型(通过枚举模拟)
const permissions = mysqlTable('permissions', {
  flags: mysqlEnum('flags', ['read', 'write', 'execute']),
});
SQLite 特有功能
// 布尔值模式
const settings = sqliteTable('settings', {
  enabled: integer('enabled', { mode: 'boolean' }),
});

// 时间戳模式
const logs = sqliteTable('logs', {
  timestamp: integer('timestamp', { mode: 'timestamp' }),     // 秒级精度
  timestampMs: integer('timestamp_ms', { mode: 'timestamp_ms' }), // 毫秒级精度
});

类型转换与映射

Drizzle ORM 提供了强大的类型转换机制,确保数据库值与 TypeScript 类型之间的正确映射:

mermaid

示例实现:

class SQLiteTimestamp extends SQLiteBaseInteger<T> {
  override mapFromDriverValue(value: number): Date {
    if (this.config.mode === 'timestamp') {
      return new Date(value * 1000);  // 秒转毫秒
    }
    return new Date(value);           // 直接使用毫秒
  }

  override mapToDriverValue(value: Date): number {
    const unix = value.getTime();
    if (this.config.mode === 'timestamp') {
      return Math.floor(unix / 1000);  // 毫秒转秒
    }
    return unix;                       // 直接使用毫秒
  }
}

这种类型系统设计确保了开发者能够在编译时捕获类型错误,同时保持与底层数据库的高效交互。Drizzle ORM 的数据类型系统不仅提供了丰富的内置类型支持,还允许开发者通过自定义类型扩展来满足特定的业务需求。

主键、外键与索引配置

在数据库设计中,主键、外键和索引是构建高效、可靠数据模型的核心要素。Drizzle ORM 提供了强大而灵活的方式来定义这些约束,确保数据的完整性和查询性能。本节将深入探讨如何在 Drizzle ORM 中配置主键、外键和索引,并通过实际示例展示最佳实践。

主键配置

主键是表中唯一标识每条记录的列或列组合。Drizzle ORM 提供了多种方式来定义主键:

单列主键

最简单的形式是在列定义时直接使用 .primaryKey() 方法:

import { pgTable, serial, varchar } from 'drizzle-orm/pg-core';

const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  email: varchar('email', { length: 255 }).notNull().unique(),
});
复合主键

对于需要多个列组合作为主键的场景,可以使用 primaryKey 函数:

import { pgTable, varchar, primaryKey } from 'drizzle-orm/pg-core';

const userRoles = pgTable('user_roles', {
  userId: varchar('user_id', { length: 36 }).notNull(),
  roleId: varchar('role_id', { length: 36 }).notNull(),
  assignedAt: timestamp('assigned_at').defaultNow(),
}, (table) => ({
  pk: primaryKey({ columns: [table.userId, table.roleId] })
}));
命名主键约束

可以为主键约束指定自定义名称:

const orders = pgTable('orders', {
  id: serial('id'),
  orderNumber: varchar('order_number', { length: 50 }).notNull(),
  customerId: integer('customer_id').notNull(),
}, (table) => ({
  pk: primaryKey({ 
    name: 'orders_custom_pk',
    columns: [table.id, table.orderNumber] 
  })
}));

外键关系配置

外键用于建立表之间的关联关系,Drizzle ORM 提供了类型安全的外键定义方式:

基本外键关系
import { pgTable, serial, varchar, integer, foreignKey } from 'drizzle-orm/pg-core';

const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
});

const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: text('content'),
  authorId: integer('author_id').notNull(),
}, (table) => ({
  authorFk: foreignKey({
    name: 'posts_author_fk',
    columns: [table.authorId],
    foreignColumns: [users.id],
  })
}));
级联操作配置

Drizzle ORM 支持完整的级联操作配置:

const orders = pgTable('orders', {
  id: serial('id').primaryKey(),
  customerId: integer('customer_id').notNull(),
  totalAmount: decimal('total_amount', { precision: 10, scale: 2 }),
}, (table) => ({
  customerFk: foreignKey({
    columns: [table.customerId],
    foreignColumns: [customers.id],
  })
  .onDelete('cascade')  // 删除客户时自动删除订单
  .onUpdate('restrict') // 阻止更新客户ID
}));

支持的级联操作选项包括:

操作类型 描述
cascade 级联操作
restrict 限制操作
no action 无操作(默认)
set null 设置为 NULL
set default 设置为默认值
多列外键

对于需要多个列的外键关系:

const orderItems = pgTable('order_items', {
  orderId: integer('order_id').notNull(),
  productSku: varchar('product_sku', { length: 50 }).notNull(),
  quantity: integer('quantity').notNull(),
}, (table) => ({
  orderFk: foreignKey({
    columns: [table.orderId, table.productSku],
    foreignColumns: [orders.id, products.sku],
  })
}));

索引配置

索引是提高查询性能的关键,Drizzle ORM 提供了灵活的索引配置选项:

单列索引
import { pgTable, varchar, index } from 'drizzle-orm/pg-core';

const products = pgTable('products', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  category: varchar('category', { length: 100 }),
  price: decimal('price', { precision: 10, scale: 2 }),
}, (table) => ({
  nameIdx: index('product_name_idx').on(table.name),
  categoryIdx: index('product_category_idx').on(table.category),
}));
复合索引
const sales = pgTable('sales', {
  id: serial('id').primaryKey(),
  region: varchar('region', { length: 50 }).notNull(),
  productCategory: varchar('product_category', { length: 100 }),
  saleDate: date('sale_date').notNull(),
  amount: decimal('amount', { precision: 10, scale: 2 }),
}, (table) => ({
  regionCategoryIdx: index('sales_region_category_idx')
    .on(table.region, table.productCategory),
  dateRegionIdx: index('sales_date_region_idx')
    .on(table.saleDate, table.region),
}));
唯一索引

确保列值的唯一性:

const employees = pgTable('employees', {
  id: serial('id').primaryKey(),
  employeeCode: varchar('employee_code', { length: 20 }).notNull(),
  email: varchar('email', { length: 255 }).notNull(),
  department: varchar('department', { length: 100 }),
}, (table) => ({
  employeeCodeUnique: index('employee_code_unique')
    .on(table.employeeCode)
    .unique(),
  emailUnique: index('employee_email_unique')
    .on(table.email)
    .unique(),
}));
部分索引(条件索引)

仅对满足特定条件的行创建索引:

const tasks = pgTable('tasks', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  status: varchar('status', { length: 20 }).notNull(),
  priority: integer('priority'),
  dueDate: date('due_date'),
}, (table) => ({
  highPriorityTasks: index('high_priority_tasks_idx')
    .on(table.priority)
    .where(sql`${table.priority} > 5`),
  activeTasks: index('active_tasks_idx')
    .on(table.status)
    .where(sql`${table.status} = 'active'`),
}));

高级配置模式

多对多关系模式
// 用户表
const users = pgTable('users', {
  id: serial('id').primaryKey(),
  username: varchar('username', { length: 50 }).notNull().unique(),
});

// 角色表
const roles = pgTable('roles', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 50 }).notNull().unique(),
});

// 用户角色关联表
const userRoles = pgTable('user_roles', {
  userId: integer('user_id').notNull(),
  roleId: integer('role_id').notNull(),
  assignedAt: timestamp('assigned_at').defaultNow(),
}, (table) => ({
  pk: primaryKey({ columns: [table.userId, table.roleId] }),
  userFk: foreignKey({
    columns: [table.userId],
    foreignColumns: [users.id],
  }).onDelete('cascade'),
  roleFk: foreignKey({
    columns: [table.roleId],
    foreignColumns: [roles.id],
  }).onDelete('cascade'),
  userRoleIdx: index('user_role_idx').on(table.userId, table.roleId),
}));
自引用关系
const employees = pgTable('employees', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  managerId: integer('manager_id'),
  position: varchar('position', { length: 100 }),
}, (table) => ({
  managerFk: foreignKey({
    columns: [table.managerId],
    foreignColumns: [table.id],
  }),
  managerIdx: index('employee_manager_idx').on(table.managerId),
}));

性能优化建议

索引策略

mermaid

外键性能考虑
场景 建议 理由
高频写入 谨慎使用外键 外键检查会增加写入开销
数据一致性要求高 使用外键 确保引用完整性
读多写少 使用外键+索引 平衡性能和数据完整性
分布式系统 考虑应用层约束 避免跨节点外键检查开销

最佳实践总结

  1. 主键选择:优先使用自增整数或UUID作为主键,避免使用业务数据作为主键
  2. 外键使用:在需要强数据一致性的场景使用外键,高频写入场景谨慎使用
  3. 索引策略:基于实际查询模式创建索引,避免过度索引
  4. 命名规范:使用一致的命名约定,如 pk_表名fk_表名_引用表名idx_表名_字段名
  5. 监控优化:定期监控索引使用情况,删除未使用的索引

通过合理配置主键、外键和索引,可以构建出既保证数据完整性又具有高性能的数据模型。Drizzle ORM 的类型安全API使得这些配置既直观又可靠,大大减少了运行时错误的风险。

关系建模与关联查询实现

Drizzle ORM 提供了强大的关系建模能力和直观的关联查询语法,让开发者能够轻松处理复杂的数据关系。通过类型安全的API设计,Drizzle ORM 确保了关系查询的编译时类型检查,大大减少了运行时错误。

关系类型定义

Drizzle ORM 支持两种主要的关系类型:一对一(one)和一对多(many)。关系定义通过 relations() 函数实现,为每个表创建明确的关系配置。

import { relations } from 'drizzle-orm';

// 用户表关系定义
export const usersConfig = relations(usersTable, ({ one, many }) => ({
  // 一对一关系:用户邀请者
  invitee: one(usersTable, {
    fields: [usersTable.invitedBy],
    references: [usersTable.id],
  }),
  // 一对多关系:用户所属群组
  usersToGroups: many(usersToGroupsTable),
  // 一对多关系:用户发布的帖子
  posts: many(postsTable),
}));

// 帖子表关系定义  
export const postsConfig = relations(postsTable, ({ one, many }) => ({
  // 一对一关系:帖子作者
  author: one(usersTable, {
    fields: [postsTable.ownerId],
    references: [usersTable.id],
  }),
  // 一对多关系:帖子的评论
  comments: many(commentsTable),
}));

关联查询语法

Drizzle ORM 的关联查询采用直观的 with 语法,支持多层嵌套查询和复杂的过滤条件。

基础关联查询
// 查询用户及其所有帖子
const usersWithPosts = await db.query.usersTable.findMany({
  with: {
    posts: true, // 加载所有关联的帖子
  },
});

// 查询结果类型推断
type UserWithPosts = {
  id: number;
  name: string;
  verified: number;
  invitedBy: number | null;
  posts: {
    id: number;
    content: string;
    ownerId: number | null;
    createdAt: Date;
  }[];
};
带限制的关联查询
// 查询用户,每个用户只加载最新的2个帖子
const usersWithLimitedPosts = await db.query.usersTable.findMany({
  with: {
    posts: {
      limit: 2,          // 限制每个用户的帖子数量
      orderBy: [desc(postsTable.createdAt)] // 按创建时间降序
    },
  },
});

// 同时限制主查询和关联查询
const limitedResults = await db.query.usersTable.findMany({
  limit: 5,              // 只返回前5个用户
  with: {
    posts: {
      limit: 3,          // 每个用户最多3个帖子
      where: eq(postsTable.content, '重要内容') // 过滤帖子内容
    },
  },
});
多层嵌套关联查询

Drizzle ORM 支持无限层级的嵌套关联查询,让复杂的数据关系变得简单易用。

// 多层嵌套:用户 -> 帖子 -> 评论 -> 点赞
const usersWithNestedRelations = await db.query.usersTable.findMany({
  with: {
    posts: {
      with: {
        comments: {
          with: {
            likes: true,  // 加载评论的点赞
            author: true  // 加载评论的作者
          },
          where: gt(commentsTable.createdAt, new Date('2024-01-01'))
        }
      },
      limit: 5
    },
    usersToGroups: {
      with: {
        group: true      // 加载用户所属的群组信息
      }
    }
  },
  where: eq(usersTable.verified, 1)
});

高级查询功能

条件关联加载

Drizzle ORM 允许根据条件动态决定是否加载关联数据,优化查询性能。

// 根据条件决定是否加载关联数据
const usersWithConditionalPosts = await db.query.usersTable.findMany({
  with: {
    posts: shouldLoadPosts ? {
      where: gt(postsTable.createdAt, lastWeek),
      limit: 10
    } : undefined
  }
});
关联查询的性能优化
// 只选择需要的字段,减少数据传输
const optimizedQuery = await db.query.usersTable.findMany({
  columns: {
    id: true,
    name: true,
    // 排除不需要的字段
    verified: false,
    invitedBy: false
  },
  with: {
    posts: {
      columns: {
        id: true,
        content: true,
        createdAt: true
      },
      limit: 5
    }
  },
  limit: 100
});
事务中的关联操作
// 在事务中执行关联操作
await db.transaction(async (tx) => {
  // 创建用户
  const [user] = await tx.insert(usersTable).values({
    name: '新用户',
    verified: 1
  }).returning();
  
  // 创建关联的帖子
  await tx.insert(postsTable).values([
    { ownerId: user.id, content: '第一篇帖子' },
    { ownerId: user.id, content: '第二篇帖子' }
  ]);
  
  // 在事务中查询关联数据
  const userWithPosts = await tx.query.usersTable.findMany({
    where: eq(usersTable.id, user.id),
    with: { posts: true }
  });
});

关系建模的最佳实践

1. 明确的关系定义
// 良好的关系定义示例
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
  post: one(postsTable, {
    fields: [commentsTable.postId],
    references: [postsTable.id],
    relationName: 'comment_post' // 明确的关系名称
  }),
  author: one(usersTable, {
    fields: [commentsTable.creator],
    references: [usersTable.id],
    relationName: 'comment_author'
  }),
  likes: many(commentLikesTable, {
    relationName: 'comment_likes'
  })
}));
2. 类型安全的查询构建

Drizzle ORM 的关联查询完全类型安全,编译器会在开发阶段捕获大多数错误:

// 类型错误会在编译时被发现
const invalidQuery = await db.query.usersTable.findMany({
  with: {
    nonExistentRelation: true, // 编译错误:关系不存在
    posts: {
      where: eq(postsTable.nonExistentColumn, 'value') // 编译错误:列不存在
    }
  }
});
3. 性能考量
// 避免N+1查询问题
const efficientQuery = await db.query.usersTable.findMany({
  with: {
    posts: {
      // 一次性加载所有需要的关联数据
      with: {
        comments: {
          limit: 10
        }
      }
    }
  },
  limit: 50
});

// 而不是在循环中单独查询
const users = await db.select().from(usersTable).limit(50);
for (const user of users) {
  const posts = await db.select().from(postsTable)
    .where(eq(postsTable.ownerId, user.id)); // 避免这种N+1查询
}

复杂关系场景处理

多对多关系
// 多对多关系:用户和群组
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
  group: one(groupsTable, {
    fields: [usersToGroupsTable.groupId],
    references: [groupsTable.id],
  }),
  user: one(usersTable, {
    fields: [usersToGroupsTable.userId],
    references: [usersTable.id],
  }),
}));

// 查询用户及其所有群组
const usersWithGroups = await db.query.usersTable.findMany({
  with: {
    usersToGroups: {
      with: {
        group: true // 通过联结表加载群组信息
      }
    }
  }
});
自引用关系
// 自引用关系:用户邀请关系
export const usersConfig = relations(usersTable, ({ one }) => ({
  invitee: one(usersTable, {
    fields: [usersTable.invitedBy],
    references: [usersTable.id],
  }),
}));

// 查询用户及其邀请者信息
const usersWithInvitors = await db.query.usersTable.findMany({
  with: {
    invitee: true // 加载邀请者信息
  }
});

查询结果处理

Drizzle ORM 的关联查询返回结构化的数据,便于前端直接使用:

const result = await db.query.usersTable.findMany({
  with: {
    posts: {
      with: {
        comments: {
          with: {
            author: true
          }
        }
      }
    }
  }
});

// 直接使用结构化的数据
result.forEach(user => {
  console.log(`用户: ${user.name}`);
  user.posts.forEach(post => {
    console.log(`  帖子: ${post.content}`);
    post.comments.forEach(comment => {
      console.log(`    评论: ${comment.content} (作者: ${comment.author.name})`);
    });
  });
});

通过Drizzle ORM的关系建模和关联查询功能,开发者可以轻松构建复杂的数据库查询,同时享受TypeScript带来的类型安全和开发体验提升。这种声明式的查询语法不仅提高了开发效率,还使得代码更加清晰易维护。

自定义类型与扩展机制

Drizzle ORM 提供了强大的自定义类型和扩展机制,允许开发者根据特定需求创建自定义数据库类型,或者扩展ORM的功能来支持特殊的数据类型和数据库扩展。这种灵活性使得Drizzle能够适应各种复杂的业务场景和数据库特性。

自定义类型系统架构

Drizzle ORM 的自定义类型系统基于两个核心类构建:

mermaid

每个数据库方言(PostgreSQL、MySQL、SQLite)都有对应的ColumnBuilder和Column基类,自定义类型需要继承这些基类并实现特定方法。

创建自定义数据类型

基本自定义类型示例

以下是一个创建PostgreSQL CITEXT类型的完整示例:

import { ColumnBuilderBaseConfig, MakeColumnConfig } from 'drizzle-orm/column-builder';
import { ColumnBaseConfig } from 'drizzle-orm/column';
import { entityKind } from 'drizzle-orm/entity';
import { AnyPgTable } from 'drizzle-orm/pg-core/table';
import { PgColumn, PgColumnBuilder } from 'drizzle-orm/pg-core/columns/common';

export class PgCITextBuilder<TData extends string = string>
  extends PgColumnBuilder<
    ColumnBuilderBaseConfig<{ data: TData; driverParam: string }>
  > {
  static readonly [entityKind] = '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<
    ColumnBaseConfig<{ tableName: TTableName; data: TData; driverParam: string }>
  > {
  static readonly [entityKind] = 'PgCIText';

  constructor(
    table: AnyPgTable<{ name: TTableName }>,
    config: PgCITextBuilder<TData>['config'],
  ) {
    super(table, config);
  }

  getSQLType(): string {
    return 'citext';
  }
}

export function citext<T extends string = string>(name: string): PgCITextBuilder<T> {
  return new PgCITextBuilder(name);
}
使用自定义类型
import { pgTable, integer } from 'drizzle-orm/pg-core';
import { citext } from './custom-types/citext';

const users = pgTable('users', {
  id: integer('id').primaryKey(),
  email: citext('email').notNull(),  // 不区分大小写的电子邮件
  username: citext('username').notNull(),  // 不区分大小写的用户名
});

通用自定义类型工厂

Drizzle ORM 提供了 customType 工厂函数,可以快速创建各种自定义类型:

import { customType } from 'drizzle-orm/pg-core';

// 创建UUID前缀类型
const prefixedUlid = <Prefix extends string>(
  name: string,
  opts: { prefix: Prefix }
) =>
  customType<{ data: `${Prefix}_${string}`; driverData: string }>({
    dataType: () => 'uuid',
    toDriver: (value) => value.replace(`${opts.prefix}_`, ''),
    fromDriver: (value) => `${opts.prefix}_${value}` as const,
  })(name);

// 使用示例
const items = pgTable('items', {
  id: prefixedUlid('id', { prefix: 'item' }).primaryKey(),
  ownerId: prefixedUlid('owner_id', { prefix: 'user' }).notNull(),
});

值转换机制

自定义类型可以通过重写 mapFromDriverValuemapToDriverValue 方法来实现数据类型转换:

class CustomDateColumn extends PgColumn<DateConfig> {
  getSQLType(): string {
    return 'timestamp';
  }

  // 数据库值 → TypeScript值
  override mapFromDriverValue(value: string): Date {
    return new Date(value);
  }

  // TypeScript值 → 数据库值
  override mapToDriverValue(value: Date): string {
    return value.toISOString();
  }
}

数据库扩展支持

PostGIS 扩展示例

Drizzle ORM 内置了对PostGIS扩展的支持,展示了如何实现复杂的地理空间数据类型:

// 几何点类型定义
export class PgGeometry<T extends ColumnBaseConfig<'array', 'PgGeometry'>> extends PgColumn<T> {
  getSQLType(): string {
    return 'geometry(point)';
  }

  override mapFromDriverValue(value: string): [number, number] {
    return parseEWKB(value); // 解析EWKB格式
  }

  override mapToDriverValue(value: [number, number]): string {
    return `point(${value[0]} ${value[1]})`;
  }
}

// 使用PostGIS类型
const locations = pgTable('locations', {
  id: integer('id').primaryKey(),
  point: geometry('point'),  // 使用元组模式 [x, y]
  pointObj: geometry('point_xy', { mode: 'xy' }),  // 使用对象模式 {x, y}
});
扩展配置选项

自定义类型可以支持丰富的配置选项:

interface GeometryConfig {
  mode?: 'tuple' | 'xy';  // 数据表示模式
  type?: 'point' | 'linestring' | 'polygon';  // 几何类型
  srid?: number;  // 空间参考标识符
}

function geometry(name: string, config?: GeometryConfig) {
  // 根据配置返回不同的实现
}

社区扩展生态系统

Drizzle ORM 拥有丰富的社区扩展生态系统,这些扩展通过统一的接口与核心ORM集成:

Zod 集成扩展
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';

// 自动从表定义生成Zod schema
const insertUserSchema = createInsertSchema(users, {
  email: (schema) => schema.email(),  // 自定义验证规则
});

const selectUserSchema = createSelectSchema(users);

// 使用生成的schema进行验证
const validData = insertUserSchema.parse({
  email: 'user@example.com',
  username: 'john_doe'
});
类型安全扩展模式

社区扩展通常遵循统一的模式:

mermaid

高级自定义场景

品牌类型(Branded Types)
// 定义品牌类型
type Email = string & { readonly __brand: 'Email' };
type UserId = number & { readonly __brand: 'UserId' };

// 创建品牌类型列
const users = pgTable('users', {
  id: integer('id').$type<UserId>().primaryKey(),
  email: text('email').$type<Email>().notNull(),
});

// 使用时的类型安全
function getUserById(id: UserId) {
  // 只能传入UserId类型,普通number会报错
}
复合数据类型
// 创建地址复合类型
const addressType = customType<{
  data: { street: string; city: string; zip: string };
  driverData: string;
}>({
  dataType: () => 'text',
  toDriver: (value) => JSON.stringify(value),
  fromDriver: (value) => JSON.parse(value),
});

// 使用复合类型
const users = pgTable('users', {
  id: integer('id').primaryKey(),
  address: addressType('address'),
});

最佳实践与注意事项

  1. 类型安全优先:始终为自定义类型提供准确的TypeScript类型定义
  2. 兼容性考虑:确保自定义类型与Drizzle Kit迁移工具兼容
  3. 性能优化:对于频繁使用的类型,优化值转换函数的性能
  4. 错误处理:在值转换过程中添加适当的错误处理和验证
  5. 文档完善:为自定义类型提供清晰的使用文档和示例

扩展机制的优势

Drizzle ORM 的自定义类型与扩展机制提供了以下优势:

  • 类型安全:完整的TypeScript类型支持
  • 灵活性:支持各种数据库特性和扩展
  • 可维护性:统一的接口和模式
  • 生态系统:丰富的社区扩展支持
  • 性能:优化的值转换和查询生成

通过合理利用自定义类型和扩展机制,开发者可以构建出高度类型安全、功能丰富且易于维护的数据库应用。

总结

Drizzle ORM提供了一个强大而灵活的类型系统,支持多种数据库方言并为每种数据库提供了专门优化的数据类型实现。通过Builder模式和链式调用,开发者可以以类型安全的方式定义数据库表结构。ORM支持丰富的主键、外键和索引配置,能够构建高效可靠的数据模型。其关系建模和关联查询功能采用直观的with语法,支持多层嵌套查询和复杂的过滤条件。最重要的是,Drizzle ORM提供了完善的自定义类型和扩展机制,允许开发者创建自定义数据库类型或扩展ORM功能来支持特殊的数据类型和数据库扩展。这种设计确保了开发者能够在编译时捕获类型错误,同时保持与底层数据库的高效交互,大大提升了开发效率和代码质量。

【免费下载链接】drizzle-orm drizzle-team/drizzle-orm: 是一个基于 C++ 的 ORM(对象关系映射)库,支持 MySQL 和 SQLite 数据库。适合对 C++、数据库开发以及想要使用轻量级 ORM 的开发者。 【免费下载链接】drizzle-orm 项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm

Logo

惟楚有才,于斯为盛。欢迎来到长沙!!! 茶颜悦色、臭豆腐、CSDN和你一个都不能少~

更多推荐