A little while ago there was a question about how to automatically update a
timestamp so I drafted the attached code.

NOTE - you need 7.1beta to use the attached code and plpgsql enabled on your
db.

I'm interested in any comments on the attached before I submit it for
possible inclusion in contrib/ (or wherever).

I'm especially interested in any comments on the readme/tutorial from
newbies who haven't experimented with plpgsql before (like me a few weeks
ago). Is it clear enough? Would you prefer HTML?

Also - any special requests for another module? I was thinking of some
functions to provide friendlier access to the system tables (index details,
triggers on a table etc)

PS - developers - where should I submit this stuff to if people like it?
pgsql-doc?

TIA

- Richard Huxton

lastchange-install.sql

README for the PostgreSQL lastchange module
===========================================
Revision: 0.2  Date: 2001-02-15

This file and all others provided as part of this package are distributed under the 
same terms as PostgreSQL itself. See http://www.postgresql.org/ for details.

You can contact the author (Richard Huxton) at [EMAIL PROTECTED]

As usual, this code comes with no warranty whatsoever - use it at your own risk. See 
the licence for details.


Provides
========
A way of creating an auto-timestamp on an existing column with a type of "timestamp". 
Whenever a row in that table is modified, the timestamp is updated accordingly.


Requirements
============
You need PostgreSQL v7.1
You need to have run "createlang plpgsql <dbname>" to provide plpgsql support


Manifest
========
lastchange-install.sql  - creates module functions
lastchange-remove.sql   - removes module functions
lastchange-README.txt   - this file
lastchange-tutorial.txt - describes how/why the module works


Summary
=======
You can add the relevant triggers using a single function call:
    select lastchg_addto(<table-name>,<timestamp column-name>);
You can remove the triggers with:
    select lastchg_remove(<table-name>,<timestamp column-name>);


Bugs/Issues
===========
You may experience problems if you already have triggers on the specified column - 
this is intended to be a quick solution for newbies.
At present there is no support for a separate "creation" timestamp which is set on 
insert. This can be done by setting a DEFAULT value on the column and not modifying it.


Usage
=====
--
-- Start by creating our table
--
richardh=> create table foo (a serial, b text, c timestamp);
NOTICE:  CREATE TABLE will create implicit sequence 'foo_a_seq' for SERIAL column 
'foo.a'
NOTICE:  CREATE TABLE/UNIQUE will create implicit index 'foo_a_key' for table 'foo'
CREATE

--
-- Now import the lastchg module (you might want to use the full path)
--
richardh=> \i lastchange-install.sql
CREATE
CREATE

--
-- Create insert/update trigger
--
richardh=> select lastchg_addto('foo','c');
      lastchg_addto
-------------------------
 Created lastchg trigger
(1 row)

--
-- Insert some data
--
richardh=> insert into foo (b) values ('aaa');
INSERT 217867 1
richardh=> insert into foo (b) values ('bbb');
INSERT 217868 1
richardh=> insert into foo (b) values ('ccc');
INSERT 217869 1

richardh=> select * from foo;
 a |  b  |           c
---+-----+------------------------
 1 | aaa | 2001-02-08 09:33:35+00
 2 | bbb | 2001-02-08 09:33:38+00
 3 | ccc | 2001-02-08 09:33:40+00
(3 rows)

--
-- Update some data
--
richardh=> update foo set b='xxx';
UPDATE 3
richardh=> select * from foo;
 a |  b  |           c
---+-----+------------------------
 1 | xxx | 2001-02-08 09:34:41+00
 2 | xxx | 2001-02-08 09:34:41+00
 3 | xxx | 2001-02-08 09:34:41+00
(3 rows)

--
-- Remove the triggers
--
richardh=> select lastchg_remove('foo','c');
     lastchg_remove
-------------------------
 Removed lastchg trigger
(1 row)

--
-- Timestamp shouldn't update now
--
richardh=> update foo set b='yyy';
UPDATE 3
richardh=> select * from foo;
 a |  b  |           c
---+-----+------------------------
 1 | yyy | 2001-02-08 09:34:41+00
 2 | yyy | 2001-02-08 09:34:41+00
 3 | yyy | 2001-02-08 09:34:41+00
(3 rows)

lastchange-remove.sql

Lastchange module - Tutorial
============================

Introduction
============
The lastchange module provides two plpgsql functions to create and remove a trigger on 
an indicated table and column. We need to do this in two steps in lastchg_addto():

1. Create a function to update a timestamp column.
2. Create a trigger to call that function.

Removing these is handled by lastchg_remove()


lastchg_addto(<table-name>,<column-name>)
=========================================
The basic structure of the function is:

        create function lastchg_addto(text,text) returns text as '
        declare
                <aliases for parameters here>
                <variable definitions here>
        begin
                <code here>
                return <return value>
        end;
        ' language 'plpgsql';

Note that everything from declare..end is quoted and ends in a semicolon. The quoting 
will be important later, but first of all we need to declare some variables:
        declare
          tbl alias for $1;
          col alias for $2;
          trigfn text;
          trignm text;
          exec1  text;
          exec2  text;
So - we set up aliases for our two parameters so the first can be referred to as "tbl" 
and the second as "col" (our table and column names).
We then create two variables to hold the names of our update function (trigfn) and 
trigger (trignm).

Now we need to provide some code within our begin...end; block:
        begin
          trignm := tbl || ''_'' || col || ''_lctrig'';
          trigfn := tbl || ''_'' || col || ''_lcupdate()'';

So - if our table is call 'foo' and our column is 'bar' our trigger will be called 
'foo_bar_lctrig' and our function 'foo_bar_lcupdate()'. Note that all our literal 
strings have to be double-quoted because we are within one set of quotes already.

Then we need to construct some SQL to add this function and trigger to the database:
          exec1 := ''create function '' || trigfn || '' returns opaque as '''' '' ||
                                ''begin '' ||
                                ''NEW.'' || col || '' = now(); '' ||
                                ''return NEW; '' ||
                                ''end; '' ||
                                '' '''' language ''''plpgsql'''';'';
          exec2 := ''create trigger '' || trignm ||
                                '' before insert or update on '' || tbl || 
                                '' for each row execute procedure '' || trigfn || 
'';'';

These just build two pieces of text that represent the SQL for a CREATE FUNCTION and a 
CREATE TRIGGER statement. The only tricky bit is getting the quoting right. Where we 
have four quotes in a row, that is because we are putting a quote inside a literal 
string which is inside another literal string (yuck).

Finally, we execute this SQL and return a confirmation to the user:
  execute exec1;
  execute exec2;
  return ''Created lastchg trigger'';

That's it - the only difficulty is getting your head around the quoting involved.


lastchg_remove()
================
This basically just creates two DROP statements and executes them (in the reverse 
order to which we created them). Note that when you drop a function you need to 
specify the parameter types (if any) and when dropping a trigger you need to specify 
the table it is attached to.


Tips/Thoughts
=============
You can see the SQL lastchg_addto() creates by removing the "execute" commands and 
doing "return exec1" or similar.

When developing I found it useful to put all my code in a little test file so I could 
create a dummy table, populate it apply triggers etc. With that and another file that 
just drops everything you can debug your code more easily.

There are some limitations on the "execute" command in plpgsql - notably you can't 
select into a variable since the execute spins off a subprocess to run the query.

Reply via email to