Suppose you have a type 'serial' field in table, it is part of the primary key, there are no other keys/indexes on the table, and you want to change the serial field to be an integer field. The standard approach would be: db_drop_primary_key(), db_change_field(), db_add_primary_key(), like we do in many other places. The primary key needs to be dropped and re-created surrounding the db_change_field() because, on pgsql but not mysql, changing a field also drops all keys/indexes using the field; the drop and add are necessary to maintain a single code path for both databases.
Unfortunately, in this specific case, the standard approach won't work on mysql because we CANNOT drop the primary key! On mysql, auto-increment fields must be included in at least one key or index. In this table the primary key is the only index the serial column is involved in, so we can't drop it until after we call db_change_field(). But on pgsql, after we call db_change_field() is the pkey is already dropped so calling db_drop_primary_key() again will result in an error.
There are two solutions, and both suck:
1. Don't call db_drop_primary_key() at all. After db_change_field(), only call db_add_primary_key() if $db_type == 'pgsql'. This sucks because it puts a $db_type dependency back into the code.
2. Create a temporary index on the column so we can drop the pkey:
db_add_index('my_table', 'id', array('id'))
db_drop_primary_key()
db_change_field()
db_add_primary_key()
db_drop_index('my_table', 'id')
This sucks because it is extra (possibly time-consuming on a large site) database work to accomplish a goal that can be done more simply on either db individually. And, while it does not reference $db_type explicitly, the fact is that it is written specifically to accommodate one particular db (mysql).
This issue arose in http://drupal.org/node/124979. In that issue, I suggested the first solution which works, but it is not a decent long-term solution.
Comments
Comment #2
Island Usurper commentedI think the same problem comes up whenever you do anything to a serial column, even if it's still serial after db_change_field().
Maybe instead of calling db_drop_primary_key() beforehand, we should let db_change_field() decide whether it needs to use $new_keys. The MySQL version of db_change_field() can check the schema for the primary key in $new_keys and does nothing if it finds it. The Postgres version would just always add the key.
A potential problem is that you might have to inspect the database directly for the primary key instead of the schema definitions, and I don't know how much that would affect performance. Database updates shouldn't happen too often, though, so it's probably not a big deal.
Comment #3
beginner commentedI just encountered this very situation in one module.
Here is my update code:
For the sake of the search engines, here is the error message I got when updating:
The first solution offered above didn't work, because the field I am trying to update is the primary key itself!
In my code above, I removed the first line, with db_drop_primary_key().
I got this error:
Comment #4
beginner commentedAs advised in the second solution, this works:
Comment #5
Crell commentedThis should perhaps be rolled into the API, so that when changing a serial field the system itself does the disable-change-enable logic for us. Anyone want to make a patch? :-)
Comment #7
hass commentedGot the same issue, when I'd like to change a serial to unsigned.
Comment #8
Crell commentedIs anyone going to pick this up? Otherwise my inclination is to leave it until D8 when we need to refactor schema API anyway to allow multiple changes in one query, and this problem goes away. #432440: Update Schema API to make it more usable
Comment #9
joachim commented> so that when changing a serial field the system itself does the disable-change-enable logic for us
Wouldn't that become a bit of a DX WTF, if *some* types of field handle that logic, while others require it to be done manually?
Comment #10
joachim commentedConfirming the fix in #4 works on D7.
Moving this to documentation so we can at least document the workaround for the time being.
Here's my code that's working for flag module:
Comment #11
jhodgdonWhere are you proposing we document this, and what are you proposing should be added to that documentation? I'm just wondering if this is enough of a common case to warrant putting it into the db_change_field() docs, or whether (since it involves a bunch of functions) it would be better to put it somewhere in the on-line docs instead?
Comment #12
joachim commentedI would say it needs to be put in db_change_field(), if only as a link to a more detailed explanation. I for one lost a fair bit of time trying to figure out why changing my field was going wrong despite following all the instructions.
Comment #13
jhodgdonOK. Let's put a brief note in db_change_field() then, and a more detailed explanation can go on ... maybe here?
http://drupal.org/node/150215
There actually isn't anything on that page about updating fields at all, and it seems like there should be?
Comment #14
hswong3i commentedI will try to roll a patch for both D7/8 later for MySQL, so handle these logic in db_change_field() internally ;-)
Comment #15
hefox commented(The technique with the indexes fixed this error for file entity update #2065213: DB update 7215 fails on upgrade to dev version from alpha)
Comment #16
kenorb commentedComment #17
RunePhilosof commentedI just ran into this issue when upgrading Flag Page to support Flag 3.x.
A serial column `content_id` needs to be renamed to `entity_id`.
It would be nice if at least an example of how to rename a serial column is added to the documentation of db_change_field.
Comment #18
amit0212 commented<?php
db_add_field('spaces_overrides', 'sid', array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
db_drop_primary_key('spaces_overrides');
db_change_field('spaces_overrides', 'sid', 'sid', array(
'type' => 'serial',
'not null' => TRUE,
'unsigned' => TRUE,
'description' => 'Holds the identifier of the override.',
),
array('primary key' => array('sid'))
);
This will add identifier key to existing schema.