我对PostgreSQL 9.3有以下问题。
有一个视图将一个非平凡的查询封装到某些资源(例如,文档)。让我们说明它就像
一样简单CREATE VIEW vw_resources AS
SELECT * FROM documents; -- there are several joined tables in fact...
客户端应用程序通常在多个字段中使用视图,但通常会对结果进行分页,因此也可能会应用WHERE
和OFFSET
。
现在,在LIMIT
计算的实际资源列表之上,我只想显示当前用户允许的资源。关于特权的规则相当复杂(它们取决于所讨论资源的几个属性,显式ACL,基于用户角色的隐式规则或与其他用户的关系......)所以我想将所有这些属性封装在一个单一功能。为了防止对每个资源进行重复的高成本查询,该函数获取资源ID列表,一次评估所有资源ID的权限,并返回所请求资源ID的集合以及相应的权限(区分读/写)。看起来大致如下:
vw_resources
问题是如何在CREATE FUNCTION list_privileges(resource_ids BIGINT[])
RETURNS TABLE (resource_id BIGINT, privilege TEXT)
AS $function$
BEGIN
-- the function lists privileges for a user that would get passed in an argument - omitting that for simplicity
RAISE NOTICE 'list_privileges called'; -- for diagnostic purposes
-- for illustration, let's simply grant write privileges for any odd resource:
RETURN QUERY SELECT id, (CASE WHEN id % 2 = 1 THEN 'write' ELSE 'none' END)
FROM unnest(resource_ids) id;
END;
$function$ LANGUAGE plpgsql STABLE;
视图中集成这样的函数,以便只提供用户有权使用的资源(即具有vw_resources
或'read'
特权。 / p>
一个简单的解决方案将使用CTE:
'write'
问题是视图本身提供了太多行 - 一些CREATE VIEW vw_resources AS
WITH base_data AS (
SELECT * FROM documents
)
SELECT base_data.*, priv.privilege
FROM base_data
JOIN list_privileges((SELECT array_agg(resource_id) FROM base_data)) AS priv USING (resource_id)
WHERE privilege IN ('read', 'write');
条件和WHERE
/ OFFSET
子句仅应用于视图本身,如LIMIT
(客户端应用程序可能会请求任何复杂的过滤。由于PostgreSQL无法将条件推向CTE,SELECT * FROM vw_resources WHERE id IN (1,2,3) LIMIT 10
函数最终会评估数据库中所有资源的权限,从而有效地降低了性能。
所以我试图使用一个窗口函数来收集整个结果集中的资源ID,并在外部查询中加入list_privileges(BIGINT[])
函数,如下图所示,但list_privileges(BIGINT[])
函数最终成为每一行重复调用(由'list_privileges称为'通知证明),这有点破坏了之前的努力:
list_privileges(BIGINT[])
我会强迫客户端提供两个单独的查询,第一个是CREATE VIEW vw_resources AS
SELECT d.*, priv.privilege
FROM (
SELECT *, array_agg(resource_id) OVER () AS collected
FROM documents
) AS d
JOIN list_privileges(d.collected) AS priv USING (resource_id)
WHERE privilege IN ('read', 'write');
没有应用权限,第二个调用vw_resources
函数传递第一个查询获取的资源ID列表,并过滤客户端上不允许的资源。然而,对于客户来说这是非常笨拙的,并且例如获得前20个允许的资源实际上是不可能的,因为限制第一个查询根本就没有得到它 - 如果某些资源因特权而被过滤掉,那么我们在整体结果中根本没有20行...
欢迎任何帮助!
P.S。为了完整起见,我附加了一个样本list_privileges(BIGINT[])
表:
documents
答案 0 :(得分:2)
如果必须使用plpgsql,则创建不带参数的函数
create function list_privileges()
returns table (resource_id bigint, privilege text)
as $function$
begin
raise notice 'list_privileges called'; -- for diagnostic purposes
return query select 1, case when 1 % 2 = 1 then 'write' else 'none' end
;
end;
$function$ language plpgsql stable;
将其加入其他复杂查询以形成vw_resources
视图
create view vw_resources as
select *
from
documents d
inner join
list_privileges() using(resource_id)
过滤条件将在查询时添加
select *
from vw_resources
where
id in (1,2,3)
and
privilege in ('read', 'write')
让规划师完成其优化魔术并在任何“过早优化”之前检查explain
输出。
这只是一个猜想:该功能可能会使规划人员难以优化。
如果plpgsql不是真的有必要,并且非常频繁,我只会创建一个视图而不是函数
create view vw_list_privileges as
select
1 as resource_id,
case when 1 % 2 = 1 then 'write' else 'none' end as privilege
以与复杂查询相同的方式加入
create view vw_resources as
select *
from
documents d
inner join
vw_list_privileges using(resource_id)