将用户标识传递给PostgreSQL触发器

我正在使用PostgreSQL 9.1。 我的数据库是结构化的,以便有我的应用程序使用的实际表。 对于每个表都有历史表,只存储更改历史logging。 历史表包含相同的字段,实际表格加上字段形成一些额外的信息,例如。 编辑时间。 历史logging表只能由触发器处理。

我有两种触发器:

  1. Before INSERT触发器在创build表时添加一些额外的信息(例如create_time)。
  2. Before UPDATE触发器before DELETEbefore 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.confCUSTOMIZED 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文档