Edgewall Software
Modify

Ticket #8575 (new defect)

Opened 12 months ago

Last modified 4 weeks ago

IntegrityError: duplicate key violates unique constraint

Reported by: shanec@… Owned by: rblank
Priority: normal Milestone: next-minor-0.12.x
Component: database backend Version: none
Severity: normal Keywords:
Cc: shanec@…, dfraser

Description

This is not a duplicate of #7007 or #3563. It may however, be a part of the cause of the many bugs posted with this title.

This may be a postgres specific issue, we have not tested with sqlite or mysql.

There are kind of two issues here depending on the table structure. One is for tables that use serialized (auto-increment) id columns, the other is for tables like the session table which has a primary key which is not auto incremented. I believe the second is not an issue rlated to this bug, but other regular bugs such as what was fixed in #3563. So far, I've only identified this in tables with primary keys on auto increment fields.

This is caused (or most easily reproduced) by how upgrades to database tables happens in trac+plugins. The problem occurs when a plugin changes a table schema. The typical formula is to move the current table to a temp table, create the new table, and then copy the data from the temp to the new table. If the table has primary keys that are sequences, and those keys are inserted into the new table, the problem will occur. Here is any easy (directly in sql) reproduction:

First, create the table as a plugin would initially do it:

set search_path to public;
CREATE TABLE test_keys
(
  id serial NOT NULL,
  foo text,
  CONSTRAINT id_pk PRIMARY KEY (id)
);

Next, add data:

set search_path to public;
INSERT INTO test_keys (foo) VALUES('test');

Now, later the plugin needs an upgrade, so we recreate that:

set search_path to public;
CREATE TEMPORARY TABLE test_keys_old AS SELECT * FROM test_keys;
DROP TABLE test_keys;
CREATE TABLE test_keys
(
  id serial NOT NULL,
  foo text,
  bar text,
  CONSTRAINT id_pk PRIMARY KEY (id)
);
INSERT INTO test_keys (id, foo) SELECT id, foo FROM test_keys_old;
DROP TABLE test_keys_old;

Alls good, right? Wrong.

set search_path to public;
INSERT INTO test_keys (foo, bar) VALUES('test2', 'this stinks');

You now get the error:

ERROR: duplicate key value violates unique constraint "id_pk"

And here's the fix:

SELECT setval('test_keys_id_seq', (SELECT MAX(id) FROM test_keys)+1)

What happened? During the upgrade we copied the rows from the temp table to the new table, including the ID column. Since we inserted the id, the new sequence (test_keys_id_seq) is not updated correctly. Any time you manually add a value to an auto sequenced column, you must manually update the sequence value. We're unaware of any postgres setting that would "fix" this on the postgres side.

Attachments

update_sequence.patch Download (2.4 KB) - added by bobbysmith007@… 4 weeks ago.
A patch to add update_sequence to the database backends

Change History

comment:1 Changed 12 months ago by Shane Caraveo <shanec@…>

I would like to propose the following as an idea for addressing this. We add a update_sequence method to the db backend classes, which is much like the get_last_id method. The plugins would be responsible for calling it, but at least there would be a db independent method for it.

For postgres:

def update_sequence(self, cursor, table, column='id'):
    sql = "SELECT setval('%s_%s_seq', (SELECT MAX(id) FROM %s)+1)" % (table, column, table)
    cursor.execute(sql)

comment:2 Changed 12 months ago by Shane Caraveo <shanec@…>

  • Cc shanec@… added

comment:3 Changed 11 months ago by dfraser

  • Cc dfraser added

comment:4 Changed 9 months ago by cboos

  • Milestone set to 0.12

See also #6466.

comment:5 Changed 8 months ago by rblank

  • Owner set to rblank

Even better would be to copy the old value of the sequence, instead of using MAX(id) + 1 (which should actually be MAX(id), as the sequence will be incremented before returning the next value).

comment:6 Changed 7 months ago by rblank

  • Milestone changed from 0.12 to next-minor-0.12.x

We don't have any DB upgrades to auto-increment tables in 0.12 or multirepos, so this is not absolutely necessary for 0.12. I'd still like to fix this in the 0.12.x cycle.

comment:7 Changed 5 months ago by Carsten Klein <carsten.klein@…>

I think that when doing a db schema upgrade you should actually ALTER instead of DROP a table.

comment:8 Changed 5 months ago by rblank

ALTER isn't well supported on SQLite, unfortunately.

Changed 4 weeks ago by bobbysmith007@…

A patch to add update_sequence to the database backends

comment:9 Changed 4 weeks ago by bobbysmith007@…

I am attaching a patch that operates a proposed. I tested against sqlite and postgresql (which are really the only two code paths) manually. I also added a unit test and tried to run it. I don't think I got an error, but I am not really sure how to interpret the results.

Either way I hope this helps, and if you need me to do anything else to help this get accepted, I can try to assist.

Russ Tyndall

comment:10 Changed 4 weeks ago by bobbysmith007@…

Also,  this patch was suggested by CBoos.

I dont want this comment to get tagged as spam, which it is, so I am hoping that adding some more content will help ease it through. It would be nice to allow a spam exception for linking to the mailing lists.

Please go through with this extra bit, Russ Tyndall

View

Add a comment

Modify Ticket

Change Properties
<Author field>
Action
as new
as The resolution will be set. Next status will be 'closed'
to The owner will be changed from rblank. Next status will be 'new'
The owner will be changed from rblank to anonymous. Next status will be 'assigned'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.