dbdiff 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/LICENSE +58 -0
- data/README +29 -0
- data/lib/dbdiff/column.rb +69 -0
- data/lib/dbdiff/database.rb +169 -0
- data/lib/dbdiff/delta.rb +266 -0
- data/lib/dbdiff/foreign_key.rb +68 -0
- data/lib/dbdiff/key.rb +69 -0
- data/lib/dbdiff/row.rb +32 -0
- data/lib/dbdiff/table.rb +50 -0
- data/lib/dbdiff/table_element.rb +16 -0
- data/lib/dbdiff.rb +207 -0
- data/test/ai_column/source.sql +10 -0
- data/test/ai_column/target.sql +7 -0
- data/test/change_pk/source.sql +11 -0
- data/test/change_pk/target.sql +8 -0
- data/test/column/source.sql +10 -0
- data/test/column/target.sql +9 -0
- data/test/fk/source.sql +19 -0
- data/test/fk/target.sql +20 -0
- data/test/key/source.sql +11 -0
- data/test/key/target.sql +10 -0
- data/test/modify_column/source.sql +9 -0
- data/test/modify_column/target.sql +10 -0
- data/test/modify_fk/source.sql +20 -0
- data/test/modify_fk/target.sql +22 -0
- data/test/modify_key_fk/source.sql +19 -0
- data/test/modify_key_fk/target.sql +20 -0
- data/test/modify_key_fk_ref/source.sql +18 -0
- data/test/modify_key_fk_ref/target.sql +20 -0
- data/test/modify_row/source.sql +8 -0
- data/test/modify_row/target.sql +9 -0
- data/test/modify_table/source.sql +8 -0
- data/test/modify_table/target.sql +9 -0
- data/test/multi_fk/source.sql +22 -0
- data/test/multi_fk/target.sql +20 -0
- data/test/multi_key/source.sql +11 -0
- data/test/multi_key/target.sql +10 -0
- data/test/multi_unique_key/source.sql +11 -0
- data/test/multi_unique_key/target.sql +10 -0
- data/test/row/source.sql +8 -0
- data/test/row/target.sql +6 -0
- data/test/suite.rb +7 -0
- data/test/table/source.sql +9 -0
- data/test/table/target.sql +0 -0
- data/test/table_fk/source.sql +13 -0
- data/test/table_fk/target.sql +20 -0
- data/test/table_fk2/source.sql +17 -0
- data/test/table_fk2/target.sql +5 -0
- data/test/test_column.rb +93 -0
- data/test/test_dbdiff.rb +92 -0
- data/test/test_foreign_key.rb +136 -0
- data/test/test_key.rb +116 -0
- data/test/test_row.rb +84 -0
- data/test/test_table.rb +30 -0
- data/test/unique_key/source.sql +11 -0
- data/test/unique_key/target.sql +9 -0
- metadata +127 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
DbDiff is copyrighted free software by Eric Kolve <ekolve@gmail.com>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see COPYING file), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) rename any non-standard executables so the names do not conflict
|
21
|
+
with standard executables, which must also be provided.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or executable
|
26
|
+
form, provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the executables and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard executables non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under this terms.
|
43
|
+
|
44
|
+
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
|
45
|
+
files under the ./missing directory. See each file for the copying
|
46
|
+
condition.
|
47
|
+
|
48
|
+
5. The scripts and library files supplied as input to or produced as
|
49
|
+
output from the software do not automatically fall under the
|
50
|
+
copyright of the software, but belong to whomever generated them,
|
51
|
+
and may be sold commercially, and may be aggregated with this
|
52
|
+
software.
|
53
|
+
|
54
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
55
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
56
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
57
|
+
PURPOSE.
|
58
|
+
|
data/README
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
= DbDiff
|
2
|
+
|
3
|
+
DbDiff allows you to compare two different databases (currently MySQL),
|
4
|
+
determine the differences between the two, and apply the deltas.
|
5
|
+
Currently the following types of deltas are supported: tables,
|
6
|
+
columns, keys, foreign keys and rows.
|
7
|
+
|
8
|
+
Ultimately, I would like to be able to compare two completely different
|
9
|
+
types of DBs (i.e. Oracle <=> MySQL) and bring them up to date as much
|
10
|
+
as possible. Things like stored procs and triggers may be out of the realm
|
11
|
+
of possibility, since they may not directly translate.
|
12
|
+
|
13
|
+
|
14
|
+
== Dependencies
|
15
|
+
|
16
|
+
* ruby 1.8.4
|
17
|
+
* mysql client
|
18
|
+
|
19
|
+
|
20
|
+
== Authors
|
21
|
+
|
22
|
+
Copyright (c) 2006 by Eric Kolve <ekolve@gmail.com>
|
23
|
+
|
24
|
+
|
25
|
+
== License
|
26
|
+
|
27
|
+
This library is distributed under the same terms as Ruby.
|
28
|
+
Please see the LICENSE file.
|
29
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class DbDiff
|
2
|
+
class Column < TableElement
|
3
|
+
attr_reader :data_type, :default, :numeric_precision, :column_type, :length, :not_null
|
4
|
+
attr_accessor :auto_increment
|
5
|
+
|
6
|
+
def initialize(info = {})
|
7
|
+
super
|
8
|
+
|
9
|
+
@name = info['COLUMN_NAME']
|
10
|
+
@data_type = info['DATA_TYPE']
|
11
|
+
@column_type = info['COLUMN_TYPE']
|
12
|
+
@numeric_precision = info['NUMERIC_PRECISION']
|
13
|
+
|
14
|
+
@default = (info['COLUMN_DEFAULT'] == 'NULL' ? nil : info['COLUMN_DEFAULT'])
|
15
|
+
|
16
|
+
# XXX add type and length parsing
|
17
|
+
@not_null = (info['IS_NULLABLE'] == 'NO' ? true : false)
|
18
|
+
@auto_increment = (info['EXTRA'] == 'auto_increment' ? true : false)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def modify_delta(new_element)
|
23
|
+
Delta::ModifyColumn.new(new_element)
|
24
|
+
end
|
25
|
+
|
26
|
+
def drop_delta
|
27
|
+
if self.auto_increment
|
28
|
+
clone = self.deep_clone
|
29
|
+
clone.auto_increment = false
|
30
|
+
|
31
|
+
# we have to change this to auto_increment = false
|
32
|
+
# since its the primary key and we can't drop a primary key without disabling
|
33
|
+
# auto_increment
|
34
|
+
return [Delta::ModifyColumnRemoveAI.new(clone), Delta::DropColumn.new(clone)]
|
35
|
+
else
|
36
|
+
return Delta::DropColumn.new(self)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_delta
|
41
|
+
if self.auto_increment
|
42
|
+
[Delta::AddColumn.new(self), Delta::ModifyColumnAddAI.new(self)]
|
43
|
+
else
|
44
|
+
Delta::AddColumn.new(self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def definition(add_sql = false)
|
50
|
+
sql = "`%s` %s" % [self.name, self.column_type]
|
51
|
+
|
52
|
+
sql += (@not_null ? ' NOT NULL' : ' NULL ')
|
53
|
+
|
54
|
+
sql += (@default ? " default '#{default}' " : '')
|
55
|
+
sql += " auto_increment" if !add_sql && self.auto_increment
|
56
|
+
|
57
|
+
sql
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
attribs = %w(column_type default name not_null auto_increment)
|
62
|
+
|
63
|
+
matches = attribs.find_all{|a| self.send(a.to_sym) == other.send(a.to_sym)}
|
64
|
+
matches.size == attribs.size
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mysql'
|
3
|
+
require 'dbdiff/delta'
|
4
|
+
require 'dbdiff/table_element'
|
5
|
+
require 'dbdiff/table'
|
6
|
+
require 'dbdiff/column'
|
7
|
+
require 'dbdiff/foreign_key'
|
8
|
+
require 'dbdiff/key'
|
9
|
+
require 'dbdiff/row'
|
10
|
+
class DbDiff
|
11
|
+
|
12
|
+
# XXX validate state of information_schema?
|
13
|
+
class Database
|
14
|
+
attr_reader :dbh, :tables, :name, :host, :user, :password
|
15
|
+
attr_accessor :deltas
|
16
|
+
|
17
|
+
# XXX add version check
|
18
|
+
def initialize(params = {})
|
19
|
+
@dbh = Mysql.real_connect(params[:host], params[:user], params[:password], 'information_schema')
|
20
|
+
@name = params[:name]
|
21
|
+
@host = params[:host]
|
22
|
+
@user = params[:user]
|
23
|
+
@password = params[:password]
|
24
|
+
@deltas = []
|
25
|
+
|
26
|
+
initialize_tables
|
27
|
+
initialize_columns
|
28
|
+
initialize_keys
|
29
|
+
initialize_foreign_keys
|
30
|
+
end
|
31
|
+
|
32
|
+
# XXX may be able to get rid of this
|
33
|
+
def all_tables
|
34
|
+
@tables + @deltas.find_all{|d| d.class == Delta::AddTable }.map {|x| x.element}
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize_tables
|
38
|
+
@tables = []
|
39
|
+
select_is(:tables, :table_schema => self.name) do |h|
|
40
|
+
t = Table.new(h)
|
41
|
+
t.charset = charset(t.collation)
|
42
|
+
|
43
|
+
@tables << t
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def charset(collation)
|
48
|
+
hash = select_is(:character_sets, :default_collate_name => collation)
|
49
|
+
return hash['CHARACTER_SET_NAME']
|
50
|
+
end
|
51
|
+
|
52
|
+
def table(name)
|
53
|
+
@tables.find{|t| t.name == name}
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize_columns
|
57
|
+
# puts "table name => #{self.name}"
|
58
|
+
|
59
|
+
select_is(:columns) do |h|
|
60
|
+
c = Column.new(h)
|
61
|
+
table(c.table_name).columns << c
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize_keys
|
66
|
+
|
67
|
+
@tables.each do|t|
|
68
|
+
@dbh.select_db(name)
|
69
|
+
res = @dbh.query("SHOW INDEXES FROM #{t.name}")
|
70
|
+
res.each_hash do |h|
|
71
|
+
k = Key.new(t.name, h)
|
72
|
+
if y = t.keys.find {|z| z.name == k.name}
|
73
|
+
y.merge(k)
|
74
|
+
else
|
75
|
+
t.keys << k
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize_foreign_keys
|
83
|
+
select_is(:key_column_usage) do |h|
|
84
|
+
next unless h['REFERENCED_TABLE_NAME']
|
85
|
+
|
86
|
+
c = ForeignKey.new(h)
|
87
|
+
|
88
|
+
t = table(c.table_name)
|
89
|
+
|
90
|
+
if y = t.foreign_keys.find {|z| z.name == c.name}
|
91
|
+
y.merge(c)
|
92
|
+
else
|
93
|
+
t.foreign_keys << c
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def select_is(table, where = {}, &block)
|
101
|
+
@dbh.select_db('information_schema')
|
102
|
+
|
103
|
+
# XXX
|
104
|
+
where[:table_schema] = self.name unless table == :character_sets
|
105
|
+
|
106
|
+
self._select(table, where, &block)
|
107
|
+
end
|
108
|
+
|
109
|
+
def select(table, where = {}, &block)
|
110
|
+
@dbh.select_db(self.name)
|
111
|
+
self._select(table, where, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
# easy way of selecting
|
115
|
+
def _select(table, where = {}, &block)
|
116
|
+
query = "SELECT * from #{table} "
|
117
|
+
if where.length > 0
|
118
|
+
query += " WHERE "
|
119
|
+
query +=
|
120
|
+
where.map do |k,v|
|
121
|
+
if v
|
122
|
+
"#{k}='" + Mysql.escape_string(v) + "'"
|
123
|
+
else
|
124
|
+
"#{k} IS NULL"
|
125
|
+
end
|
126
|
+
end.join(" AND ")
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
# puts "sending query #{query}"
|
131
|
+
result = @dbh.query(query)
|
132
|
+
if block
|
133
|
+
result.each_hash do |h|
|
134
|
+
yield(h)
|
135
|
+
end
|
136
|
+
else
|
137
|
+
return result.fetch_hash
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def add(element)
|
142
|
+
@deltas += element.add_delta.to_a
|
143
|
+
end
|
144
|
+
|
145
|
+
def modify(element, new_element)
|
146
|
+
@deltas += element.modify_delta(new_element).to_a
|
147
|
+
end
|
148
|
+
|
149
|
+
def drop(element)
|
150
|
+
@deltas += element.drop_delta.to_a
|
151
|
+
end
|
152
|
+
|
153
|
+
def load_rows(t)
|
154
|
+
t.each do |table_name|
|
155
|
+
|
156
|
+
table = table(table_name)
|
157
|
+
|
158
|
+
if table
|
159
|
+
select(table_name) do |h|
|
160
|
+
table.rows << Row.new(table, h)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
data/lib/dbdiff/delta.rb
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
class DbDiff
|
2
|
+
class Delta
|
3
|
+
attr_reader :element
|
4
|
+
|
5
|
+
def initialize(element)
|
6
|
+
@element = element.deep_clone
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_a
|
10
|
+
[self]
|
11
|
+
end
|
12
|
+
|
13
|
+
def table(database)
|
14
|
+
database.table(element.table_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
class DropColumn < Delta
|
19
|
+
|
20
|
+
|
21
|
+
def sql
|
22
|
+
"ALTER TABLE #{element.table_name} DROP COLUMN `#{element.name}`"
|
23
|
+
end
|
24
|
+
|
25
|
+
def process(database)
|
26
|
+
table = table(database)
|
27
|
+
table.columns.delete(element)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class ModifyColumn < Delta
|
34
|
+
|
35
|
+
def sql
|
36
|
+
"ALTER table #{element.table_name} MODIFY COLUMN " + element.definition
|
37
|
+
end
|
38
|
+
|
39
|
+
def process(database)
|
40
|
+
table = table(database)
|
41
|
+
old_column = table.columns.find{|c| c.name == element.name}
|
42
|
+
table.columns.delete(old_column)
|
43
|
+
table.columns << element
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ModifyColumnRemoveAI < ModifyColumn
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
class ModifyColumnAddAI < ModifyColumn
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class AddColumn < Delta
|
56
|
+
|
57
|
+
def initialize(element)
|
58
|
+
super
|
59
|
+
@element.auto_increment = false
|
60
|
+
end
|
61
|
+
|
62
|
+
def sql
|
63
|
+
"ALTER TABLE #{element.table_name} ADD COLUMN " + element.definition(true)
|
64
|
+
end
|
65
|
+
|
66
|
+
def process(database)
|
67
|
+
table = table(database)
|
68
|
+
table.columns << element
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
class ModifyRow < Delta
|
74
|
+
|
75
|
+
def sql
|
76
|
+
data = element.data
|
77
|
+
where = element.primary_key.map {|k| "#{k}=" + Mysql.escape_string(data[k]) }.join(" AND ")
|
78
|
+
sql = "UPDATE #{element.table_name} SET "
|
79
|
+
|
80
|
+
updates = data.keys.map do |c|
|
81
|
+
if data[c]
|
82
|
+
" #{c}='" + Mysql.escape_string(data[c]) + "'"
|
83
|
+
else
|
84
|
+
" #{c}= NULL"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
sql += updates.join(",") + " WHERE #{where}"
|
89
|
+
|
90
|
+
return sql
|
91
|
+
end
|
92
|
+
|
93
|
+
def process(database)
|
94
|
+
table = table(database)
|
95
|
+
old_row = table.rows.find{|r| r.name == element.name}
|
96
|
+
table.rows.delete(old_row)
|
97
|
+
table.rows << element
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class AddRow < Delta
|
103
|
+
|
104
|
+
def sql
|
105
|
+
sql = "INSERT INTO #{element.table_name}"
|
106
|
+
data = element.data
|
107
|
+
columns = data.keys
|
108
|
+
|
109
|
+
sql += " (" + columns.join(",") + ")"
|
110
|
+
|
111
|
+
values = columns.map do |c|
|
112
|
+
if data[c]
|
113
|
+
"'" + Mysql.escape_string(data[c]) + "'"
|
114
|
+
else
|
115
|
+
"NULL"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
sql += " VALUES (" + values.join(",") + ")"
|
120
|
+
|
121
|
+
return sql
|
122
|
+
end
|
123
|
+
|
124
|
+
def process(database)
|
125
|
+
table = table(database)
|
126
|
+
table.rows.delete(element)
|
127
|
+
table.rows << element # XXX test this to make sure modifys work
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
class DropRow < Delta
|
133
|
+
|
134
|
+
def sql
|
135
|
+
data = element.data
|
136
|
+
where = element.primary_key.map {|k| "#{k}=" + Mysql.escape_string(data[k]) }.join(" AND ")
|
137
|
+
return "DELETE FROM #{element.table_name} WHERE #{where}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def process(database)
|
141
|
+
table = table(database)
|
142
|
+
table.rows.delete(element)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
class AddKey < Delta
|
148
|
+
|
149
|
+
def sql
|
150
|
+
"ALTER table #{element.table_name} ADD" + element.definition
|
151
|
+
end
|
152
|
+
|
153
|
+
def process(database)
|
154
|
+
table = table(database)
|
155
|
+
table.keys << element
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
class ModifyKey < Delta
|
161
|
+
|
162
|
+
def sql
|
163
|
+
"ALTER TABLE #{element.table_name} DROP KEY `#{element.name}`, ADD" + element.definition
|
164
|
+
end
|
165
|
+
|
166
|
+
def process(database)
|
167
|
+
table = table(database)
|
168
|
+
old_key = table.keys.find{|k| k.name == element.name}
|
169
|
+
table.keys.delete(old_key)
|
170
|
+
table.keys << element
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class DropKey < Delta
|
175
|
+
|
176
|
+
def sql
|
177
|
+
"ALTER TABLE #{element.table_name} DROP KEY `#{element.name}`"
|
178
|
+
end
|
179
|
+
|
180
|
+
def process(database)
|
181
|
+
table = table(database)
|
182
|
+
table.keys.delete(element)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class AddForeignKey < Delta
|
187
|
+
|
188
|
+
def sql
|
189
|
+
"ALTER table #{element.table_name} ADD " + element.definition
|
190
|
+
end
|
191
|
+
|
192
|
+
def process(database)
|
193
|
+
table(database).foreign_keys << element
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
class DropForeignKey < Delta
|
200
|
+
|
201
|
+
def sql
|
202
|
+
"ALTER table #{element.table_name} DROP FOREIGN KEY `#{element.name}`;\n"
|
203
|
+
end
|
204
|
+
|
205
|
+
def process(database)
|
206
|
+
table(database).foreign_keys.delete(element)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class AddTable < Delta
|
211
|
+
|
212
|
+
def initialize(element)
|
213
|
+
super
|
214
|
+
# we don't clone foreign_keys or rows since
|
215
|
+
# the table_elements diff will take care of them
|
216
|
+
@element.rows = []
|
217
|
+
@element.foreign_keys = []
|
218
|
+
end
|
219
|
+
|
220
|
+
def sql
|
221
|
+
# XXX may need to SET FOREIGN_KEY_CHECKS = 0;
|
222
|
+
sql = "CREATE TABLE `%s` (\n" % element.name
|
223
|
+
|
224
|
+
# intentionally skip foreign_keys to avoid missing tables
|
225
|
+
# foreign_keys get picked up with the table_element diff
|
226
|
+
sql += (element.columns.map{|c| c.definition} +
|
227
|
+
element.keys.map{|k| k.definition}).join(",\n")
|
228
|
+
|
229
|
+
sql += "\n) ENGINE=%s DEFAULT CHARSET=%s" % [element.engine, element.charset]
|
230
|
+
sql
|
231
|
+
end
|
232
|
+
|
233
|
+
def process(database)
|
234
|
+
database.tables << element
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
class ModifyTable < Delta
|
240
|
+
|
241
|
+
def sql
|
242
|
+
"ALTER TABLE #{element.name} ENGINE=%s DEFAULT CHARSET=%s" % [element.engine, element.charset]
|
243
|
+
end
|
244
|
+
|
245
|
+
def process(database)
|
246
|
+
cur_table = database.tables.find{|t| t.name == element.name}
|
247
|
+
# need full copy of meta-data, but can't swap out the deltas or columns
|
248
|
+
cur_table.engine = element.engine.dup
|
249
|
+
cur_table.collation = element.collation.dup
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class DropTable < Delta
|
254
|
+
|
255
|
+
def sql
|
256
|
+
"DROP TABLE #{element.name}"
|
257
|
+
end
|
258
|
+
|
259
|
+
def process(database)
|
260
|
+
database.tables.delete(element)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
require 'dbdiff/table_element'
|
3
|
+
class DbDiff
|
4
|
+
class ForeignKey < TableElement
|
5
|
+
attr_reader :ref_table, :ref_column_data, :column_data, :name
|
6
|
+
|
7
|
+
def initialize(info = {})
|
8
|
+
super
|
9
|
+
|
10
|
+
# XXX need to work on tihs
|
11
|
+
@name = info['CONSTRAINT_NAME']
|
12
|
+
seq_index = info['ORDINAL_POSITION'].to_i - 1
|
13
|
+
@column_data = {}
|
14
|
+
@ref_column_data = {}
|
15
|
+
|
16
|
+
@column_data[seq_index] = info['COLUMN_NAME']
|
17
|
+
|
18
|
+
ref_seq_index = info['POSITION_IN_UNIQUE_CONSTRAINT'].to_i - 1
|
19
|
+
@ref_table = info['REFERENCED_TABLE_NAME']
|
20
|
+
@ref_column_data[ref_seq_index] = info['REFERENCED_COLUMN_NAME']
|
21
|
+
end
|
22
|
+
|
23
|
+
def merge(fk)
|
24
|
+
|
25
|
+
unless fk.name == self.name
|
26
|
+
raise "Error - names don't match #{fk.name} #{self.name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
@column_data.merge!(fk.column_data)
|
30
|
+
@ref_column_data.merge!(fk.ref_column_data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ref_columns
|
34
|
+
@ref_column_data.keys.sort.map {|k| @ref_column_data[k]}
|
35
|
+
end
|
36
|
+
|
37
|
+
def columns
|
38
|
+
@column_data.keys.sort.map {|k| @column_data[k]}
|
39
|
+
end
|
40
|
+
|
41
|
+
def definition
|
42
|
+
return "CONSTRAINT `#{self.name}` FOREIGN KEY (`" + self.columns.join("`,`") + "`) references #{self.ref_table}(`" +
|
43
|
+
self.ref_columns.join("`,`") + "`)"
|
44
|
+
end
|
45
|
+
|
46
|
+
def modify_delta(new_element)
|
47
|
+
[self.drop_delta, new_element.add_delta]
|
48
|
+
end
|
49
|
+
|
50
|
+
def drop_delta
|
51
|
+
Delta::DropForeignKey.new(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_delta
|
55
|
+
Delta::AddForeignKey.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
|
60
|
+
self.name == other.name &&
|
61
|
+
self.columns == other.columns &&
|
62
|
+
self.table_name == other.table_name &&
|
63
|
+
self.ref_table == other.ref_table &&
|
64
|
+
self.ref_columns == other.ref_columns
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|