到 addPgTableCondition)
PostGraphile v5 遷移指南從 makeAddPgTableConditionPlugin 升級(jí)到 addPgTableCondition【免費(fèi)下載鏈接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!項(xiàng)目地址: https://gitcode.com/gh_mirrors/cry/crystal本指南聚焦 PostGraphile 從 v4 遷移到 v5 時(shí)自定義表集合collection過(guò)濾條件插件 API 的核心變化舊 APImakeAddPgTableConditionPlugin來(lái)自graphile-utils如何升級(jí)為新的addPgTableCondition來(lái)自postgraphile/utils。你將掌握新舊兩代函數(shù)簽名的逐項(xiàng)對(duì)照、conditionGenerator到apply寫(xiě)法的演進(jìn)以及sqlValueWithCodec替代sql.value的原因并能在實(shí)際項(xiàng)目中直接照搬遷移代碼。PostGraphile 會(huì)自動(dòng)為它構(gòu)建的各種表集合字段添加condition參數(shù)用于把結(jié)果集過(guò)濾到用戶(hù)關(guān)心的記錄。默認(rèn)情況下PostGraphile 會(huì)把表的列加入 condition 輸入對(duì)象你可以指定其值如果指定為null則只返回該列IS NULL的記錄。許多 GraphQL 專(zhuān)家認(rèn)為 GraphQL 過(guò)濾器不應(yīng)過(guò)于復(fù)雜、也不應(yīng)過(guò)多暴露底層數(shù)據(jù)存儲(chǔ)的細(xì)節(jié)因此 PostGraphile 默認(rèn)不提供高級(jí)過(guò)濾功能若你需要更強(qiáng)大的過(guò)濾可參考 PostGraphile 過(guò)濾功能文檔。然而有時(shí)你需要按比表字段更復(fù)雜的條件過(guò)濾例如按關(guān)聯(lián)表的字段、按某個(gè)計(jì)算值甚至按一個(gè)子查詢(xún)的結(jié)果。這時(shí)就需要自定義 condition 插件。舊版本中完成這一工作的工具是makeAddPgTableConditionPlugin在 v5 中它已更名為addPgTableCondition接口也發(fā)生了若干重要調(diào)整。V4 時(shí)代的經(jīng)典簽名makeAddPgTableConditionPlugin在 PostGraphile v4graphile-utils v4.4.5中makeAddPgTableConditionPlugin的簡(jiǎn)化簽名如下// 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;它的五個(gè)參數(shù)依次為目標(biāo) schema 名稱(chēng)、目標(biāo)表名稱(chēng)、新增 condition 字段的名稱(chēng)、字段規(guī)格生成器返回一個(gè) GraphQL 輸入字段配置以及條件生成器根據(jù)用戶(hù)輸入值返回一段 SQL 片段。注意在 v4 中conditionGenerator是必填參數(shù)且它能夠拿到完整的build對(duì)象。一個(gè)典型的 V4 用法如下過(guò)濾出寵物數(shù)量不少于指定值的用戶(hù)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表的集合字段新增了一個(gè)名為petCountAtLeast的 condition客戶(hù)端傳入一個(gè)整數(shù)插件就在生成的 SQLWHERE子句中追加一個(gè)子查詢(xún)片段統(tǒng)計(jì)該用戶(hù)擁有的寵物數(shù)量并與傳入值比較。V5 的兩大變化match 對(duì)象與 conditionGenerator 重構(gòu)進(jìn)入 v5 后接口發(fā)生了兩處變化其中第一處是輕量級(jí)的第二處則可能影響你的既有代碼。變化一schemaName/tableName 合并為 match 對(duì)象第一個(gè)變化非常簡(jiǎn)單原來(lái)前兩個(gè)參數(shù)schemaName與tableName被合并進(jìn)了一個(gè) match 對(duì)象該對(duì)象還可選地接受serviceNamematch: { serviceName?: string; schemaName: string; tableName: string }其中serviceName默認(rèn)值為main用于在配置了多個(gè) Postgres 服務(wù)service時(shí)指定該表屬于哪一個(gè)服務(wù)。從源碼看函數(shù)內(nèi)部正是這樣解構(gòu)并賦予默認(rèn)值的見(jiàn) graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.tsconst { serviceName main, schemaName, tableName } match;變化二conditionGenerator 簽名重寫(xiě)queryBuilder 換成 condition第二個(gè)變化更值得注意。conditionGenerator的簽名發(fā)生了調(diào)整不再使用queryBuilder而是新增了一個(gè)可供寫(xiě)入的condition類(lèi)型為PgCondition。新的 helpers 對(duì)象包含sqlpg-sql2 的 SQL 構(gòu)建工具sqlTableAlias當(dāng)前表在此次查詢(xún)中的 SQL 別名SQL類(lèi)型sqlValueWithCodec結(jié)合 codec 將 JavaScript 值編碼為正確 SQL 形態(tài)的工具函數(shù)buildpruneBuild(build)的返回值經(jīng)過(guò)裁剪的 build見(jiàn)下文源碼解析condition當(dāng)前 select 的PgCondition修改器。你依然可以用sql.value(value)嵌入值但官方推薦改用sqlValueWithCodec(value, codec)它會(huì)通過(guò)相關(guān) codec 負(fù)責(zé)值的類(lèi)型轉(zhuǎn)換保證值到達(dá)數(shù)據(jù)庫(kù)時(shí)形狀正確。這一點(diǎn)對(duì)于數(shù)組、JSON、日期等較復(fù)雜類(lèi)型尤為重要。V5 新簽名逐參數(shù)詳解V5 中新的簡(jiǎn)化簽名如下// 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現(xiàn)在是可選的因?yàn)槟憧梢赃x擇在fieldSpecGenerator的返回結(jié)果里直接放入apply或extensions.grafast.apply入口來(lái)實(shí)現(xiàn)過(guò)濾邏輯。這標(biāo)志著 v5 推薦的寫(xiě)法從返回 SQL 片段轉(zhuǎn)向了在 apply 中操作 condition 對(duì)象。各參數(shù)的作用參數(shù)類(lèi)型說(shuō)明match{ serviceName?, schemaName, tableName }定位要附加 condition 的表serviceName可選默認(rèn)mainconditionFieldNamestring新增 condition 字段的名稱(chēng)例如petCountAtLeastfieldSpecGenerator(build) GrafastInputFieldConfig返回該輸入字段的 GraphQL 配置description、type 等可附帶applyconditionGenerator可選回調(diào)舊式寫(xiě)法返回一段 SQL或null/undefined表示不追加過(guò)濾推薦改用applyfieldSpecGenerator返回的是 Grafast 的輸入字段配置GrafastInputFieldConfig相比 V4 的GraphQLInputFieldConfig其類(lèi)型來(lái)源也換成了 Grafast 體系。示例 1 遷移對(duì)照petCountAtLeast下面把本指南開(kāi)頭那個(gè) 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 版本的差異一目了然導(dǎo)入來(lái)源從graphile-utils變?yōu)閜ostgraphile/utils同時(shí)從postgraphile/dataplan/pg導(dǎo)入TYPES內(nèi)置 codec 集合graphile_utils, users兩個(gè)平鋪參數(shù)合并為{ schemaName: graphile_utils, tableName: users }conditionGenerator的第三個(gè)參數(shù)build不再需要V5 中該位置已被合并進(jìn) helpers 的build且是裁剪后的版本sql.value(value)替換為sqlValueWithCodec(value, TYPES.int)讓整數(shù)類(lèi)型在進(jìn)入 SQL 前經(jīng)過(guò)intcodec 的正確編碼。更現(xiàn)代的寫(xiě)法在 fieldSpecGenerator 中提供 apply從 V5 起官方推薦的新方式是不再提供conditionGenerator而是在fieldSpecGenerator返回的字段配置中直接給出apply。apply接收兩個(gè)參數(shù)condition當(dāng)前 select 的PgCondition修改器和value運(yùn)行時(shí)實(shí)際的輸入值。典型的用法是調(diào)用condition.where((sql) ...)來(lái)對(duì)查詢(xún)施加過(guò)濾并用condition.alias指代當(dāng)前表。下面的例子為app_public.forums表新增idIn條件用于按一組主鍵過(guò)濾完整示例見(jiàn) 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!]假設(shè)你使用整數(shù)主鍵 type: new GraphQLList(new GraphQLNonNull(GraphQLInt)), apply(condition /* : PgCondition */, ids) { condition.where( (sql) sql${condition.alias}.id ANY(${sqlValueWithCodec( ids, listOfCodec(TYPES.int), )}), ); }, }; }, );再看一個(gè)按關(guān)聯(lián)表過(guò)濾的例子篩選出某指定用戶(hù)曾發(fā)過(guò)帖的論壇帖子存在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 關(guān)鍵提示condition.alias在上面的例子中代表app_public.forums表本身即schemaName.tableName對(duì)應(yīng)的表如果在你的實(shí)現(xiàn)里沒(méi)有使用condition.alias那么插件很可能寫(xiě)錯(cuò)了——因?yàn)橥粡埍砜赡茉谝粋€(gè)查詢(xún)中被請(qǐng)求多次必須通過(guò)別名定位到當(dāng)前這一份實(shí)例。 :::源碼解析addPgTableCondition 底層是如何工作的addPgTableCondition的完整實(shí)現(xiàn)位于 graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.ts從源碼可以確認(rèn)以下關(guān)鍵行為1. 掛載時(shí)機(jī)與加載順序。生成的插件聲明了before: [PgConnectionArgOrderByPlugin]目的是確保條件插件先于默認(rèn)排序插件加載否則由 condition 帶來(lái)的排序效果會(huì)被默認(rèn)排序覆蓋。2. 通過(guò) GraphQLInputObjectType_fields hook 注入字段。插件監(jiān)聽(tīng)GraphQLInputObjectType_fieldshook只有滿(mǎn)足以下全部條件時(shí)才把conditionFieldName注入字段當(dāng)前 Input 對(duì)象確實(shí)是條件輸入對(duì)象isPgCondition該對(duì)象關(guān)聯(lián)的 codecpgCodec存在且有attributes表所屬的serviceName、schemaName、name與 match 對(duì)象完全一致。3. apply 與 conditionGenerator 二選一。源碼會(huì)檢查字段規(guī)格中是否帶有apply或extensions.grafast.apply若已提供apply卻同時(shí)傳了conditionGenerator會(huì)直接拋出錯(cuò)誤You supplied apply for your field spec, so you cannot also supply a conditionGenerator若兩者都沒(méi)有也會(huì)拋出錯(cuò)誤提示至少提供其一若只提供了conditionGenerator源碼會(huì)通過(guò)EXPORTABLE機(jī)制把它包裝成一個(gè)apply函數(shù)調(diào)用conditionGenerator(val, { sql, sqlTableAlias: condition.alias, sqlValueWithCodec, build, condition })并把返回的非空表達(dá)式通過(guò)condition.where(expression)施加到查詢(xún)上。這解釋了為什么新簽名里conditionGenerator的返回值可以是SQL | null | undefined——返回null/undefined表示不追加過(guò)濾條件。4. pruneBuild 裁剪 build。傳入conditionGenerator的build并非完整的 build 對(duì)象而是經(jīng)過(guò)pruneBuild裁剪后的版本只暴露sql、grafast、graphql、dataplanPg與input.pgRegistry。這與 postgraphile/postgraphile/CHANGELOG.md 中記錄的變更一致v5 禁止導(dǎo)出完整的 build 對(duì)象舊式第四參回調(diào)里整個(gè) build 對(duì)象已不可用若遷移中遇到依賴(lài)完整 build 的代碼最佳做法是改用三個(gè)參數(shù)的apply()新寫(xiě)法。5. 名稱(chēng)與失敗告警。插件名稱(chēng)按makeAddPgTableConditionPlugin__${schemaName}__${tableName}__${conditionFieldName}生成如果最終沒(méi)有匹配到目標(biāo)表例如 schema/table 名稱(chēng)寫(xiě)錯(cuò)finalizehook 會(huì)打印警告failed to add condition ... to table .......; did you get the schema/table name right?。6. 兼容性導(dǎo)出。同一文件末尾保留了makeAddPgTableConditionPlugin作為別名導(dǎo)出標(biāo)記為deprecated已重命名為addPgTableCondition并在 graphile-build/graphile-utils/src/index.ts 中同時(shí)導(dǎo)出兩個(gè)名字方便老代碼平滑過(guò)渡。遷移要點(diǎn)速查導(dǎo)入路徑v4 用graphile-utilsv5 用postgraphile/utilsaddPgTableConditioncodec 相關(guān)工具TYPES、sqlValueWithCodec、listOfCodec從postgraphile/dataplan/pg導(dǎo)入。前兩個(gè)參數(shù)schemaName、tableName合并進(jìn){ serviceName?, schemaName, tableName }。值編碼sql.value(value)建議替換為sqlValueWithCodec(value, codec)復(fù)雜類(lèi)型數(shù)組、JSON、日期等必須使用后者以保證值到達(dá)數(shù)據(jù)庫(kù)時(shí)的形態(tài)正確。過(guò)濾寫(xiě)法conditionGenerator返回SQL片段即可可選但更推薦在fieldSpecGenerator返回的配置里寫(xiě)apply(condition, value)通過(guò)condition.where(...)施加過(guò)濾兩者不能同時(shí)提供。build 對(duì)象conditionGenerator 的 helpers 中build是裁剪版pruneBuild結(jié)果不要再依賴(lài)完整的 build需要更多能力時(shí)請(qǐng)遷移到apply寫(xiě)法并在fieldSpecGenerator中通過(guò)build閉包獲取。表別名務(wù)必使用condition.alias或 helpers 中的sqlTableAlias來(lái)引用當(dāng)前表而不是硬編碼表名否則多表聯(lián)查或重復(fù)請(qǐng)求同一表時(shí)會(huì)出錯(cuò)。完成上述調(diào)整后原有的自定義 condition 插件即可在 PostGraphile v5 中正常工作新項(xiàng)目則應(yīng)從一開(kāi)始就采用addPgTableCondition與apply的新式寫(xiě)法。【免費(fèi)下載鏈接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!項(xiàng)目地址: https://gitcode.com/gh_mirrors/cry/crystal創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考