后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载导读本文聚焦 PostGraphile V5 中基于condition参数的连接connection数据过滤体系覆盖开箱即用的等值过滤、behavior filterBy智能标签的显式控制、四类高级过滤扩展手段并深入剖析addPgTableCondition插件生成器的函数签名与源码实现。读完本文你将掌握在 PostGraphile V5 中为表集合字段安全、精准地添加自定义过滤条件的完整实战方案同时理解为何官方强烈建议避免引入通用过滤插件。开箱即用的 condition 等值过滤PostGraphile 在默认情况下会为其构建的各种表集合字段table collection fields如allForums、allPeople自动添加一个condition参数用来把结果集过滤到只包含你关心的记录。这种过滤基于等值比较例如username: Alice或category: ARTICLE这样指定具体值也可以通过传null只保留该列为IS NULL的记录。query ForumsCreatedByUser1 { allForums(condition: { creatorId: 1 }) { nodes { id name } } }默认情况下PostGraphile 会把表的已建立索引的列加入condition输入对象中。你可以在该输入中指定这些列的值或者指定为null以过滤出该列为空的记录。这一设计同时回答了“为什么不能任意列都可过滤”的问题——过滤条件会直接翻译为 SQL 的WHERE子句如果没有索引支撑全表扫描将带来严重的性能问题。仓库中的测试用例可以验证condition参数的实际行为。例如 connections-condition-computed-column.test.graphql 展示了通过condition按计算列过滤computedOut: o1 Budd Deey的查询写法。基于索引的过滤准入控制与 behavior filterBy为什么默认只允许过滤有索引的列官方文档明确指出默认情况下除非你使用 V4 presetPostGraphile 不允许你对没有索引的列进行过滤。这是一项刻意为之的性能保护措施——过滤最终会落到数据库的WHERE子句上无索引列上的过滤很容易造成全表扫描。用 behavior filterBy 显式放行或移除列要强制让某一列出现在过滤选项中可以对该列应用behavior filterBy智能标签反过来也可以用behavior -filterBy强制把它从过滤选项中移除。以 SQL 智能注释Smart Comment为例-- 让 my_column 出现在 condition 参数中即使它没有索引 comment on column my_schema.my_table.my_column is Ebehavior filterBy; -- 把 my_column 从 condition 参数中移除 comment on column my_schema.my_table.my_column is Ebehavior -filterBy;从源码实现看filterBy并不是一个笼统的开关而是一组精细的行为位behavior flags。在 behavior.md 中列出了与过滤相关的完整行为位体系filterBy- 是否可按该实体列、表等过滤proc:filterBy- 是否可按某函数资源proc的结果过滤attribute:filterBy- 是否可按该属性列、属性过滤condition:attribute:filterBy- 是否可在condition参数中按该属性过滤attribute:aggregate:filterBy- 是否可按该属性的聚合结果过滤sum:attribute:aggregate:filterBy- 是否可按该属性的sum聚合过滤resource:aggregates:filterBy- 是否可按另一资源的该资源的聚合过滤sum:resource:aggregates:filterBy- 是否可按该资源sum聚合过滤。这组行为位可以直接在 presets/relay.ts 等 preset 配置中看到实际应用。例如在 v4 preset 的globalBehavior中会全局追加condition:attribute:filterBy与attribute:orderBy以模拟 V4 时代的默认行为同时对于tsvector/tsquery、二进制binary等不适合过滤或排序的列类型又通过entityBehavior显式追加-condition:attribute:filterBy、-attribute:orderBy将其排除见 v4.ts。对于从 V4 迁移过来的用户PgV4SmartTagsPlugin 会把旧式的filterable智能标签翻译为行为位集合filter filterProc filterBy把sortable翻译为orderBy order resource:connection:backwards而旧式omit filter则对应-filter -filterBy见 PgV4SmartTagsPlugin.ts。高级过滤的四种官方推荐路径当等值过滤无法满足需求时官方文档推荐用以下四种方式之一来扩展 schema它们都遵循“只添加非常具体的过滤器、让输入尽可能简单”的原则自定义查询custom queries为Query类型新增专门的查询字段计算列computed columns把数据库函数暴露为字段再配合behavior filterBy使其可被过滤extendSchema直接扩展现有类型与字段addPgTableCondition为现有表集合字段的condition参数追加新的自定义条件本文下一节重点展开。此外还可以通过自定义 Graphile Engine 插件 增强现有连接connection例如接入社区过滤插件见下文“Filter Plugin”。深度剖析addPgTableCondition 插件生成器为什么需要它有时你需要按比“本表字段”更复杂的东西过滤比如按关联表上的字段、按某个计算结果、或者按一组主键。addPgTableCondition就是为这种场景而生的——它帮你为某个表集合字段的condition输入对象新增一个自定义条件字段并在运行时把该字段翻译为一段 SQLWHERE片段。函数签名addPgTableCondition从postgraphile/utils导出完整实现见 makeAddPgTableConditionPlugin.ts其函数签名为function addPgTableCondition( match: { serviceName?: string; schemaName: string; tableName: string }, conditionFieldName: string, fieldSpecGenerator: (build: GraphileBuild.Build) GrafastInputFieldConfig, conditionGenerator?: ( value: unknown, helpers: { sql: typeof sql; sqlTableAlias: SQL; sqlValueWithCodec: typeof sqlValueWithCodec; build: ReturnTypetypeof pruneBuild; condition: PgCondition; }, ) SQL | null | undefined, ): GraphileConfig.Plugin;参数含义match要匹配的表即 schema 名为schemaName、表名为tableName的表serviceName可选默认值为main用于匹配非默认 PG 服务conditionFieldName新增条件字段的名称会出现在该表集合字段的condition输入对象中fieldSpecGenerator返回一个GrafastInputFieldConfig指定该条件的 GraphQL 表示类型、描述等。其中必须包含apply方法声明该条件如何工作conditionGenerator已弃用旧式写法返回一段 SQL 片段或null/undefinedundefined表示该条件不生效。当conditionFieldName命中的字段在查询中被使用时apply会在运行时被调用最终在生成的 SQL 上追加一个WHERE子句多个条件之间以AND组合。从源码结构看makeAddPgTableConditionPlugin.ts该插件通过挂接GraphQLInputObjectType_fieldshook在构建PgCondition输入对象时把自定义字段注入其中并通过before: [PgConnectionArgOrderByPlugin]保证自身先于默认排序插件加载——这样由条件引入的排序如按过滤相关性排序不会被默认排序覆盖。如果 schema/表名写错导致字段注入失败finalizehook 会在构建结束时打印警告提示你检查 schema/表名是否正确。核心 APIPgCondition 与 condition.whereapply收到的第一个参数condition是运行时对当前 SELECT 的PgCondition修改器其实现位于 pgCondition.ts。值得关注的关键点condition.alias代表被过滤表的 SQL 别名例如示例中的app_public.forums表官方特别提醒如果你没有使用condition.alias那么你的插件很可能是错的condition.where((sql) ...)用于向查询追加一个WHERE条件pgCondition.ts多个条件以AND组合插值值必须通过sqlValueWithCodec(...)安全绑定而不是直接拼 SQL 字符串以避免注入风险PgCondition还支持orPlan()/andPlan()/notPlan()/existsPlan()等组合模式见 pgCondition.ts其中existsPlan会生成EXISTS (select 1 from ...)形式的子查询——这也是社区连接过滤插件实现“按关联记录过滤”的底层机制之一。示例 1按一组主键过滤下面的插件为app_public.forums表新增idIn条件返回 id 匹配列表中任意一个的记录import { addPgTableCondition } from postgraphile/utils; import { TYPES, listOfCodec } from postgraphile/dataplan/pg; export default addPgTableCondition( { schemaName: app_public, tableName: forums }, idIn, (build) { const { sqlValueWithCodec, listOfCodec, TYPES } build.dataplanPg; const { GraphQLList, GraphQLNonNull, GraphQLInt } build.graphql; return { description: Filters to records matching one of these ids, // 这是 graphql-js 的 [Int!]前提是你用的是整型主键。 type: new GraphQLList(new GraphQLNonNull(GraphQLInt)), apply(condition /* : PgCondition */, ids) { condition.where( (sql) sql${condition.alias}.id ANY(${sqlValueWithCodec( ids, listOfCodec(TYPES.int), )}), ); }, }; }, );对应的 GraphQL 用法query ForumsWithIds1And2 { allForums(condition: { idIn: [1, 2] }) { nodes { id name } } }示例 2按关联表中的记录过滤EXISTS 子查询要过滤出“某用户曾在其中发过帖”的论坛帖子存储在app_public.posts可以创建如下插件import { addPgTableCondition } from postgraphile/utils; import { TYPES } from postgraphile/dataplan/pg; export default addPgTableCondition( { schemaName: app_public, tableName: forums }, containsPostsByUserId, (build) { const { sqlValueWithCodec, TYPES } build.dataplanPg; const { GraphQLInt } build.graphql; return { description: Filters the list of forums to only those which contain posts written by the specified user., type: GraphQLInt, apply(condition /* : PgCondition */, userId) { condition.where((sql) { const sqlIdentifier sql.identifier(Symbol(postsByUser)); return sqlexists( select 1 from app_public.posts as ${sqlIdentifier} where ${sqlIdentifier}.forum_id ${condition.alias}.id and ${sqlIdentifier}.user_id ${sqlValueWithCodec( userId, TYPES.int, )} ); }); }, }; }, );上面的插件会给app_public.forums表的集合字段添加containsPostsByUserId条件用法如下query ForumsContainingPostsByUser1 { allForums(condition: { containsPostsByUserId: 1 }) { nodes { id name } } }关于弃用的 conditionGenerator在addPgTableCondition的实现中conditionGenerator参数被明确标注为DEPRECATED官方建议迁移到apply写法见 makeAddPgTableConditionPlugin.ts。如果你在fieldSpecGenerator里提供了apply却又同时传了conditionGenerator插件会抛出错误反之如果两者都没提供也会抛出错误提示“不知道如何处理该条件”见 makeAddPgTableConditionPlugin.ts。旧式conditionGenerator会被包装成apply并从build中剪裁prune出最小可导出子集sql、grafast、graphql、dataplanPg、pgRegistry用于导出场景见pruneBuildmakeAddPgTableConditionPlugin.ts。Filter Plugin社区通用过滤插件与官方警告官方明确警告通用过滤能力可能是个错误文档用一整段警告框强调为 GraphQL API 添加强大的通用过滤能力是被强烈不鼓励的。这不仅出自 PostGraphile 维护者 Benjie也出自 GraphQL 发明者之一的 Lee Byron 以及 GraphQL 生态中的多位专家。官方强烈建议只使用上文提到的某一种技术添加非常具体的过滤器并且让输入尽量简单而不是使用类似本文下面的通用过滤插件。不听从这一建议可能会在将来引发非常严重的性能问题而且这些问题一旦出现会非常难以摆脱。当然文档也客观地补充道这是你的 schema你比文档更了解它的受众和用法——通用过滤确实存在合理的使用场景但请务必慎重思考不要一时冲动就开启它。Matt Bretl 的 connection-filter 插件一个非常流行的插件是 Matt Bretl 的 connection-filter 插件位于 graphile-contrib 组织下的postgraphile-plugin-connection-filter。它会为连接connection添加一个filter参数支持对其他表中的关联记录进行过滤使用大于、小于、范围等操作符过滤甚至针对函数的输出结果进行过滤。如果你确实需要高级过滤并且可以使用类似持久化查询persisted queries的机制来防止恶意方发起复杂请求官方推荐了解一下它——但务必牢记上述警告。关于持久化查询的细节可参考 生产环境文档中的简单查询白名单/持久化操作一节。从源码角度印证PgCondition.ignoreUnlessAmended()方法的存在注释中明确写道它“专门为了让 postgraphile-plugin-connection-filter 能返回一个供子级使用的条件但如果没有任何子级添加需求则不真正应用该条件”见 pgCondition.ts这从侧面说明官方在实现层面对该插件的兼容性做了考量。其他社区过滤插件社区中还有其他与过滤相关的插件你可以在 社区插件页面 了解它们的更多信息。从 V4 迁移时的过滤行为差异如果你正在使用 V4 preset即通过postgraphile的 V4 兼容模式或迁移文档中描述的方式启用需要注意过滤默认行为的差异V4 preset 在globalBehavior中全局追加condition:attribute:filterBy与attribute:orderBy见 v4.ts即默认允许对任意列过滤这与 V5 默认“仅允许过滤有索引的列”形成对比旧式智能标签filterable、sortable、omit会由 PgV4SmartTagsPlugin 自动翻译为 V5 的行为位语法即便在 V4 preset 下tsvector/tsquery与二进制列也仍会被显式排除在过滤/排序之外见 v4.ts。小结与最佳实践优先使用内置condition等值过滤对已索引列做等值过滤是零成本、零风险的方案用behavior filterBy精确控制过滤白名单对确需过滤但未建索引的列显式放行对敏感列显式移除同时注意为高频过滤列补充数据库索引避免全表扫描需要复杂过滤时走官方推荐的四条路径自定义查询、计算列、extendSchema 或addPgTableCondition并坚持“具体、简单”的过滤设计谨慎评估通用过滤插件connection-filter 类插件功能强大但请先阅读官方警告、评估你的使用场景与受众并考虑配合持久化查询等机制控制请求复杂度编写自定义条件时务必使用condition.alias与sqlValueWithCodec前者确保 SQL 引用了正确的表后者保证值的安全插值。通过合理组合上述手段你可以在保持 GraphQL API 简洁、性能可控的前提下构建出恰好满足业务需求的过滤能力。赞分享后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载相关推荐PostGraphile v4 数据过滤实战指南从 condition 基础过滤到高级过滤扩展PostGraphile v4 数据过滤实战指南从 condition 基础过滤到高级过滤扩展 本指南系统梳理 PostGraphile v4 中围绕数据过后端API网关使用 addPgTableCondition 为 PostGraphile 表集合扩展自定义 condition 过滤使用 addPgTableCondition 为 PostGraphile 表集合扩展自定义 condition 过滤 本篇指南讲解 PostGraphile后端API网关PostGraphile v5 迁移指南从 makeAddPgTableConditionPlugin 到 addPgTableConditionPostGraphile v5 迁移指南从 makeAddPgTableConditionPlugin 到 addPgTableCondition 导读 本文后端API网关创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
