将用户ID传递给PostgreSQL触发器

问题描述 投票:37回答:2

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

我有两种触发器:

  1. [Before INSERT触发器在创建表时向表添加一些额外的信息(例如,create_time)。
  2. [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
postgresql triggers
2个回答
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中,为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 ROLEDISCARD ALL;执行DISCARD ALL;)。

演示的通用设置:

Though it is not documented as doing so

使用GUC:

  • DISCARD 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(); 的行。在9.2之前的PostgreSQL版本上,还必须设置CUSTOMIZED OPTIONS
  • 重新启动PostgreSQL。现在,您将能够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 LOCALDISCARD 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知识,时间和耐心。


10
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.