我正在使用PostgreSQL 9.1.我的数据库是 struct 化的,因此我的应用程序使用的是实际的表.对于每个表,都有一个只存储更改历史的历史表.历史记录表包含与实际表相同的字段,再加上字段形成一些额外信息,例如编辑时间.历史记录表仅由触发器处理.

我有两种触发器:

  1. Before INSERT触发在创建表时向表中添加一些额外信息(例如创建时间).
  2. Before UPDATE个触发器和before DELETE个触发器将旧值从实际表复制到历史表.

问题是,我想使用触发器来存储做出这些更改的用户的id.我所说的id是来自php应用程序的id,而不是PostgreSQL用户id.

有什么合理的方法吗?

使用INSERT和UPDATE,只需将id的额外字段添加到实际表中,并将用户id作为SQL查询的一部分传递给SQL即可.据我所知,这不适用于删除.

所有触发器的 struct 如下:

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';.然后在触发器中,使用current_setting('my_app.username') function获取值.实际上,您正在滥用GUC机制来提供会话变量.Read the documentation appropriate to your server version, as custom GUCs changed in 9.2

  • 调整应用程序,使其具有每个应用程序用户的数据库角色.SET ROLE在工作前发送给该用户.这不仅让您可以使用内置的current_user变量函数来设置SELECT current_user;,还可以设置enforce security in the database.见this question.您可以直接以用户身份登录,而不是使用SET ROLE,但这往往会使连接池变得困难.

在这三种情况下,您都是连接池,当您将连接返回到连接池时,必须小心设置为DISCARD ALL;.(Though it is not documented as doing soDISCARD ALLRESET 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';,或者如果希望事务是事务本地的,则在BEGINning事务后使用SET LOCAL myapp.username = 'the_user';,这对于池连接来说很方便.

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)

另外,请注意,不能将SETSET LOCAL与服务器端绑定参数一起使用.如果您想使用绑定参数("准备语句"),请考虑使用函数窗体set_config(...).见system adminstration functions

使用临时桌子

这种方法需要使用一个触发器(或者触发器调用的助手函数,最好是这样),它试图从每个会话应该具有的临时表中读取一个值.如果找不到临时表,则会提供默认值.这个数字可能是somewhat slow.仔细测试.

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

安全会话变量

还有人建议在PostgreSQL中添加"安全会话变量".这些有点像包变量.截至PostgreSQL 12,该功能尚未包括在内,但请密切关注,如果您需要,请在黑客名单上发言.

高级:拥有共享内存区的您自己的扩展

对于高级用途,您甚至可以让自己的C扩展注册共享内存区域,并使用读取/写入DSA段中值的C函数调用在后端之间进行通信.有关详细信息,请参见PostgreSQL编程示例.你需要知识、时间和耐心.

Postgresql相关问答推荐

如何使用Docker安装PostgreSQL扩展

在SqlalChemy中转义动态CamelCase场

复合索引列的顺序导致不同的计划

无法使用PGx连接到Postgres数据库AWS RDS

sqlalchemy在Flask 下运行时出现无法解释的错误

求和直到达到一个值 postgresql

如何使 Postgres 优化引擎具有确定性

连接到 PostgreSQL 时没有属性执行错误

Supabase 数据库大小问题

连接 Supbase Postgresql 数据库时,Stepzen Graphiql 资源管理器中的主机名解析错误

转换数组类型

从左连接更新 Postgres

为什么 SQLAlchemy 不创建串行列?

是否可以在 PostgreSQL 中部分刷新materialized视图?

Select 中的 PostgreSQL 正则表达式捕获组

如何在 postgres json 列中查询嵌套数组?

如何正确索引多对多关联表?

在 postgres 中插入语句用于没有时区 NOT NULL 的数据类型时间戳,

postgres regexp_replace 只想允许 a-z 和 A-Z

如何使用 postgresql 按计数排序?