简体   繁体   English

DRY postgresql 9.4触发函数

[英]DRY postgresql 9.4 trigger functions

I've written 3 functions to log transactions to designated tables: 我编写了3个函数来将事务记录到指定表中:

CREATE OR REPLACE FUNCTION log_sites() RETURNS TRIGGER AS $body$
DECLARE
    target_row sites%ROWTYPE;
BEGIN
    IF (TG_OP = 'DELETE') THEN
        -- No NEW row
        target_row = OLD;
    ELSE
        target_row = NEW;
    END IF;
    INSERT INTO sites_history (transaction_type,
                               transaction_time,
                               site_id,
                               address,
                               name,
                               shared_key)
    VALUES (TG_OP,
            NOW(),
            target_row.site_id,
            target_row.address,
            target_row.name,
            target_row.shared_key);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION log_licenses() RETURNS TRIGGER AS $body$
DECLARE
    target_row licenses%ROWTYPE;
BEGIN
    IF (TG_OP = 'DELETE') THEN
        target_row = OLD;
    ELSE
        target_row = NEW;
    END IF;
    INSERT INTO licenses_history (transaction_type,
                                  transaction_time,
                                  license_id,
                                  start_date,
                                  expiration_date,
                                  site_id)
    VALUES (TG_OP,
            NOW(),
            target_row.license_id,
            target_row.start_date,
            target_row.expiration_date,
            target_row.site_id);
    RETURN target_row;
    END;
    $body$
    LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION log_clients() RETURNS TRIGGER AS $body$
DECLARE
    target_row clients%ROWTYPE;
BEGIN
    IF (TG_OP = 'DELETE') THEN
        target_row = OLD;
    ELSE
        target_row = NEW;
    END IF;
    INSERT INTO clients_history (transaction_type,
                                 transaction_time,
                                 mac_address,
                                 hardware,
                                 license_id,
                                 site_id)
    VALUES (TG_OP,
            NOW(),
            target_row.mac_address,
            target_row.hardware,
            target_row.license_id,
            target_row.site_id);
    RETURN target_row;
    END;
    $body$
    LANGUAGE plpgsql;

This results in a big ugly block of PL/pgSQL, which nobody at my job is especially familiar with, myself included. 这导致PL / pgSQL变得很丑陋,我工作中没有人特别熟悉,包括我在内。 A coworker suggested it'd be nice to consolidate/DRYify all this stuff, but for the life of me, I'm not sure how, especially considering each table needs a separate trigger, and the way triggers pass data to their functions . 一位同事建议对所有这些东西进行合并/干燥会很好,但是对于我来说,我不确定这样做的方式,尤其是考虑到每个表都需要一个单独的触发器,以及触发器将数据传递到其函数的方式 Any suggestions? 有什么建议么?

ETA: 1) Here are the triggers: 预计到达时间:1)以下是触发条件:

CREATE TRIGGER sites_log
AFTER INSERT OR UPDATE OR DELETE
ON sites
FOR EACH ROW EXECUTE PROCEDURE log_transactions();

CREATE TRIGGER licenses_log
    AFTER INSERT OR UPDATE OR DELETE
    ON licenses
    FOR EACH ROW EXECUTE PROCEDURE log_transactions();

CREATE TRIGGER clients_log
    AFTER INSERT OR UPDATE OR DELETE
    ON clients
    FOR EACH ROW EXECUTE PROCEDURE log_transactions();

Here's what I've got now, after quite a bit of messing around: 经过一番混乱之后,这就是我现在所拥有的:

CREATE OR REPLACE FUNCTION log_transactions() RETURNS TRIGGER LANGUAGE plpgsql AS $body$
DECLARE
    target_row RECORD;
    target_cols text[];
    col_name RECORD;
    col_name_str text;
    right_now timestamp without time zone;
    q_str text;
BEGIN
    right_now := now();
    target_cols := '{}';
    FOR col_name IN SELECT column_name::text FROM information_schema.columns WHERE table_name = TG_TABLE_NAME AND table_schema = TG_TABLE_SCHEMA LOOP
        col_name_str := col_name.column_name::text;
        target_cols = ARRAY_APPEND(target_cols, col_name_str);
    END LOOP;
    RAISE NOTICE 'target_cols: %', target_cols;
    IF (TG_OP = 'DELETE') THEN
        target_row := OLD;
    ELSE
        target_row := NEW;
    END IF; 

    RAISE NOTICE 'target_row: %', target_row;
    EXECUTE format('INSERT INTO %I_history (transaction_time, transaction_type) VALUES (%L, %L)', TG_TABLE_NAME, right_now, TG_OP);
    q_str := format('UPDATE %I_history SET (%s) = ', TG_TABLE_NAME, array_to_string(target_cols, ', ')) || '$1' || format(' WHERE transaction_type = %L AND transaction_time = %L', TG_OP, right_now);
    EXECUTE q_str USING target_row;
    RETURN target_row;
END;
$body$;

This doesn't work either, and it's spiraling out of control, complexity-wise. 这也不起作用,并且在复杂性方面不断失控。

Personally, I use a set of home-grown functions Below for auditing any table I want. 我个人使用下面的一组本地函数来审核我想要的任何表。 All I do is run audit.enable() on any table I want to audit and it's stored in the general tables I have here. 我要做的就是在要审核的任何表上运行audit.enable(),并将其存储在此处的常规表中。 Yes, it's not exactly what you're doing, but it's the most "DRY" think there is really, I do it once and never again-- ever. 是的,这不完全是您在做什么,但这是最“干”的想法,我确实曾经做过一次,而且再也没有做过。

CREATE TABLE audit.audit_log 
(
    audit_log_seq serial primary key,
    schema_name text NOT NULL,
    table_name text NOT NULL,
    db_username text,
    user_seq bigint,
    logged_ip inet,
    log_timestamp TIMESTAMP NOT NULL DEFAULT (now()),
    action TEXT NOT NULL CHECK (action IN ('I','D','U')),
    comment varchar(500),
    old_data hstore,
    new_data hstore,
    query text
) WITH 
(
    fillfactor=100
);

CREATE INDEX audit_log_schema_table_idx ON audit.audit_log(schema_name,table_name);
CREATE INDEX audit_log_timestamp_utc_idx ON audit.audit_log(log_timestamp);
CREATE INDEX audit_log_uname on audit.audit_log(user_seq);



-- generic function for all tables


CREATE OR REPLACE FUNCTION audit.log_func() RETURNS TRIGGER AS $body$
DECLARE
    v_old_data hstore;
    v_new_data hstore;  
    v_query text;
    v_comment varchar;
--  v_old_data TEXT;
--  v_new_data TEXT;
BEGIN
    v_query=current_query();

    IF (TG_OP = 'UPDATE') THEN
        v_old_data := hstore(OLD.*);
        v_new_data := hstore(NEW.*);
        v_comment=v_new_data -> 'audit_comment';
    ELSIF (TG_OP = 'DELETE') THEN
        v_old_data := hstore(OLD.*);
    ELSIF (TG_OP = 'INSERT') THEN
        v_new_data := hstore(NEW.*);
    v_comment=v_new_data -> 'audit_comment';
    ELSE
        RAISE WARNING '[audit.log_func] - Other action occurred: %, at %',TG_OP,now();
        RETURN NULL;
    END IF;


    INSERT INTO audit.audit_log (schema_name,table_name,db_username,user_seq,logged_ip,action,old_data,new_data,query, comment) 
            VALUES (TG_TABLE_SCHEMA::TEXT,
            TG_TABLE_NAME::TEXT,
            session_user::TEXT,
            coalesce(current_setting('mvc.user_seq'),'0')::bigint, --current user
            inet_client_addr(),
            substring(TG_OP,1,1),
            v_old_data,
            v_new_data,
            v_query,
           v_comment);

    IF (TG_OP = 'DELETE') THEN
        RETURN OLD;
    END IF;
    RETURN NEW;

EXCEPTION
    WHEN data_exception THEN
        RAISE WARNING '[audit.log_func] - UDF ERROR [DATA EXCEPTION] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
        RETURN NULL;
    WHEN unique_violation THEN
        RAISE WARNING '[audit.log_func] - UDF ERROR [UNIQUE] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
        RETURN NULL;
    --WHEN OTHERS THEN
    --    RAISE WARNING '[audit.log_func] - UDF ERROR [OTHER] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
    --    RETURN NULL;
END;
$body$ language plpgsql security definer;

CREATE OR REPLACE FUNCTION audit.enable(p_table_name text,p_schema_name text DEFAULT 'dallas') RETURNS VOID as $body$
DECLARE
BEGIN
EXECUTE 'create trigger trg_audit_'||p_table_name||' BEFORE INSERT OR UPDATE OR DELETE ON '||p_schema_name||'.'||p_table_name|| ' FOR EACH ROW EXECUTE PROCEDURE audit.log_func()'; 
exception when duplicate_object then null;
END;
$body$ language plpgsql security definer;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM