2015-05-07 3 views
0

я написал 3 функцию для входа транзакций в назначенные таблицы:СУХИХ 9.4 триггерного PostgreSQL функции

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; 

Это приводит к большому уродливому блоку PL/PgSQL, что никто в моей работе особенно знаком, я включил. Сотрудник предположил, что было бы неплохо объединить/DRYify все это, но для жизни меня не знаю, как, особенно учитывая, что для каждой таблицы нужен отдельный триггер, а также way triggers pass data to their functions. Какие-либо предложения?

ETA: 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(); 

Вот что у меня есть сейчас, после того, как совсем немного бездельничал:

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$; 

Это не работает либо , и он выходит из-под контроля, сложный.

+1

http://stackoverflow.com/q/15567503/330315 и http://stackoverflow.com/q/23247105/330315 и http://stackoverflow.com/q/1997337/330315 и http://webproverka.com/ //stackoverflow.com/q/7519044/330315 –

+0

Используйте 'EXECUTE' и динамический SQL. В вики есть много примеров. (Ирония отправки FAQ о DRY мне не потеряна). –

+0

Вы должны добавить фактический триггер (ы), чтобы идти с функциями, или есть место для двусмысленностей/ошибок. И некоторое описание того, что триггеры должны делать * точно *. –

ответ

0

Лично я использую набор домашних функций Ниже для аудита любой таблицы, которую я хочу. Все, что я делаю, это запустить audit.enable() в любой таблице, которую я хочу проверить, и она хранится в общих таблицах, которые у меня есть. Да, это не совсем то, что вы делаете, но это самый «СУХОЙ», думаю, что это действительно так, я делаю это один раз и никогда больше никогда.

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; 
Смежные вопросы