PostGraphile v5 迁移指南从 makeAddPgTableConditionPlugin 升级到 addPgTableCondition【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址: https://gitcode.com/gh_mirrors/cry/crystal本指南聚焦 PostGraphile 从 v4 迁移到 v5 时自定义表集合collection过滤条件插件 API 的核心变化旧 APImakeAddPgTableConditionPlugin来自graphile-utils如何升级为新的addPgTableCondition来自postgraphile/utils。你将掌握新旧两代函数签名的逐项对照、conditionGenerator到apply写法的演进以及sqlValueWithCodec替代sql.value的原因并能在实际项目中直接照搬迁移代码。PostGraphile 会自动为它构建的各种表集合字段添加condition参数用于把结果集过滤到用户关心的记录。默认情况下PostGraphile 会把表的列加入 condition 输入对象你可以指定其值如果指定为null则只返回该列IS NULL的记录。许多 GraphQL 专家认为 GraphQL 过滤器不应过于复杂、也不应过多暴露底层数据存储的细节因此 PostGraphile 默认不提供高级过滤功能若你需要更强大的过滤可参考 PostGraphile 过滤功能文档。然而有时你需要按比表字段更复杂的条件过滤例如按关联表的字段、按某个计算值甚至按一个子查询的结果。这时就需要自定义 condition 插件。旧版本中完成这一工作的工具是makeAddPgTableConditionPlugin在 v5 中它已更名为addPgTableCondition接口也发生了若干重要调整。V4 时代的经典签名makeAddPgTableConditionPlugin在 PostGraphile v4graphile-utils v4.4.5中makeAddPgTableConditionPlugin的简化签名如下// V4 signature function makeAddPgTableConditionPlugin( schemaName: string, tableName: string, conditionFieldName: string, fieldSpecGenerator: (build: Build) GraphQLInputFieldConfig, conditionGenerator: ( value: unknown, helpers: { queryBuilder: QueryBuilder; sql: PgSQL; sqlTableAlias: SQL }, build: Build, ) SQL, ): Plugin;它的五个参数依次为目标 schema 名称、目标表名称、新增 condition 字段的名称、字段规格生成器返回一个 GraphQL 输入字段配置以及条件生成器根据用户输入值返回一段 SQL 片段。注意在 v4 中conditionGenerator是必填参数且它能够拿到完整的build对象。一个典型的 V4 用法如下过滤出宠物数量不少于指定值的用户import { makeAddPgTableConditionPlugin } from graphile-utils; const PetsCountPlugin makeAddPgTableConditionPlugin( graphile_utils, users, petCountAtLeast, (build) ({ description: Filters users to those that have at least this many pets, type: build.graphql.GraphQLInt, }), (value, helpers, build) { const { sqlTableAlias, sql } helpers; return sql.fragment(select count(*) from graphile_utils.pets where pets.user_id ${sqlTableAlias}.id) ${sql.value( value, )}; }, );这段代码为graphile_utils.users表的集合字段新增了一个名为petCountAtLeast的 condition客户端传入一个整数插件就在生成的 SQLWHERE子句中追加一个子查询片段统计该用户拥有的宠物数量并与传入值比较。V5 的两大变化match 对象与 conditionGenerator 重构进入 v5 后接口发生了两处变化其中第一处是轻量级的第二处则可能影响你的既有代码。变化一schemaName/tableName 合并为 match 对象第一个变化非常简单原来前两个参数schemaName与tableName被合并进了一个 match 对象该对象还可选地接受serviceNamematch: { serviceName?: string; schemaName: string; tableName: string }其中serviceName默认值为main用于在配置了多个 Postgres 服务service时指定该表属于哪一个服务。从源码看函数内部正是这样解构并赋予默认值的见 graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.tsconst { serviceName main, schemaName, tableName } match;变化二conditionGenerator 签名重写queryBuilder 换成 condition第二个变化更值得注意。conditionGenerator的签名发生了调整不再使用queryBuilder而是新增了一个可供写入的condition类型为PgCondition。新的 helpers 对象包含sqlpg-sql2 的 SQL 构建工具sqlTableAlias当前表在此次查询中的 SQL 别名SQL类型sqlValueWithCodec结合 codec 将 JavaScript 值编码为正确 SQL 形态的工具函数buildpruneBuild(build)的返回值经过裁剪的 build见下文源码解析condition当前 select 的PgCondition修改器。你依然可以用sql.value(value)嵌入值但官方推荐改用sqlValueWithCodec(value, codec)它会通过相关 codec 负责值的类型转换保证值到达数据库时形状正确。这一点对于数组、JSON、日期等较复杂类型尤为重要。V5 新签名逐参数详解V5 中新的简化签名如下// V5 signature function addPgTableCondition( match: { serviceName?: string; schemaName: string; tableName: string }, conditionFieldName: string, fieldSpecGenerator: (build: GraphileBuild.Build) GrafastInputFieldConfig, // OPTIONAL: conditionGenerator?: ( value: unknown, helpers: { sql: typeof sql; sqlTableAlias: SQL; sqlValueWithCodec: typeof sqlValueWithCodec; build: ReturnTypetypeof pruneBuild; condition: PgCondition; }, ) SQL | null | undefined, ): GraphileConfig.Plugin;注意conditionGenerator现在是可选的因为你可以选择在fieldSpecGenerator的返回结果里直接放入apply或extensions.grafast.apply入口来实现过滤逻辑。这标志着 v5 推荐的写法从返回 SQL 片段转向了在 apply 中操作 condition 对象。各参数的作用参数类型说明match{ serviceName?, schemaName, tableName }定位要附加 condition 的表serviceName可选默认mainconditionFieldNamestring新增 condition 字段的名称例如petCountAtLeastfieldSpecGenerator(build) GrafastInputFieldConfig返回该输入字段的 GraphQL 配置description、type 等可附带applyconditionGenerator可选回调旧式写法返回一段 SQL或null/undefined表示不追加过滤推荐改用applyfieldSpecGenerator返回的是 Grafast 的输入字段配置GrafastInputFieldConfig相比 V4 的GraphQLInputFieldConfig其类型来源也换成了 Grafast 体系。示例 1 迁移对照petCountAtLeast下面把本指南开头那个 V4 示例完整迁移到 V5。迁移后的代码为import { addPgTableCondition } from postgraphile/utils; import { TYPES } from postgraphile/dataplan/pg; const PetsCountPlugin addPgTableCondition( { schemaName: graphile_utils, tableName: users }, petCountAtLeast, (build) ({ description: Filters users to those that have at least this many pets, type: build.graphql.GraphQLInt, }), (value, helpers) { const { sqlTableAlias, sql, sqlValueWithCodec } helpers; return sql.fragment(select count(*) from graphile_utils.pets where pets.user_id ${sqlTableAlias}.id) ${sqlValueWithCodec( value, TYPES.int, )}; }, );与 V4 版本的差异一目了然导入来源从graphile-utils变为postgraphile/utils同时从postgraphile/dataplan/pg导入TYPES内置 codec 集合graphile_utils, users两个平铺参数合并为{ schemaName: graphile_utils, tableName: users }conditionGenerator的第三个参数build不再需要V5 中该位置已被合并进 helpers 的build且是裁剪后的版本sql.value(value)替换为sqlValueWithCodec(value, TYPES.int)让整数类型在进入 SQL 前经过intcodec 的正确编码。更现代的写法在 fieldSpecGenerator 中提供 apply从 V5 起官方推荐的新方式是不再提供conditionGenerator而是在fieldSpecGenerator返回的字段配置中直接给出apply。apply接收两个参数condition当前 select 的PgCondition修改器和value运行时实际的输入值。典型的用法是调用condition.where((sql) ...)来对查询施加过滤并用condition.alias指代当前表。下面的例子为app_public.forums表新增idIn条件用于按一组主键过滤完整示例见 add-pg-table-condition.mdimport { 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), )}), ); }, }; }, );再看一个按关联表过滤的例子筛选出某指定用户曾发过帖的论坛帖子存在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 } } }:::tip 关键提示condition.alias在上面的例子中代表app_public.forums表本身即schemaName.tableName对应的表如果在你的实现里没有使用condition.alias那么插件很可能写错了——因为同一张表可能在一个查询中被请求多次必须通过别名定位到当前这一份实例。 :::源码解析addPgTableCondition 底层是如何工作的addPgTableCondition的完整实现位于 graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.ts从源码可以确认以下关键行为1. 挂载时机与加载顺序。生成的插件声明了before: [PgConnectionArgOrderByPlugin]目的是确保条件插件先于默认排序插件加载否则由 condition 带来的排序效果会被默认排序覆盖。2. 通过 GraphQLInputObjectType_fields hook 注入字段。插件监听GraphQLInputObjectType_fieldshook只有满足以下全部条件时才把conditionFieldName注入字段当前 Input 对象确实是条件输入对象isPgCondition该对象关联的 codecpgCodec存在且有attributes表所属的serviceName、schemaName、name与 match 对象完全一致。3. apply 与 conditionGenerator 二选一。源码会检查字段规格中是否带有apply或extensions.grafast.apply若已提供apply却同时传了conditionGenerator会直接抛出错误You supplied apply for your field spec, so you cannot also supply a conditionGenerator若两者都没有也会抛出错误提示至少提供其一若只提供了conditionGenerator源码会通过EXPORTABLE机制把它包装成一个apply函数调用conditionGenerator(val, { sql, sqlTableAlias: condition.alias, sqlValueWithCodec, build, condition })并把返回的非空表达式通过condition.where(expression)施加到查询上。这解释了为什么新签名里conditionGenerator的返回值可以是SQL | null | undefined——返回null/undefined表示不追加过滤条件。4. pruneBuild 裁剪 build。传入conditionGenerator的build并非完整的 build 对象而是经过pruneBuild裁剪后的版本只暴露sql、grafast、graphql、dataplanPg与input.pgRegistry。这与 postgraphile/postgraphile/CHANGELOG.md 中记录的变更一致v5 禁止导出完整的 build 对象旧式第四参回调里整个 build 对象已不可用若迁移中遇到依赖完整 build 的代码最佳做法是改用三个参数的apply()新写法。5. 名称与失败告警。插件名称按makeAddPgTableConditionPlugin__${schemaName}__${tableName}__${conditionFieldName}生成如果最终没有匹配到目标表例如 schema/table 名称写错finalizehook 会打印警告failed to add condition ... to table .......; did you get the schema/table name right?。6. 兼容性导出。同一文件末尾保留了makeAddPgTableConditionPlugin作为别名导出标记为deprecated已重命名为addPgTableCondition并在 graphile-build/graphile-utils/src/index.ts 中同时导出两个名字方便老代码平滑过渡。迁移要点速查导入路径v4 用graphile-utilsv5 用postgraphile/utilsaddPgTableConditioncodec 相关工具TYPES、sqlValueWithCodec、listOfCodec从postgraphile/dataplan/pg导入。前两个参数schemaName、tableName合并进{ serviceName?, schemaName, tableName }。值编码sql.value(value)建议替换为sqlValueWithCodec(value, codec)复杂类型数组、JSON、日期等必须使用后者以保证值到达数据库时的形态正确。过滤写法conditionGenerator返回SQL片段即可可选但更推荐在fieldSpecGenerator返回的配置里写apply(condition, value)通过condition.where(...)施加过滤两者不能同时提供。build 对象conditionGenerator 的 helpers 中build是裁剪版pruneBuild结果不要再依赖完整的 build需要更多能力时请迁移到apply写法并在fieldSpecGenerator中通过build闭包获取。表别名务必使用condition.alias或 helpers 中的sqlTableAlias来引用当前表而不是硬编码表名否则多表联查或重复请求同一表时会出错。完成上述调整后原有的自定义 condition 插件即可在 PostGraphile v5 中正常工作新项目则应从一开始就采用addPgTableCondition与apply的新式写法。【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址: https://gitcode.com/gh_mirrors/cry/crystal创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
