如何在 Postgres 中以只读用户访问 information_schema 外键约束?

How to access information_schema foreign key constraints with read-only user in Postgres?

简介

我一直在开发一个向导来为没有任何 programming/SQL 背景的用户创建复杂的数据库 Postgres 查询。由于存储在 information_schema 中的视图中的外键约束,用户可以 select 任意数量的 table 并且该工具将找到正确的连接设置(因此,用户没有添加 ON table_a.field_1 = table_b.field_2).

在开发过程中,我一直在使用管理数据库用户,现在想将其更改为只读用户以使其更安全。但是,这个只读用户似乎不能访问外键约束。

现状

当多个 table 被 select 编辑时,该工具会尝试获取各个 table 之间的连接,以便了解如何加入它们。在此过程中,将执行以下查询:

SELECT 
  tc.constraint_name, 
  tc.table_name, 
  kcu.column_name, 
  ccu.table_name AS foreign_table_name, 
  ccu.column_name AS foreign_column_name 
FROM information_schema.table_constraints AS tc 
JOIN information_schema.key_column_usage AS kcu 
  ON tc.constraint_name = kcu.constraint_name 
JOIN information_schema.constraint_column_usage AS ccu 
  ON ccu.constraint_name = tc.constraint_name 
WHERE constraint_type = 'FOREIGN KEY' 
  AND ccu.table_name = 'TableB' 
  AND tc.table_name IN ('TableA');

(注意:最后一个WHERE子句使用IN因为可以有多个基数table可用。表A是基数table 并且每个成功的 connected/joined table 将可用于其他连接,例如第三个 table 可以使用 AND ccu.table_name = 'TableC' AND tc.table_name IN ('TableA', 'TableB'); 等等。)

当使用 admin db 用户(具有 GRANT、SELECT、INSERT、UPDATE、DELETE、TRUNCATE 等最常见的权限...)执行查询时,结果如下所示:

constraint_name | table_name | column_name | foreign_table_name | foreign_column_name
----------------+------------+-------------+--------------------+---------------------
constraint1     | TableA     | field_1     | TableB             | field_2
(1 row) 

但是当只读数据库用户运行该查询时,它 returns:

constraint_name | table_name | column_name | foreign_table_name | foreign_column_name
----------------+------------+-------------+--------------------+---------------------
(0 rows)

由于存在但未 returned 的外键约束条目,连接不能正确地写为 SQL 并且用户生成的查询(通过使用向导)失败。

我试过的

当然,首先,我认为只读用户 (ro_user) 可能没有权限访问 table 和数据库 information_schema 中的视图。所以我运行

GRANT SELECT ON ALL TABLES IN SCHEMA information_schema TO ro_user;

作为管理员,但无济于事。更深入地研究文档,我发现 information_schema 中的所有 table 和视图在 postgres 中默认情况下都可用并可供任何用户访问。所以 g运行 赋予 select 特权甚至不应该改变任何东西。

为了确认一下,我也运行

GRANT REFERENCES ON ALL TABLES IN SCHEMA actual_database TO ro_user;

当然,这也没有改变任何东西,因为 REFERENCES 只需要 创建 新外键,我只需要阅读它们。

接下来,我想,可能是工具中的sql由于某些信息不可用而失败,所以我分别通过运行:

查询了三个视图
SELECT * FROM information_schema.table_constraints AS tc WHERE constraint_type = 'FOREIGN KEY';
SELECT * FROM information_schema.key_column_usage AS kcu;
SELECT * FROM information_schema.constraint_column_usage AS ccu;

果然,最后一个不会 return ro_user:

的任何一行
psql=> SELECT * FROM information_schema.constraint_column_usage AS ccu;
 table_catalog | table_schema | table_name | column_name | constraint_catalog | constraint_schema | constraint_name
---------------+--------------+------------+-------------+--------------------+-------------------+-----------------
(0 rows)

而管理员用户得到了很多结果。所以,它归结为一种观点 information_schema.constraint_column_usage

当我在一个小时的时间里输入这个问题,回忆和总结我在过去几天尝试过的所有想法时,我终于找到了原因。

The view constraint_column_usage identifies all columns in the current database that are used by some constraint. Only those columns are shown that are contained in a table owned by a currently enabled role.

来自documentation via this SO answer

通过这个我找到了解决方案

SELECT 
  conrelid::regclass AS table_from,
  conname,
  pg_get_constraintdef(c.oid) AS cdef 
FROM pg_constraint c 
JOIN pg_namespace n 
  ON n.oid = c.connamespace 
WHERE contype IN ('f') 
AND n.nspname = 'public' 
AND pg_get_constraintdef(c.oid) LIKE '%"TableB"%' 
AND conrelid::regclass::text IN ('"TableA"') 
ORDER BY conrelid::regclass::text, contype DESC;

它不会输出与旧查询相同的格式,但它包含相同的信息并且 - 最重要的是 - 可用于 ro_user。