| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ************************************************************************* | 
| 2 |  |  |  |  |  |  | # Copyright (c) 2014-2017, SUSE LLC | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # All rights reserved. | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | # Redistribution and use in source and binary forms, with or without | 
| 7 |  |  |  |  |  |  | # modification, are permitted provided that the following conditions are met: | 
| 8 |  |  |  |  |  |  | # | 
| 9 |  |  |  |  |  |  | # 1. Redistributions of source code must retain the above copyright notice, | 
| 10 |  |  |  |  |  |  | # this list of conditions and the following disclaimer. | 
| 11 |  |  |  |  |  |  | # | 
| 12 |  |  |  |  |  |  | # 2. Redistributions in binary form must reproduce the above copyright | 
| 13 |  |  |  |  |  |  | # notice, this list of conditions and the following disclaimer in the | 
| 14 |  |  |  |  |  |  | # documentation and/or other materials provided with the distribution. | 
| 15 |  |  |  |  |  |  | # | 
| 16 |  |  |  |  |  |  | # 3. Neither the name of SUSE LLC nor the names of its contributors may be | 
| 17 |  |  |  |  |  |  | # used to endorse or promote products derived from this software without | 
| 18 |  |  |  |  |  |  | # specific prior written permission. | 
| 19 |  |  |  |  |  |  | # | 
| 20 |  |  |  |  |  |  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
| 21 |  |  |  |  |  |  | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
| 22 |  |  |  |  |  |  | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
| 23 |  |  |  |  |  |  | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | 
| 24 |  |  |  |  |  |  | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
| 25 |  |  |  |  |  |  | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
| 26 |  |  |  |  |  |  | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
| 27 |  |  |  |  |  |  | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
| 28 |  |  |  |  |  |  | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
| 29 |  |  |  |  |  |  | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
| 30 |  |  |  |  |  |  | # POSSIBILITY OF SUCH DAMAGE. | 
| 31 |  |  |  |  |  |  | # ************************************************************************* | 
| 32 |  |  |  |  |  |  | # | 
| 33 |  |  |  |  |  |  | # sql/audit_Config.pm | 
| 34 |  |  |  |  |  |  | # | 
| 35 |  |  |  |  |  |  | # - audit-related SQL statements | 
| 36 |  |  |  |  |  |  | # - code to initialize audit schema and related triggers | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | # | 
| 39 |  |  |  |  |  |  | # the audit trigger expects to be able to do "SELECT current_setting('dochazka.eid')" | 
| 40 |  |  |  |  |  |  | # to get the EID of the current user. Therefore, when the connection is established | 
| 41 |  |  |  |  |  |  | # we need to always do "SET dochazka.eid = $EID" | 
| 42 |  |  |  |  |  |  | set( 'SQL_SET_DOCHAZKA_EID_GUC', 'SET LOCAL dochazka.eid = ?' ); | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | # | 
| 45 |  |  |  |  |  |  | set( 'DBINIT_AUDIT', [ | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | q# | 
| 48 |  |  |  |  |  |  | -- An audit history is important on most tables. Provide an audit trigger that logs to | 
| 49 |  |  |  |  |  |  | -- a dedicated audit table for the major relations. | 
| 50 |  |  |  |  |  |  | -- | 
| 51 |  |  |  |  |  |  | -- This file should be generic and not depend on application roles or structures, | 
| 52 |  |  |  |  |  |  | -- as it's being listed here: | 
| 53 |  |  |  |  |  |  | -- | 
| 54 |  |  |  |  |  |  | --    https://wiki.postgresql.org/wiki/Audit_trigger_91plus | 
| 55 |  |  |  |  |  |  | -- | 
| 56 |  |  |  |  |  |  | -- This trigger was originally based on | 
| 57 |  |  |  |  |  |  | --   http://wiki.postgresql.org/wiki/Audit_trigger | 
| 58 |  |  |  |  |  |  | -- but has been completely rewritten. | 
| 59 |  |  |  |  |  |  | -- | 
| 60 |  |  |  |  |  |  | #, | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | q# | 
| 63 |  |  |  |  |  |  | CREATE SCHEMA audit | 
| 64 |  |  |  |  |  |  | #, | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | q# | 
| 67 |  |  |  |  |  |  | REVOKE ALL ON SCHEMA audit FROM public | 
| 68 |  |  |  |  |  |  | #, | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | q# | 
| 71 |  |  |  |  |  |  | COMMENT ON SCHEMA audit IS 'Out-of-table audit/history logging tables and trigger functions' | 
| 72 |  |  |  |  |  |  | #, | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | q# | 
| 75 |  |  |  |  |  |  | -- | 
| 76 |  |  |  |  |  |  | -- Audited data. Lots of information is available, it's just a matter of how much | 
| 77 |  |  |  |  |  |  | -- you really want to record. See: | 
| 78 |  |  |  |  |  |  | -- | 
| 79 |  |  |  |  |  |  | --   http://www.postgresql.org/docs/9.1/static/functions-info.html | 
| 80 |  |  |  |  |  |  | -- | 
| 81 |  |  |  |  |  |  | -- Remember, every column you add takes up more audit table space and slows audit | 
| 82 |  |  |  |  |  |  | -- inserts. | 
| 83 |  |  |  |  |  |  | -- | 
| 84 |  |  |  |  |  |  | -- Every index you add has a big impact too, so avoid adding indexes to the | 
| 85 |  |  |  |  |  |  | -- audit table unless you REALLY need them. The hstore GIST indexes are | 
| 86 |  |  |  |  |  |  | -- particularly expensive. | 
| 87 |  |  |  |  |  |  | -- | 
| 88 |  |  |  |  |  |  | -- It is sometimes worth copying the audit table, or a coarse subset of it that | 
| 89 |  |  |  |  |  |  | -- you're interested in, into a temporary table where you CREATE any useful | 
| 90 |  |  |  |  |  |  | -- indexes and do your analysis. | 
| 91 |  |  |  |  |  |  | -- | 
| 92 |  |  |  |  |  |  | CREATE TABLE audit.logged_actions ( | 
| 93 |  |  |  |  |  |  | event_id bigserial primary key, | 
| 94 |  |  |  |  |  |  | schema_name text not null, | 
| 95 |  |  |  |  |  |  | table_name text not null, | 
| 96 |  |  |  |  |  |  | relid oid not null, | 
| 97 |  |  |  |  |  |  | session_user_name text, | 
| 98 |  |  |  |  |  |  | action_tstamp_tx TIMESTAMP WITH TIME ZONE NOT NULL, | 
| 99 |  |  |  |  |  |  | action_tstamp_stm TIMESTAMP WITH TIME ZONE NOT NULL, | 
| 100 |  |  |  |  |  |  | action_tstamp_clk TIMESTAMP WITH TIME ZONE NOT NULL, | 
| 101 |  |  |  |  |  |  | transaction_id bigint, | 
| 102 |  |  |  |  |  |  | dochazka_eid integer, | 
| 103 |  |  |  |  |  |  | client_addr inet, | 
| 104 |  |  |  |  |  |  | client_port integer, | 
| 105 |  |  |  |  |  |  | client_query text, | 
| 106 |  |  |  |  |  |  | action TEXT NOT NULL CHECK (action IN ('I','D','U', 'T')), | 
| 107 |  |  |  |  |  |  | row_data hstore, | 
| 108 |  |  |  |  |  |  | changed_fields hstore, | 
| 109 |  |  |  |  |  |  | statement_only boolean not null | 
| 110 |  |  |  |  |  |  | ) | 
| 111 |  |  |  |  |  |  | #, | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | q#REVOKE ALL ON audit.logged_actions FROM public#, | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | q#COMMENT ON TABLE audit.logged_actions IS 'History of auditable actions on audited tables, from audit.if_modified_func()'#, | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.event_id IS 'Unique identifier for each auditable event'#, | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.schema_name IS 'Database schema audited table for this event is in'#, | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.table_name IS 'Non-schema-qualified table name of table event occured in'#, | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.relid IS 'Table OID. Changes with drop/create. Get with ''tablename''::regclass'#, | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.session_user_name IS 'Login / session user whose statement caused the audited event'#, | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.action_tstamp_tx IS 'Transaction start timestamp for tx in which audited event occurred'#, | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.action_tstamp_stm IS 'Statement start timestamp for tx in which audited event occurred'#, | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.action_tstamp_clk IS 'Wall clock time at which audited event''s trigger call occurred'#, | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.transaction_id IS 'Identifier of transaction that made the change. May wrap, but unique paired with action_tstamp_tx.'#, | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.client_addr IS 'IP address of client that issued query. Null for unix domain socket.'#, | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.client_port IS 'Remote peer IP port address of client that issued query. Undefined for unix socket.'#, | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.client_query IS 'Top-level query that caused this auditable event. May be more than one statement.'#, | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.dochazka_eid IS 'Dochazka EID of the employee who conducted the database operation.'#, | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.action IS 'Action type; I = insert, D = delete, U = update, T = truncate'#, | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.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.'#, | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.changed_fields IS 'New values of fields changed by UPDATE. Null except for row-level UPDATE events.'#, | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | q#COMMENT ON COLUMN audit.logged_actions.statement_only IS '''t'' if audit event is from an FOR EACH STATEMENT trigger, ''f'' for FOR EACH ROW'#, | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | q#CREATE INDEX logged_actions_relid_idx ON audit.logged_actions(relid)#, | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | q#CREATE INDEX logged_actions_action_tstamp_tx_stm_idx ON audit.logged_actions(action_tstamp_stm)#, | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | q#CREATE INDEX logged_actions_action_idx ON audit.logged_actions(action)#, | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | q#CREATE OR REPLACE FUNCTION audit.get_dochazka_eid() RETURNS INTEGER AS $body$ | 
| 158 |  |  |  |  |  |  | BEGIN | 
| 159 |  |  |  |  |  |  | RETURN current_setting('dochazka.eid'); | 
| 160 |  |  |  |  |  |  | EXCEPTION | 
| 161 |  |  |  |  |  |  | WHEN OTHERS THEN | 
| 162 |  |  |  |  |  |  | RETURN -1; | 
| 163 |  |  |  |  |  |  | END; | 
| 164 |  |  |  |  |  |  | $body$ | 
| 165 |  |  |  |  |  |  | LANGUAGE plpgsql | 
| 166 |  |  |  |  |  |  | #, | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | q#CREATE OR REPLACE FUNCTION audit.if_modified_func() RETURNS TRIGGER AS $body$ | 
| 169 |  |  |  |  |  |  | DECLARE | 
| 170 |  |  |  |  |  |  | audit_row audit.logged_actions; | 
| 171 |  |  |  |  |  |  | include_values boolean; | 
| 172 |  |  |  |  |  |  | log_diffs boolean; | 
| 173 |  |  |  |  |  |  | h_old hstore; | 
| 174 |  |  |  |  |  |  | h_new hstore; | 
| 175 |  |  |  |  |  |  | excluded_cols text[] = ARRAY[]::text[]; | 
| 176 |  |  |  |  |  |  | BEGIN | 
| 177 |  |  |  |  |  |  | IF TG_WHEN <> 'AFTER' THEN | 
| 178 |  |  |  |  |  |  | RAISE EXCEPTION 'audit.if_modified_func() may only run as an AFTER trigger'; | 
| 179 |  |  |  |  |  |  | END IF; | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | audit_row = ROW( | 
| 182 |  |  |  |  |  |  | nextval('audit.logged_actions_event_id_seq'), -- event_id | 
| 183 |  |  |  |  |  |  | TG_TABLE_SCHEMA::text,                        -- schema_name | 
| 184 |  |  |  |  |  |  | TG_TABLE_NAME::text,                          -- table_name | 
| 185 |  |  |  |  |  |  | TG_RELID,                                     -- relation OID for much quicker searches | 
| 186 |  |  |  |  |  |  | session_user::text,                           -- session_user_name | 
| 187 |  |  |  |  |  |  | current_timestamp,                            -- action_tstamp_tx | 
| 188 |  |  |  |  |  |  | statement_timestamp(),                        -- action_tstamp_stm | 
| 189 |  |  |  |  |  |  | clock_timestamp(),                            -- action_tstamp_clk | 
| 190 |  |  |  |  |  |  | txid_current(),                               -- transaction ID | 
| 191 |  |  |  |  |  |  | audit.get_dochazka_eid(),                     -- client application | 
| 192 |  |  |  |  |  |  | inet_client_addr(),                           -- client_addr | 
| 193 |  |  |  |  |  |  | inet_client_port(),                           -- client_port | 
| 194 |  |  |  |  |  |  | current_query(),                              -- top-level query or queries (if multistatement) from client | 
| 195 |  |  |  |  |  |  | substring(TG_OP,1,1),                         -- action | 
| 196 |  |  |  |  |  |  | NULL, NULL,                                   -- row_data, changed_fields | 
| 197 |  |  |  |  |  |  | 'f'                                           -- statement_only | 
| 198 |  |  |  |  |  |  | ); | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | IF NOT TG_ARGV[0]::boolean IS DISTINCT FROM 'f'::boolean THEN | 
| 201 |  |  |  |  |  |  | audit_row.client_query = NULL; | 
| 202 |  |  |  |  |  |  | END IF; | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | IF TG_ARGV[1] IS NOT NULL THEN | 
| 205 |  |  |  |  |  |  | excluded_cols = TG_ARGV[1]::text[]; | 
| 206 |  |  |  |  |  |  | END IF; | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | IF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN | 
| 209 |  |  |  |  |  |  | audit_row.row_data = hstore(OLD.*); | 
| 210 |  |  |  |  |  |  | audit_row.changed_fields =  (hstore(NEW.*) - audit_row.row_data) - excluded_cols; | 
| 211 |  |  |  |  |  |  | IF audit_row.changed_fields = hstore('') THEN | 
| 212 |  |  |  |  |  |  | -- All changed fields are ignored. Skip this update. | 
| 213 |  |  |  |  |  |  | RETURN NULL; | 
| 214 |  |  |  |  |  |  | END IF; | 
| 215 |  |  |  |  |  |  | ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN | 
| 216 |  |  |  |  |  |  | audit_row.row_data = hstore(OLD.*) - excluded_cols; | 
| 217 |  |  |  |  |  |  | ELSIF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN | 
| 218 |  |  |  |  |  |  | audit_row.row_data = hstore(NEW.*) - excluded_cols; | 
| 219 |  |  |  |  |  |  | ELSIF (TG_LEVEL = 'STATEMENT' AND TG_OP IN ('INSERT','UPDATE','DELETE','TRUNCATE')) THEN | 
| 220 |  |  |  |  |  |  | audit_row.statement_only = 't'; | 
| 221 |  |  |  |  |  |  | ELSE | 
| 222 |  |  |  |  |  |  | RAISE EXCEPTION '[audit.if_modified_func] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL; | 
| 223 |  |  |  |  |  |  | RETURN NULL; | 
| 224 |  |  |  |  |  |  | END IF; | 
| 225 |  |  |  |  |  |  | INSERT INTO audit.logged_actions VALUES (audit_row.*); | 
| 226 |  |  |  |  |  |  | RETURN NULL; | 
| 227 |  |  |  |  |  |  | END; | 
| 228 |  |  |  |  |  |  | $body$ | 
| 229 |  |  |  |  |  |  | LANGUAGE plpgsql | 
| 230 |  |  |  |  |  |  | SECURITY DEFINER | 
| 231 |  |  |  |  |  |  | SET search_path = pg_catalog, public | 
| 232 |  |  |  |  |  |  | #, | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | q#COMMENT ON FUNCTION audit.if_modified_func() IS $body$ | 
| 235 |  |  |  |  |  |  | Track changes to a table at the statement and/or row level. | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | Optional parameters to trigger in CREATE TRIGGER call: | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | param 0: boolean, whether to log the query text. Default 't'. | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | param 1: text[], columns to ignore in updates. Default []. | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | Updates to ignored cols are omitted from changed_fields. | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | Updates with only ignored cols changed are not inserted | 
| 246 |  |  |  |  |  |  | into the audit log. | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | Almost all the processing work is still done for updates | 
| 249 |  |  |  |  |  |  | that ignored. If you need to save the load, you need to use | 
| 250 |  |  |  |  |  |  | WHEN clause on the trigger instead. | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | No warning or error is issued if ignored_cols contains columns | 
| 253 |  |  |  |  |  |  | that do not exist in the target table. This lets you specify | 
| 254 |  |  |  |  |  |  | a standard set of ignored columns. | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | There is no parameter to disable logging of values. Add this trigger as | 
| 257 |  |  |  |  |  |  | a 'FOR EACH STATEMENT' rather than 'FOR EACH ROW' trigger if you do not | 
| 258 |  |  |  |  |  |  | want to log row values. | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | Note that the user name logged is the login role for the session. The audit trigger | 
| 261 |  |  |  |  |  |  | cannot obtain the active role because it is reset by the SECURITY DEFINER invocation | 
| 262 |  |  |  |  |  |  | of the audit trigger its self. | 
| 263 |  |  |  |  |  |  | $body$; | 
| 264 |  |  |  |  |  |  | #, | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | q#CREATE OR REPLACE FUNCTION audit.audit_table(target_table regclass, audit_rows boolean, audit_query_text boolean, ignored_cols text[]) RETURNS void AS $body$ | 
| 268 |  |  |  |  |  |  | DECLARE | 
| 269 |  |  |  |  |  |  | stm_targets text = 'INSERT OR UPDATE OR DELETE OR TRUNCATE'; | 
| 270 |  |  |  |  |  |  | _q_txt text; | 
| 271 |  |  |  |  |  |  | _ignored_cols_snip text = ''; | 
| 272 |  |  |  |  |  |  | BEGIN | 
| 273 |  |  |  |  |  |  | EXECUTE 'DROP TRIGGER IF EXISTS audit_trigger_row ON ' || target_table; | 
| 274 |  |  |  |  |  |  | EXECUTE 'DROP TRIGGER IF EXISTS audit_trigger_stm ON ' || target_table; | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | IF audit_rows THEN | 
| 277 |  |  |  |  |  |  | IF array_length(ignored_cols,1) > 0 THEN | 
| 278 |  |  |  |  |  |  | _ignored_cols_snip = ', ' || quote_literal(ignored_cols); | 
| 279 |  |  |  |  |  |  | END IF; | 
| 280 |  |  |  |  |  |  | _q_txt = 'CREATE TRIGGER audit_trigger_row AFTER INSERT OR UPDATE OR DELETE ON ' || | 
| 281 |  |  |  |  |  |  | target_table || | 
| 282 |  |  |  |  |  |  | ' FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func(' || | 
| 283 |  |  |  |  |  |  | quote_literal(audit_query_text) || _ignored_cols_snip || ');'; | 
| 284 |  |  |  |  |  |  | RAISE NOTICE '%',_q_txt; | 
| 285 |  |  |  |  |  |  | EXECUTE _q_txt; | 
| 286 |  |  |  |  |  |  | stm_targets = 'TRUNCATE'; | 
| 287 |  |  |  |  |  |  | ELSE | 
| 288 |  |  |  |  |  |  | END IF; | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | _q_txt = 'CREATE TRIGGER audit_trigger_stm AFTER ' || stm_targets || ' ON ' || | 
| 291 |  |  |  |  |  |  | target_table || | 
| 292 |  |  |  |  |  |  | ' FOR EACH STATEMENT EXECUTE PROCEDURE audit.if_modified_func('|| | 
| 293 |  |  |  |  |  |  | quote_literal(audit_query_text) || ');'; | 
| 294 |  |  |  |  |  |  | RAISE NOTICE '%',_q_txt; | 
| 295 |  |  |  |  |  |  | EXECUTE _q_txt; | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | END; | 
| 298 |  |  |  |  |  |  | $body$ | 
| 299 |  |  |  |  |  |  | language 'plpgsql'; | 
| 300 |  |  |  |  |  |  | #, | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | q#COMMENT ON FUNCTION audit.audit_table(regclass, boolean, boolean, text[]) IS $body$ | 
| 303 |  |  |  |  |  |  | Add auditing support to a table. | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | Arguments: | 
| 306 |  |  |  |  |  |  | target_table:     Table name, schema qualified if not on search_path | 
| 307 |  |  |  |  |  |  | audit_rows:       Record each row change, or only audit at a statement level | 
| 308 |  |  |  |  |  |  | audit_query_text: Record the text of the client query that triggered the audit event? | 
| 309 |  |  |  |  |  |  | ignored_cols:     Columns to exclude from update diffs, ignore updates that change only ignored cols. | 
| 310 |  |  |  |  |  |  | $body$; | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | -- Pg doesn't allow variadic calls with 0 params, so provide a wrapper | 
| 313 |  |  |  |  |  |  | CREATE OR REPLACE FUNCTION audit.audit_table(target_table regclass, audit_rows boolean, audit_query_text boolean) RETURNS void AS $body$ | 
| 314 |  |  |  |  |  |  | SELECT audit.audit_table($1, $2, $3, ARRAY[]::text[]); | 
| 315 |  |  |  |  |  |  | $body$ LANGUAGE SQL; | 
| 316 |  |  |  |  |  |  | #, | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | q# | 
| 319 |  |  |  |  |  |  | -- And provide a convenience call wrapper for the simplest case | 
| 320 |  |  |  |  |  |  | -- of row-level logging with no excluded cols and query logging enabled. | 
| 321 |  |  |  |  |  |  | -- | 
| 322 |  |  |  |  |  |  | CREATE OR REPLACE FUNCTION audit.audit_table(target_table regclass) RETURNS void AS $$ | 
| 323 |  |  |  |  |  |  | SELECT audit.audit_table($1, BOOLEAN 't', BOOLEAN 't'); | 
| 324 |  |  |  |  |  |  | $$ LANGUAGE 'sql'; | 
| 325 |  |  |  |  |  |  | #, | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | q# | 
| 328 |  |  |  |  |  |  | COMMENT ON FUNCTION audit.audit_table(regclass) IS $body$ | 
| 329 |  |  |  |  |  |  | Add auditing support to the given table. Row-level changes will be logged with full client query text. No cols are ignored. | 
| 330 |  |  |  |  |  |  | $body$; | 
| 331 |  |  |  |  |  |  | #, | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | ] ); | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | # | 
| 336 |  |  |  |  |  |  | set( 'DBINIT_CREATE_AUDIT_TRIGGERS', | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | q#SELECT audit.audit_table( | 
| 339 |  |  |  |  |  |  | '?', 'true', 'false', '{version_col, changed_by, changed_timestamp}'::text[] | 
| 340 |  |  |  |  |  |  | )#, | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | ); | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | # ----------------------------------- | 
| 346 |  |  |  |  |  |  | # DO NOT EDIT ANYTHING BELOW THIS LINE | 
| 347 |  |  |  |  |  |  | # ----------------------------------- | 
| 348 | 41 |  |  | 41 |  | 32282 | use strict; | 
|  | 41 |  |  |  |  | 108 |  | 
|  | 41 |  |  |  |  | 1140 |  | 
| 349 | 41 |  |  | 41 |  | 203 | use warnings; | 
|  | 41 |  |  |  |  | 87 |  | 
|  | 41 |  |  |  |  | 1783 |  | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | 1; |