PostGraphile 数据库函数画廊:用 PostgreSQL 函数驱动自定义查询、计算列与自定义 Mutation

发布时间:2026/9/24 6:12:02
PostGraphile 数据库函数画廊:用 PostgreSQL 函数驱动自定义查询、计算列与自定义 Mutation 后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载PostGraphile 的核心设计理念之一是数据库优先——你可以把业务逻辑直接写成 PostgreSQL 函数PostGraphile 会自动将其暴露为 GraphQL Schema 中的查询字段、表类型字段或 Mutation 字段从而省去手写解析器的工作。本篇技术指南以官方文档 function-gallery.md 为主体带你逐个拆解当前登录用户查询计算列批量插入 Mutation三类真实可运行的函数示例并结合配套文档与源码解释其底层判定规则、性能影响与调试方法。读完本文你将掌握在 PostGraphile 中通过create function快速扩展 API 的完整套路并能判断一个函数会被暴露为查询、字段还是变更。三种函数形态一览在 PostGraphile 中PostgreSQL 函数会根据其签名特征被归类为三种 GraphQL 形态形态GraphQL 表现核心判定条件计算列Computed Column表类型上的额外字段函数名以表名加_开头、首参数为表类型、STABLE/IMMUTABLE、与表同 Schema自定义查询Custom Query根级Query字段首参数不是表类型、不返回VOID、STABLE/IMMUTABLE、位于被内省的 Schema自定义 Mutation根级Mutation字段VOLATILEPostgreSQL 函数默认值、位于被内省的 Schema三种形态都需遵守 PostGraphile 通用函数限制例如参数类型与返回类型必须可映射到 GraphQL。从源码结构看函数与表、视图在 dataplan-pg 中统一抽象为PgResource——datasource.ts 中注释明确写道 PgResource represents any resource of SELECT-able data in Postgres: tables, views, functions, etc.并通过isMutation、returnsSetof等选项见 PgFunctionResourceOptions区分其 GraphQL 暴露方式。自定义查询当前登录用户字段原文档第一个例子解决了一个非常常见的需求暴露一个viewer字段返回当前登录用户在users表中的记录。SQL 定义create function viewer() returns users as $$ select * from users where id current_user_id(); /* * current_user_id() is a function * that returns the logged in users * id, e.g. by extracting from the JWT * or indicated via pgSettings. */ $$ language sql stable set search_path from current;要点拆解language sql stableSTABLE向 PostgreSQL 声明该函数不修改数据且在同一表扫描内对相同参数返回一致结果。PostGraphile 正是依据STABLE/IMMUTABLE将函数归类为查询而非变更VOLATILE是函数默认值会被视为 mutation。set search_path from current继承调用者当前的search_path避免硬编码 Schema便于函数在不同 Schema 语境下复用。current_user_id()这是业务约定的辅助函数通常从 JWT 声明或pgSettingsPostGraphile 的pgSettings参数中提取登录用户 ID。PostGraphile 在请求期间通过pgSettings将角色、JWT 声明等注入数据库会话使这类函数成为可能。该函数无参数、返回单行复合类型因此 PostGraphile 会将其注册为根级Query.viewer字段返回类型为User。生成的 GraphQL Schema原文档给出了 Schema 变更 diff要点是在Query类型上新增字段--- Original GraphQL Schema Modified GraphQL Schema -1795,6 1795,7 Chosen by fair dice roll. Guaranteed to be random. XKCD#221 randomNumber: Int viewer: User Reads a single Forum using its globally unique ID. forumByNodeId(之后客户端即可直接查询{ viewer { id username avatarUrl } }若你想在 JavaScript 侧实现同样的字段可使用extendSchema插件用 GraphQL SDL 扩展类型并定义 Grafast plan——这是官方推荐的另一种方式详见 custom-queries.md。自定义查询的通用规则与建议除了viewer之外自定义查询还支持返回标量、记录、列表与集合RETURNS SETOF ...后者暴露为连接或列表函数若有参数首参数不能是表类型否则会被识别为计算列不能返回VOID必须位于 PostGraphile 内省的 Schema 中参数建议全部命名未命名参数会被自动命名为arg1、arg2……见 functions.md。性能提醒对函数做分页时PostGraphile 内部只使用LIMIT/OFFSET映射游标分页若分页到第 100000 条记录PostgreSQL 必须先把函数执行到第 100000 条。建议在函数内部自行施加限制与过滤暴露为 GraphQL 参数把数据产量控制在小范围内。详细分析与免责声明见 custom-queries.md。计算列仅本人可见的主邮箱第二个例子展示了计算列的典型用法给User类型附加一个primaryEmail字段且出于隐私考虑仅当目标用户就是当前登录用户时才返回值其他人得到null。SQL 定义/* * Returns the primary email of the * current user; for all other users * this function will return null. */ create function users_primaryEmail(u users) returns text as $$ select email from user_emails where user_id current_user_id() and user_id u.id and is_verified is true order by id asc limit 1; $$ language sql stable set search_path from current;要点拆解命名规则函数名users_primaryEmail以表名users加下划线开头这是 PostGraphile 识别计算列的第一要件首参数为表类型u users表明该函数作用于users表的每一行——这是计算列的第二个要件stable计算列必须是STABLE或更少见的IMMUTABLEVOLATILE默认会被视为 mutation同 Schema函数必须与目标表定义在同一个 PostgreSQL Schema 中隐私逻辑user_id current_user_id() and user_id u.id同时校验这是当前用户与这行就是目标用户is_verified is true只取已验证邮箱order by id asc limit 1保证在多个邮箱时取最早的一个。生成的 GraphQL Schema--- Original GraphQL Schema Modified GraphQL Schema -3130,6 3130,7 condition: QuizEntryCondition ): QuizEntriesConnection! primaryEmail: String }客户端查询时甚至察觉不到这是函数计算的结果{ userById(id: 42) { primaryEmail } }计算列的底层原理与性能PostGraphile 会把计算列函数内联进主SELECT语句不额外发起 SQL 查询详见 computed-columns.md 的性能提示。这也是 PostgreSQL 原生支持的语法糖——官方文档指出col(table)与table.col两种记法可互换因此person.person_full_name看起来就像表的真实列select person.id, person.person_full_name as full_name -- 等价写法 -- person_full_name(person) as full_name from person where id $1;若函数返回SETOF如users_friends(u users)返回该用户的全部好友PostGraphile 会自动用子查询聚合包裹防止父查询多出额外行select person.id, array( select posts.* from person_favorite_posts(person) posts ) as favorite_posts from person where id $1;性能警告SQL 函数调用本身有开销在数千行上逐行调用会累积。PostgreSQL 可以内联LANGUAGE sql的函数需满足一组规则如函数体为单个SELECT但plpgsql函数永远无法内联。若计算列拖慢查询官方建议用extendSchema插件把逻辑搬到应用层、将 SQL 直接注入查询见 functions.md 中的users_filtered_things改造示例。调试清单摘自 computed-columns.md计算列缺失或异常时依次检查——函数名是否为表名_xxx、首参数是否为表类型、是否标记stable/immutable、是否与表同 Schema、能否在 SQL 中直接以table.function或function(table)语法执行。自定义 Mutation批量插入多条答题记录第三个例子是一个真实的多步变更注册一条问卷答题记录同时把每个答案写入独立的答案表。这体现了自定义 Mutation 相比自动生成的 CRUD Mutation 的价值——一次 GraphQL 请求完成跨表写入。SQL 定义/** * This type is used for input in the mutation */ create type quiz_entry_input as ( question text, answer int ); /** * Heres the function that gets turned into a custom mutation */ create function add_quiz_entry( quiz_id int, answers quiz_entry_input[] ) returns quiz_entry as $$ declare q quiz_entry; a quiz_entry_answer; begin insert into quiz_entry(user_id, quiz_id) values(current_user_id(), quiz_id) returning * into q; foreach a in array answers loop insert into quiz_entry_answer(quiz_entry_id, question, answer) values (quiz_id, a.question, a.answer); end loop; return q; end; $$ language plpgsql volatile strict set search_path from current;要点拆解language plpgsql volatileVOLATILE是 PostgreSQL 函数默认值PostGraphile 依据它把函数识别为变更这里使用plpgsql是因为需要变量声明declare与循环foreach。strict任一参数为NULL时函数直接返回NULL而不执行这使 GraphQL 中的quizId与answers成为必填参数。复合数组入参quiz_entry_input[]映射为 GraphQL 中的[QuizEntryInputRecordInput!]!展示了一个自定义复合类型如何自动转成输入对象。跨表事务先插入quiz_entry主记录再循环插入每条答案最后返回主记录——整个过程在一个数据库事务中完成。生成的 GraphQL Schema原文档给出了完整的 Schema diff这里提炼核心结构All input for the addQuizEntry mutation. input AddQuizEntryInput { clientMutationId: String quizId: Int! answers: [QuizEntryInputRecordInput]! } The output of our addQuizEntry mutation. type AddQuizEntryPayload { clientMutationId: String quizEntry: QuizEntry query: Query user: User quiz: Quiz quizEntryEdge(orderBy: [QuizEntriesOrderBy!] [PRIMARY_KEY_ASC]): QuizEntriesEdge } An input for mutations affecting QuizEntryInputRecord input QuizEntryInputRecordInput { question: String answer: Int }注意其输出同时包含关联的user、quiz与quizEntryEdge字段——PostGraphile 会自动为自定义 Mutation 生成符合 Relay Input Object Mutations 规范 的input/payload结构并在 payload 中附带可继续查询的关系字段。自定义 Mutation 的规则与进阶规则简单函数必须为VOLATILE默认、定义在被内省的 Schema 中、遵守通用函数限制无需像查询那样声明STABLE。返回类型自由可返回void、标量、记录、列表或SETOF记录集合。若返回SETOF如批量插入后返回全部新记录则暴露为列表而非连接——因为无法对变更结果分页见 functions.md。SECURITY DEFINER慎用函数默认以调用者SECURITY INVOKER权限执行受 RLS/GRANT 约束标记SECURITY DEFINER后将以定义者通常是数据库所有者权限执行可绕过 RLS 与 RBAC——官方明确警告这相当于sudo务必谨慎见 custom-mutations.md。批量插入范式对插入并返回多条记录的场景推荐使用generate_series加insert ... returning *的集合写法而非循环create function app_public.create_documents(num integer, type text, location text) returns setof app_public.document as $$ insert into app_public.document (type, location) select create_documents.type, create_documents.location from generate_series(1, num) i returning *; $$ language sql strict volatile;原文档add_quiz_entry使用foreach循环属于刻意展示的 plpgsql 写法真实生产环境应优先考虑集合操作详见下文性能章节。pgStrictFunctions全局参数必填若希望所有函数参数有默认值的除外在 GraphQL 中一律必填可开启preset.gather.pgStrictFunctions见 custom-mutations.mdexport default { // ... gather: { pgStrictFunctions: true, }, };这与逐函数标记STRICT类似但有细微差别开启后无默认值的参数必填有默认值的参数可选仍可显式传NULL而不会使函数返回NULL。例如create function foo(a int, b int, c int 0, d int null)...将生成foo(a: Int!, b: Int!, c: Int, d: Int)。写高性能函数PostgreSQL 侧的必修课函数写得好不好直接决定 API 快不快。以下是 functions.md 强调的核心原则避免循环拥抱集合操作FOR、FOREACH、LOOP是过程式编程者的本能但在 PostgreSQL 中每个函数调用、每条语句都有执行开销。向archive_forums(int[])传入 100 个 ID若用循环则会产生 200 条 SQL 语句改用批量更新只需 2 条-- BADforeach 循环逐条 update -- GOOD集合操作 create function archive_forums(forum_ids int[]) returns void as $$ update forums set is_archived true where id ANY(forum_ids); update posts set is_archived true where forum_id ANY(forum_ids); $$ language sql volatile;后表依赖前表结果时可用 CTE 合并为单条语句with updated_forums as (update ... returning id) update posts ... from updated_forums。函数内联SQL 是唯一可内联的语言大多数函数对 PostgreSQL 优化器而言是黑盒无法下推WHERE/ORDER BY必须先物化全部结果再排序。唯一例外是满足内联规则的LANGUAGE sql函数如函数体是单条SELECT——plpgsql函数永远无法内联。因此能写 SQL 函数就写 SQL 函数无法内联且昂贵的表现层函数搜索、摘要等非安全关键逻辑建议迁到extendSchema插件中将 SQL 直接注入查询让优化器完全可见。RLS 函数反模式绝不要把行数据传入函数常见错误是把行数据作为参数传入 RLS 策略调用的函数-- BAD对每行/每个 organization_id 都要执行一次 security definer 函数 create function current_user_is_member_of(target_organization_id int) returns boolean ... create policy select_members for select on members using (current_user_is_member_of(members.organization_id));正确做法是让函数无参或仅常量参数一次调用、结果复用再结合索引过滤create function current_user_organization_ids() returns setof int as $$ select organization_id from members where user_id current_user_id(); $$ language sql stable security definer; create policy select_members for select on members using (organization_id in (select current_user_organization_ids()));官方文档指出这种写法可以比前者快成千上万倍。VOLATILE / STABLE / IMMUTABLE一次说清PostgreSQL 的VOLATILE、STABLE、IMMUTABLE三档声明同时影响优化行为与 PostGraphile 的函数归类声明语义PostgreSQL 文档PostGraphile 归类HTTP 类比VOLATILE默认函数值在单次表扫描内也可能变化禁止优化有副作用必须声明为此自定义 MutationPOST/PUT/PATCH/DELETESTABLE不修改数据库单次表扫描内同参同值跨语句可能变化自定义查询 / 计算列GET/HEADIMMUTABLE不修改数据库同参永远同值可被常量折叠替换自定义查询 / 计算列较少见GET/HEAD实践建议拿不准就用STABLEIMMUTABLE要求函数完全依赖参数、不做数据库查询、不读配置变量官方建议成为专家前尽量少用详见 functions.md。补充技巧命名参数与冲突消解必须使用命名参数GraphQL 只允许命名参数PostgreSQL 中未命名的参数会被 PostGraphile 命名为arg1、arg2……对 Schema 与同事都不友好。参数与列名冲突可用函数名限定参数users.id get_user.id或用数字参数$1plpgsql 的INSERT ... ON CONFLICT (id)场景可用#variable_conflict use_column指令让 PL/pgSQL 优先把标识符当作列。命名返回类型避免returns table(...)会生成匿名类型、无法挂接关系优先returns setof my_type或returns my_type便于后续用插件增强类型见 custom-queries.md。结语从viewer查询、primaryEmail计算列到add_quiz_entry批量变更function-gallery.md 的三段代码展示了 PostGraphile 数据库函数即 API 的完整工作流写好函数 → 声明正确的稳定性级别 → 遵守命名与位置规则 → PostGraphile 自动生成符合规范的 GraphQL Schema。进一步深入学习建议通读 functions.md函数性能与语言选择、computed-columns.md、custom-queries.md 与 custom-mutations.md它们共同构成 v5 中以数据库函数扩展 Schema的完整知识体系。赞分享后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载相关推荐PostGraphile 数据库函数画廊实战自定义查询、计算列与自定义变更的完整示例PostGraphile 数据库函数画廊实战自定义查询、计算列与自定义变更的完整示例 数据库函数是 PostGraphile 扩展 GraphQL Schem后端API网关PostGraphile 自定义变更Custom Mutations用 PostgreSQL 函数编写业务级 MutationPostGraphile 自定义变更Custom Mutations用 PostgreSQL 函数编写业务级 Mutation PostGraphile后端API网关PostGraphile v5 数据库函数完全指南计算列、自定义查询与变更的编写与性能优化PostGraphile v5 数据库函数完全指南计算列、自定义查询与变更的编写与性能优化 PostgreSQL 函数是给 PostGraphile Grap后端API网关创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考