Advertisement
Guest User

Untitled

a guest
Jul 2nd, 2015
198
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.76 KB | None | 0 0
  1. Assumptions: The tables you want audited will have an integer column called `id`. This will then be later accessible in the audit table by the column `row_id`.
  2.  
  3. A table to store audit actions in:
  4. ``` sql
  5. create table logged_action (
  6. id serial,
  7. table_name text not null,
  8. user_name text not null,
  9. action_tstamp timestamp with time zone
  10. not null default current_timestamp,
  11. action text not null check(action in ('I', 'D', 'U')),
  12. original_data text,
  13. new_data text,
  14. schema text not null,
  15. row_id integer not null
  16. );
  17. ```
  18.  
  19. A function to execute when CUD happens:
  20. ``` sql
  21. CREATE OR REPLACE FUNCTION if_modified_func() RETURNS TRIGGER AS $body$
  22. DECLARE
  23. v_old_data TEXT;
  24. v_new_data TEXT;
  25. v_schema_query TEXT;
  26. v_schema TEXT;
  27. msg TEXT;
  28. BEGIN
  29. /* If this actually for real auditing (where you need to log
  30. EVERY action), then you would need to use something like
  31. dblink or plperl that could log outside the transaction,
  32. regardless of whether the transaction committed or rolled back.
  33. */
  34.  
  35. v_schema_query := format(
  36. 'with x as ' ||
  37. '(select column_name from information_schema.columns ' ||
  38. E'where table_name = \'%I\' ' ||
  39. 'order by ordinal_position) ' ||
  40. 'select array_agg(column_name::text) from x', TG_TABLE_NAME);
  41. execute v_schema_query into v_schema;
  42.  
  43. /* This dance with casting the NEW and OLD values to a ROW is
  44. not necessary in pg 9.0+ */
  45. IF (TG_OP = 'UPDATE') THEN
  46. v_old_data := ROW(OLD.*);
  47. v_new_data := ROW(NEW.*);
  48. INSERT INTO logged_action (
  49. table_name,
  50. user_name,
  51. action,
  52. original_data,
  53. new_data,
  54. schema,
  55. row_id)
  56. VALUES (
  57. TG_TABLE_NAME::TEXT,
  58. user::TEXT,
  59. substring(TG_OP,1,1),
  60. v_old_data,
  61. v_new_data,
  62. v_schema,
  63. old.id);
  64. RETURN NEW;
  65. ELSIF (TG_OP = 'DELETE') THEN
  66. v_old_data := ROW(OLD.*);
  67. INSERT INTO logged_action (
  68. table_name,
  69. user_name,
  70. action,
  71. original_data,
  72. schema,
  73. row_id)
  74. VALUES (
  75. TG_TABLE_NAME::TEXT,
  76. user::TEXT,
  77. substring(TG_OP,1,1),
  78. v_old_data,
  79. v_schema,
  80. old.id);
  81. RETURN OLD;
  82. ELSIF (TG_OP = 'INSERT') THEN
  83. v_new_data := ROW(NEW.*);
  84. INSERT INTO logged_action (
  85. table_name,
  86. user_name,
  87. action,
  88. new_data,
  89. schema,
  90. row_id)
  91. VALUES (
  92. TG_TABLE_NAME::TEXT,
  93. user::TEXT,
  94. substring(TG_OP,1,1),
  95. v_new_data,
  96. v_schema,
  97. new.id);
  98. RETURN NEW;
  99. ELSE
  100. raise warning '[IF_MODIFIED_FUNC] - Other action occurred: %, at %', TG_OP, now();
  101. RETURN NULL;
  102. END IF;
  103.  
  104. EXCEPTION
  105. WHEN data_exception THEN
  106. raise warning '[IF_MODIFIED_FUNC] - UDF ERROR [DATA EXCEPTION] - SQLSTATE: %, SQLERRM: %', SQLSTATE, SQLERRM;
  107. RETURN NULL;
  108. WHEN unique_violation THEN
  109. raise warning '[IF_MODIFIED_FUNC] - UDF ERROR [UNIQUE] - SQLSTATE: %, SQLERRM: %', SQLSTATE, SQLERRM;
  110. RETURN NULL;
  111. WHEN OTHERS THEN
  112. raise warning '[IF_MODIFIED_FUNC] - UDF ERROR [OTHER] - SQLSTATE: %, SQLERRM: %', SQLSTATE, SQLERRM;
  113. RETURN NULL;
  114. END;
  115. $body$
  116. LANGUAGE plpgsql;
  117. ```
  118.  
  119. A view to show which tables have an audit trigger installed. Don't forget to make an exception to not look at the _audit table itself!:
  120. ``` sql
  121. drop view table_audit_trigger;
  122. create or replace view table_audit_trigger as
  123. select table_name, tgname
  124. from
  125. (select table_name
  126. from information_schema.tables
  127. where table_schema = 'public'
  128. and table_type = 'BASE TABLE') y
  129. left join
  130. (select relname, tgname
  131. from pg_trigger
  132. join pg_class on tgrelid=pg_class.oid
  133. join pg_proc on tgfoid=pg_proc.oid
  134. where tgname like '%_audit') x
  135. on relname = table_name;
  136. ```
  137.  
  138. Some dynamic sql to drop and reapply all the triggers on the tables
  139. ``` sql
  140. do
  141. $$
  142. begin
  143. execute(select
  144. string_agg(
  145. 'drop trigger if exists _audit on ' ||
  146. quote_ident(t.table_name) || ';' ||
  147. 'create trigger _audit ' ||
  148. ' after insert or update or delete on ' ||
  149. quote_ident(t.table_name) ||
  150. ' for each row execute procedure if_modified_func();', E'\n')
  151. from (select table_name from table_audit_trigger) t);
  152. end
  153. $$;
  154. ```
  155.  
  156. And here's what you get:
  157. ```
  158. db=# select * from logged_action;
  159. table_name | user_name | action_tstamp | action | original_data | new_data | schema | row_id | id
  160. -------------+-----------+-------------------------------+--------+--------------------------------------+--------------------------------------+-------------------------------------------+--------+----
  161. patient_lab | nobody | 2015-06-03 16:24:12.729897+00 | U | (182,26,1,"2015-06-03 00:00:00",11) | (182,26,1,"2015-06-03 00:00:00",10) | {id,patient_id,lab_id,lab_time,lab_value} | 182 | 43
  162. patient_lab | nobody | 2015-06-03 16:25:28.695848+00 | I | | (183,26,30,"2015-06-04 10:25:00",12) | {id,patient_id,lab_id,lab_time,lab_value} | 183 | 44
  163. patient_lab | nobody | 2015-06-03 16:25:41.056173+00 | D | (183,26,30,"2015-06-04 10:25:00",12) | | {id,patient_id,lab_id,lab_time,lab_value} | 183 | 45
  164. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement