Executing perl code on PostgreSQL triggers
Executing perl code on PostgreSQL triggers
Ruben de Groot, 12-06-2009
This document describes a way to execute external perl code each time an
INSERT, DELETE or SELECT happens in a postgres database. It is an
example, nothing is said here about securing the database; do not do
this on a public server.
In this example, we are using a database with 2 tables. One table will
only get INSERTs while the other will act as a work queue for the
actions to be taken.
Software installation
These examples were done on a FreeBSD 7.2 server. To get all the
required software simply execute the following commands:
-
# pkg_add -r postgresql82-server
-
# pkg_add -r p5-DBD-Pg
Setting up the database
When above mentioned packages are installed, we have to start the
postgresql server and create a database for testing.
-
# echo "postgresql_enable=YES" >>/etc/rc.conf
-
# /usr/local/etc/rc.d/postgresql initdb
-
# /usr/local/etc/rc.d/postgresql start
-
# createdb test -U pgsql
-
# psql -d test -U pgsql
Now we create two simple tables :
CREATE TABLE requests (
id SERIAL NOT NULL,
package_id INTEGER,
project_nr CHARACTER(10),
request_date DATE
);
CREATE TABLE workqueue (
id SERIAL NOT NULL,
package_id INTEGER,
project_nr CHARACTER(10),
request_date DATE
);
The idea is to save a history of all requests in the requests
table. On receiving a request, some action should be taken by an
external perl program though, therefore each new entry should also go in
the workqueue table from where it will be deleted when the action
has been done.
The duplication of new entries to the queue table will be done by a
stored procedure and a trigger in the plpgsql language. These will also
send a notification to the external perl script.
Before we can write anything in plpgsql we may need to add the
language:
CREATE OR REPLACE FUNCTION plpgsql_call_handler () RETURNS OPAQUE AS
'/usr/local/lib/postgresql/plpgsql.so' LANGUAGE 'C';
CREATE TRUSTED PROCEDURAL LANGUAGE 'plpgsql'
HANDLER plpgsql_call_handler
LANCOMPILER 'PL/pgSQL';
Now we can add the stored procedure and the trigger :
CREATE FUNCTION queue_request() RETURNS trigger AS '
BEGIN
INSERT INTO workqueue ( id, package_id, project_nr, request_date )
VALUES ( NEW.id, NEW.package_id, NEW.project_nr, NEW.request_date );
NOTIFY request;
RETURN NULL;
END;
' LANGUAGE plpgsql;
CREATE TRIGGER requesttrigger AFTER INSERT ON requests
FOR EACH ROW EXECUTE PROCEDURE queue_request();
From now on, every insert into the requests table will be duplicated in
the workqueue table. Also, postgres will raise a "request" notification.
This will be used by an external program written in perl.
Perl program
The following example perl code opens a connection to the database,
LISTENs for notifications, and then acts upon them.
#!/usr/bin/perl -w
# -------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42 - bzerk)
# <rdg@bzerk.org> wrote this file. As long as you retain this notice
# you can do whatever you want with this stuff. If we meet some day,
# and you think this stuff is worth it, you can buy me a beer in
# return. Ruben de Groot
# -------------------------------------------------------------------
# This script will wait for notifications from a postgres database.
# Upon such notifications, it will iterate through a queue of
# requests, perform some action and then remove the request from
# the work queue.
use strict;
use warnings;
use DBI;
use IO::Select;
my $dbcon_test = "dbi:Pg:dbname=test;host=127.0.0.1";
my $dbuser = "pgsql";
my $dbpass = "";
my $dbattr = {RaiseError => 1, AutoCommit => 1};
my $dbh = DBI->connect($dbcon_test, $dbuser, $dbpass, $dbattr);
my $select_handle = $dbh->prepare("select id, package_id, project_nr from workqueue");
my $delete_handle = $dbh->prepare("delete from workqueue where id = ?");
$dbh->do("LISTEN request");
my $fd = $dbh->func("getfd");
my $sel = IO::Select->new($fd);
while (1) {
print "waiting...\n";
$sel->can_read;
my $notify = $dbh->func("pg_notifies");
if ($notify) {
$select_handle->execute();
while (my $h = $select_handle->fetchrow_hashref()) {
my ($id, $package_id, $project_nr) = ($h->{id}, $h->{package_id}, $h->{project_nr});
#####################################################################
# Here the code to be executed upon each request #
#####################################################################
$delete_handle->execute($id);
}
}
}