Thread: BUG #4620: Unexpected(doc'd) side effects of using serial and rules

BUG #4620: Unexpected(doc'd) side effects of using serial and rules

From
"Simon Keen"
Date:
The following bug has been logged online:

Bug reference:      4620
Logged by:          Simon Keen
Email address:      simon.keen@eglimited.co.uk
PostgreSQL version: 8.3.5
Operating system:   Ubuntu Linux
Description:        Unexpected(doc'd) side effects of using serial and rules
Details:

I have used serial columns as primary keys in some tables.  I have a rule
that on insert to a table inserts in another table.  However, the rule
appears to cause the seq nextval() to be invoked multiple times thus
destroying any value in NEW.serial_col.

Here is an example that causes the issue:

DROP RULE IF EXISTS base_ins ON base;
DROP TABLE IF EXISTS TC;
DROP TABLE IF EXISTS base;

CREATE TABLE base (
        id      serial PRIMARY KEY,
        owner   integer REFERENCES base(id),
        value   varchar(10));

CREATE TABLE TC (
        parent  integer REFERENCES base(id),
        child   integer REFERENCES base(id),
        distance        smallint);

CREATE OR REPLACE RULE base_ins AS ON INSERT TO base DO ALSO (
        INSERT INTO TC (parent, child, distance) VALUES
                (NEW.owner, NEW.id, 1);
        INSERT INTO TC (parent, child, distance)
        SELECT parent, NEW.id, distance+1
        FROM TC
        WHERE child=NEW.owner );

NEW.id has the wrong value in it and appears to have a value 1 higher than
the row inserted in the base table.

To test this theory I replaced the rule with the following:

CREATE OR REPLACE RULE base_ins AS ON INSERT TO base DO ALSO (
SELECT NEW.id);

This reports a value of NEW.id 1 higher than the inserted row.  If I change
the rule to:

CREATE OR REPLACE RULE base_ins AS ON INSERT TO base DO ALSO (
SELECT NEW.id, NEW.id);

I get 2 different values.  If the value of id inserted in the base table is
1 the values reported are 2 and 3.

I would view this as a bug in the rule re-write system as it is incorrectly
invoking a function.  However, it shoudl at least have a warning in the
documentation for SERIAL about this.  It makes the use of functions as
defaults dangerous when rules are used.

Re: BUG #4620: Unexpected(doc'd) side effects of using serial and rules

From
Tom Lane
Date:
"Simon Keen" <simon.keen@eglimited.co.uk> writes:
> I have used serial columns as primary keys in some tables.  I have a rule
> that on insert to a table inserts in another table.  However, the rule
> appears to cause the seq nextval() to be invoked multiple times thus
> destroying any value in NEW.serial_col.

This is a well-known issue arising from the fact that a rule is really a
macro: multiple references to any expression in the original statement
result in multiple evaluations of that expression.

By and large, the best way to propagate inserted information to other
tables is with a trigger.

            regards, tom lane

Re: BUG #4620: Unexpected(doc'd) side effects of using serial and rules

From
"Simon Keen"
Date:
Many thanks. I guessed as much.  It would be useful to add something to the
documentation to explain that functions used as defaults are re-written into
the sql and the implications of this if used with rules.

I did re-write it with a trigger but because statement level triggers do not
have access to NEW and OLD it means row level triggers and the performance
issues they bring with changes in large numbers of rows.

Cheers

Simon Keen

-----Original Message-----
From: Tom Lane [mailto:tgl@sss.pgh.pa.us]
Sent: 18 January 2009 16:35
To: Simon Keen
Cc: pgsql-bugs@postgresql.org
Subject: Re: [BUGS] BUG #4620: Unexpected(doc'd) side effects of using
serial and rules

"Simon Keen" <simon.keen@eglimited.co.uk> writes:
> I have used serial columns as primary keys in some tables.  I have a
> rule that on insert to a table inserts in another table.  However, the
> rule appears to cause the seq nextval() to be invoked multiple times
> thus destroying any value in NEW.serial_col.

This is a well-known issue arising from the fact that a rule is really a
macro: multiple references to any expression in the original statement
result in multiple evaluations of that expression.

By and large, the best way to propagate inserted information to other tables
is with a trigger.

            regards, tom lane