convergence 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +32 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +88 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +34 -0
- data/bin/convergence +23 -0
- data/convergence-0.0.1.gem +0 -0
- data/convergence.gemspec +33 -0
- data/database.yml.example +5 -0
- data/lib/convergence.rb +24 -0
- data/lib/convergence/column.rb +36 -0
- data/lib/convergence/command.rb +56 -0
- data/lib/convergence/command/apply.rb +51 -0
- data/lib/convergence/command/diff.rb +26 -0
- data/lib/convergence/command/dryrun.rb +39 -0
- data/lib/convergence/command/export.rb +15 -0
- data/lib/convergence/config.rb +17 -0
- data/lib/convergence/database_connector.rb +25 -0
- data/lib/convergence/database_connector/mysql_connector.rb +27 -0
- data/lib/convergence/default_parameter.rb +32 -0
- data/lib/convergence/default_parameter/mysql_default_parameter.rb +159 -0
- data/lib/convergence/diff.rb +148 -0
- data/lib/convergence/dsl.rb +20 -0
- data/lib/convergence/dumper.rb +68 -0
- data/lib/convergence/dumper/mysql_schema_dumper.rb +149 -0
- data/lib/convergence/foreign_key.rb +11 -0
- data/lib/convergence/index.rb +9 -0
- data/lib/convergence/logger.rb +12 -0
- data/lib/convergence/module.rb +1 -0
- data/lib/convergence/pretty_diff.rb +55 -0
- data/lib/convergence/sql_generator.rb +2 -0
- data/lib/convergence/sql_generator/mysql_generator.rb +208 -0
- data/lib/convergence/table.rb +37 -0
- data/lib/convergence/version.rb +3 -0
- data/spec/config/spec_database.yml +6 -0
- data/spec/convergence/diff_spec.rb +242 -0
- data/spec/convergence/dsl_spec.rb +78 -0
- data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +87 -0
- data/spec/convergence/dumper_spec.rb +40 -0
- data/spec/convergence/table_spec.rb +106 -0
- data/spec/fixtures/add_columns_to_paper.schema +25 -0
- data/spec/fixtures/add_table.schema +28 -0
- data/spec/fixtures/change_comment_columns_to_paper.schema +24 -0
- data/spec/fixtures/change_table_comment_to_paper.schema +24 -0
- data/spec/fixtures/drop_table.schema +15 -0
- data/spec/fixtures/remove_columns_to_paper.schema +23 -0
- data/spec/fixtures/test_db.sql +27 -0
- data/spec/integrations/command_dryrun.rb +73 -0
- data/spec/spec_helper.rb +18 -0
- metadata +268 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
class Convergence::Table
|
2
|
+
attr_accessor :table_name, :table_options, :columns, :indexes, :foreign_keys
|
3
|
+
|
4
|
+
Convergence::Column::COLUMN_TYPE.each do |column_type|
|
5
|
+
define_method "#{column_type}" do |column_name, options = {}|
|
6
|
+
@columns[column_name.to_s] = Convergence::Column.new(column_type, column_name.to_s, options)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def index(index_columns, options = {})
|
11
|
+
index_name = options[:name]
|
12
|
+
index_name = "index_#{table_name}_on_#{[index_columns].flatten.join('_')}" if index_name.nil?
|
13
|
+
@indexes[index_name] = Convergence::Index.new(index_name, index_columns, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def foreign_key(key_columns, options = {})
|
17
|
+
if options[:reference].nil? || options[:reference_column].nil?
|
18
|
+
fail ArgumentError.new("#{@table_name} - #{key_columns}: require reference/reference_column parameters")
|
19
|
+
end
|
20
|
+
key_name = options[:name]
|
21
|
+
key_name = "#{table_name}_#{[key_columns].flatten.join('_')}_fk" if key_name.nil?
|
22
|
+
@foreign_keys[key_name] = Convergence::ForeignKey.new(
|
23
|
+
key_name,
|
24
|
+
key_columns,
|
25
|
+
options[:reference],
|
26
|
+
[options[:reference_column]].flatten,
|
27
|
+
options.reject { |k, _v| k == :reference || k == :reference_column })
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(table_name, options = {})
|
31
|
+
@table_name = table_name
|
32
|
+
@table_options = options
|
33
|
+
@columns = {}
|
34
|
+
@indexes = {}
|
35
|
+
@foreign_keys = {}
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Convergence::DSL do
|
4
|
+
describe '#diff' do
|
5
|
+
let(:simple_table) do
|
6
|
+
Convergence::Table.new('simple_table').tap do |t|
|
7
|
+
t.int('id', primary_key: true)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
let(:simple_table2) do
|
11
|
+
Convergence::Table.new('simple_table2').tap do |t|
|
12
|
+
t.int('id', primary_key: true)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when from database and to database are same' do
|
17
|
+
let(:from_db) do
|
18
|
+
{ 'simple_table' => simple_table }
|
19
|
+
end
|
20
|
+
let(:to_db) do
|
21
|
+
from_db
|
22
|
+
end
|
23
|
+
|
24
|
+
subject { Convergence::Diff.new.diff(from_db, to_db) }
|
25
|
+
|
26
|
+
it 'should return empty' do
|
27
|
+
expect(subject[:add_table]).to be_empty
|
28
|
+
expect(subject[:remove_table]).to be_empty
|
29
|
+
expect(subject[:change_table]).to be_empty
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when new tables are added' do
|
34
|
+
let(:from_db) do
|
35
|
+
{ 'simple_table' => simple_table }
|
36
|
+
end
|
37
|
+
let(:to_db) do
|
38
|
+
{ 'simple_table' => simple_table, 'simple_table2' => simple_table2 }
|
39
|
+
end
|
40
|
+
|
41
|
+
subject { Convergence::Diff.new.diff(from_db, to_db) }
|
42
|
+
|
43
|
+
it 'should be able to detect add table' do
|
44
|
+
expect(subject[:add_table]).not_to be_empty
|
45
|
+
expect(subject[:add_table]['simple_table2']).not_to be_nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when tables are removed' do
|
50
|
+
let(:from_db) do
|
51
|
+
{ 'simple_table' => simple_table, 'simple_table2' => simple_table2 }
|
52
|
+
end
|
53
|
+
let(:to_db) do
|
54
|
+
{ 'simple_table' => simple_table }
|
55
|
+
end
|
56
|
+
|
57
|
+
subject { Convergence::Diff.new.diff(from_db, to_db) }
|
58
|
+
|
59
|
+
it 'should be able to detect remove table' do
|
60
|
+
expect(subject[:remove_table]).not_to be_empty
|
61
|
+
expect(subject[:remove_table]['simple_table2']).not_to be_nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'table column are changed' do
|
66
|
+
let(:table_from) do
|
67
|
+
Convergence::Table.new('table1').tap do |t|
|
68
|
+
t.int('id', primary_key: true)
|
69
|
+
t.varchar('name', limit: 200, null: false)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'new column are added' do
|
74
|
+
let(:table_to) do
|
75
|
+
Convergence::Table.new('table1').tap do |t|
|
76
|
+
t.int('id', primary_key: true)
|
77
|
+
t.varchar('name', limit: 200, null: false)
|
78
|
+
t.varchar('data', limit: 300, null: false)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it do
|
83
|
+
results = Convergence::Diff.new.diff({ 'table1' => table_from }, { 'table1' => table_to })
|
84
|
+
expect(results[:change_table]).not_to be_empty
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'column are deleted' do
|
89
|
+
let(:table_to) do
|
90
|
+
Convergence::Table.new('table1').tap do |t|
|
91
|
+
t.int('id', primary_key: true)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it do
|
96
|
+
results = Convergence::Diff.new.diff({ 'table1' => table_from }, { 'table1' => table_to })
|
97
|
+
expect(results[:change_table]).not_to be_empty
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'column definition are changed' do
|
102
|
+
let(:table_to) do
|
103
|
+
Convergence::Table.new('table1').tap do |t|
|
104
|
+
t.int('id')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it do
|
109
|
+
results = Convergence::Diff.new.diff({ 'table1' => table_from }, { 'table1' => table_to })
|
110
|
+
expect(results[:change_table]).not_to be_empty
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#diff_table' do
|
117
|
+
let(:table_from) do
|
118
|
+
Convergence::Table.new('table1').tap do |t|
|
119
|
+
t.int('id', primary_key: true)
|
120
|
+
t.varchar('name', limit: 200, null: false)
|
121
|
+
|
122
|
+
t.index('name')
|
123
|
+
t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'change column options' do
|
128
|
+
let(:table_to) do
|
129
|
+
Convergence::Table.new('table1').tap do |t|
|
130
|
+
t.int('id', primary_key: true)
|
131
|
+
t.varchar('name', limit: 300, null: true)
|
132
|
+
|
133
|
+
t.index('name')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it do
|
138
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
139
|
+
expect(results[:change_column]['name']).not_to be_nil
|
140
|
+
expect(results[:change_column]['name'][:limit]).to eq('300')
|
141
|
+
expect(results[:change_column]['name'][:null]).to eq('true')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'change column order' do
|
146
|
+
let(:table_to) do
|
147
|
+
Convergence::Table.new('table1').tap do |t|
|
148
|
+
t.varchar('name', limit: 300, null: true)
|
149
|
+
t.int('id', primary_key: true)
|
150
|
+
|
151
|
+
t.index('name')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it do
|
156
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
157
|
+
expect(results[:change_column]['name']).not_to be_nil
|
158
|
+
expect(results[:change_column]['id']).not_to be_nil
|
159
|
+
expect(results[:change_column]['id'][:after]).to eq('name')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'remove index' do
|
164
|
+
let(:table_to) do
|
165
|
+
Convergence::Table.new('table1').tap do |t|
|
166
|
+
t.int('id', primary_key: true)
|
167
|
+
t.varchar('name', limit: 300, null: true)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
it do
|
172
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
173
|
+
expect(results[:remove_index].values.first.index_columns).to eq(['name'])
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'add index' do
|
178
|
+
let(:table_to) do
|
179
|
+
Convergence::Table.new('table1').tap do |t|
|
180
|
+
t.int('id', primary_key: true)
|
181
|
+
t.varchar('name', limit: 300, null: true)
|
182
|
+
|
183
|
+
t.index('id')
|
184
|
+
t.index('name')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it do
|
189
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
190
|
+
expect(results[:add_index].values.first.index_columns).to eq(['id'])
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'remove foreign key' do
|
195
|
+
let(:table_to) do
|
196
|
+
Convergence::Table.new('table1').tap do |t|
|
197
|
+
t.int('id', primary_key: true)
|
198
|
+
t.varchar('name', limit: 300, null: true)
|
199
|
+
|
200
|
+
t.index('name')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it do
|
205
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
206
|
+
expect(results[:remove_foreign_key].values.first.from_columns).to eq(['id'])
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'add foreign key' do
|
211
|
+
let(:table_to) do
|
212
|
+
Convergence::Table.new('table1').tap do |t|
|
213
|
+
t.int('id', primary_key: true)
|
214
|
+
t.varchar('name', limit: 300, null: true)
|
215
|
+
|
216
|
+
t.index('name')
|
217
|
+
t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
|
218
|
+
t.foreign_key('id2', reference: 'ref_tables2', reference_column: 'ref_id2')
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
it do
|
223
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
224
|
+
expect(results[:add_foreign_key].values.first.from_columns).to eq(['id2'])
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context 'change table options' do
|
229
|
+
let(:table_to) do
|
230
|
+
Convergence::Table.new('table1', engine: 'MyISAM').tap do |t|
|
231
|
+
t.varchar('name', limit: 300, null: true)
|
232
|
+
t.int('id', primary_key: true)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
it do
|
237
|
+
results = Convergence::Diff.new.diff_table(table_from, table_to)
|
238
|
+
expect(results[:change_table_option][:engine]).to eq('MyISAM')
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Convergence::DSL do
|
4
|
+
let(:dsl_single_table) do
|
5
|
+
<<-DSL
|
6
|
+
create_table "users", comment: 'Users' do |t|
|
7
|
+
t.int "id", primary_key: true, null: false, limit: 11, extra: "auto_increment"
|
8
|
+
t.datetime "created_at", null: true
|
9
|
+
|
10
|
+
t.index 'created_at'
|
11
|
+
t.foreign_key "user_id", reference: "users", reference_column: "id"
|
12
|
+
end
|
13
|
+
DSL
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:dsl_multi_table) do
|
17
|
+
<<-DSL
|
18
|
+
create_table "users", comment: 'Users' do |t|
|
19
|
+
t.int "id", primary_key: true, extra: "auto_increment"
|
20
|
+
t.varchar "name", limit: 100
|
21
|
+
t.datetime "created_at", null: true
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table "posts", comment: "Posts" do |t|
|
25
|
+
t.int "id", primary_key: true, extra: "auto_increment"
|
26
|
+
t.int "user_id"
|
27
|
+
t.datetime "created_at", null: true
|
28
|
+
|
29
|
+
t.foreign_key "user_id", reference: "users", reference_column: "id"
|
30
|
+
end
|
31
|
+
DSL
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#parse' do
|
35
|
+
context 'when exist only one table' do
|
36
|
+
subject { Convergence::DSL.parse(dsl_single_table) }
|
37
|
+
|
38
|
+
it 'should be able to parse tables' do
|
39
|
+
expect(subject['users']).not_to be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should be able to parse table options' do
|
43
|
+
expect(subject['users'].table_options[:comment]).to eq('Users')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should be able to parse table columns' do
|
47
|
+
columns = subject['users'].columns
|
48
|
+
expect(columns['id']).not_to be_nil
|
49
|
+
expect(columns['created_at']).not_to be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be able to parse table column options' do
|
53
|
+
columns = subject['users'].columns
|
54
|
+
expect(columns['id'].options[:primary_key]).to be_truthy
|
55
|
+
expect(columns['id'].options[:null]).to be_falsy
|
56
|
+
expect(columns['id'].options[:limit]).to eq(11)
|
57
|
+
expect(columns['id'].options[:extra]).to eq('auto_increment')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should be able to parse indexes' do
|
61
|
+
expect(subject['users'].indexes).not_to be_nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should be able to parse foreign keys' do
|
65
|
+
expect(subject['users'].foreign_keys).not_to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when multiple tables exists' do
|
70
|
+
subject { Convergence::DSL.parse(dsl_multi_table) }
|
71
|
+
|
72
|
+
it 'should be able to parse tables' do
|
73
|
+
expect(subject['users']).not_to be_nil
|
74
|
+
expect(subject['posts']).not_to be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Convergence::Dumper::MysqlSchemaDumper do
|
4
|
+
before do
|
5
|
+
# load fixtures
|
6
|
+
end
|
7
|
+
let(:connector) do
|
8
|
+
Convergence::DatabaseConnector.new(mysql_settings)
|
9
|
+
end
|
10
|
+
let(:dumper) do
|
11
|
+
Convergence::Dumper::MysqlSchemaDumper.new(connector)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#dump' do
|
15
|
+
subject { dumper.dump }
|
16
|
+
|
17
|
+
it 'should be dump tables' do
|
18
|
+
expect(subject['papers']).not_to be_nil
|
19
|
+
expect(subject['authors']).not_to be_nil
|
20
|
+
expect(subject['paper_authors']).not_to be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'table options' do
|
24
|
+
it 'shoulb be dump options' do
|
25
|
+
papers = subject['papers']
|
26
|
+
expect(papers.table_options[:engine]).to eq('InnoDB')
|
27
|
+
expect(papers.table_options[:comment]).to eq('Paper')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'table columns' do
|
32
|
+
it 'should be dump table columns' do
|
33
|
+
papers = subject['papers']
|
34
|
+
expect(papers.columns['id']).not_to be_nil
|
35
|
+
expect(papers.columns['title1']).not_to be_nil
|
36
|
+
expect(papers.columns['title2']).not_to be_nil
|
37
|
+
expect(papers.columns['description']).not_to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should be dump columns in the correct order' do
|
41
|
+
papers = subject['papers']
|
42
|
+
expect(papers.columns.keys).to eq(%w(id title1 title2 description))
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'table options' do
|
46
|
+
it 'should be dump primary key' do
|
47
|
+
expect(subject['papers'].columns['id'].options[:primary_key]).to be_truthy
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should be dump extra' do
|
51
|
+
expect(subject['papers'].columns['id'].options[:extra]).to eq('auto_increment')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should be dump comment' do
|
55
|
+
expect(subject['papers'].columns['title1'].options[:comment]).to eq('Title 1')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should be dump limit' do
|
59
|
+
expect(subject['papers'].columns['title1'].options[:limit]).to eq('300')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should be dump not null definition' do
|
63
|
+
expect(subject['papers'].columns['title1'].options[:null]).to be_falsy
|
64
|
+
expect(subject['authors'].columns['created_at'].options[:null]).to be_truthy
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'indexes' do
|
70
|
+
it do
|
71
|
+
index = subject['authors'].indexes['index_authors_on_created_at']
|
72
|
+
expect(index).not_to be_nil
|
73
|
+
expect(index.index_columns).to eq(['created_at'])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'foreign keys' do
|
78
|
+
it do
|
79
|
+
foreign_key = subject['paper_authors'].foreign_keys['paper_authors_author_id_fk']
|
80
|
+
expect(foreign_key).not_to be_nil
|
81
|
+
expect(foreign_key.from_columns).to eq(['author_id'])
|
82
|
+
expect(foreign_key.to_table).to eq('authors')
|
83
|
+
expect(foreign_key.to_columns).to eq(['id'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|