- 后端
- API网关
【免费下载链接】crystal
🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
PostGraphile 会在启动时自动对数据库中被检视的 schema 进行内省,并基于其中的表与列生成一整套对应的 GraphQL 类型、查询字段、变更操作与关系字段。本文以app_public.users示例表为主线,系统讲解 PostGraphile v5(当前仓库postgraphile/postgraphile包对应的版本)如何为一张普通 PostgreSQL 表推导出User类型、allUsers连接、userByKey唯一键查询与nodeId查询,并深入PgTablesPlugin、PgRBACPlugin等源码,说明权限反射(RBAC)与 unlogged 表的处理原理。读完本文,你将能准确预测任意一张表在 PostGraphile 生成的 schema 中会“长出”哪些字段,并掌握通过 GRANT/REVOKE 与 smart tags 精细化控制暴露面的方法。
一张示例表会生成什么?
先看文档给出的典型示例表(来源:tables.md):
create table app_public.users ( id serial primary key, username citext not null unique, name text not null, about text, organization_id int not null references app_public.organizations on delete cascade, is_admin boolean not null default false, created_at timestamptz not null default now(), updated_at timestamptz not null default now() );对于这样一张表,PostGraphile 会自动执行以下生成工作:
- 创建 GraphQL 类型:为表创建名为
User的类型(UpperCamelCase + 单数化命名,对应 inflection 中的tableType); - 为类型添加列字段:如
id、username、about、organizationId、isAdmin、createdAt、updatedAt,全部以 camelCase 命名; - 添加
nodeId字段:当表存在主键时,生成全局唯一标识字段nodeId(详见 node-id.md); - 添加关系字段:如
organizationByOrganizationId这类外键关系字段(详见 relations.md); - 反向关系:在相关表类型上添加反向关系字段,例如
Organization.usersByOrganizationId; - CRUD Mutations:在根
Mutation类型上添加增删改变更操作(详见 crud-mutations.md); - Query 字段:在根
Query类型上添加连接查询、唯一键查询与nodeId查询。
文档给出了最终生成在根Query上的字段形态:
type Query implements Node { allUsers( first: Int last: Int offset: Int before: Cursor after: Cursor orderBy: [UsersOrderBy!] = [PRIMARY_KEY_ASC] condition: UserCondition ): UsersConnection userById(id: Int!): User userByUsername(username: String!): User user(nodeId: ID!): User }按命名规约生成查询与类型:inflector 的作用
PostGraphile 的命名并非硬编码,而是由可定制的 inflection 系统统一产出。文档中提到的几个关键规约分别是:
tableType:决定表对应的 GraphQL 类型名(users→User)以及列字段名(camelCase);allRows:决定连接/列表查询的前缀,对应allUsers这类allXxx字段;rowByUniqueKeys:决定按唯一约束取单行的字段,例如userById、userByUsername。
这些规约在源码中有清晰对应。在 PgAllRowsPlugin.ts 中,allRowsConnection通过build.inflection.allRowsConnection(resource)生成连接字段名,而allRowsList生成列表字段名;字段描述也会引用build.inflection.tableType(resource.codec)来拼出类型名。也就是说,allUsers连接字段的名称、描述与返回类型都源自同一个 inflection 管线。
再看唯一键查询。PgRowByUniquePlugin.ts 会枚举表的每个唯一约束(unique key),将约束中的属性列表(如["id"]或["username"])拼接成语义化字段名,并逐一为每个属性生成对应入参;id serial primary key与username citext not null unique因此分别推导出userById(id: Int!)与userByUsername(username: String!)。文档中补充说明:user(nodeId: ID!)则是通过nodeId取任意行的通用入口,由表的全局唯一标识机制提供(详见 node-id.md)。
需要注意的是,关系字段名(如organizationByOrganizationId)在 v5 默认规约下比较冗长。文档提示:加载@graphile/simplify-inflection插件即可简化这些字段名(例如直接使用organization这样的简洁命名)。相关最佳实践见 best-practices.md 中对@graphile/simplify-inflection的使用说明。
连接、过滤与排序是表查询的标准装备
allUsers之所以能携带first/last/offset/before/after等游标分页参数、orderBy排序参数与condition过滤参数,是因为 PostGraphile 的插件体系为每个表资源默认装配了连接行为:
- 连接与分页参数由
PgConnectionArgOrderByPlugin、PgFirstLastBeforeAfterArgsPlugin等插件补充,遵循 GraphQL Cursor Connections 规范并做了增强(如额外的offset参数),详见 connections.md; orderBy默认值为[PRIMARY_KEY_ASC],由PgConnectionArgOrderByDefaultValuePlugin注入,保证默认行为是按主键升序返回;condition: UserCondition由 PgConditionArgumentPlugin.ts 生成,类型名基于tableType推导(conditionType),所有字段按相等条件匹配并取逻辑“与”,即文档 filtering.md 中描述的基础过滤能力。
在实现层面,allRowsConnection与allRowsList分别复用connectionField与listField两条生成路径(PgAllRowsPlugin.ts),因此连接与列表两种形态共享同一套资源定义与排序/过滤逻辑。
权限反射:PgRBACPlugin 如何把 GRANT/REVOKE 翻译成 schema
文档强调:使用PgRBACPlugin时(默认开启,前提是你没有使用makeV4Preset()的 v4 兼容预设,makeV4Preset定义于 v4.ts),PostGraphile 只会暴露你实际拥有权限的表、列与字段。
举例来说,执行:
GRANT UPDATE (username, name) ON users TO graphql_visitor;之后,updateUser变更操作只接受username和name两个字段,其余列不会出现在该 mutation 的入参中。
其底层原理可以从 PgRBACPlugin.ts 看到:该插件被标记为 “Converts the database GRANT/REVOKE privileges to behaviors. Experimental.”,即把数据库的 GRANT/REVOKE 权限转换为 PostGraphile 的 behavior(行为)系统。在pgCodecs_attribute钩子中,插件针对每个列与所属表分别计算select/insert/update权限(通过entityPermissions查询 ACL),再把结果写入属性扩展:
const canSelect = attributePermissions.select || tablePermissions.select; const canInsert = attributePermissions.insert || tablePermissions.insert; const canUpdate = attributePermissions.update || tablePermissions.update;这正是“列级权限反射进 schema”的实现位置。而 PostgreSQL 侧的角色权限信息来自 utils/pg-introspection 包中的 ACL 内省能力(acl.ts):expandRoles会递归展开某个角色被授予的所有角色成员关系(含 PUBLIC,并尊重NOINHERIT),aclContainsRole则判断某条 ACL 是否命中当前角色或其继承链上的角色。整个内省结果由PgIntrospectionPlugin通过pgService.pgSettingsForIntrospection注入连接参数后获取(PgIntrospectionPlugin.ts)。
关键行为:一个 schema,而不是按用户多个 schema
需要特别强调文档中的最佳实践结论:即使数据库中存在多个不同权限的角色,PostGraphile 依然只会生成一个GraphQL schema(而不是每个用户一份)。具体流程是:
- 使用连接字符串中配置的用户身份连接 PostgreSQL;
- 遍历该用户在当前数据库中“可以成为”的全部角色(即其直接与间接成员角色);
- 取所有这些角色权限的并集,作为 schema 的暴露面。
换句话说,schema 暴露的是“你连接的账号在整个角色继承链上能碰到的所有能力”,因此文档建议通过pgService.pgSettingsForIntrospection对象来影响内省时的会话设置(例如切换role或自定义内省 session 变量),从而控制权限并集的边界。该配置项在 dataplan-pg 与 pg.ts 适配器 中均有定义与透传实现。
由于暴露面是权限并集,文档给出两条配套建议:
- 强烈推荐使用
PgRBACPlugin:它让 schema 更精简,不包含你实际用不了的功能; - 强烈建议避免基于列的
SELECT授权(见 requirements.md):列级 SELECT 权限与并集语义配合时容易产生意料之外的暴露,更优做法是把不同权限关注点拆分为独立的表,再用一对一关系连接。
Unlogged 表:默认不暴露,如何放行
PostgreSQL 允许通过CREATE UNLOGGED TABLE创建不写入预写日志(WAL)的表。出于性能与语义考量,PostGraphile 默认不会把 unlogged 表加入 GraphQL schema。
这一行为的实现位于 PgTablesPlugin.ts 的unloggedOrTempBehaviors辅助函数:当表的持久性被判定为"u"(unlogged)或"t"(temp)时,它会追加一组负向 behavior:
[ "-resource:select", "-resource:connection", "-resource:list", "-resource:array", "-resource:single", "-resource:insert", "-resource:update", "-resource:delete", ]由于这些行为被显式关闭,PostGraphile 的 behavior 系统会阻止为该表生成查询、连接、增删改等一切相关字段,最终表现为“不出现在 schema 中”。持久性信息来源于pgClass.relpersistence !== "p"的内省判断(PgTablesPlugin.ts)。
如果确实需要暴露某张 unlogged 表,文档给出的方法是:通过 smart-tags.md(或在源码中直接操作 behavior 扩展)显式地为该表赋予所需行为,例如补充resource:select等正向行为来覆盖默认的负向行为。behavior 字符串的语法与叠加规则见 behavior.md(行为片段用空格分隔、按顺序求值),这也是理解“为何 smart tags 可以覆盖默认排除”的关键。
小结
对 PostGraphile v5 而言,一张 PostgreSQL 表在 GraphQL schema 中的“长相”是确定性推导的结果:tableType决定类型与字段命名,唯一约束决定userByKey系列查询,主键决定nodeId,连接插件装配分页/排序/过滤,PgRBACPlugin按权限并集裁剪暴露面,而 behavior 系统统一决定某张表(如 unlogged 表)是否可见。掌握了这张映射表,你就能在写CREATE TABLE之前,先在脑海中勾勒出它将生成的完整 GraphQL API。
继续深入可阅读仓库中的关联文档:relations、connections、filtering、crud-mutations,以及pgRBAC相关实现 PgRBACPlugin.ts 与 ACL 工具 acl.ts。
- 后端
- API网关
【免费下载链接】crystal
🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
相关推荐
PostGraphile 表驱动的 GraphQL Schema 生成指南:从 PostgreSQL 表到自动化的查询、连接与 CRUD
PostGraphile 表驱动的 GraphQL Schema 生成指南:从 PostgreSQL 表到自动化的查询、连接与 CRUD PostGraphil
后端API网关PostGraphile v5 调试完全指南:从 GraphQL 请求、生成 SQL 到 Schema 与性能问题排查
PostGraphile v5 调试完全指南:从 GraphQL 请求、生成 SQL 到 Schema 与性能问题排查 本文以 PostGraphile v5
后端API网关PostGraphile v4 枚举(Enums)完全指南:从 PostgreSQL 类型映射到枚举表、Domain 与 Schema 扩展
PostGraphile v4 枚举(Enums)完全指南:从 PostgreSQL 类型映射到枚举表、Domain 与 Schema 扩展 导读 本篇指南聚焦
后端API网关
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考