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/lib/dbdiff/key.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
class DbDiff
|
2
|
+
class Key < TableElement
|
3
|
+
attr_reader :column_data, :unique, :primary
|
4
|
+
|
5
|
+
def initialize(table_name, info= {})
|
6
|
+
@table_name = table_name
|
7
|
+
|
8
|
+
|
9
|
+
# XXX need to work on tihs
|
10
|
+
@name = info['Key_name']
|
11
|
+
|
12
|
+
@primary = (@name == 'PRIMARY' ? true : false)
|
13
|
+
seq_index = info['Seq_in_index'].to_i - 1
|
14
|
+
@unique = (info['Non_unique'].to_i == 1 ? false : true)
|
15
|
+
@column_data = {}
|
16
|
+
@column_data[seq_index] = info['Column_name']
|
17
|
+
end
|
18
|
+
|
19
|
+
def columns
|
20
|
+
@column_data.keys.sort.map {|k| @column_data[k]}
|
21
|
+
end
|
22
|
+
|
23
|
+
def merge(key)
|
24
|
+
|
25
|
+
unless key.name == self.name
|
26
|
+
raise "Error - names don't match #{key.name} #{self.name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
@column_data.merge!(key.column_data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def definition
|
33
|
+
|
34
|
+
sql = ""
|
35
|
+
if @primary
|
36
|
+
sql += " PRIMARY KEY "
|
37
|
+
elsif @unique
|
38
|
+
sql += " UNIQUE KEY `%s`" % self.name
|
39
|
+
else
|
40
|
+
sql += " KEY `%s`" % self.name
|
41
|
+
end
|
42
|
+
|
43
|
+
sql += " (" + self.columns.map{|c| '`' + c + '`' }.join(",") + " )"
|
44
|
+
|
45
|
+
sql
|
46
|
+
end
|
47
|
+
|
48
|
+
def modify_delta(new_element)
|
49
|
+
Delta::ModifyKey.new(new_element)
|
50
|
+
end
|
51
|
+
|
52
|
+
def drop_delta
|
53
|
+
Delta::DropKey.new(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_delta
|
57
|
+
Delta::AddKey.new(self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
self.columns == other.columns &&
|
62
|
+
self.table_name == other.table_name &&
|
63
|
+
self.primary == other.primary &&
|
64
|
+
self.unique == other.unique &&
|
65
|
+
self.name == other.name
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/dbdiff/row.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'dbdiff/table_element'
|
2
|
+
|
3
|
+
class DbDiff
|
4
|
+
|
5
|
+
class Row < TableElement
|
6
|
+
attr_reader :primary_key, :data
|
7
|
+
|
8
|
+
def initialize(table, info = {})
|
9
|
+
@data = info.dup
|
10
|
+
@primary_key = table.primary_key
|
11
|
+
@name = @primary_key.map {|k|@data[k] }.join("|") # XXX fix this to be more unique of a name
|
12
|
+
@table_name = table.name
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def add_delta
|
17
|
+
Delta::AddRow.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def modify_delta(new_element)
|
21
|
+
Delta::ModifyRow.new(new_element)
|
22
|
+
end
|
23
|
+
|
24
|
+
def drop_delta
|
25
|
+
Delta::DropRow.new(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
self.data == other.data
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/dbdiff/table.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
class DbDiff
|
2
|
+
class Table
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :charset, :rows, :keys, :foreign_keys, :columns, :collation, :engine
|
5
|
+
|
6
|
+
def initialize(info = {})
|
7
|
+
@data = {}
|
8
|
+
|
9
|
+
@rows = []
|
10
|
+
@foreign_keys = []
|
11
|
+
@keys = []
|
12
|
+
@columns = []
|
13
|
+
|
14
|
+
@engine = info['ENGINE']
|
15
|
+
@collation = info['TABLE_COLLATION']
|
16
|
+
@name = info['TABLE_NAME']
|
17
|
+
end
|
18
|
+
|
19
|
+
alias :table_name :name
|
20
|
+
|
21
|
+
def primary_key
|
22
|
+
pk = keys.find{|k| k.primary}
|
23
|
+
return (pk ? pk.columns : nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
self.engine == other.engine &&
|
28
|
+
self.collation == other.collation &&
|
29
|
+
self.name == other.name
|
30
|
+
end
|
31
|
+
|
32
|
+
def deep_clone
|
33
|
+
t = Marshal::load(Marshal.dump(self))
|
34
|
+
# must clear rows out since they won't necessary be in the copy
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_delta
|
38
|
+
Delta::AddTable.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def modify_delta(new_element)
|
42
|
+
Delta::ModifyTable.new(new_element)
|
43
|
+
end
|
44
|
+
|
45
|
+
def drop_delta
|
46
|
+
Delta::DropTable.new(self)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/dbdiff.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'dbdiff/database'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
# = Synopsis
|
5
|
+
# DbDiff allows you to compare two different databases (currently MySQL),
|
6
|
+
# determine the differences between the two, and apply the deltas.
|
7
|
+
# Currently the following types of deltas are supported: tables,
|
8
|
+
# columns, keys, foreign keys and rows.
|
9
|
+
#
|
10
|
+
# Specifying tables in the constructor causes these tables to be
|
11
|
+
# replicated.
|
12
|
+
#
|
13
|
+
# Nothing in dropped or removed unless you explicitly pass :drop_row,
|
14
|
+
# :drop_column or :drop_table.
|
15
|
+
#
|
16
|
+
# By default, calling apply_diffs does nothing - you must explicitly
|
17
|
+
# set dry_run to false on the dbdiff object.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# == Example
|
21
|
+
# require 'rubygems'
|
22
|
+
# require 'dbdiff'
|
23
|
+
#
|
24
|
+
# dbdiff = DbDiff.new( {:host => 127.0.0.1, :password => nil, :user => 'root', :name => 'source_db'},
|
25
|
+
# {:host => 127.0.0.1, :password => nil, :user => 'root', :name => 'target_db'},
|
26
|
+
# :logger => Logger.new(STDOUT),
|
27
|
+
# :tables => %w(authors books)
|
28
|
+
# )
|
29
|
+
# # don't actually execute any SQL against the target
|
30
|
+
# dbdiff.dry_run = true
|
31
|
+
#
|
32
|
+
# # perform diff between source and target
|
33
|
+
# dbiff.diff
|
34
|
+
#
|
35
|
+
# # process diffs - arguments indicate that drop_column, drop_row (only for replicated tables),
|
36
|
+
# # and drop_table are allowed within diffs
|
37
|
+
# # are allowed
|
38
|
+
# diff.apply_diffs(:drop_column, :drop_row, :drop_table)
|
39
|
+
#
|
40
|
+
|
41
|
+
class DbDiff
|
42
|
+
attr_reader :source, :target, :tables, :logger
|
43
|
+
attr_accessor :dry_run
|
44
|
+
|
45
|
+
# Creates a new dbdiff object with the connect strings s/t (source/target)
|
46
|
+
def initialize(s, t, params = {})
|
47
|
+
@source = DbDiff::Database.new(s)
|
48
|
+
@target = DbDiff::Database.new(t)
|
49
|
+
@tables = params[:tables] || []
|
50
|
+
@logger = params[:logger] || Logger.new(nil)
|
51
|
+
|
52
|
+
@source.load_rows(@tables)
|
53
|
+
@target.load_rows(@tables)
|
54
|
+
@dry_run = true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Compare tables and all elements (columns, rows, keys, foreign keys)
|
58
|
+
# differences are stored on the deltas attribute of the target
|
59
|
+
# database
|
60
|
+
def diff
|
61
|
+
compare_tables
|
62
|
+
compare_elements(:columns, :rows, :keys, :foreign_keys)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Apply deltas stored on the target
|
66
|
+
# all SQL executed will be sent to the logger as info messages
|
67
|
+
def apply_diffs (*allowed_deltas)
|
68
|
+
|
69
|
+
|
70
|
+
# these deltas must be manually
|
71
|
+
# resolved or explicitly allowed
|
72
|
+
confirm_deltas = {
|
73
|
+
:drop_row => DbDiff::Delta::DropRow,
|
74
|
+
:drop_column => DbDiff::Delta::DropColumn,
|
75
|
+
:drop_table => DbDiff::Delta::DropTable
|
76
|
+
}
|
77
|
+
|
78
|
+
confirm_deltas = confirm_deltas.delete_if {|k,v| allowed_deltas.include?(k)}
|
79
|
+
|
80
|
+
|
81
|
+
delta_order = [
|
82
|
+
DbDiff::Delta::AddTable,
|
83
|
+
DbDiff::Delta::ModifyTable,
|
84
|
+
DbDiff::Delta::DropForeignKey,
|
85
|
+
DbDiff::Delta::DropTable, # can only drop a table after foreign keys have been dropped
|
86
|
+
|
87
|
+
DbDiff::Delta::AddColumn,
|
88
|
+
|
89
|
+
DbDiff::Delta::ModifyColumnRemoveAI, # ai must be removed before we can drop/modify a pk
|
90
|
+
|
91
|
+
DbDiff::Delta::ModifyKey, # must have columns exist prior to adding a key
|
92
|
+
DbDiff::Delta::AddKey, # must have new keys before old ones can be dropped
|
93
|
+
# to support FKs
|
94
|
+
|
95
|
+
DbDiff::Delta::DropKey, # must have FK dropped before we drop a key
|
96
|
+
|
97
|
+
DbDiff::Delta::ModifyColumnAddAI, # ai must be added before we can add a pk
|
98
|
+
|
99
|
+
DbDiff::Delta::DropRow,
|
100
|
+
DbDiff::Delta::ModifyColumn,
|
101
|
+
|
102
|
+
DbDiff::Delta::AddForeignKey,
|
103
|
+
DbDiff::Delta::DropColumn,
|
104
|
+
DbDiff::Delta::AddRow,
|
105
|
+
DbDiff::Delta::ModifyRow
|
106
|
+
]
|
107
|
+
|
108
|
+
drop_deltas = target.deltas.find_all {|d| confirm_deltas.values.include?(d.class) }
|
109
|
+
|
110
|
+
if drop_deltas && drop_deltas.size > 0
|
111
|
+
message = "The following deltas must be either handled by a rename or explicitly allowed"
|
112
|
+
drop_deltas.each do|d|
|
113
|
+
message += "\n #{d.class.name}: " + d.sql
|
114
|
+
end
|
115
|
+
raise message
|
116
|
+
end
|
117
|
+
|
118
|
+
target.dbh.query("SET FOREIGN_KEY_CHECKS = 0")
|
119
|
+
|
120
|
+
target.deltas.sort_by {|d| delta_order.index(d.class) }.each do |d|
|
121
|
+
logger.info("processing #{d.sql}")
|
122
|
+
d.process(target)
|
123
|
+
run_sql(d.sql) unless @dry_run
|
124
|
+
end
|
125
|
+
|
126
|
+
target.dbh.query("SET FOREIGN_KEY_CHECKS = 1")
|
127
|
+
|
128
|
+
target.deltas = []
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def run_sql(sql)
|
134
|
+
begin
|
135
|
+
target.dbh.select_db(target.name)
|
136
|
+
logger.info("executing #{sql}")
|
137
|
+
result = target.dbh.query(sql)
|
138
|
+
rescue => e
|
139
|
+
raise "error executing query '#{sql}': #{e}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def compare_elements(*elements)
|
144
|
+
|
145
|
+
elements.each do |e|
|
146
|
+
diff_table_elements(source, target, e, :diff_add_modify)
|
147
|
+
diff_table_elements(target, source, e, :diff_drop)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def compare_tables
|
152
|
+
|
153
|
+
# only handle modify/add
|
154
|
+
source.tables.each do |t|
|
155
|
+
# XXX add authors2
|
156
|
+
diff_add_modify(source, target, t, target.tables.find{|x| x.name == t.name})
|
157
|
+
end
|
158
|
+
|
159
|
+
## only handle removals here
|
160
|
+
target.tables.each do |t|
|
161
|
+
# xxx drop authors
|
162
|
+
diff_drop(target, source, t, source.tables.find{|x| x.name == t.name})
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
def diff_drop(a, b, element, target_element)
|
168
|
+
if !target_element
|
169
|
+
# source is actually the target database
|
170
|
+
logger.info("dropping #{element.class.name} #{element.name} #{element.table_name} from #{a.class.name} #{a.name}")
|
171
|
+
a.drop(element)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def diff_add_modify(a, b, element, target_element)
|
176
|
+
|
177
|
+
if !target_element
|
178
|
+
logger.info("adding element #{element.class.name} #{element.name} #{element.table_name} > #{b.name}")
|
179
|
+
b.add(element)
|
180
|
+
elsif target_element != element
|
181
|
+
logger.info("modifying element #{element.class.name} #{element.name} #{element.table_name} > #{b.name}")
|
182
|
+
b.modify(target_element, element)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
def diff_table_elements(a, b, table_method, diff_method)
|
188
|
+
|
189
|
+
|
190
|
+
# all_tables allows us to add the FKs after adding the tables
|
191
|
+
# otherwise the table won't be there to add the FK
|
192
|
+
a.all_tables.each do |at|
|
193
|
+
bt = b.all_tables.find{|z| z.name == at.name}
|
194
|
+
# XXX possibly skip this and make the add check determine whether
|
195
|
+
# we can skip the add because we have already encountered the add
|
196
|
+
next unless bt
|
197
|
+
|
198
|
+
at.send(table_method).each do |ae|
|
199
|
+
be = bt.send(table_method).find{|x| x.name == ae.name}
|
200
|
+
send(diff_method, a, b, ae, be)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
data/test/fk/source.sql
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE authors (
|
3
|
+
`id` int(11) NOT NULL auto_increment,
|
4
|
+
`name` char(60) NOT NULL,
|
5
|
+
`address1` char(60) NOT NULL,
|
6
|
+
PRIMARY KEY (`id`)
|
7
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
8
|
+
|
9
|
+
CREATE TABLE books (
|
10
|
+
id int(11) NOT NULL auto_increment,
|
11
|
+
author_id int not null,
|
12
|
+
name char(60) NOT NULL,
|
13
|
+
pages int not null,
|
14
|
+
FOREIGN KEY (author_id) references authors(id),
|
15
|
+
PRIMARY KEY (`id`)
|
16
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
17
|
+
|
18
|
+
|
19
|
+
|
data/test/fk/target.sql
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
DROP TABLE IF EXISTS `authors`;
|
2
|
+
|
3
|
+
CREATE TABLE authors (
|
4
|
+
`id` int(11) NOT NULL auto_increment,
|
5
|
+
`name` char(60) NOT NULL,
|
6
|
+
`address1` char(60) NOT NULL,
|
7
|
+
PRIMARY KEY (`id`)
|
8
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
9
|
+
|
10
|
+
CREATE TABLE books (
|
11
|
+
id int(11) NOT NULL auto_increment,
|
12
|
+
author_id int not null,
|
13
|
+
name char(60) NOT NULL,
|
14
|
+
pages int not null,
|
15
|
+
PRIMARY KEY (`id`)
|
16
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
data/test/key/source.sql
ADDED
data/test/key/target.sql
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE authors (
|
3
|
+
`id` int(11) NOT NULL auto_increment,
|
4
|
+
`name` char(60) NOT NULL,
|
5
|
+
`address1` char(60) NOT NULL,
|
6
|
+
PRIMARY KEY (`id`)
|
7
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
8
|
+
|
9
|
+
CREATE TABLE books (
|
10
|
+
id int(11) NOT NULL auto_increment,
|
11
|
+
author_id int not null,
|
12
|
+
new_id int not null,
|
13
|
+
name char(60) NOT NULL,
|
14
|
+
pages int not null,
|
15
|
+
CONSTRAINT `author_fk1` FOREIGN KEY (new_id) references authors(id),
|
16
|
+
PRIMARY KEY (`id`)
|
17
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
18
|
+
|
19
|
+
|
20
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
DROP TABLE IF EXISTS `authors`;
|
2
|
+
|
3
|
+
CREATE TABLE authors (
|
4
|
+
`id` int(11) NOT NULL auto_increment,
|
5
|
+
`name` char(60) NOT NULL,
|
6
|
+
`address1` char(60) NOT NULL,
|
7
|
+
PRIMARY KEY (`id`)
|
8
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
9
|
+
|
10
|
+
CREATE TABLE books (
|
11
|
+
id int(11) NOT NULL auto_increment,
|
12
|
+
author_id int not null,
|
13
|
+
new_id int not null,
|
14
|
+
name char(60) NOT NULL,
|
15
|
+
pages int not null,
|
16
|
+
CONSTRAINT `author_fk1` FOREIGN KEY (author_id) references authors(id),
|
17
|
+
PRIMARY KEY (`id`)
|
18
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE authors (
|
3
|
+
id int(11) NOT NULL auto_increment,
|
4
|
+
name char(60) NOT NULL,
|
5
|
+
address1 char(60) NOT NULL,
|
6
|
+
PRIMARY KEY (`id`)
|
7
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
8
|
+
|
9
|
+
CREATE TABLE books (
|
10
|
+
id int(11) NOT NULL auto_increment,
|
11
|
+
author_id int not null,
|
12
|
+
name char(60) NOT NULL,
|
13
|
+
unique(author_id),
|
14
|
+
FOREIGN KEY (author_id) references authors(id),
|
15
|
+
PRIMARY KEY (`id`)
|
16
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
17
|
+
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE authors (
|
3
|
+
id int(11) NOT NULL auto_increment,
|
4
|
+
name char(60) NOT NULL,
|
5
|
+
address1 char(60) NOT NULL,
|
6
|
+
PRIMARY KEY (`id`)
|
7
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
8
|
+
|
9
|
+
CREATE TABLE books (
|
10
|
+
id int(11) NOT NULL auto_increment,
|
11
|
+
author_id int not null,
|
12
|
+
name char(60) NOT NULL,
|
13
|
+
index(author_id),
|
14
|
+
FOREIGN KEY (author_id) references authors(id),
|
15
|
+
PRIMARY KEY (`id`)
|
16
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE authors (
|
3
|
+
id int(11) NOT NULL auto_increment,
|
4
|
+
name char(60) NOT NULL,
|
5
|
+
address1 char(60) NOT NULL,
|
6
|
+
PRIMARY KEY (`id`)
|
7
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
8
|
+
|
9
|
+
CREATE TABLE books (
|
10
|
+
id int(11) NOT NULL auto_increment,
|
11
|
+
author_id int not null,
|
12
|
+
address_id int not null,
|
13
|
+
name char(60) NOT NULL,
|
14
|
+
unique(author_id),
|
15
|
+
FOREIGN KEY (author_id) references authors(id),
|
16
|
+
PRIMARY KEY (`id`)
|
17
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
18
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE authors (
|
3
|
+
id int(11) NOT NULL auto_increment,
|
4
|
+
name char(60) NOT NULL,
|
5
|
+
address1 char(60) NOT NULL,
|
6
|
+
PRIMARY KEY (`id`)
|
7
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
8
|
+
|
9
|
+
CREATE TABLE books (
|
10
|
+
id int(11) NOT NULL auto_increment,
|
11
|
+
author_id int not null,
|
12
|
+
name char(60) NOT NULL,
|
13
|
+
index(author_id),
|
14
|
+
FOREIGN KEY (author_id) references authors(id),
|
15
|
+
PRIMARY KEY (`id`)
|
16
|
+
) ENGINE=innodb DEFAULT CHARSET=latin1;
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|