>From: Hannu Krosing [mailto:[EMAIL PROTECTED]
>Sent: dinsdag 27 maart 2007 15:45
>To: Joris Dobbelsteen
>Subject: Re: [HACKERS] Guarenteeing complex referencial
>integrity throughcustom triggers
>Ühel kenal päeval, E, 2007-03-26 kell 16:05, kirjutas Joris
>> Oracle has choosen to allow constraint enforcement by locking on the
>> parent tuple. In contrast postgres has chosen (historically, see RI
>> triggers) to fail on detecting conflicting newly inserted rows (the
>Could you give an example, where postgresql fails to detect
>conflicting newly inserted rows ?
I'm re-using Greg Sabino Mullane example, so here goes the first part
- -- Generic type of printer
CREATE TABLE printer (
id SERIAL NOT NULL PRIMARY KEY,
brand TEXT NOT NULL,
model TEXT NOT NULL
INSERT INTO printer (brand,model) VALUES ('epson','1200');
INSERT INTO printer (brand,model) VALUES ('hp','laserjet99');
- -- A printer can hold one or more cartridges, distinguished by 'color'
CREATE TABLE cartridge_types (
ptype INT NOT NULL,
CONSTRAINT cc_ptype FOREIGN KEY (ptype) REFERENCES printer(id) ON DELETE
color TEXT NOT NULL
CREATE UNIQUE INDEX ctype ON cartridge_types(ptype,color);
INSERT INTO cartridge_types VALUES (1,'black');
INSERT INTO cartridge_types VALUES (2,'black');
INSERT INTO cartridge_types VALUES (2,'color');
- -- Specific printers in the company
CREATE TABLE printers (
id SERIAL NOT NULL PRIMARY KEY,
ptype INTEGER NOT NULL,
CONSTRAINT ptype FOREIGN KEY (ptype) REFERENCES printer(id),
location TEXT NOT NULL
INSERT INTO printers(ptype,location) VALUES (1,'Room 234');
INSERT INTO printers(ptype,location) VALUES (2,'Break room #2');
INSERT INTO printers(ptype,location) VALUES (2,'NE corner of warehouse');
- -- Printers require lots of pesky hardware updates
CREATE TABLE cartridge_change (
printer_id INT NOT NULL,
CONSTRAINT change_printer FOREIGN KEY (printer_id) REFERENCES printers(id),
color TEXT NOT NULL,
whenchanged TIMESTAMPTZ NOT NULL DEFAULT now() );
ALTER TABLE cartridge_change ADD CONSTRAINT cc
FOREIGN KEY (printer_id, color)
REFERENCES cartridge_types (ptype,color);
INSERT INTO cartridge_change (printer_id, color) VALUES (1,'black');
As a slight note: I'm not going to write out all the triggers that should
normally be needed on the cartidge_change, printers, printer and cartridge_type
tables to normally enforce the following constraint:
FOR ALL t IN cartridge_change
THERE MUST EXIST (SELECT 1
INNER JOIN cartridge_types ON cartridge_types.ptype =
WHERE printers.id = cartridge_change.printer_id
AND cartridge_types.color = cartridge_change.color
Obviously we follow good practices:
Before inserting a row into cartridge_change, we should check the
cartridge_types table and lock the row.
After deleting a row in cartridge_types, we should check the cartridge_change
table for constraint violation.
We will first lock the parent and then the childs. This gives consistency and
reduces the chance of deadlocks.
As you should note, this cannot be done with a referencial constraint (unless
you are really willing to materialize a quite large view "SELECT p.id, c.color
FROM printers p INNER JOIN cartridge_types c ON p.ptype = c.ptype"). In these
cases I'm even not sure that the constraint can be enforced (need to think this
For the the actions:
We will happen to do the following at the same time: Change the color toner on
printer #2 (break room), and remove the definition of the "color" toner from
the "hp laserjet99". Trigger actions are in implicit...
The T1 and T2 prefixes will denote the executing transactions. Statements might
wait for locks, in which case you should continue with the next ones (unless
its for the same transaction, in which case I did it wrong).
T1: BEGIN ISOLATION LEVEL SERIALIZABLE;
T2: BEGIN ISOLATION LEVEL SERIALIZABLE;
-- T1 will insert into cartridge_change, but first the trigger
-- it will lock the parent (it should return exactly a single row).
T1: SELECT 1 FROM printers p INNER JOIN cartridge_change c ON p.ptype = c.ptype
WHERE p.printer_id = 2 AND c.color = 'color' FOR SHARE;
-- Now the actual function
T1: INSERT INTO cartridge_change (printer_id, color) VALUES (2,'color');
-- T2 will now delete the 'color' cartridge definition for hp laserjet99.
-- Trigger doing a check, that dependencies do not exists...
-- First delete statement will wait for the lock of T1...
T2: DELETE FROM cartridge_change WHERE ptype=2 AND color='color';
-- T1 commits in the meanwhile
-- T2 is now out of the lock, so execute trigger
T2: SELECT 1
FROM cartridge_change c
INNER JOIN printers p ON p.id = c.printer_id
WHERE c.color = 'color'
AND p.ptype = 2; -- Could also be delete...
-- Please notice we didn't find any rows.
-- It works under read commited nevertheless...
At this point my constraint is violated...
Another go would be to instead of the SELECT FOR SHARE we had executed an
UPDATE, but this is really ugly, hidious, counter-intuitive and concurrency
---------------------------(end of broadcast)---------------------------
TIP 7: You can help support the PostgreSQL project by donating at