Drizzle Kit 0.27.0 实战:在 Drizzle ORM 中构建 PostgreSQL 行级安全(RLS)的完整指南
Drizzle Kit 0.27.0 实战在 Drizzle ORM 中构建 PostgreSQL 行级安全RLS的完整指南【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm本指南围绕 drizzle-kit 0.27.0 的旗舰特性展开PostgreSQL 行级安全Row-Level SecurityRLS的一站式支持。你将学会如何为任意 Postgres 表启用 RLS、用pgRole定义与管理角色、用pgPolicy编写细粒度策略、通过drizzle.config.ts的entities.roles控制迁移范围并掌握与 Neon、Supabase 两大托管数据库厂商预定义角色与辅助函数的集成方案。读完本文你可以在 Drizzle 的 TypeScript schema 中以纯代码方式完成从建表→建角色→建策略→生成迁移的完整 RLS 落地链路。版本背景与前置要求drizzle-kit 0.27.0 的 RLS 功能与 drizzle-orm 0.36.0 强绑定该版本要求搭配drizzle-orm0.36.0才能启用全部新特性。RLS 相关 API 全部位于drizzle-orm侧pg-core、/neon、/supabase导入路径而迁移生成、内省与 push 由 drizzle-kit 侧负责两者需同步升级。Row-Level SecurityRLS核心模型Drizzle 对 Postgres RLS 的抽象由三层实体构成对应源码中的三个核心模块实体导入来源对应源码职责pgTable().enableRLS()drizzle-orm/pg-coredrizzle-orm/src/pg-core/table.ts在表上开启行级安全pgRoledrizzle-orm/pg-coredrizzle-orm/src/pg-core/roles.ts定义数据库角色及其属性pgPolicydrizzle-orm/pg-coredrizzle-orm/src/pg-core/policies.ts定义绑定到某张表上的访问策略Drizzle 同时保留对 Postgres 原生策略与角色的原始表示你可以用任意自定义方式组合它们这套 API 与 Neon、Supabase 等主流 Postgres 托管服务兼容。此外Drizzle 为这两家厂商预置了特定的 RLS 角色与辅助函数同时也允许你完全自定义自己的逻辑。启用 RLS.enableRLS()如果你只想在表上开启 RLS、暂不添加任何策略直接调用.enableRLS()即可import { integer, pgTable } from drizzle-orm/pg-core; export const users pgTable(users, { id: integer(), }).enableRLS();正如 PostgreSQL 官方文档所强调的如果表上不存在任何策略将使用默认拒绝default-deny策略即任何行都不可见、不可修改。作用于整张表的操作如 TRUNCATE 和 REFERENCES不受行安全限制。需要特别注意的是一旦你为某张表添加了策略RLS 会被自动启用此时无需再显式调用.enableRLS()。这一行为在 drizzle-kit 的差分测试中有直接印证——见 drizzle-kit/tests/rls/pg-policy.test.ts当 schema 从无策略表演化为带策略表时生成的迁移语句同时包含ALTER TABLE users ENABLE ROW LEVEL SECURITY;与CREATE POLICY ...;。定义角色pgRole当前版本 Drizzle 支持的角色选项如下更多选项将在未来版本中加入import { pgRole } from drizzle-orm/pg-core; export const admin pgRole(admin, { createRole: true, createDb: true, inherit: true });在源码层面PgRoleConfig定义了createDb、createRole、inherit三个可选布尔属性见 drizzle-orm/src/pg-core/roles.ts。它们分别对应 Postgres 的CREATEDB、CREATEROLE与INHERIT权限缺省时 drizzle-kit 生成的CREATE ROLE语句将使用 Postgres 默认值。如果角色已经存在于数据库中、且你不希望 drizzle-kit 看到它或把它纳入迁移可以将其标记为 existingimport { pgRole } from drizzle-orm/pg-core; export const admin pgRole(admin).existing();.existing()在源码中通过内部标记_existing true实现见上引 roles.tsdrizzle-kit 遇到此类角色时会跳过其创建语句但仍允许你在策略to中引用它。编写策略pgPolicy要充分利用 RLS需要在 Drizzle 表内定义策略。由于 Postgres 中策略必须挂接在已存在的表上Drizzle 将策略定义设计为pgTable的第三个参数表扩展回调。包含全部可用属性的 pgPolicy 示例import { sql } from drizzle-orm; import { integer, pgPolicy, pgRole, pgTable } from drizzle-orm/pg-core; export const admin pgRole(admin); export const users pgTable(users, { id: integer(), }, (t) [ pgPolicy(policy, { as: permissive, to: admin, for: delete, using: sql, withCheck: sql, }), ]);从 drizzle-orm/src/pg-core/policies.ts 可以看到PgPolicyConfig的完整定义as策略类型permissive宽容多个策略取并集默认值或restrictive严格多个策略取交集for适用的命令all | select | insert | update | delete默认allto策略适用的角色可以是public、current_role、current_user、session_user、任意字符串、角色数组或一个PgRole实例usingUSING表达式用于现有行的过滤select / update / deletewithCheckWITH CHECK表达式用于新写入行的校验insert / update。测试用例pg-policy.test.ts展示了最小配置生成的 SQL 形态pgPolicy(test, { as: permissive })会生成CREATE POLICY test ON users AS PERMISSIVE FOR ALL TO public;——未指定for时默认FOR ALL未指定to时默认TO public。将策略链接到已有表.link()有些场景下需要把策略挂接到数据库里已存在的表最常见的是 Neon、Supabase 这类服务商托管的内置表。此时使用.link()API它在源码中通过_linkedTable内部字段记录目标表见 policies.tsimport { sql } from drizzle-orm; import { pgPolicy } from drizzle-orm/pg-core; import { authenticatedRole, realtimeMessages } from drizzle-orm/supabase; export const policy pgPolicy(authenticated role insert policy, { for: insert, to: authenticatedRole, using: sql, }).link(realtimeMessages);迁移管理entities.roles配置当你用 drizzle-kit 管理 schema 与角色时可能会引用未在 Drizzle schema 中定义的角色例如数据库服务商内置的角色。此时你既不想逐个用.existing()标记又希望 drizzle-kit 跳过对这些角色的管理就可以使用drizzle.config.ts中的entities.roles选项。重要前提drizzle-kit 默认不管理角色需要显式在配置中开启// drizzle.config.ts import { defineConfig } from drizzle-kit; export default defineConfig({ dialect: postgresql, schema: ./drizzle/schema.ts, dbCredentials: { url: process.env.DATABASE_URL! }, verbose: true, strict: true, entities: { roles: true } });entities.roles支持布尔值或对象两种形态。drizzle-kit 序列化器中的prepareRoles函数drizzle-kit/src/serializer/pgSerializer.ts负责解析该配置其逻辑为布尔true开启角色管理对象形态则支持provider、include、exclude三个字段的任意组合。排除角色你有一个admin角色希望它不在可管理角色列表中// drizzle.config.ts import { defineConfig } from drizzle-kit; export default defineConfig({ ... entities: { roles: { exclude: [admin] } } });包含角色你有一个admin角色希望它进入可管理角色列表// drizzle.config.ts import { defineConfig } from drizzle-kit; export default defineConfig({ ... entities: { roles: { include: [admin] } } });Neon 场景排除 Neon 预定义角色// drizzle.config.ts import { defineConfig } from drizzle-kit; export default defineConfig({ ... entities: { roles: { provider: neon } } });Supabase 场景排除 Supabase 预定义角色// drizzle.config.ts import { defineConfig } from drizzle-kit; export default defineConfig({ ... entities: { roles: { provider: supabase } } });组合场景Drizzle 内置角色列表可能落后于数据库厂商新增的角色。此时可以同时使用provider与exclude// drizzle.config.ts import { defineConfig } from drizzle-kit; export default defineConfig({ ... entities: { roles: { provider: supabase, exclude: [new_supabase_role] } } });从源码看provider: supabase会默认排除anon、authenticator、authenticated、service_role、supabase_auth_admin、supabase_storage_admin、dashboard_user、supabase_admin共 8 个角色provider: neon则排除authenticated与anonymous两个角色随后include与exclude数组会被合并进对应列表参与最终过滤。在视图上应用 RLSDrizzle 同样支持在视图上指定 RLS 策略方法是在视图的 WITH 选项中设置security_invoker。启用该选项后视图将以调用者的权限而非定义者的权限执行从而与 RLS 策略协同生效... export const roomsUsersProfiles pgView(rooms_users_profiles) .with({ securityInvoker: true, }) .as((qb) qb .select({ ...getTableColumns(roomsUsers), email: profiles.email, }) .from(roomsUsers) .innerJoin(profiles, eq(roomsUsers.userId, profiles.id)) );与 Neon 集成crudPolicy与预置角色Neon 团队帮助 Drizzle 实现了基于原始策略 API 的封装。Drizzle 提供了专门的/neon导入路径其中包含crudPolicy函数以及 Neon 的默认角色。使用crudPolicy的示例import { crudPolicy } from drizzle-orm/neon; import { integer, pgRole, pgTable } from drizzle-orm/pg-core; export const admin pgRole(admin); export const users pgTable(users, { id: integer(), }, (t) [ crudPolicy({ role: admin, read: true, modify: false }), ]);从 drizzle-orm/src/neon/rls.ts 的源码实现看crudPolicy接受{ role, read, modify }三个选项role可以是单个PgRole、角色名或它们的数组read与modify接受SQL表达式、布尔值或null——true/false会被转换为sql\true/sqlfalse传null则跳过对应策略的生成省略时直接抛错。最终它返回 4 条命名规则为crud- -policy-select|insert|update|delete 的策略数组。上述示例展开后等价于import { sql } from drizzle-orm; import { integer, pgPolicy, pgRole, pgTable } from drizzle-orm/pg-core; export const admin pgRole(admin); export const users pgTable(users, { id: integer(), }, (t) [ pgPolicy(crud-${admin.name}-policy-insert, { for: insert, to: admin, withCheck: sqlfalse, }), pgPolicy(crud-${admin.name}-policy-update, { for: update, to: admin, using: sqlfalse, withCheck: sqlfalse, }), pgPolicy(crud-${admin.name}-policy-delete, { for: delete, to: admin, using: sqlfalse, }), pgPolicy(crud-${admin.name}-policy-select, { for: select, to: admin, using: sqltrue, }), ]);Neon 预置角色与函数Neon 暴露了authenticated与anonymous两个预定义角色及关联函数。这些角色已被标记为 existing.existing()可直接用于 RLS 查询// drizzle-orm/neon export const authenticatedRole pgRole(authenticated).existing(); export const anonymousRole pgRole(anonymous).existing(); export const authUid (userIdColumn: AnyPgColumn) sql(select auth.user_id() ${userIdColumn});例如你可以这样使用 Neon 的预定义角色import { sql } from drizzle-orm; import { authenticatedRole } from drizzle-orm/neon; import { integer, pgPolicy, pgRole, pgTable } from drizzle-orm/pg-core; export const admin pgRole(admin); export const users pgTable(users, { id: integer(), }, (t) [ pgPolicy(policy-insert, { for: insert, to: authenticatedRole, withCheck: sqlfalse, }), ]);与 Supabase 集成预置角色与内置表Drizzle 提供/supabase导入路径包含一组标记为 existing 的预定义角色。该导入路径将在未来版本中扩展更多函数与辅助工具进一步简化 Supabase 下的 RLS 使用// drizzle-orm/supabase export const anonRole pgRole(anon).existing(); export const authenticatedRole pgRole(authenticated).existing(); export const serviceRole pgRole(service_role).existing(); export const postgresRole pgRole(postgres_role).existing(); export const supabaseAuthAdminRole pgRole(supabase_auth_admin).existing();完整实现见 drizzle-orm/src/supabase/rls.ts。例如你可以将serviceRole直接用于策略的to选项import { sql } from drizzle-orm; import { serviceRole } from drizzle-orm/supabase; import { integer, pgPolicy, pgRole, pgTable } from drizzle-orm/pg-core; export const admin pgRole(admin); export const users pgTable(users, { id: integer(), }, (t) [ pgPolicy(policy-insert, { for: insert, to: serviceRole, withCheck: sqlfalse, }), ]);预置表与函数/supabase导入还包含若干预定义的表与函数供应用直接使用。drizzle-kit 会将它们视为已存在的数据库对象仅作为关联其他实体的信息源// drizzle-orm/supabase const auth pgSchema(auth); export const authUsers auth.table(users, { id: uuid().primaryKey().notNull(), }); const realtime pgSchema(realtime); export const realtimeMessages realtime.table( messages, { id: bigserial({ mode: bigint }).primaryKey(), topic: text().notNull(), extension: text({ enum: [presence, broadcast, postgres_changes], }).notNull(), }, ); export const authUid sql(select auth.uid()); export const realtimeTopic sqlrealtime.topic();综合示例以authUsers为主键的外键 基于预置角色的策略下面的profiles表把主键外键关联到 Supabase 的auth.users表并给authenticatedRole开放全量查询import { foreignKey, pgPolicy, pgTable, text, uuid } from drizzle-orm/pg-core; import { sql } from drizzle-orm/sql; import { authenticatedRole, authUsers } from drizzle-orm/supabase; export const profiles pgTable( profiles, { id: uuid().primaryKey().notNull(), email: text().notNull(), }, (table) [ foreignKey({ columns: [table.id], // reference to the auth table from Supabase foreignColumns: [authUsers.id], name: profiles_id_fk, }).onDelete(cascade), pgPolicy(authenticated can view all profiles, { for: select, // using predefined role from Supabase to: authenticatedRole, using: sqltrue, }), ] );给 Supabase 已存在的表添加策略则复用前面介绍过的.link()import { sql } from drizzle-orm; import { pgPolicy } from drizzle-orm/pg-core; import { authenticatedRole, realtimeMessages } from drizzle-orm/supabase; export const policy pgPolicy(authenticated role insert policy, { for: insert, to: authenticatedRole, using: sql, }).link(realtimeMessages);本版本的 Bug 修复drizzle-kit 0.27.0 还修复了以下问题Studio MySQL 默认模式下时间相关格式显示错误的问题Drizzle Studio 的 CORS 错误TIMESTAMPS 在 Drizzle Studio 中显示不正确的问题。源码速查与深入阅读策略模型与PgPolicyConfig全部属性drizzle-orm/src/pg-core/policies.ts角色模型与.existing()实现drizzle-orm/src/pg-core/roles.ts表上的.enableRLS()类型与方法drizzle-orm/src/pg-core/table.tsNeon 的crudPolicy与预置角色/函数drizzle-orm/src/neon/rls.tsSupabase 的预置角色/表/函数drizzle-orm/src/supabase/rls.tsentities.roles的 provider/include/exclude 解析逻辑drizzle-kit/src/serializer/pgSerializer.tsRLS 迁移差分与 SQL 生成的测试佐证drizzle-kit/tests/rls/pg-policy.test.ts、drizzle-kit/tests/rls/pg-role.test.ts完整的 drizzle.config.ts 配置参考可对照 drizzle-kit/tests/cli/postgres.config.ts 等测试配置文件适用前提说明本文所有 API 与配置行为均以当前仓库中 drizzle-kit 0.27.0 / drizzle-orm 0.36.0 版本的实现为准使用前请确保两个包版本匹配并确认目标数据库支持 Postgres 行级安全特性。【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考