Last active
January 15, 2019 14:18
-
-
Save drbobbeaty/1c4a513e14fdb1e4e123ff9443f978c4 to your computer and use it in GitHub Desktop.
Nice Postgres Trigger for Immutable Data with Audit Log
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | |
-- Assume that you have a table - `customers`... create a new table called | |
-- `customers_audit` with the following qualities: | |
-- | |
create table if not exists customer ( | |
-- assumming you have a primary key that's a uuid | |
id uuid not null, | |
-- ...make sure to have a version and timestamp | |
version integer not null, | |
as_of timestamp with time zone not null, | |
-- add in all the other fields you want... | |
last_name varchar, | |
first_name varchar, | |
age integer, | |
-- add in the primary key | |
primary key (id, version, as_of); | |
); | |
-- add all the indexes you want... | |
create index idx_cust_age on customer (age); | |
-- create the audit table... where all the historical entries will go | |
-- this will be the same structure, indexes, etc. | |
create table if not exists customer_audit like customer including all; | |
-- | |
-- create the trigger on INSERT and UPDATE to update the version if it's | |
-- not provided, and to maintain all versions in the audit table, but have | |
-- the current version in the non-audit table. Importantly, NOTHING is | |
-- deleted. | |
-- | |
create or replace function audit_customer() | |
returns trigger as $body$ | |
declare | |
ver integer; | |
begin | |
-- get the advisory lock on this id | |
perform pg_advisory_xact_lock(('x' || translate(left(new.id::text, 18), '-', ''))::bit(64)::bigint); | |
-- get the max of the existing version for the data now | |
select max(version) into ver | |
from customer_audit | |
where id = new.id; | |
-- and bump it up one and use that | |
if ver is null then | |
new.version := 1; | |
else | |
new.version := ver + 1; | |
end if; | |
-- if an update, then we need to insert the new | |
if tg_op = 'UPDATE' then | |
-- now let's insert the old row into the audit table | |
insert into customer_audit | |
values (new.*); | |
elsif tg_op = 'INSERT' then | |
-- now let's insert the new row into the audit table | |
insert into customer_audit | |
values (new.*); | |
-- and delete the old one in the customer table | |
delete from customer | |
where id = new.id | |
and version <= ver; | |
end if; | |
-- finally, return the row to be inserted to customer | |
return new; | |
end | |
$body$ language plpgsql; | |
create trigger set_version before insert or update on customer | |
for each row execute procedure audit_customer(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you want to use a general
int
field for the primary key, you can change the first lines in the trigger to be:where the column
id
is of typeint
. We are just casting theint
to abigint
and that will work just as well.