多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

drizzle-orm-sqlite 0.15.0 复合主键支持:在表结构定义中声明多列联合主键

drizzle-orm-sqlite 0.15.0 复合主键支持:在表结构定义中声明多列联合主键 drizzle-orm-sqlite 0.15.0 复合主键支持在表结构定义中声明多列联合主键【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-ormdrizzle-orm-sqlite0.15.0 正式在 SQLite 表结构table schema定义中引入**复合主键composite primary key**支持。在此之前主键只能通过单列上的.primaryKey()声明而复合主键多列联合作为主键往往需要退回到原始 SQL 或绕过 ORM 手动处理。本篇将基于该版本的发布说明changelogs/drizzle-orm-sqlite/0.15.0.md与仓库源码完整讲解复合主键的声明语法、primaryKey()构建器的实现原理、命名规则及实际工程中的使用注意事项帮助你直接在本仓库的drizzle-orm代码中落地这一能力。版本变更核心表级复合主键drizzle-orm-sqlite0.15.0 的发布说明核心只有一条能力变更Add composite PKs on table schema definition即在表结构定义阶段通过第三个参数extraConfig内的primaryKey(...)构建器将多个列组合声明为一个主键并由 Drizzle 在生成 SQL 时自动产出对应的表级约束。这与 SQLite 原生 DDL 中的PRIMARY KEY (col_a, col_b)表约束一一对应。从发布说明继承的最小示例发布说明给出了如下最小示例它也是理解该特性最直接的入口import { sqliteTable, integer, text, primaryKey } from drizzle-orm/sqlite-core; const pkExample sqliteTable(pk_example, { id: integer(id), name: text(name).notNull(), email: text(email).notNull(), }, (table) ({ compositePk: primaryKey(table.id, table.name) }));要点拆解sqliteTable的第三个参数是一个回调函数接收table即已构建好的列集合类型为BuildColumnsTTableName, TColumnsMap, sqlite返回一组表级额外配置在返回的对象中compositePk只是给这个约束起的语义化字段名真正起作用的是右侧的primaryKey(table.id, table.name)参与联合主键的列在列定义中不要再单独调用.primaryKey()否则会同时产生单列主键与复合主键冲突。源码级解析primaryKey()构建器与PrimaryKey实体primaryKey函数定义在 drizzle-orm/src/sqlite-core/primary-keys.ts。当前版本提供两种重载形态// 推荐形态对象配置支持自定义约束名 export function primaryKey TTableName extends string, TColumn extends AnySQLiteColumn{ tableName: TTableName }, TColumns extends AnySQLiteColumn{ tableName: TTableName }[], (config: { name?: string; columns: [TColumn, ...TColumns] }): PrimaryKeyBuilder; // 已废弃形态直接把列作为可变参数传入 /** * deprecated: Please use primaryKey({ columns: [] }) instead of this function */ export function primaryKey TTableName extends string, TColumns extends AnySQLiteColumn{ tableName: TTableName }[], (...columns: TColumns): PrimaryKeyBuilder;从源码可以看到运行时逻辑会先判断首个参数是否含有columns属性传入{ columns: [col1, col2], name: xxx }对象 → 走new PrimaryKeyBuilder(config[0].columns, config[0].name)直接传列如发布说明示例primaryKey(table.id, table.name)→ 走new PrimaryKeyBuilder(config)此时name为undefined。PrimaryKeyBuilder内部仅持有columns: SQLiteColumn[]与可选的name?: string其build(table)方法primary-keys.ts将其转换为最终的PrimaryKey实体。PrimaryKey类中值得关注的是默认命名规则getName()getName(): string { return this.name ?? ${this.table[SQLiteTable.Symbol.Name]}_${this.columns.map((column) column.name).join(_)}_pk; }也就是说若不显式指定name生成的约束名默认为${表名}_${列名1}_${列名2}_pk的形式。例如上文的pk_example表、id与name两列默认主键约束名即为pk_example_id_name_pk。若希望约束名可控例如用于迁移脚本对比、数据库约束管理推荐使用对象形态const pkExample sqliteTable(pk_example, { id: integer(id), name: text(name).notNull(), email: text(email).notNull(), }, (table) ({ compositePk: primaryKey({ columns: [table.id, table.name], name: pk_example_id_name }), }));该推荐写法在仓库的集成测试中大量使用例如 integration-tests/tests/sqlite/d1-batch.test.ts 与 integration-tests/tests/sqlite/durable-objects/index.ts 中均采用primaryKey({ columns: [t.userId, t.groupId] })的对象形态。extraConfig的两种返回形态对象与数组sqliteTable的第三个参数extraConfig在 drizzle-orm/src/sqlite-core/table.ts 中被类型化为返回SQLiteTableExtraConfigValue[]或已废弃的SQLiteTableExtraConfig对象数组形态推荐直接返回一组构建器例如[primaryKey(...), index(...), foreignKey(...)]对象形态deprecated为每个构建器命名如上文示例中的{ compositePk: primaryKey(...) }。对象形态在SQLiteTableFn的重载声明中标注了deprecated未来的 API 方向是统一改为数组。两种形态最终都会进入getTableConfigdrizzle-orm/src/sqlite-core/utils.ts统一处理const extraValues Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); for (const builder of Object.values(extraValues)) { // ... if (is(builder, PrimaryKeyBuilder)) { primaryKeys.push(builder.build(table)); } }可见无论采用哪种书写形态PrimaryKeyBuilder都会在表配置收集阶段被识别并构建为PrimaryKey实体随后参与后续 SQL 生成与类型推导。getTableConfig返回结构中的primaryKeys字段正是表级主键约束的最终载体。与单列主键的对比及联合主键的业务场景在 0.15.0 之前SQLite 表声明主键的唯一方式是列内修饰符.primaryKey()const users sqliteTable(users, { id: integer(id).primaryKey(), // 单列主键 name: text(name), });primaryKey()在列构建器上的实现参见 drizzle-orm/src/sqlite-core/columns/integer.ts会自动将列标记为NOT NULL且具备自增默认语义IsPrimaryKeyHasDefaultNotNullthis。而复合主键属于表级约束参与联合的列需要在定义时显式声明约束条件如示例中的.notNull()因为 SQLite 规范要求主键列不可为空。复合主键典型适用于以下业务建模场景关联表如多对多中间表以(userId, groupId)或(orderId, productId)联合唯一标识一条关系记录分区 / 分片键以业务键 时间戳等组合定位数据无自增 ID 的业务实体自然键本身由多个字段构成如(countryCode, postalCode)。从 SQL 层面看上述pk_example声明最终等价于CREATE TABLE pk_example ( id integer, name text NOT NULL, email text NOT NULL, PRIMARY KEY (id, name) );这一能力在 drizzle-kit 的迁移生成与db.push流程中同样被完整支持声明了复合主键的表可以被稳定地 introspection、diff 与生成迁移语句保证 schema 与数据库两端一致。完整可运行的工程示例结合本仓库的 SQLite 驱动如better-sqlite3下面是一个完整可运行的复合主键示例覆盖建表与插入写入import { sqliteTable, integer, text, primaryKey } from drizzle-orm/sqlite-core; import { drizzle } from drizzle-orm/better-sqlite3; import Database from better-sqlite3; // 1. 定义带复合主键的表 const groupMembers sqliteTable(group_members, { groupId: integer(group_id).notNull(), userId: integer(user_id).notNull(), role: text(role).notNull(), }, (table) ({ pk: primaryKey({ columns: [table.groupId, table.userId] }), })); // 2. 实例化驱动内存数据库便于本地验证 const sqlite new Database(:memory:); const db drizzle(sqlite); // 3. 建表并写入数据 db.run(sqlCREATE TABLE group_members ( group_id integer NOT NULL, user_id integer NOT NULL, role text NOT NULL, PRIMARY KEY (group_id, user_id) )); db.insert(groupMembers).values({ groupId: 1, userId: 10, role: admin });说明db.run(sql\...)仅为演示底层 DDL 效果实际工程中建议直接通过 drizzle-kit 的generate/push 从 schema 定义生成表结构避免手工维护 SQL。注意事项小结不要叠加单列主键复合主键涉及的列不应再调用.primaryKey()否则生成重复/冲突的主键约束参与列尽量显式.notNull()SQLite 要求主键列非空复合主键不会像单列.primaryKey()那样自动追加NOT NULL修饰参考列构建器实现 columns/integer.ts优先使用对象形态primaryKey({ columns: [...] })是当前推荐 API变参数形态已标记 deprecated且对象形态支持通过name指定约束名便于迁移脚本的稳定对比extraConfig建议返回数组sqliteTable第三参数的数组形态是未来方向对象形态为兼容旧代码而保留详见 table.ts 中的 deprecation 注释默认约束名可预期未显式命名时约束名遵循${表名}_${列名下划线连接}_pk规则可在getName()primary-keys.ts中确认。至此drizzle-orm-sqlite0.15.0 引入的复合主键能力已覆盖声明语法 → 构建器实现 → 命名规则 → 工程落地全链路。从本仓库的 sqlite-core/README.md 到 integration-tests/tests/sqlite 下的各类驱动测试复合主键均已得到广泛使用与验证你可以放心在生产 schema 中采用该特性来建模多列联合唯一实体。【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表