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/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
|
+
|