pip*_*ipo 37 postgresql triggers
我正在使用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
Run Code Online (Sandbox Code Playgroud)
Cra*_*ger 43
选项包括:
当你打开一个连接,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喜欢my_app.username = 'unknown';.每当您创建连接运行时SET my_app.username = 'the_user';.然后在触发器中,使用该current_setting('my_app.username')函数获取值.实际上,您滥用GUC机制来提供会话变量.阅读适用于您的服务器版本的文档,因为9.2中更改了自定义GUC.
调整应用程序,使其具有每个应用程序用户的数据库角色.SET ROLE在做工作之前给那个用户.这不仅允许您使用内置的current_user类似变量的函数SELECT current_user;,还允许您在数据库中强制执行安全性.看到这个问题.您可以直接以用户身份登录而不是使用SET ROLE,但这往往会使连接池变得困难.
在这三种情况下,您都是连接池,DISCARD ALL;在返回池连接时必须小心.(虽然没有记录这样做,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();
Run Code Online (Sandbox Code Playgroud)
CUSTOMIZED OPTIONS部分中postgresql.conf,添加一行myapp.username = 'unknown_user'.在9.2以上的PostgreSQL版本中,您还必须设置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;
Run Code Online (Sandbox Code Playgroud)
使用SET LOCALfor transaction-local current username进行演示:
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)
Run Code Online (Sandbox Code Playgroud)
如果您使用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)
Run Code Online (Sandbox Code Playgroud)
这种方法需要使用触发器(或者优选地由触发器调用的辅助函数),该触发器尝试从每个会话应该具有的临时表中读取值.如果找不到临时表,则提供默认值.这可能有点慢.仔细测试.
该SET定义:
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;
Run Code Online (Sandbox Code Playgroud)
演示:
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
Run Code Online (Sandbox Code Playgroud)
lok*_*ori 10
set有一个未在此处提及的变体集会话.这很可能是应用程序开发人员通常真正想要的,而不是普通集或本地集.
set session trolol.userr = 'Lol';
Run Code Online (Sandbox Code Playgroud)
我的测试触发器设置稍微简单一点,但这个想法与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();
Run Code Online (Sandbox Code Playgroud)
我觉得这在目前是可以接受的.如果由于某种原因意外未设置会话变量,则不会有DDL语句和插入/更新.
使用DISCARD ALL可能不是一个好主意,因为它会丢弃所有内容.例如,SqlKorma完全不喜欢这个.相反,您可以使用重置变量
SET software.theuser TO DEFAULT
Run Code Online (Sandbox Code Playgroud)
我简要考虑了第四种选择.在标准变量集中,可以使用"application_name".该解决方案具有一些局限性,但取决于上下文也具有一些明显的优点.
有关此第四个选项的更多信息,请参阅以下内容:
| 归档时间: |
|
| 查看次数: |
14449 次 |
| 最近记录: |