我正在使用PostgreSQL 9.1。我的数据库是结构化的,因此有我的应用程序使用的实际表。对于每个表,都有一个历史表仅存储更改历史记录。历史表包含与实际表相同的字段,外加字段构成一些额外的信息,例如。编辑时间。历史记录表仅由触发器处理。
我有两种触发器:
Before INSERT
触发器在创建表时向表添加一些额外的信息(例如,create_time)。Before UPDATE
触发器和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');
。然后在您的触发器中,单击SELECT username FROM current_app_user
以获取当前的用户名(可能作为子查询)。
在postgresql.conf
中,为custom GUC创建一个条目,例如my_app.username = 'unknown';
。每当创建连接run SET my_app.username = 'the_user';
时。然后在触发器中,使用SET my_app.username = 'the_user';
获得该值。实际上,您正在滥用GUC机制来提供会话变量。 阅读适用于您服务器版本的文档,因为自定义GUC在9.2中已更改。
调整您的应用程序,使其对每个应用程序用户都有数据库角色。 current_setting('my_app.username')
function在执行工作之前向该用户。这不仅使您可以将内置的current_setting('my_app.username')
类似于变量的函数用于SET ROLE
,还可以使您在数据库中强制执行安全性。参见current_user
。您可以直接以用户身份登录,而不必使用SELECT current_user;
,但这会使连接池变得困难。
在这三种情况下都是连接池,将连接返回到池时必须小心this question。 (SET ROLE
,DISCARD ALL;
执行DISCARD ALL;
)。
Though it is not documented as doing so
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();
的行。在9.2之前的PostgreSQL版本上,还必须设置CUSTOMIZED OPTIONS
。postgresql.conf
并获得值myapp.username = 'unknown_user'
。现在,您可以在建立连接时使用custom_variable_classes = 'myapp'
,或者,如果希望使它成为本地事务,则在SHOW myapp.username
设置事务后也可以选择unknown_user
,这对于合并连接很方便。
SET myapp.username = 'the_user';
函数定义:
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)
。参见SET
此方法需要使用触发器(最好是由触发器调用的辅助函数),该触发器试图从每个会话应具有的临时表中读取一个值。如果找不到临时表,则提供默认值。这可能是有点慢。仔细测试。
SET LOCAL
定义:
set_config(...)
演示:
system adminstration functions
还有一个建议在PostgreSQL中添加“安全会话变量”。这些有点像包变量。从PostgreSQL 12开始,该功能尚未包括在内,但是如果需要,请注意并在黑客列表中大声疾呼。
对于高级用途,您甚至可以让自己的C扩展注册一个共享内存区域,并使用C函数调用在后端之间进行通信,这些C函数调用可在DSA段中读取/写入值。有关详细信息,请参见PostgreSQL编程示例。您需要C知识,时间和耐心。
set具有此处未提及的变体set会话。这是应用程序开发人员通常真正想要的,而不是简单设置或设置为本地设置。
get_app_user()
我的测试触发器设置有点简单,但思路与Craig Ringer的选项2相同。
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;
我发现这一点目前可以接受。如果由于某种原因意外未设置会话变量,则没有DDL语句,并且插入/更新将不会成功。
使用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
可能不是一个好主意,因为它会丢弃所有内容。例如,SqlKorma根本不喜欢这样。相反,您可以使用
set session trolol.userr = 'Lol';
我曾简要考虑过第四个选择。在标准变量集中,可以使用“ application_name”。此解决方案有一些局限性,但根据上下文也有一些明显的优势。
有关第四个选项的更多信息,请参考以下内容:
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();
DISCARD ALL