"Gustavo de Sá Carvalho Honorato" <gustavohonorato wrote in post 
#968351:
> On Tue, Dec 14, 2010 at 2:39 PM, Marnen Laibow-Koser
> <[email protected]> wrote:
>>
>> "Gustavo de S Carvalho Honorato" <gustavohonorato wrote in post
>> #968329:
>> > Hi,
>> >
>> > after changing a primary key column name, the auto-increment information
>> > (MySQL) and sequence (Oracle) are lost.
>>
>> So what? The actual value of the key should never be significant
>> anyway.
>
> Sorry Marnen, I think I didn't make myself clear, let me show one
> example which ilustrates betteer what I'm trying to say. Consider this
> user table, in MySQL database:
>
> +----------+--------------+------+-----+---------+----------------+
> | Field    | Type         | Null | Key | Default | Extra          |
> +----------+--------------+------+-----+---------+----------------+
> | id       | int(11)      | NO   | PRI | NULL    | auto_increment |
> | username | varchar(255) | YES  |     | NULL    |                |
> | password | varchar(255) | YES  |     | NULL    |                |
> +----------+--------------+------+-----+---------+----------------+
>
> Look that id column has auto_increment extra.
>
> When I rename :id column using "rename_column :users, :id, :key" my
> new :key column loses its "auto_increment" as bellow:
>
> +----------+--------------+------+-----+---------+-------+
> | Field    | Type         | Null | Key | Default | Extra |
> +----------+--------------+------+-----+---------+-------+
> | key      | int(11)      | NO   | PRI | 0       |       |
> | username | varchar(255) | YES  |     | NULL    |       |
> | password | varchar(255) | YES  |     | NULL    |       |
> +----------+--------------+------+-----+---------+-------+

I did not get this to work with the default rename_column function
(I believe there is a bug, more details below).

So at first, I resorted to this (based on the standard mysql
documentation for ALTER TABLE/CREATE TABLE):

class RenamePrimaryKeyPayments < ActiveRecord::Migration
  def self.up
    connection.execute("ALTER TABLE payments CHANGE id `key` INTEGER NOT 
NULL AUTO_INCREMENT;")
  end

  def self.down
    connection.execute("ALTER TABLE payments CHANGE `key` id INTEGER NOT 
NULL AUTO_INCREMENT;")
  end
end


Note: be careful with direct SQL here, you cannot access the `key` 
column
without the backticks, maybe a reserved word in SQL ...

mysql> describe payments;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| key        | int(11)      | NO   | PRI | NULL    | auto_increment |
| user_id    | int(11)      | NO   |     | NULL    |                |
| testing    | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

mysql> select `key`, testing from payments;
+-----+---------+
| key | testing |
+-----+---------+
|   1 | alfa    |
|   2 | beta    |
|   3 | gamma   |
|   4 | delta   |
+-----+---------+
4 rows in set (0.00 sec)

mysql> select id, testing from payments;
+----+---------+
| id | testing |
+----+---------+
|  1 | alfa    |
|  2 | beta    |
|  3 | gamma   |
|  4 | delta   |
+----+---------+
4 rows in set (0.00 sec)

mysql> describe payments;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| user_id    | int(11)      | NO   |     | NULL    |                |
| testing    | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

mysql>

The good news is that the AUOT_INCREMENT automatically does the right
thing and starts inserting new records with the primary key 1 higher 
than
the existing records. I was concerned how this works out for records
for which a transaction was started, but was rolled back ... or if the 
highest record was deleted in the meanwhile).

mysql> DELETE from payments WHERE id = 4;
Query OK, 1 row affected (0.00 sec)

mysql> select id, testing from payments;
+----+---------+
| id | testing |
+----+---------+
|  1 | alfa    |
|  2 | beta    |
|  3 | gamma   |
+----+---------+
3 rows in set (0.00 sec)

mysql> INSERT INTO payments (testing) VALUES ('zeta') ;
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select id, testing from payments;
+----+---------+
| id | testing |
+----+---------+
|  1 | alfa    |
|  2 | beta    |
|  3 | gamma   |
|  5 | zeta    |  # GOOD ! it remembers about the 4 that is consumed
+----+---------+
4 rows in set (0.00 sec)

mysql> DELETE from payments WHERE id = 5;
Query OK, 1 row affected (0.00 sec)

# migrating ...

mysql> select key, testing from payments;
ERROR 1064 (42000): You have an error in your SQL syntax; check the 
manual that corresponds to your MySQL server version for the right 
syntax to use near 'key, testing from payments' at line 1
mysql> select `key`, testing from payments;
+-----+---------+
| key | testing |
+-----+---------+
|   1 | alfa    |
|   2 | beta    |
|   3 | gamma   |
|   6 | peta    |  # GOOD !! it still remembers the 4 and 5 consumed
+-----+---------+
4 rows in set (0.00 sec)

mysql> DELETE FROM payments WHERE `key` = 6;  # deleting the 6 here
Query OK, 1 row affected (0.00 sec)

Changing the migration to use "rename_column" first and re-add the 
AUTO_INCREMENT afterwards:

  def self.down
    rename_column :payments, :key, :id
    connection.execute("ALTER TABLE payments CHANGE id id INTEGER NOT 
NULL AUTO_INCREMENT;")
  end

which does:
...
  SQL (0.1ms)  SELECT `schema_migrations`.`version` FROM 
`schema_migrations`
  SQL (0.3ms)  SHOW COLUMNS FROM `payments` LIKE 'key'
  SQL (8.3ms)  ALTER TABLE `payments` CHANGE `key` `id` int(11) NOT NULL
  SQL (5.2ms)  ALTER TABLE payments CHANGE id id INTEGER NOT NULL 
AUTO_INCREMENT;
  AREL (0.5ms)  DELETE FROM `schema_migrations` WHERE 
`schema_migrations`.`version` = '20101217234428'
...

and then you get:

mysql> select id, testing FROM payments ;
+----+---------+
| id | testing |
+----+---------+
|  1 | alfa    |
|  2 | beta    |
|  3 | gamma   |
+----+---------+
3 rows in set (0.00 sec)

mysql> INSERT INTO payments (testing) VALUES ('peta');
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select id, testing FROM payments ;
+----+---------+
| id | testing |
+----+---------+
|  1 | alfa    |
|  2 | beta    |
|  3 | gamma   |
|  4 | peta    | # BAD, we forgot the consumed id's 5,6,7
+----+---------+
4 rows in set (0.00 sec)


So, I believe the "direct" migration from the 1 column name to the new 
column name, while maintaining the AUTO_INCREMENT is required.

Is it possible to add this as an extra option in the Rails migration ?
I did not immediately find on the api.rubyonrails.org site how you could 
give an additional option (as a 4th argument then), to rename_column:
rename_column(table_name, column_name, new_column_name).

Digging deeper ... this could be a bug in Rails 3.0.3 ....

This code in lib/active_record/connection_adapters around line 499,

current_type = select_one("SHOW COLUMNS FROM 
#{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]

rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE 
#{quote_column_name(column_name)} #{quote_column_name(new_column_name)} 
#{current_type}"

Only takes the "type" into account, but ... when inspecting what comes 
back from the database, there is also "extra" info:

I instrumented:
select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE 
'#{column_name}'").inspect
and got:

{"Default"=>nil, "Extra"=>"auto_increment", "Field"=>"key", 
"Key"=>"PRI", "Null"=>"NO", "Type"=>"int(11)"}

And I have the impression the "Extra" field is not used, but is required 
by mysql to keep the auto_increment active on that column.

So, with this changed version, the behaviour is actually better, with a 
standard "rename_column" and not requiring raw SQL in the migration.


pet...@asus:~/g/activerecord-3.0.3/lib/active_record/connection_adapters$ 
diff -u mysql_adapter.rb.ORIG mysql_adapter.rb
--- mysql_adapter.rb.ORIG  2010-12-18 02:23:26.000000000 +0100
+++ mysql_adapter.rb  2010-12-18 02:22:47.000000000 +0100
@@ -496,7 +496,9 @@
         else
           raise ActiveRecordError, "No such column: 
#{table_name}.#{column_name}"
         end
-        current_type = select_one("SHOW COLUMNS FROM 
#{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+        column_info = select_one("SHOW COLUMNS FROM 
#{quote_table_name(table_name)} LIKE '#{column_name}'")
+        current_type = column_info["Type"]
+        current_type << " AUTO_INCREMENT" if column_info["Extra"] =~ 
/auto_increment/i
         rename_column_sql = "ALTER TABLE 
#{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} 
#{quote_column_name(new_column_name)} #{current_type}"
         add_column_options!(rename_column_sql, options)
         execute(rename_column_sql)

The migration code (also testing for a column that is not 
AUTO_INCREMENT):

class RenamePrimaryKeyPayments < ActiveRecord::Migration
  def self.up
    rename_column :payments, :id, :key
    rename_column :payments, :testing, :greek
  end

  def self.down
    rename_column :payments, :key, :id
    rename_column :payments, :greek, :testing
  end
end


Up migration log:

  SQL (0.1ms)  SELECT `schema_migrations`.`version` FROM 
`schema_migrations`
  SQL (0.4ms)  SHOW COLUMNS FROM `payments` LIKE 'id'
  SQL (2.9ms)  ALTER TABLE `payments` CHANGE `id` `key` int(11) 
AUTO_INCREMENT NOT NULL
  SQL (0.5ms)  SHOW COLUMNS FROM `payments` LIKE 'testing'
  SQL (3.4ms)  ALTER TABLE `payments` CHANGE `testing` `greek` 
varchar(255) DEFAULT NULL
  SQL (1.2ms)  INSERT INTO `schema_migrations` (`version`) VALUES 
('20101217234428')

Down migration:

  SQL (0.1ms)  SELECT `schema_migrations`.`version` FROM 
`schema_migrations`
  SQL (0.3ms)  SHOW COLUMNS FROM `payments` LIKE 'key'
  SQL (3.1ms)  ALTER TABLE `payments` CHANGE `key` `id` int(11) 
AUTO_INCREMENT NOT NULL
  SQL (0.4ms)  SHOW COLUMNS FROM `payments` LIKE 'greek'
  SQL (2.6ms)  ALTER TABLE `payments` CHANGE `greek` `testing` 
varchar(255) DEFAULT NULL
  AREL (1.5ms)  DELETE FROM `schema_migrations` WHERE 
`schema_migrations`.`version` = '20101217234428'

If this is relevant, I could try to file bug ticket against Rails 3.0.3.
(and try to add tests).

HTH,

Peter

-- 
Posted via http://www.ruby-forum.com/.

-- 
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en.

Reply via email to