50

PostgreSQL9.1を使用しています。私のデータベースは、アプリケーションが使用する実際のテーブルが存在するように構成されています。すべてのテーブルには、変更履歴のみを格納する履歴テーブルがあります。履歴テーブルには、実際のテーブルとフィールドがいくつかの追加情報を形成するのと同じフィールドが含まれています。編集時間。履歴テーブルはトリガーによってのみ処理されます。

2種類のトリガーがあります。

  1. Before INSERTテーブルの作成時にテーブルにいくつかの追加情報を追加するトリガー(例:create_time)。
  2. Before UPDATEトリガーとbefore DELETEトリガーを使用して、古い値を実際のテーブルから履歴テーブルにコピーします。

問題は、トリガーを使用して、それらの変更を行ったユーザーのIDも保存したいということです。また、idとは、PostgreSQLユーザーIDではなく、phpアプリケーションからのidを意味します。

それを行うための合理的な方法はありますか?

INSERTとUPDATEを使用すると、idのフィールドを実際のテーブルに追加し、SQLクエリの一部としてユーザーIDを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
4

4 に答える 4

54

オプションは次のとおりです。

  • 接続を開くと、CREATE TEMPORARY TABLE current_app_user(username text); INSERT INTO current_app_user(username) VALUES ('the_user');。次に、トリガーでSELECT username FROM current_app_user、おそらくサブクエリとして、現在のユーザー名を取得します。

  • のようなカスタムGUCpostgresql.confのエントリを作成します。接続を作成するときはいつでも実行します。次に、トリガーで、関数を使用して値を取得します。事実上、セッション変数を提供するためにGUC機構を悪用しています。カスタムGUCは9.2で変更されたため、サーバーのバージョンに適したドキュメントをお読みくださいmy_app.username = 'unknown';SET my_app.username = 'the_user';current_setting('my_app.username')

  • すべてのアプリケーションユーザーのデータベースロールを持つようにアプリケーションを調整します。SET ROLE仕事をする前にそのユーザーに。これにより、組み込みのcurrent_user変数のような関数を使用できるだけでなく、データベースのセキュリティSELECT current_user;を強化することもできます。この質問を参照してください。を使用する代わりにユーザーとして直接ログインすることもできますが、それでは接続プールが困難になる傾向があります。SET ROLE

DISCARD ALL;3つのケースすべてで接続プールを使用している場合は、接続をプールに戻すときに注意する必要があります。(そうするように文書化されていませんが、そう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の使用:

  • CUSTOMIZED OPTIONSセクションにpostgresql.conf、のような行を追加しmyapp.username = 'unknown_user'ます。9.2より古いバージョンのPostgreSQLでは、も設定する必要がありますcustom_variable_classes = 'myapp'
  • PostgreSQLを再起動します。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(...)ください。システム管理機能を参照してください

一時テーブルの使用

このアプローチでは、すべてのセッションで必要な一時テーブルから値を読み取ろうとするトリガー(またはトリガーによって呼び出されるヘルパー関数が望ましい)を使用する必要があります。一時テーブルが見つからない場合は、デフォルト値が提供されます。これはやや遅い可能性があります。注意深くテストしてください。

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プログラミングの例を参照してください。Cの知識、時間、そして忍耐力が必要です。

于 2012-11-01T07:46:31.570 に答える
14

set には、ここで言及されていないバリアント セット セッションがあります。アプリケーション開発者が通常本当に望んでいるのは、単純なセットまたはセット ローカルではなく、おそらくこれです。

set session trolol.userr = 'Lol';

テスト トリガーのセットアップはもう少し単純ですが、アイデアは 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();

現時点では、これはかなり受け入れられると思います。セッション変数が何らかの理由で誤って設定されていない場合、DDL ステートメントはなく、挿入/更新は成功しません。

すべてを破棄するため、使用DISCARD ALLはお勧めできません。たとえば、SqlKorma はこれをまったく好みません。代わりに、次を使用して変数をリセットできます

SET software.theuser TO DEFAULT

私が簡単に検討した4番目のオプションがありました。変数の標準セットには、使用できる「application_name」があります。このソリューションにはいくつかの制限がありますが、コンテキストによっては明らかな利点もあります。

この 4 番目のオプションの詳細については、次を参照してください。

JDBC を介して application_name を設定する

application_name に関する postgre ドキュメント

于 2013-10-16T18:23:44.860 に答える