Boško Ivanišević
Developer, Theoretical Physicist
Ruby, JavaScript, Elixir, C++ and few more

ActiveRecord SchemaDumper and MySQL problem

2010-10-03

After finishing first version of Rmre and issuing fix gem dependency in version 0.0.2, I got an idea for additional functionality. Why not use Rmre for dumping complete schema with all foreign keys data? What would be possible scenario for using this, one might ask? We have possibility to create ActiveRecord models in order to move to Ruby on Rails where main premise is to keep logic out of database and maintain it in application. Therefore we do not need foreign keys since we already have constraints defined in models.

But what if you cannot move to Ruby on Rails and you only have to change DBE, i.e. instead of MS SQL you must use Oracle? In that case you still have to work with legacy database from PHP or Hibernate in Java and “*only*” thing you have to do is to make create script for all tables but for another DBE. When database has hundreds of tables with lots of relations this can turn into nightmare, especially if you have to maintain both versions.

Rmre should simplify this. First you use Rmre to dump schema to some file and later you can use ActiveRecord’s capabilities to load it on different DBE. Since loading schema in ActiveRecord is DBE agnostic it should correctly create tables, indices and foreign keys on any database engine. That’s theory and, as usual, practice is a little bit different. On a very first step I’ve faced problem in MySQL database.

Let’s examine database with just a two tables - city and country. Create script would look like (example from Sakila database):

CREATE TABLE city (
  city_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
  city VARCHAR(50) NOT NULL,
  country_id SMALLINT UNSIGNED NOT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT
    CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (city_id),
  KEY idx_fk_country_id (country_id),
  CONSTRAINT `fk_city_country`
    FOREIGN KEY (country_id)
    REFERENCES country (country_id)
    ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE country (
  country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
  country VARCHAR(50) NOT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (country_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

As can be seen from above script table city has foreign key on table country. Now let’s see what is result of a dump:

ActiveRecord::Schema.define(:version => 0) do

  create_table "actor", :primary_key => "actor_id", :force => true do |t|
  create_table "city", :primary_key => "city_id", :force => true do |t|
    t.string    "city",        :limit => 50, :null => false
    t.integer   "country_id",  :limit => 2,  :null => false
    t.timestamp "last_update",               :null => false
  end

  add_index "city", ["country_id"], :name => "idx_fk_country_id"

  create_table "country", :primary_key => "country_id", :force => true do |t|
    t.string    "country",     :limit => 50, :null => false
    t.timestamp "last_update",               :null => false
  end

  execute "ALTER TABLE city ADD CONSTRAINT fk_city_country FOREIGN KEY (country_id) REFERENCES country(country_id)"
end

At the first glance this looks good but unfortunately doesn’t work. Problem is that loading this schema through ActiveRecord will create columns city_id in table city and country_id in table country as integer type but column country_id in table city is created as smallint. Defining constraint on columns which are not of same type is not allowed so last statement for altering table fails. At the moment I have no idea how to fix this and any suggestion is very welcome. I still have to check what happens on other DBEs: PostgreSQL, Oracle and MS SQL.