I’m using PostgreSQL 9.1. My database is structured so that there is actual tables that my application uses. For every table there is history table that stores only change history. History tables contain same fields that actual tables plus fields form some extra information eg. edit time. History tables are only handled by triggers.
I have 2 kind of triggers:
Before INSERTtrigger to add some extra information to tables when they are created (eg. create_time).Before UPDATEtrigger andbefore DELETEtriggers to copy old values from actual table to history table.
Problem is that I’d like to use triggers to store also the id of user who made those changes. And by id I mean id from php application, not PostgreSQL user id.
Is there any reasonable way to do that?
With INSERT and UPDATE it could be possible to just add extra field for id to actual tables and pass user id to SQL as part of SQL query. As far as I know this doesn’t work with DELETE.
All triggers are structured as follows:
CREATE OR REPLACE FUNCTION before_delete_customer() RETURNS trigger AS $BODY$
BEGIN
INSERT INTO _customer (
edited_by,
edit_time,
field1,
field2,
...,
fieldN
) VALUES (
-1, // <- This should be user id.
NOW(),
OLD.field1,
OLD.field2,
...,
OLD.fieldN
);
RETURN OLD;
END; $BODY$
LANGUAGE plpgsql
Options include:
When you open a connection,
CREATE TEMPORARY TABLE current_app_user(username text); INSERT INTO current_app_user(username) VALUES ('the_user');. Then in your trigger,SELECT username FROM current_app_userto get the current username, possibly as a subquery.In
postgresql.confcreate an entry for a custom GUC likemy_app.username = 'unknown';. Whenever you create a connection runSET my_app.username = 'the_user';. Then in triggers, use thecurrent_setting('my_app.username')function to obtain the value. Effectively, you’re abusing the GUC machinery to provide session variables. Read the documentation appropriate to your server version, as custom GUCs changed in 9.2.Adjust your application so that it has database roles for every application user.
SET ROLEto that user before doing work. This not only lets you use the built-incurrent_uservariable-like function toSELECT current_user;, it also allows you to enforce security in the database. See this question. You could log in directly as the user instead of usingSET ROLE, but that tends to make connection pooling hard.In both all three cases you’re connection pooling you must be careful to
DISCARD ALL;when you return a connection to the pool. (Though it is not documented as doing so,DISCARD ALLdoes aRESET ROLE).Common setup for demos:
Using a GUC:
CUSTOMIZED OPTIONSsection ofpostgresql.conf, add a line likemyapp.username = 'unknown_user'. On PostgreSQL versions older than 9.2 you also have to setcustom_variable_classes = 'myapp'.SHOW myapp.usernameand get the valueunknown_user.Now you can use
SET myapp.username = 'the_user';when you establish a connection, or alternatelySET LOCAL myapp.username = 'the_user';afterBEGINning a transaction if you want it to be transaction-local, which is convenient for pooled connections.The
get_app_userfunction definition:Demo using
SET LOCALfor transaction-local current username:If you use
SETinstead ofSET LOCALthe setting won’t get reverted at commit/rollback time, so it’s persistent across the session. It is still reset byDISCARD ALL:Also, note that you can’t use
SETorSET LOCALwith server-side bind parameters. If you want to use bind parameters (“prepared statements”), consider using the function formset_config(...). See system adminstration functionsUsing a temporary table
This approach requires the use of a trigger (or helper function called by a trigger, preferably) that tries to read a value from a temporary table every session should have. If the temporary table cannot be found, a default value is supplied. This is likely to be somewhat slow. Test carefully.
The
get_app_user()definition:Demo:
Secure session variables
There’s also a proposal to add “secure session variables” to PostgreSQL. These are a bit like package variables. As of PostgreSQL 12 the feature has not been included, but keep an eye out and speak up on the hackers list if this is something you need.
Advanced: your own extension with shared memory area
For advanced uses you can even have your own C extension register a shared memory area and communicate between backends using C function calls that read/write values in a DSA segment. See the PostgreSQL programming examples for details. You’ll need C knowledge, time, and patience.