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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +32 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +88 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +40 -0
  11. data/Rakefile +34 -0
  12. data/bin/convergence +23 -0
  13. data/convergence-0.0.1.gem +0 -0
  14. data/convergence.gemspec +33 -0
  15. data/database.yml.example +5 -0
  16. data/lib/convergence.rb +24 -0
  17. data/lib/convergence/column.rb +36 -0
  18. data/lib/convergence/command.rb +56 -0
  19. data/lib/convergence/command/apply.rb +51 -0
  20. data/lib/convergence/command/diff.rb +26 -0
  21. data/lib/convergence/command/dryrun.rb +39 -0
  22. data/lib/convergence/command/export.rb +15 -0
  23. data/lib/convergence/config.rb +17 -0
  24. data/lib/convergence/database_connector.rb +25 -0
  25. data/lib/convergence/database_connector/mysql_connector.rb +27 -0
  26. data/lib/convergence/default_parameter.rb +32 -0
  27. data/lib/convergence/default_parameter/mysql_default_parameter.rb +159 -0
  28. data/lib/convergence/diff.rb +148 -0
  29. data/lib/convergence/dsl.rb +20 -0
  30. data/lib/convergence/dumper.rb +68 -0
  31. data/lib/convergence/dumper/mysql_schema_dumper.rb +149 -0
  32. data/lib/convergence/foreign_key.rb +11 -0
  33. data/lib/convergence/index.rb +9 -0
  34. data/lib/convergence/logger.rb +12 -0
  35. data/lib/convergence/module.rb +1 -0
  36. data/lib/convergence/pretty_diff.rb +55 -0
  37. data/lib/convergence/sql_generator.rb +2 -0
  38. data/lib/convergence/sql_generator/mysql_generator.rb +208 -0
  39. data/lib/convergence/table.rb +37 -0
  40. data/lib/convergence/version.rb +3 -0
  41. data/spec/config/spec_database.yml +6 -0
  42. data/spec/convergence/diff_spec.rb +242 -0
  43. data/spec/convergence/dsl_spec.rb +78 -0
  44. data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +87 -0
  45. data/spec/convergence/dumper_spec.rb +40 -0
  46. data/spec/convergence/table_spec.rb +106 -0
  47. data/spec/fixtures/add_columns_to_paper.schema +25 -0
  48. data/spec/fixtures/add_table.schema +28 -0
  49. data/spec/fixtures/change_comment_columns_to_paper.schema +24 -0
  50. data/spec/fixtures/change_table_comment_to_paper.schema +24 -0
  51. data/spec/fixtures/drop_table.schema +15 -0
  52. data/spec/fixtures/remove_columns_to_paper.schema +23 -0
  53. data/spec/fixtures/test_db.sql +27 -0
  54. data/spec/integrations/command_dryrun.rb +73 -0
  55. data/spec/spec_helper.rb +18 -0
  56. 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,3 @@
1
+ module Convergence
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,6 @@
1
+ mysql:
2
+ adapter: mysql
3
+ database: convergence_test_db
4
+ host: localhost
5
+ port: 3389
6
+ username: root
@@ -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