Postgres change tracking
I've always avoided TRIGGERS and FUNCTIONS inside databases, due
to being scar(r?)ed as a wee lad by a huge business app that was
mostly implemented in Sybase stored procedures, but today I wanted a
lazy history for a simple app. It's a link
redirector and one thing that's
useful in such a thing is a simple list of changes that have happened
for each link, but no real need to mutate it or customise it from the
app. I was also feeling pretty lazy, so didn't want to write a bunch
of code, so thought I'd try using a trigger function. It was actually
extremely easy.
Here's the existing schema:
CREATE TABLE IF NOT EXISTS urls (
name text PRIMARY KEY,
url text NOT NULL,
date_added timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
creator integer REFERENCES users(id) NOT NULL,
clicks integer DEFAULT 0 NOT NULL
);
So I added a simple history table for it:
-- History of changes to the urls table.
CREATE TABLE IF NOT EXISTS urls_history (
id SERIAL,
name text NOT NULL,
tstamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
"user" integer REFERENCES users(id) NOT NULL,
"type" text NOT NULL,
url text NOT NULL,
)
Then I added a function, that handles each case -
INSERT/UPDATE/DELETE, just logging the useful info. NEW and
OLD are pseudo-tables that let you get the new and old values for
the row we're dealing with:
CREATE OR REPLACE FUNCTION change_trigger() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT'
THEN
INSERT INTO urls_history ("user", "name", url, type)
VALUES (NEW.creator, NEW.name, NEW.url, 'ADDED');
RETURN NEW;
ELSIF TG_OP = 'UPDATE'
THEN
INSERT INTO urls_history ("user", "name", url, type)
VALUES (NEW.creator, NEW.name, NEW.url, 'UPDATED');
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
INSERT INTO urls_history ("user", "name", url, type)
VALUES (OLD.creator, OLD.name, OLD.url, 'DELETED');
RETURN OLD;
END IF;
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
Then just strap it on to the data table:
CREATE TRIGGER t
AFTER INSERT OR UPDATE OR DELETE ON urls
FOR EACH ROW EXECUTE PROCEDURE change_trigger();
And that was it. Then I just need a simple read query in the app and to render the history.