diff --git a/lib/lhm/migrator.rb b/lib/lhm/migrator.rb index b0ee6608..4d6ccf16 100644 --- a/lib/lhm/migrator.rb +++ b/lib/lhm/migrator.rb @@ -187,6 +187,11 @@ def validate error("could not find origin table #{ @origin.name }") end + unless @origin.satisfies_references_column_requirement? + tables = @origin.references.map{|a| "#{a['table_name']}:#{a['constraint_name']}"}.join(', ') + error("foreign key constraint fails for tables (#{tables}); before running LHM migration you need to drop this foreign keys;") + end + unless @origin.satisfies_id_column_requirement? error('origin does not satisfy `id` key requirements') end diff --git a/lib/lhm/table.rb b/lib/lhm/table.rb index 7ff30fd2..65b9fde9 100644 --- a/lib/lhm/table.rb +++ b/lib/lhm/table.rb @@ -5,7 +5,7 @@ module Lhm class Table - attr_reader :name, :columns, :indices, :pk, :ddl + attr_reader :name, :columns, :indices, :pk, :ddl, :references def initialize(name, pk = 'id', ddl = nil) @name = name @@ -13,6 +13,7 @@ def initialize(name, pk = 'id', ddl = nil) @indices = {} @pk = pk @ddl = ddl + @references = [] end def satisfies_id_column_requirement? @@ -20,6 +21,10 @@ def satisfies_id_column_requirement? id[:type] =~ /(bigint|int)\(\d+\)/) end + def satisfies_references_column_requirement? + !references.any? + end + def destination_name "lhmn_#{ @name }" end @@ -64,6 +69,10 @@ def parse extract_indices(read_indices).each do |idx, columns| table.indices[idx] = columns end + + extract_references(read_references).each do |columns| + table.references << columns + end end end @@ -85,6 +94,16 @@ def read_indices } end + def read_references + @connection.select_all %Q{ + select constraint_name,table_name, table_schema, column_name + from information_schema.key_column_usage + where referenced_table_name = '#{ @table_name }' + and referenced_table_schema = '#{ @schema_name }' + } + end + + def extract_indices(indices) indices. map do |row| @@ -111,6 +130,11 @@ def extract_primary_key(schema) keys.length == 1 ? keys.first : keys end + + def extract_references(references) + references + end + end end end diff --git a/spec/fixtures/fk_child_table.ddl b/spec/fixtures/fk_child_table.ddl new file mode 100644 index 00000000..647a1142 --- /dev/null +++ b/spec/fixtures/fk_child_table.ddl @@ -0,0 +1,6 @@ +CREATE TABLE `fk_child_table` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `origin_table_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_origin_table_id` FOREIGN KEY (`origin_table_id`) REFERENCES `origin_example` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 diff --git a/spec/fixtures/origin_example.ddl b/spec/fixtures/origin_example.ddl new file mode 100644 index 00000000..1fd905be --- /dev/null +++ b/spec/fixtures/origin_example.ddl @@ -0,0 +1,6 @@ +CREATE TABLE `origin_example` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `master_id` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 diff --git a/spec/integration/references_spec.rb b/spec/integration/references_spec.rb new file mode 100644 index 00000000..afc41b18 --- /dev/null +++ b/spec/integration/references_spec.rb @@ -0,0 +1,65 @@ +# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias +# Schmidt + +require File.expand_path(File.dirname(__FILE__)) + '/integration_helper' + +require 'lhm' + +describe Lhm do + include IntegrationHelper + + + describe 'the simplest case' do + before(:each) do + connect_master! + Lhm.cleanup(true) + %w(fk_child_table origin_example).each do |table| + execute "drop table if exists #{table}" + end + %w(origin_example fk_child_table).each do |table| + execute "drop table if exists `fk_child_table`" + table_create(table) + end + end + + after(:each) do + Lhm.cleanup(true) + end + + it 'should show the foreign key constraints for given table' do + actual = table_read(:origin_example).references + expected = [{ + "constraint_name"=> "fk_origin_table_id", + "table_name"=> "fk_child_table", + "table_schema"=> "lhm", + "column_name"=> "origin_table_id" + }] + actual.must_equal(expected) + end + + it 'should raise an exception for foreign key constraint fails for referencing tables' do + exception = assert_raises(Exception) { + Lhm.change_table(:origin_example) do |t| + t.add_column(:new_column, "INT(12) DEFAULT '0'") + end + } + references = table_read(:origin_example).references + tables = references.map{|a| "#{a['table_name']}:#{a['constraint_name']}"}.join(', ') + message = "foreign key constraint fails for tables (#{tables}); before running LHM migration you need to drop this foreign keys;" + assert_equal(message, exception.message ) + end + + it 'should add a column after droping foreign key constraints' do + execute "alter table `fk_child_table` drop foreign key `fk_origin_table_id`" + Lhm.change_table(:origin_example) do |t| + t.add_column(:new_column, "INT(12) DEFAULT '0'") + end + connect_master! + table_read(:origin_example).columns['new_column'].must_equal({ + :type => 'int(12)', + :is_nullable => 'YES', + :column_default => '0', + }) + end + end +end diff --git a/spec/unit/table_spec.rb b/spec/unit/table_spec.rb index c75181f4..ef7777b0 100644 --- a/spec/unit/table_spec.rb +++ b/spec/unit/table_spec.rb @@ -20,6 +20,11 @@ def set_columns(table, columns) table.instance_variable_set('@columns', columns) end + def set_references(table, references) + table.instance_variable_set('@references', references) + end + + it 'should be satisfied with a single column primary key called id' do @table = Lhm::Table.new('table', 'id') set_columns(@table, { 'id' => { :type => 'int(1)' } }) @@ -37,5 +42,18 @@ def set_columns(table, columns) set_columns(@table, { 'id' => { :type => 'varchar(255)' } }) @table.satisfies_id_column_requirement?.must_equal false end + + it 'should be satisfied if origin table not refer by any other table using foreign key' do + @table = Lhm::Table.new('table') + set_references(@table, []) + @table.satisfies_references_column_requirement?.must_equal true + end + + it 'should NOT be satisfied if origin table refer by any other table using foreign key' do + @table = Lhm::Table.new('table', 'id') + set_references(@table, [{"CONSTRAINT_NAME"=>"fk_rails_40ebb3948d", "TABLE_NAME"=>"child_table", "TABLE_SCHEMA"=>"schema", "COLUMN_NAME"=>"table_id"}]) + @table.satisfies_references_column_requirement?.must_equal false + end + end end