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