2017-03-29 5 views
0

テーブル名の配列を受け入れる関数(audit.create_audit_table())があります。単一の関数audit.if_modified_func()を作成し、各テーブル名をループして監査テーブルを作成し、メインテーブルにトリガを適用します。関数はコンパイルされ、エラーなしで作成されます。私は機能現在のテーブル名を持つPostgresqlの動的関数

select audit.create_audit_table(ARRAY['organization']) 

を実行すると、私は次のエラーを取得しておくと、私はTG_TABLE_NAMEが私にaudit.if_modified_func()

を実行している現在のテーブルへのアクセス権を与える自動変数であると考えたので、私はなぜわかりませんERROR:

ERROR: column "tg_table_name" does not exist 
LINE 3: audit_row audit.' || quote_ident(TG_TABLE_NAME::TEXT)||'; 
             ^

ここでは、関数の:

CREATE OR REPLACE FUNCTION audit.create_audit_table(table_names character varying[]) 
RETURNS character varying AS 
$BODY$ 
    DECLARE 
    table_name varchar; 
    i int; 
BEGIN 
    EXECUTE 'CREATE OR REPLACE FUNCTION audit.if_modified_func() RETURNS TRIGGER AS $$ 
    DECLARE  
     audit_row audit.' || quote_ident(TG_TABLE_NAME::TEXT)||'; 
     include_values boolean; 
     log_diffs boolean; 
     h_old hstore; 
     h_new hstore; 
     excluded_cols text[] = ARRAY[]::text[]; 
    BEGIN 
     IF TG_WHEN <> ''AFTER'' THEN 
      RAISE EXCEPTION ''audit.if_modified_func() may only run as an AFTER trigger''; 
     END IF; 

     audit_row = ROW(
      nextval(''audit.'|| quote_ident(TG_TABLE_NAME::text) ||'_event_id_seq''), -- event_id 
      TG_TABLE_SCHEMA::text,      -- schema_name 
      TG_TABLE_NAME::text,       -- table_name 
      TG_RELID,          -- relation OID for much quicker searches 
      session_user::text,       -- session_user_name 
      current_timestamp,       -- action_tstamp_tx 
      statement_timestamp(),      -- action_tstamp_stm 
      clock_timestamp(),       -- action_tstamp_clk 
      txid_current(),        -- transaction ID 
      current_setting(''application_name''),   -- client application 
      inet_client_addr(),       -- client_addr 
      inet_client_port(),       -- client_port 
      current_query(),        -- top-level query or queries (if multistatement) from client 
      substring(TG_OP,1,1),       -- action 
      NULL, NULL,         -- row_data, changed_fields 
      ''f''           -- statement_only 
      ); 

     IF NOT TG_ARGV[0]::boolean IS DISTINCT FROM ''f''::boolean THEN 
      audit_row.client_query = NULL; 
     END IF; 

     IF TG_ARGV[1] IS NOT NULL THEN 
      excluded_cols = TG_ARGV[1]::text[]; 
     END IF; 

     IF (TG_OP = ''UPDATE'' AND TG_LEVEL = ''ROW'') THEN 
      audit_row.row_data = hstore(OLD.*) - excluded_cols; 
      audit_row.changed_fields = (hstore(NEW.*) - audit_row.row_data) - excluded_cols; 
      IF audit_row.changed_fields = hstore('''') THEN 
       -- All changed fields are ignored. Skip this update. 
       RETURN NULL; 
      END IF; 
     ELSIF (TG_OP = ''DELETE'' AND TG_LEVEL = ''ROW'') THEN 
      audit_row.row_data = hstore(OLD.*) - excluded_cols; 
     ELSIF (TG_OP = ''INSERT'' AND TG_LEVEL = ''ROW'') THEN 
      audit_row.row_data = hstore(NEW.*) - excluded_cols; 
     ELSIF (TG_LEVEL = ''STATEMENT'' AND TG_OP IN (''INSERT'',''UPDATE'',''DELETE'',''TRUNCATE'')) THEN 
      audit_row.statement_only = ''t''; 
     ELSE 
      RAISE EXCEPTION ''[audit.if_modified_func] - Trigger func added as trigger for unhandled case: %%, %%'',TG_OP, TG_LEVEL; 
      RETURN NULL; 
     END IF; 
     INSERT INTO audit.'|| quote_ident(TG_TABLE_NAME::TEXT) ||' VALUES (audit_row.*); 
    RETURN null; 
    END; 
    $$ 
    LANGUAGE plpgsql; 
    ALTER FUNCTION audit.if_modified_func() 
    OWNER TO postgres;'; 

    FOR i in 1..array_upper(table_names, 1) LOOP 

     EXECUTE format(' 
     DROP TABLE IF EXISTS audit.%1$s; 
     CREATE TABLE audit.%1$s (
     event_id bigserial primary key, 
     schema_name text not null, 
     table_name text not null, 
     relid oid not null, 
     session_user_name text, 
     action_tstamp_tx TIMESTAMP WITH TIME ZONE NOT NULL, 
     action_tstamp_stm TIMESTAMP WITH TIME ZONE NOT NULL, 
     action_tstamp_clk TIMESTAMP WITH TIME ZONE NOT NULL, 
     transaction_id bigint, 
     application_name text, 
     client_addr inet, 
     client_port integer, 
     client_query text, 
     action TEXT NOT NULL CHECK (action IN (''I'',''D'',''U'', ''T'')), 
     row_data hstore, 
     changed_fields hstore, 
     statement_only boolean not null 
     ); 

     REVOKE ALL ON audit.%1$s FROM public; 

     COMMENT ON TABLE audit.%1$s IS ''History of auditable actions on audited tables, from audit.if_modified_func()''; 
     COMMENT ON COLUMN audit.%1$s.event_id IS ''Unique identifier for each auditable event''; 
     COMMENT ON COLUMN audit.%1$s.schema_name IS ''Database schema audited table for this event is in''; 
     COMMENT ON COLUMN audit.%1$s.table_name IS ''Non-schema-qualified table name of table event occured in''; 
     COMMENT ON COLUMN audit.%1$s.relid IS ''Table OID. Changes with drop/create. Get with ''''tablename''''::regclass''; 
     COMMENT ON COLUMN audit.%1$s.session_user_name IS ''Login/session user whose statement caused the audited event''; 
     COMMENT ON COLUMN audit.%1$s.action_tstamp_tx IS ''Transaction start timestamp for tx in which audited event occurred''; 
     COMMENT ON COLUMN audit.%1$s.action_tstamp_stm IS ''Statement start timestamp for tx in which audited event occurred''; 
     COMMENT ON COLUMN audit.%1$s.action_tstamp_clk IS ''Wall clock time at which audited event''''s trigger call occurred''; 
     COMMENT ON COLUMN audit.%1$s.transaction_id IS ''Identifier of transaction that made the change. May wrap, but unique paired with action_tstamp_tx.''; 
     COMMENT ON COLUMN audit.%1$s.client_addr IS ''IP address of client that issued query. Null for unix domain socket.''; 
     COMMENT ON COLUMN audit.%1$s.client_port IS ''Remote peer IP port address of client that issued query. Undefined for unix socket.''; 
     COMMENT ON COLUMN audit.%1$s.client_query IS ''Top-level query that caused this auditable event. May be more than one statement.''; 
     COMMENT ON COLUMN audit.%1$s.application_name IS ''Application name set when this audit event occurred. Can be changed in-session by client.''; 
     COMMENT ON COLUMN audit.%1$s.action IS ''Action type; I = insert, D = delete, U = update, T = truncate''; 
     COMMENT ON COLUMN audit.%1$s.row_data IS ''Record value. Null for statement-level trigger. For INSERT this is the new tuple. For DELETE and UPDATE it is the old tuple.''; 
     COMMENT ON COLUMN audit.%1$s.changed_fields IS ''New values of fields changed by UPDATE. Null except for row-level UPDATE events.''; 
     COMMENT ON COLUMN audit.%1$s.statement_only IS ''''''t'''' if audit event is from an FOR EACH STATEMENT trigger, ''''f'''' for FOR EACH ROW''; 

     CREATE INDEX %1$s_relid_idx ON audit.%1$s(relid); 
     CREATE INDEX %1$s_action_tstamp_tx_stm_idx ON audit.%1$s(action_tstamp_stm); 
     CREATE INDEX %1$s_action_idx ON audit.%1$s(action);   
     ', table_names[i]);  

     EXECUTE format(' 
     DROP TRIGGER IF EXISTS audit_trigger_row ON %1$s; 
     CREATE TRIGGER audit_trigger_row 
     AFTER INSERT OR UPDATE OR DELETE 
     ON public.%1$s 
     FOR EACH ROW 
     EXECUTE PROCEDURE audit.if_modified_func();', table_names[i]); 

     EXECUTE format(' 
     DROP TRIGGER IF EXISTS audit_trigger_stm ON %1$s; 
     CREATE TRIGGER audit_trigger_stm 
     AFTER TRUNCATE 
     ON public.%1$s 
     FOR EACH STATEMENT 
     EXECUTE PROCEDURE audit.if_modified_func();', table_names[i]); 

    END LOOP; 

RETURN 'SUCCESS'; 
END; 
$BODY$ 
LANGUAGE plpgsql; 
ALTER FUNCTION audit.create_audit_table(character varying[]) 
OWNER TO postgres; 

UPDATE 03/31:

OK]をので、私は、動的SQLなしif_modified_func()機能を作成し、私は、「値の挿入時にキャストを必要とする」の一部についてはよく分からないですaudit_row RECORD;としてaudit_rowを宣言しました。私も、これは私が今私が実行したときに、このエラーを取得しています

EXECUTE format($string$INSERT INTO audit.%1$s VALUES (audit_row.*);$string$, TG_TABLE_NAME::text);

挿入を行うための正しい方法であるかどうかわからないのですselect audit.create_audit_table(ARRAY['organization'])

ERROR:

ここ
ERROR: record "audit_row" has no field "row_data" 
CONTEXT: PL/pgSQL function audit.if_modified_func() line 42 at assignment 

が更新されます機能:

CREATE OR REPLACE FUNCTION audit.if_modified_func() RETURNS TRIGGER AS $$ 
     DECLARE  
      audit_row RECORD;    
      include_values boolean; 
      log_diffs boolean; 
      h_old hstore; 
      h_new hstore; 
      excluded_cols text[] = ARRAY[]::text[]; 
     BEGIN 
      IF TG_WHEN <> 'AFTER' THEN 
       RAISE EXCEPTION 'audit.if_modified_func() may only run as an AFTER trigger'; 
      END IF; 

      audit_row = ROW(
       nextval(format('audit.%1$s_event_id_seq',TG_TABLE_NAME::text)), -- event_id 
       TG_TABLE_SCHEMA::text,      -- schema_name 
       TG_TABLE_NAME::text,       -- table_name 
       TG_RELID,          -- relation OID for much quicker searches 
       session_user::text,       -- session_user_name 
       current_timestamp,       -- action_tstamp_tx 
       statement_timestamp(),      -- action_tstamp_stm 
       clock_timestamp(),       -- action_tstamp_clk 
       txid_current(),        -- transaction ID 
       current_setting('application_name'),   -- client application 
       inet_client_addr(),       -- client_addr 
       inet_client_port(),       -- client_port 
       current_query(),        -- top-level query or queries (if multistatement) from client 
       substring(TG_OP,1,1),       -- action 
       NULL, NULL,         -- row_data, changed_fields 
       'f'           -- statement_only 
       ); 

      IF NOT TG_ARGV[0]::boolean IS DISTINCT FROM 'f'::boolean THEN 
       audit_row.client_query = NULL; 
      END IF; 

      IF TG_ARGV[1] IS NOT NULL THEN 
       excluded_cols = TG_ARGV[1]::text[]; 
      END IF; 

      IF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN 
       audit_row.row_data = hstore(OLD.*) - excluded_cols; 
       audit_row.changed_fields = (hstore(NEW.*) - audit_row.row_data) - excluded_cols; 
       IF audit_row.changed_fields = hstore('') THEN 
        -- All changed fields are ignored. Skip this update. 
        RETURN NULL; 
       END IF; 
      ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN 
       audit_row.row_data = hstore(OLD.*) - excluded_cols; 
      ELSIF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN 
       audit_row.row_data = hstore(NEW.*) - excluded_cols; 
      ELSIF (TG_LEVEL = 'STATEMENT' AND TG_OP IN ('INSERT','UPDATE','DELETE','TRUNCATE')) THEN 
       audit_row.statement_only = 't'; 
      ELSE 
       RAISE EXCEPTION '[audit.if_modified_func] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL; 
       RETURN NULL; 
      END IF; 
      EXECUTE format('INSERT INTO audit.%1$s VALUES (audit_row.*)', TG_TABLE_NAME::text); 

     RETURN null; 
     END; 
     $$ 
     LANGUAGE plpgsql; 
     ALTER FUNCTION audit.if_modified_func() 
     OWNER TO postgres; 
+0

「quote_ident」を削除します。 'quote_ident(TG_TABLE_NAME :: TEXT)'は引数を関係名として引用するのに必要なアクションを適用します。だから、 'TG_TABLE_NAME'は変数ではなく、テーブル名が –

答えて

0

TG_TABLE_NAMEは、トリガ関数内でのみ使用できます特別な、トリガー変数、です。 create_audit_table()はトリガー機能ではありません。

また、以前に作成したトリガーを「無効にする」実際のトリガー機能(if_modified_func())を常に再定義します。

トリガー機能を作成しないでください。ダイナミックSQLマジック(ダイナミックSQLはこれらの監査テーブルに値を挿入するだけです)。次に、あなたがテーブルに監査・ロジックを追加することができます。

CREATE TRIGGER audit_trigger_row 
    AFTER INSERT OR UPDATE OR DELETE 
    ON public.<your_table_name> 
    FOR EACH ROW 
    EXECUTE PROCEDURE <your_audit_trigger_function_name>(); 

あなたはこの(しかし、これだけ - 多分drop if exists付き)を置くことができ、より簡単にこの監査ロジックを装着できるように、関数内。

ノート

  • トリガ機能内部(あなたが正確なテーブルを知らないので、あなただけの、その名前を持っている。)、あなたは%ROWTYPE変数を使用することはできません。解決策は簡単です:代わりにRECORDタイプを使用してください(値を挿入する際にキャストが必要です)。
  • このような長い文字列には一重引用符を使用しないでください。代わりに$your_keyword$<string_value>$your_keyword$形式を使用してください。場合によっては値を連結するのではなく、format()関数を使用します。あなたのコードははるかに読みやすくなります。

編集:あなたのRECORD変数を利用するために、あなたはどちらかのはず:

  • は、構造とそれを初期化します。あなたはf.exであなたのケースでこれを行うことができます。

    SELECT nextval('audit.'|| quote_ident(TG_TABLE_NAME) || '_event_id_seq') AS event_id, 
         TG_TABLE_SCHEMA      AS schema_name, 
         TG_TABLE_NAME       AS table_name, 
         TG_RELID        AS relid, 
         session_user       AS session_user_name, 
         current_timestamp      AS action_tstamp_tx, 
         statement_timestamp()     AS action_tstamp_stm, 
         clock_timestamp()      AS action_tstamp_clk, 
         txid_current()       AS transaction_id, 
         current_setting('application_name') AS application_name, 
         inet_client_addr()      AS client_addr, 
         inet_client_port()      AS client_port, 
         current_query()      AS client_query, 
         substring(TG_OP, 1, 1)     AS action, 
         NULL::hstore       AS row_data, 
         NULL::hstore       AS changed_fields, 
         FALSE         AS statement_only 
    INTO audit_row; 
    
  • ROW()コンストラクタの定義済みの名前を使用します。

    EXECUTE format('INSERT INTO audit.%1$s VALUES (($1::text::audit.%1$s).*)', quote_ident(TG_TABLE_NAME)) USING audit_row; 
    

    :最初の列の名前はf1場合、第二のは、あなたのような行を挿入する必要があり、など

    audit_row.f15 = hstore(OLD.*) - excluded_cols; 
    

上記の方法のいずれかを選択した後、f2次のとおりです。実際にaudit_rowの構造を知ることができないため、textへのキャストも必要です。EXECUTE

http://rextester.com/GUAJ1339

+0

だと言うと、「トリガー関数の中で%ROWTYPE変数を使うことはできません」と言うと、この行は' audit_row audit 'です。 || quote_ident(TG_TABLE_NAME :: TEXT)|| ';' – iambdot

+0

@iambdotはい。そこに 'DECLARE audit_row RECORD'を使用することができます。 – pozs

+0

あなたの投稿への更新をチェックアウトできますか? – iambdot

1

qu ote_ident(TG_TABLE_NAME :: TEXT)は、引数をrelation nameとして修正するために必要なアクションを適用します。

私はたとえば、execute format('statement')代わりのconcatinationsを使用することをお勧めします:

t=# do $$ begin raise info '%',format('I am %I, now is %L',current_user,now()); end;$$; 
INFO: I am postgres, now is '2017-03-30 07:33:53.579476+00' 
DO 

の代わりに:

t=# do $$ begin raise info '%','I am '||quote_ident(current_user)||', now is '||quote_ident(now()::text); end;$$; 
INFO: I am postgres, now is "2017-03-30 07:36:20.495887+00" 
DO 
関連する問題