Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abort migration if a given table refer by other tables using foreign key constraints #147

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/lhm/migrator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion lib/lhm/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@

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
@columns = {}
@indices = {}
@pk = pk
@ddl = ddl
@references = []
end

def satisfies_id_column_requirement?
!!((id = columns['id']) &&
id[:type] =~ /(bigint|int)\(\d+\)/)
end

def satisfies_references_column_requirement?
!references.any?
end

def destination_name
"lhmn_#{ @name }"
end
Expand Down Expand Up @@ -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

Expand All @@ -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|
Expand All @@ -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
6 changes: 6 additions & 0 deletions spec/fixtures/fk_child_table.ddl
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions spec/fixtures/origin_example.ddl
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions spec/integration/references_spec.rb
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions spec/unit/table_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)' } })
Expand All @@ -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