dbdiff 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|