[pgsql-jp: 28261] Re: トリガファンクションのカラム名を変数で使いたい

Chie.M gontakun @ check.ne.jp
2002年 12月 11日 (水) 16:14:51 JST


Chieです。Mashikiさん何度もありがとうございます。

> >「トリガーファンクションとトリガーをCreateするようなファンクションを定義」
> >するサンプルを作ってみました。
> >トリガも発行できると言う事、ご報告いたします。
> おおなるほど、無責任発言の検証をしていただきありがとうございます。

とんでもないです。色々助言いただきまして、ありがとうございます。

> >下記に作成した簡単なサンプルを貼っておきます。
> >トリガファンクションとトリガを発行するものです。
> 
> トリガーを作成する際は、システムカタログをみて必要があればDrop Trigger
> も発行するんでしょうか。

えーっとそれについては何も考えてませんでした(^_^;)
TRIGGERが存在したらDROPする物を追加した物を作成しました。
ありがとうございます。

>  ところで、Create Functionを発行する関数は、参照しているシステム
> カタログのトリガーになるように定義すると、全自動でいけそうな気も
> しますね。(できるのかわからず、さらに無責任モードで書いています。
> だまされないでください)

ものすごく良い考えですね!早速テストしてみます!!

ということで、今まで教えていただいた事のご報告を兼ねて
一つのテーブルに複数のテーブルの更新履歴をとる汎用の関数を
作ってみましたので貼ります。
汎用なので、下記の「準備1」のものさえ用意してあれば
普通に使えるんじゃないかと思いますが自信はないです...。

上記の全自動の件については、末尾に貼っておきました。
(長文になります。申し訳ありません)

・準備1
システムカタログからテーブル名とカラム名を参照するビューと
更新履歴を格納するテーブルを作成します。
-----------------------------------------------
--テーブルとカラムの一覧のビュー
----抽出条件に権限指定を含めているのでビューに表示させたい
----テーブルは必ず権限を指定する必要有

CREATE VIEW Vs_AllTableObject AS
SELECT
  pc.oid AS tbl_id, --テーブルID
  pc.relname AS tbl_name, -- テーブル名
  pa.attname AS tbl_cal, --列名
  pt.typname AS tbl_type, -- データ型
  pc.relnatts AS tbl_cal_num -- 列数
FROM 
  pg_attribute as pa
  JOIN pg_class as pc
    ON (pa.attrelid = pc.oid)
  JOIN pg_type as pt
    ON (pa.atttypid = pt.oid)
WHERE
  pc.relkind = 'r' AND 
  pc.relacl IS NOT NULL AND
  pa.attnum > 0 AND
  pc.oid > 200000
ORDER BY pc.oid;

COMMENT ON VIEW Vs_AllTableObject Is 'DB格納テーブル一覧';
COMMENT ON COLUMN Vs_AllTableObject.tbl_id IS 'テーブルID';
COMMENT ON COLUMN Vs_AllTableObject.tbl_name IS 'テーブル名';
COMMENT ON COLUMN Vs_AllTableObject.tbl_cal IS '列名';
COMMENT ON COLUMN Vs_AllTableObject.tbl_type IS 'データ型';
COMMENT ON COLUMN Vs_AllTableObject.tbl_cal_num IS '列数';

GRANT SELECT ON Vs_AllTableObject to GROUP mygroup;

-----

--変更履歴用テーブル
CREATE TABLE update_log(
    method_name VARCHAR(10),
    tbl_name VARCHAR(100),
    tbl_cal VARCHAR(100),
    old_data TEXT,
    new_data TEXT,
    update_time DATETIME,
    update_name NAME
);

COMMENT ON TABLE update_log Is '更新履歴';
COMMENT ON COLUMN update_log.method_name IS 'テーブルID';
COMMENT ON COLUMN update_log.tbl_name IS 'テーブル名';
COMMENT ON COLUMN update_log.tbl_cal IS '列名';
COMMENT ON COLUMN update_log.old_data IS '旧データ';
COMMENT ON COLUMN update_log.new_data IS '新データ';
COMMENT ON COLUMN update_log.update_time IS '更新日時';
COMMENT ON COLUMN update_log.update_name IS '更新者';

GRANT SELECT ON update_log to GROUP mygroup;
-----------------------------------------------

・準備2
更新履歴をテストする為のテーブルを作成。
-----------------------------------------------
--DROP TABLE test_data;
--データ更新テーブル
CREATE TABLE test_data(
    t_id int4,
    t_data1 CHAR(10),
    t_data2 int4,
    t_data3 CHAR(1)
);

--データを入れておく
INSERT INTO test_data VALUES(10,'ABCDE',123,'A');
INSERT INTO test_data VALUES(20,'TOKYO',456,'B');
INSERT INTO test_data VALUES(30,'SAITAMA',789,'C');
INSERT INTO test_data VALUES(40,'TEST',101,'D');
INSERT INTO test_data VALUES(50,'KANAGAWA',999,'E');

GRANT SELECT ON test_data to GROUP mygroup;
-----------------------------------------------

・本題
関数とトリガの作成
-----------------------------------------------
-- 「変更履歴挿入関数」をセットする関数
CREATE OR REPLACE FUNCTION fnc_Set_updatelog(text) RETURNS INT4 AS '
  DECLARE
    cu refcursor;
    rec record;
    tbl_rel ALIAS FOR $1;
    colname varchar(100);
    insert_sql text;
    create_function text;
    create_trigger text;

  BEGIN
    insert_sql := '''';
    -- テーブル名カラム一覧を開く
    OPEN cu FOR EXECUTE 
      ''SELECT vsa.tbl_name, vsa.tbl_cal
        FROM Vs_AllTableObject AS vsa
        WHERE tbl_name = '' || quote_literal(tbl_rel) || '';'';
      LOOP
        -- 一行ずつ取り出す
        FETCH cu INTO rec;
        IF NOT FOUND THEN
          EXIT;
        END IF;

        -- 列名を代入
        colname := TRIM(rec.tbl_cal);

        -- データ挿入用SQL作成
        insert_sql := insert_sql || 
        ''INSERT INTO update_log ( method_name,
            tbl_name, tbl_cal, old_data, new_data, 
            update_time, update_name)
          VALUES(TG_OP, \'\'\'\''' || tbl_rel || ''\'\'\'\', \'\'\'\''' || colname || ''\'\'\'\', 
            OLD.'' || colname || ''::text, NEW.'' || colname || ''::text, 
            now_timestamp, now_user);
          '';

      END LOOP;
    CLOSE cu;

    --変更履歴挿入関数を作成する文
    create_function := ''CREATE OR REPLACE FUNCTION fnc_updatelog_'' || tbl_rel || ''() 
      RETURNS OPAQUE AS ''''
      DECLARE
        now_timestamp datetime;
        now_user varchar(10);
      BEGIN
        now_timestamp := ''''''''now'''''''';
        now_user := current_user;
      IF TG_OP = ''''''''UPDATE'''''''' THEN 
      ''
        || insert_sql 
        || ''DELETE FROM update_log WHERE old_data = new_data; 
        RETURN new;
      END IF;
    END; 
    '''' LANGUAGE ''''plpgsql'''';'';

    --トリガー作成の文
    create_trigger := ''CREATE TRIGGER trg_updatelog
        BEFORE UPDATE
        ON '' || tbl_rel || ''
        FOR EACH ROW
        EXECUTE PROCEDURE fnc_updatelog_'' || tbl_rel || ''();'';

    --トリガが存在したら削除
    SELECT INTO rec *
    FROM pg_trigger as t, pg_class as c
    WHERE t.tgrelid = c.oid
      AND t.tgname = ''trg_updatelog''
      AND c.relname = quote_literal(tbl_rel);

    IF NOT FOUND THEN
    ELSE
      DROP TRIGGER trg_updatelog ON tbl_rel;
    END IF;
    
    --関数とトリガを作成
    EXECUTE create_function;
    EXECUTE create_trigger;

  RETURN 1;
END; '
 LANGUAGE 'plpgsql';
-----------------------------------------------

・テスト
テスト用テーブルにセットして、テストする
-----------------------------------------------
--テスト用のtest_dataテーブルに対して関数とトリガをセット
SELECT fnc_Set_updatelog('test_data');

--データ変更テスト
  UPDATE test_data
    SET t_id = 32, t_data1 = 'AAAZZB',
      t_data2 = 902
    WHERE t_id = 30;

--確認
  SELECT * FROM update_log;
-----------------------------------------------

さてここで、Mashikiさんのおっしゃっていた
・システムカタログのトリガーになるように定義すると全自動
という事が是非やりたかったので、いくつかテストしてみました。

関数を実行する部分をトリガにしてみました。
----
CREATE TRIGGER trg_set_updatelog
  BEFORE INSERT
  ON pg_class
  FOR EACH ROW
  EXECUTE PROCEDURE fnc_Set_updatelog('test_Data');
----
これを実行すると
----
ERROR: CreateTrigger: can't create trigger for relation pg_class
----
と返ってきました。
システムカタログにトリガはつくれないみたいです。残念・・・。

次にルールで挑戦してみました。
----
--DROP RURE ru_create_updatelog;
CREATE RURE ru_create_updatelog AS
  ON INSERT
  TO pg_class
  DO INSTEAD
  SELECT fnc_Set_updatelog(NEW.relname);
----
ルールは作成できます。
しかし、新しいテーブルを作成しても無反応。
エラーも出ないし実行もされないです。

# それ以前にルールの書き方があっているか全く
# 自信がないです・・・(^_^;)

システムカタログの動作がよくわからないのですが、
テーブル作成時にINSERT句で挿入しているわけじゃ
ないんじゃないか、と思いました。
ソースを読み取るほどの知識がないのでスミマセン・・・。
INSERTが実行されないので動かない、と言う事では
ないでしょうか??

# 以上、何か変だったら突っ込んでください<(_ _)>

結果:
全自動で実行させる事は無理そうですが、カナリ汎用に近い
更新履歴の関数を作成する事ができました!
どうもありがとうございました!

------------------------
From:Chie.M
 gontakun @ check.ne.jp
------------------------




pgsql-jp メーリングリストの案内