create table audit.record_version( id bigserial primary key, -- auditing metadata record_id uuid, -- identifies a new record by it's table + primary key old_record_id uuid, -- ^ op varchar(8) not null, -- INSERT/UPDATE/DELETE/TRUNCATE ts timestamptz not nulldefault now(), -- table identifiers table_oid oid not null, -- pg internal id for a table table_schema name not null, -- audited table's schema name e.g. 'public' table_name name not null, -- audited table's table name e.g. 'account' -- record data record jsonb, -- contents of the new record old_record jsonb -- previous record contents (for UPDATE/DELETE) );
create or replace function audit.primary_key_columns(entity_oid oid) returns text[] stable security definer language sql as $$ -- Looks up the names of a table's primary key columns select coalesce( array_agg(pa.attname::text order by pa.attnum), array[]::text[] ) column_names from pg_index pi join pg_attribute pa on pi.indrelid = pa.attrelid and pa.attnum = any(pi.indkey) where indrelid = $1 and indisprimary $$;
另一个使用table_oid和主键,将结果转换为记录的 UUID。
create or replace function audit.to_record_id( entity_oid oid, pkey_cols text[], rec jsonb ) returns uuid stable language sql as $$ select case when rec is null then null -- if no primary key exists, use a random uuid when pkey_cols = array[]::text[] then uuid_generate_v4() else ( select uuid_generate_v5( 'fd62bc3d-8d6e-43c2-919c-802ba3762271', ( jsonb_build_array(to_jsonb($1)) || jsonb_agg($3 ->> key_) )::text ) from unnest($2) x(key_) ) end $$;
-- index record_id for fast searching create index record_version_record_id on audit.record_version(record_id) where record_id is not null;
-- index old_record_id for fast searching create index record_version_old_record_id on audit.record_version(record_id) where old_record_id is not null;
create or replace function audit.enable_tracking(regclass) returns void volatile security definer language plpgsql as $$ declare statement_row text = format(' create trigger audit_i_u_d before insert or update or delete on %I for each row execute procedure audit.insert_update_delete_trigger();', $1 );
pkey_cols text[] = audit.primary_key_columns($1); begin if pkey_cols = array[]::text[] then raise exception 'Table % can not be audited because it has no primary key', $1; end if;
if not exists(select 1 from pg_trigger where tgrelid = $1 and tgname = 'audit_i_u_d') then execute statement_row; end if; end; $$;
create or replace function audit.disable_tracking(regclass) returns void volatile security definer language plpgsql as $$ declare statement_row text = format( 'drop trigger if exists audit_i_u_d on %I;', $1 ); begin execute statement_row; end; $$;