将用户标识传递给PostgreSQL触发器
我正在使用PostgreSQL 9.1。 我的数据库是结构化的,以便有我的应用程序使用的实际表。 对于每个表都有历史表,只存储更改历史logging。 历史表包含相同的字段,实际表格加上字段形成一些额外的信息,例如。 编辑时间。 历史logging表只能由触发器处理。
我有两种触发器:
-
Before INSERT
触发器在创build表时添加一些额外的信息(例如create_time)。 -
Before UPDATE
触发器before DELETE
和before DELETE
触发器before DELETE
,将旧的值从实际表格复制到历史表格。
问题是我想使用触发器来存储进行这些更改的用户的ID。 而由id我的意思是从PHP应用程序的ID,而不是PostgreSQL的用户ID。
有没有合理的方法来做到这一点?
通过INSERT和UPDATE,可以为实际表添加额外的id字段,并将用户ID作为SQL查询的一部分传递给SQL。 据我所知,这不适用于DELETE。
所有触发器的结构如下:
CREATE OR REPLACE FUNCTION before_delete_customer() RETURNS trigger AS $BODY$ BEGIN INSERT INTO _customer ( edited_by, edit_time, field1, field2, ..., fieldN ) VALUES ( -1, // <- This should be user id. NOW(), OLD.field1, OLD.field2, ..., OLD.fieldN ); RETURN OLD; END; $BODY$ LANGUAGE plpgsql
选项包括:
-
当你打开一个连接时,
CREATE TEMPORARY TABLE current_app_user(username text); INSERT INTO current_app_user(username) VALUES ('the_user');
CREATE TEMPORARY TABLE current_app_user(username text); INSERT INTO current_app_user(username) VALUES ('the_user');
。 然后在你的触发器中,SELECT username FROM current_app_user
获取当前的用户名,可能是一个子查询。 -
在
postgresql.conf
为自定义GUC创build一个条目,如my_app.username = 'unknown';
。 每当你创build一个连接运行SET my_app.username = 'the_user';
。 然后在触发器中,使用current_setting('my_app.username')
函数获取该值。 实际上,您滥用GUC机制来提供会话variables。 阅读适用于您的服务器版本的文档,因为在9.2中更改了自定义的GUC 。 -
调整您的应用程序,使其具有每个应用程序用户的数据库angular色。 在做工作之前给用户
SET ROLE
。 这不仅可以让你使用内build的current_user
类似于variables的函数SELECT current_user;
,它也允许你在数据库中执行安全性 。 看到这个问题 。 您可以直接以用户身份login,而不是使用SET ROLE
,但这往往会使连接池变得困难。
在这三种情况下,你都要连接池,所以你必须小心地DISCARD ALL;
当你返回一个连接池。 ( 虽然没有logging , DISCARD ALL
会执行RESET ROLE
)。
演示的通用设置:
CREATE TABLE tg_demo(blah text); INSERT INTO tg_demo(blah) VALUES ('spam'),('eggs'); -- Placeholder; will be replaced by demo functions CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$ SELECT 'unknown'; $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION tg_demo_trigger() RETURNS trigger AS $$ BEGIN RAISE NOTICE 'Current user is: %',get_app_user(); RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER tg_demo_tg AFTER INSERT OR UPDATE OR DELETE ON tg_demo FOR EACH ROW EXECUTE PROCEDURE tg_demo_trigger();
使用GUC:
- 在
postgresql.conf
的CUSTOMIZED OPTIONS
部分,添加一行,如myapp.username = 'unknown_user'
。 在9.2以前的PostgreSQL版本中,您还必须设置custom_variable_classes = 'myapp'
。 - 重新启动PostgreSQL。 您现在可以
SHOW myapp.username
并获取值unknown_user
。
现在你可以使用SET myapp.username = 'the_user';
当你build立连接时,或者交替地SET LOCAL myapp.username = 'the_user';
在BEGIN
事务之后,如果你希望它是交易本地的,这对于汇集连接是方便的。
get_app_user
函数定义:
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$ SELECT current_setting('myapp.username'); $$ LANGUAGE sql;
使用SET LOCAL
进行交易本地当前用户名的演示:
regress=> BEGIN; BEGIN regress=> SET LOCAL myapp.username = 'test_user'; SET regress=> INSERT INTO tg_demo(blah) VALUES ('42'); NOTICE: Current user is: test_user INSERT 0 1 regress=> COMMIT; COMMIT regress=> SHOW myapp.username; myapp.username ---------------- unknown_user (1 row)
如果使用SET
而不是SET LOCAL
则设置将不会在提交/回滚时被恢复,因此它在整个会话中保持不变。 它仍然被DISCARD ALL
重置:
regress=> SET myapp.username = 'test'; SET regress=> SHOW myapp.username; myapp.username ---------------- test (1 row) regress=> DISCARD ALL; DISCARD ALL regress=> SHOW myapp.username; myapp.username ---------------- unknown_user (1 row)
使用临时表
这种方法需要使用触发器(或由触发器调用的辅助函数,最好是)每次会话都应尝试从临时表读取值。 如果无法find临时表,则会提供默认值。 这可能会有点慢 。 仔细testing。
get_app_user()
定义:
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$ DECLARE cur_user text; BEGIN BEGIN cur_user := (SELECT username FROM current_app_user); EXCEPTION WHEN undefined_table THEN cur_user := 'unknown_user'; END; RETURN cur_user; END; $$ LANGUAGE plpgsql VOLATILE;
演示:
regress=> CREATE TEMPORARY TABLE current_app_user(username text); CREATE TABLE regress=> INSERT INTO current_app_user(username) VALUES ('testuser'); INSERT 0 1 regress=> INSERT INTO tg_demo(blah) VALUES ('42'); NOTICE: Current user is: testuser INSERT 0 1 regress=> DISCARD ALL; DISCARD ALL regress=> INSERT INTO tg_demo(blah) VALUES ('42'); NOTICE: Current user is: unknown_user INSERT 0 1
集有一个变种设置会话没有在这里提到。 这很可能是应用程序开发人员通常真正想要的,而不是简单的设置或本地设置。
set session trolol.userr = 'Lol';
我的testing触发器设置稍微简单一些,但是这个想法和Craig Ringer的选项2是一样的。
create table lol ( pk varchar(3) not null primary key, createuser varchar(20) not null); CREATE OR REPLACE function update_created() returns trigger as $$ begin new.createuser := current_setting('trolol.userr'); return new; end; $$ language plpgsql; create trigger lol_update before update on lol for each row execute procedure update_created(); create trigger lol_insert before insert on lol for each row execute procedure update_created();
在这一点上我觉得这是完全可以接受的。 如果由于某种原因偶然不设置会话variables,则不会有DDL语句和插入/更新成功。
使用DISCARD ALL
可能不是一个好主意,因为它丢弃了一切。 例如SqlKorma根本不喜欢这个。 相反,你可以使用重置variables
SET software.theuser TO DEFAULT
我简要考虑了第四种select。 在标准的variables集中有“application_name”可以使用。 这个解决scheme有一些局限性,但也有一些明显的优势取决于上下文。
有关第四个选项的更多信息,请参考以下内容:
通过JDBC设置application_name
关于application_name的postgre文档