如何在Hasura中基于关联表配置带过滤规则的数组关系
可行方案总结
该需求可完全在Hasura服务端实现,无需客户端传递禁玩游戏ID集合,不存在绕过风险,共有3种成熟实现路径,优先选择原生权限规则方案,复用逻辑选SQL函数方案,固定查询场景可选视图方案。
两个核心认知澄清
- 函数参数仅支持
IN模式和NOT IN语法无任何冲突:前者是SQL函数的参数传参方向定义(IN为入参、OUT为出参、INOUT为双向传参),后者是标准SQL集合过滤语法,函数内部可正常使用NOT IN/NOT EXISTS等所有合法过滤语法。 - 自定义函数被标记为VOLATILE无法使用的核心原因:Hasura要求用于关联查询、计算字段的函数必须标记为
STABLE(相同输入返回稳定结果,无数据修改逻辑)或IMMUTABLE,你之前创建的函数未手动指定稳定性标记,默认使用VOLATILE(代表函数存在数据修改逻辑、或返回结果随外部状态无规律变动),因此无法被Hasura识别为可关联的查询函数。
方案1:Hasura原生权限规则(优先推荐,零自定义SQL)
该方案无需编写任何自定义函数/视图,直接利用Hasura内置的行级权限能力实现强制过滤,是稳定性最高、维护成本最低的方案:
- 先确认JWT令牌解析后已在Hasura会话变量中注入
x-hasura-user-id(当前登录用户ID)、x-hasura-site-id(当前用户所属站点ID)。 - 找到
categories表关联games的数组关系(即你查询示例中的games字段),为面向普通用户的角色(如user角色)配置Select权限,在该关系的where过滤规则中写入以下逻辑:
{ "_and": [ // 过滤站点全局禁用游戏 {"id": {"_nin": "SELECT game_id FROM site_disabled_games WHERE site_id = X-Hasura-Site-Id"}}, // 过滤当前用户个人禁玩游戏 {"id": {"_nin": "SELECT game_id FROM user_banned_games WHERE user_id = X-Hasura-User-Id"}}, // 仅返回当前站点已签约开放供应商旗下的游戏 { "provider_games": { "provider": { "site_providers": {"site_id": {"_eq": "X-Hasura-Site-Id"}} } } } ] }
该层过滤规则在服务端强制生效,和客户端传入的查询参数无关,配置完成后原有GraphQL查询中手动传的
_nin参数可以完全删除,客户端无论如何构造查询语句都无法绕过过滤规则获取未授权的游戏数据。
方案2:修正SQL函数实现(适合多场景复用过滤逻辑)
如果需要在多个查询、多个关联关系中复用这套可用游戏过滤逻辑,可以修正原有函数的稳定性标记,补全全部业务规则,注意尽量用NOT EXISTS替代NOT IN,避免字段空值导致的过滤异常:
CREATE OR REPLACE FUNCTION public.available_games(category_row categories) RETURNS SETOF games LANGUAGE sql STABLE -- 必须添加STABLE标记,Hasura才可正常识别为可关联的计算字段 AS $function$ SELECT g.* FROM games g -- 关联当前分类下的游戏 INNER JOIN game_category gc ON gc.game_id = g.id AND gc.category_id = category_row.id -- 关联供应商关系,校验供应商对当前站点开放 INNER JOIN provider_games pg ON pg.game_id = g.id INNER JOIN site_providers sp ON sp.provider_id = pg.provider_id AND sp.site_id = category_row.site_id -- 排除站点全局禁用游戏 WHERE NOT EXISTS ( SELECT 1 FROM site_disabled_games sdg WHERE sdg.site_id = category_row.site_id AND sdg.game_id = g.id ) -- 排除当前用户个人禁玩游戏,从Hasura会话变量读取用户ID AND NOT EXISTS ( SELECT 1 FROM user_banned_games ubg WHERE ubg.user_id = (current_setting('hasura.user', true)::jsonb->>'x-hasura-user-id')::uuid AND ubg.game_id = g.id ) $function$;
函数创建完成后在Hasura控制台追踪(Track)该函数,即可将其作为categories表下的计算字段,直接在GraphQL查询中调用,自动携带所有过滤规则。
方案3:视图实现(适合固定维度查询场景)
如果你的业务查询场景固定为「按分类拉取可用游戏」,可以直接将过滤逻辑写入视图,给视图配置行级权限即可直接查询:
CREATE OR REPLACE VIEW public.category_available_games AS SELECT c.id AS category_id, c.title AS category_title, c.site_id, g.id AS game_id, g.name AS game_name, p.id AS provider_id, p.name AS provider_name FROM categories c INNER JOIN game_category gc ON gc.category_id = c.id INNER JOIN games g ON g.id = gc.game_id INNER JOIN provider_games pg ON pg.game_id = g.id INNER JOIN providers p ON p.id = pg.provider_id INNER JOIN site_providers sp ON sp.provider_id = p.id AND sp.site_id = c.site_id WHERE NOT EXISTS ( SELECT 1 FROM site_disabled_games sdg WHERE sdg.site_id = c.site_id AND sdg.game_id = g.id ) AND NOT EXISTS ( SELECT 1 FROM user_banned_games ubg WHERE ubg.user_id = (current_setting('hasura.user', true)::jsonb->>'x-hasura-user-id')::uuid AND ubg.game_id = g.id );
视图创建完成后在Hasura控制台追踪该视图,为普通用户角色配置site_id = X-Hasura-Site-Id的行级过滤规则,即可直接在GraphQL中查询视图数据,无需额外关联。
内容的提问来源于stack exchange,提问作者siniradam
相关产品推荐
相关产品推荐

