web-dev-qa-db-ja.com

トリガー機能で変数設定を使用するにはどうすればよいですか?

SETを使用してセッション/トランザクションでユーザーのIDを記録したいので、_current_setting_を使用して、トリガー関数で後でアクセスできるようにします。基本的に、私は 以前に投稿された非常に類似したチケット からオプションn2を試していますが、PG 10.1を使用している点が異なります。

私は変数を設定するために3つのアプローチを試みてきました:

  • _SET local myvars.user_id = 4_、それによってトランザクションでローカルに設定します。
  • _SET myvars.user_id = 4_、それによりセッションでそれを設定します。
  • SELECT set_config('myvars.user_id', '4', false)は、最後の引数に応じて、前の2つのオプションのショートカットになります。

これらはいずれも、_current_setting_を介して変数を取得するときにNULLを受け取るトリガーでは使用できません。以下は、トラブルシューティングのために考案したスクリプトです(postgres dockerイメージで簡単に使用できます)。

_database=$POSTGRES_DB
user=$POSTGRES_USER
[ -z "$user" ] && user="postgres"

psql -v ON_ERROR_STOP=1 --username "$user" $database <<-EOSQL
    DROP TRIGGER IF EXISTS add_transition1 ON houses;
    CREATE TABLE IF NOT EXISTS houses (
        id SERIAL NOT NULL,
        name VARCHAR(80),
        created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
        PRIMARY KEY(id)
    );

    CREATE TABLE IF NOT EXISTS transitions1 (
        id SERIAL NOT NULL,
        house_id INTEGER,
        user_id INTEGER,
        created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
        PRIMARY KEY(id),
        FOREIGN KEY(house_id) REFERENCES houses (id) ON DELETE CASCADE

    );

    CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS \$\$
        DECLARE
            user_id integer;
        BEGIN
            user_id := current_setting('myvars.user_id')::integer || NULL;
            INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
            RETURN NULL;
        END;
    \$\$ LANGUAGE plpgsql;

    CREATE TRIGGER add_transition1 AFTER INSERT OR UPDATE ON houses FOR EACH ROW EXECUTE PROCEDURE add_transition1();

    BEGIN;
    %1% SELECT current_setting('myvars.user_id');
    %2% SELECT set_config('myvars.user_id', '55', false);
    %3% SELECT current_setting('myvars.user_id');
    INSERT INTO houses (name) VALUES ('HOUSE PARTY') RETURNING houses.id;
    SELECT * from houses;
    SELECT * from transitions1;
    COMMIT;
    DROP TRIGGER IF EXISTS add_transition1 ON houses;
    DROP FUNCTION IF EXISTS add_transition1;
    DROP TABLE transitions1;
        DROP TABLE houses;
EOSQL
_

結論は、関数は別のトランザクションと別の(?)セッションでトリガーされるということです。これは、すべて同じコンテキスト内で発生するように構成できるものですか?

13
ChuckE

customized optionのすべての可能なケースを適切に処理します。

  1. オプションはまだ設定されていません

    2番目のパラメーターmissing_okで呼び出されない限り、 current_setting() を含め、すべての参照は exception を発生させます。 マニュアル:

    setting_nameという名前の設定がない場合、current_settingが指定され、trueでない限り、missing_okはエラーをスローします。

  2. 有効な整数リテラルに設定されたオプション

  3. オプションが無効な整数リテラルに設定されています

  4. オプションのリセット(3.の特殊なケースに焼き付きます)

    たとえば、カスタマイズされたオプションをSET LOCALまたはset_config('myvars.user_id3', '55', true)で設定した場合、オプションの値はトランザクションの終了時にリセットされます。それはまだ存在します、参照できますが、空の文字列を返します('')-integerにキャストできません。

デモの明らかな間違いは別として、4つのケースすべてに備える必要があります。そう:

CREATE OR REPLACE FUNCTION add_transition1()
  RETURNS trigger AS
$func$
DECLARE
   _user_id text := current_setting('myvars.user_id', true);  -- see 1.
BEGIN
   IF _user_id ~ '^\d+$' THEN  -- one or more digits?

      INSERT INTO transitions1 (user_id, house_id)
      VALUES (_user_id::int, NEW.id);  -- valid int, cast is safe

   ELSE

      INSERT INTO transitions1 (user_id, house_id)
      VALUES (NULL, NEW.id);           -- use NULL instead

      RAISE WARNING 'Invalid user_id % for house_id % was reset to NULL!'
                  , quote_literal(_user_id), NEW.id;  -- optional
   END IF;

   RETURN NULL;  -- OK for AFTER trigger
END
$func$  LANGUAGE plpgsql;

db <>フィドル here

ノート:

  • 列名と一致する変数名は避けてください。非常にエラーが発生しやすい。よく使用される命名規則の1つは、変数名の先頭にアンダースコアを付けることです:_user_id

  • 宣言時に割り当てて、1つの割り当てを保存します。データ型textに注意してください。無効な入力を整理した後、後でキャストします。

  • 例外の発生/トラップは避けてください可能であればマニュアル:

    EXCEPTION句を含むブロックは、ブロックを含まないブロックよりも、出入りするのに非常にコストがかかります。したがって、EXCEPTIONを必要なく使用しないでください。

  • 有効な整数文字列をテストします。この単純な正規表現では、数字のみを使用できます(先行符号なし、空白なし):_user_id ~ '^\d+$'。無効な入力がある場合は、NULLにリセットします。あなたのニーズに適応します。

  • デバッグの便宜のために、オプションのWARNINGを追加しました。

  • カスタマイズされたオプションが文字列リテラル(タイプtext)であるため、3.および4.のケースが発生するだけで、有効なデータタイプを自動的に適用できません。

関連:

それはさておき、正確な要件に応じて、カスタマイズされたオプションなしで実行しようとしていることに、より洗練されたソリューションがあるかもしれません。多分これ:

6

NULLuser_idに連結しようとしている理由は明らかではありませんが、それが明らかに問題の原因です。それを取り除く:

CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
    DECLARE
        user_id integer;
    BEGIN
        user_id := current_setting('myvars.user_id')::integer;
        INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;

ご了承ください

SELECT 55 || NULL

常にNULLを与えます。

7
klin

値が存在しないときに例外をキャッチできます。これを機能させるために行った変更は次のとおりです。

CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
    DECLARE
        user_id integer;
    BEGIN
        BEGIN
            user_id := current_setting('myvars.user_id')::integer;
        EXCEPTION WHEN OTHERS THEN
            user_id := 0;
        END;

        INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;

 CREATE OR REPLACE FUNCTION insert_house() RETURNS void as $$
 DECLARE
    user_id integer;
 BEGIN 
   PERFORM set_config('myvars.user_id', '55', false);

   INSERT INTO houses (name) VALUES ('HOUSE PARTY');
 END; $$ LANGUAGE plpgsql;
4
garysieling