convergence 1.0.6 → 1.2.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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -4
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +57 -0
  5. data/CONTRIBUTING.md +80 -0
  6. data/Gemfile.lock +25 -9
  7. data/README.md +160 -1
  8. data/Rakefile +45 -2
  9. data/convergence.gemspec +5 -1
  10. data/lib/convergence/cli.rb +10 -2
  11. data/lib/convergence/column.rb +3 -2
  12. data/lib/convergence/command/apply.rb +38 -9
  13. data/lib/convergence/command/dryrun.rb +5 -5
  14. data/lib/convergence/command/export.rb +19 -1
  15. data/lib/convergence/command.rb +6 -0
  16. data/lib/convergence/database_connector/postgres_connector.rb +25 -0
  17. data/lib/convergence/database_connector/sqlite_connector.rb +22 -0
  18. data/lib/convergence/database_connector.rb +6 -0
  19. data/lib/convergence/default_parameter/mysql_default_parameter.rb +1 -1
  20. data/lib/convergence/default_parameter/postgres_default_parameter.rb +57 -0
  21. data/lib/convergence/default_parameter/sqlite_default_parameter.rb +57 -0
  22. data/lib/convergence/default_parameter.rb +6 -0
  23. data/lib/convergence/diff.rb +46 -3
  24. data/lib/convergence/dsl.rb +18 -3
  25. data/lib/convergence/dumper/mysql_schema_dumper.rb +37 -9
  26. data/lib/convergence/dumper/postgres_schema_dumper.rb +193 -0
  27. data/lib/convergence/dumper/sqlite_schema_dumper.rb +132 -0
  28. data/lib/convergence/dumper.rb +113 -0
  29. data/lib/convergence/sql_generator/mysql_generator.rb +32 -3
  30. data/lib/convergence/sql_generator/postgres_generator.rb +270 -0
  31. data/lib/convergence/sql_generator/sqlite_generator.rb +192 -0
  32. data/lib/convergence/table.rb +3 -2
  33. data/lib/convergence/version.rb +1 -1
  34. data/spec/config/spec_database.yml +12 -0
  35. data/spec/convergence/config_spec.rb +75 -0
  36. data/spec/convergence/diff_spec.rb +291 -44
  37. data/spec/convergence/dsl_spec.rb +26 -0
  38. data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +25 -2
  39. data/spec/convergence/dumper/postgres_schema_dumper_spec.rb +103 -0
  40. data/spec/convergence/dumper/sqlite_schema_dumper_spec.rb +85 -0
  41. data/spec/convergence/dumper_spec.rb +110 -16
  42. data/spec/convergence/foreign_key_spec.rb +33 -0
  43. data/spec/convergence/index_spec.rb +52 -0
  44. data/spec/convergence/pretty_diff_spec.rb +85 -0
  45. data/spec/convergence/table_spec.rb +46 -0
  46. data/spec/fixtures/add_table_with_enum_set.schema +5 -0
  47. data/spec/fixtures/change_table_comment_to_paper.schema +2 -0
  48. data/spec/fixtures/execute_raw_sql.schema +28 -0
  49. data/spec/fixtures/postgres/add_columns_to_paper.schema +29 -0
  50. data/spec/fixtures/postgres/add_table.schema +32 -0
  51. data/spec/fixtures/postgres/change_comment_columns_to_paper.schema +28 -0
  52. data/spec/fixtures/postgres/change_table_comment_to_paper.schema +28 -0
  53. data/spec/fixtures/postgres/drop_foreign_key.schema +25 -0
  54. data/spec/fixtures/postgres/drop_table.schema +19 -0
  55. data/spec/fixtures/postgres/remove_columns_to_paper.schema +27 -0
  56. data/spec/fixtures/postgres/rename_column_on_paper.schema +28 -0
  57. data/spec/fixtures/postgres/rename_table.schema +28 -0
  58. data/spec/fixtures/postgres_test_db.sql +41 -0
  59. data/spec/fixtures/rename_column_to_author.schema +26 -0
  60. data/spec/fixtures/rename_table.schema +26 -0
  61. data/spec/fixtures/sqlite/add_columns_to_paper.schema +29 -0
  62. data/spec/fixtures/sqlite/add_table.schema +32 -0
  63. data/spec/fixtures/sqlite/change_columns_to_paper.schema +28 -0
  64. data/spec/fixtures/sqlite/drop_foreign_key.schema +25 -0
  65. data/spec/fixtures/sqlite/drop_table.schema +19 -0
  66. data/spec/fixtures/sqlite/remove_columns_to_paper.schema +27 -0
  67. data/spec/fixtures/sqlite/rename_column_on_paper.schema +28 -0
  68. data/spec/fixtures/sqlite/rename_table.schema +27 -0
  69. data/spec/fixtures/sqlite_test_db.sql +31 -0
  70. data/spec/fixtures/test_db.sql +10 -0
  71. data/spec/integrations/command_diff.rb +35 -0
  72. data/spec/integrations/command_dryrun.rb +59 -2
  73. data/spec/integrations/command_export.rb +28 -0
  74. data/spec/postgres_integrations/command_dryrun.rb +101 -0
  75. data/spec/spec_helper.rb +13 -0
  76. data/spec/sqlite_integrations/command_dryrun.rb +92 -0
  77. metadata +135 -7
@@ -64,6 +64,96 @@ describe Convergence::DSL do
64
64
  end
65
65
  end
66
66
 
67
+ context 'when a table is renamed' do
68
+ let(:renamed_table) do
69
+ Convergence::Table.new('simple_table_renamed', renamed_from: 'simple_table').tap do |t|
70
+ t.int('id', primary_key: true)
71
+ end
72
+ end
73
+ let(:from_db) do
74
+ { 'simple_table' => simple_table }
75
+ end
76
+ let(:to_db) do
77
+ { 'simple_table_renamed' => renamed_table }
78
+ end
79
+
80
+ subject { Convergence::Diff.new.diff(from_db, to_db) }
81
+
82
+ it 'should detect it as a rename, not add/remove' do
83
+ expect(subject[:rename_table]).to eq('simple_table' => 'simple_table_renamed')
84
+ expect(subject[:add_table]).to be_empty
85
+ expect(subject[:remove_table]).to be_empty
86
+ expect(subject[:change_table]).to be_empty
87
+ end
88
+ end
89
+
90
+ context 'when renamed_from references a table that does not exist' do
91
+ let(:renamed_table) do
92
+ Convergence::Table.new('simple_table2', renamed_from: 'nonexistent_table').tap do |t|
93
+ t.int('id', primary_key: true)
94
+ end
95
+ end
96
+ let(:from_db) do
97
+ { 'simple_table' => simple_table }
98
+ end
99
+ let(:to_db) do
100
+ { 'simple_table' => simple_table, 'simple_table2' => renamed_table }
101
+ end
102
+
103
+ subject { Convergence::Diff.new.diff(from_db, to_db) }
104
+
105
+ it 'should fall back to a normal add' do
106
+ expect(subject[:rename_table]).to be_empty
107
+ expect(subject[:add_table]['simple_table2']).not_to be_nil
108
+ end
109
+ end
110
+
111
+ context 'when the renamed_from target already exists (idempotent re-apply)' do
112
+ let(:renamed_table) do
113
+ Convergence::Table.new('simple_table2', renamed_from: 'simple_table').tap do |t|
114
+ t.int('id', primary_key: true)
115
+ end
116
+ end
117
+ let(:from_db) do
118
+ { 'simple_table' => simple_table, 'simple_table2' => simple_table2 }
119
+ end
120
+ let(:to_db) do
121
+ { 'simple_table' => simple_table, 'simple_table2' => renamed_table }
122
+ end
123
+
124
+ subject { Convergence::Diff.new.diff(from_db, to_db) }
125
+
126
+ it 'should be a no-op for the rename' do
127
+ expect(subject[:rename_table]).to be_empty
128
+ expect(subject[:add_table]).to be_empty
129
+ expect(subject[:remove_table]).to be_empty
130
+ expect(subject[:change_table]).to be_empty
131
+ end
132
+ end
133
+
134
+ context 'when multiple tables claim the same renamed_from (ambiguous)' do
135
+ let(:renamed_table1) do
136
+ Convergence::Table.new('simple_table_a', renamed_from: 'simple_table').tap do |t|
137
+ t.int('id', primary_key: true)
138
+ end
139
+ end
140
+ let(:renamed_table2) do
141
+ Convergence::Table.new('simple_table_b', renamed_from: 'simple_table').tap do |t|
142
+ t.int('id', primary_key: true)
143
+ end
144
+ end
145
+ let(:from_db) do
146
+ { 'simple_table' => simple_table }
147
+ end
148
+ let(:to_db) do
149
+ { 'simple_table_a' => renamed_table1, 'simple_table_b' => renamed_table2 }
150
+ end
151
+
152
+ it 'should raise an ArgumentError' do
153
+ expect { Convergence::Diff.new.diff(from_db, to_db) }.to raise_error(ArgumentError)
154
+ end
155
+ end
156
+
67
157
  context 'table column are changed' do
68
158
  let(:table_from) do
69
159
  Convergence::Table.new('table1').tap do |t|
@@ -104,6 +194,7 @@ describe Convergence::DSL do
104
194
  let(:table_to) do
105
195
  Convergence::Table.new('table1').tap do |t|
106
196
  t.int('id')
197
+ t.varchar('name', limit: 200, null: false)
107
198
  end
108
199
  end
109
200
 
@@ -131,134 +222,290 @@ describe Convergence::DSL do
131
222
  expect(included_after_option).to eq false
132
223
  end
133
224
  end
225
+
226
+ context 'all columns are renamed' do
227
+ let(:table_to) do
228
+ Convergence::Table.new('table1').tap do |t|
229
+ t.int('id_rename', primary_key: true, renamed_from: 'id')
230
+ t.varchar('name_rename', limit: 200, null: false, renamed_from: 'name')
231
+ end
232
+ end
233
+
234
+ it 'should detect it as a change_table with rename_column, not a drop+add' do
235
+ results = Convergence::Diff.new.diff({ 'table1' => table_from }, { 'table1' => table_to })
236
+ expect(results[:add_table]).to be_empty
237
+ expect(results[:remove_table]).to be_empty
238
+ expect(results[:change_table]).not_to be_empty
239
+ expect(results[:change_table]['table1'][:rename_column]).to eq('id' => 'id_rename', 'name' => 'name_rename')
240
+ end
241
+ end
134
242
  end
135
243
  end
136
244
 
137
245
  describe '#diff_table' do
138
- let(:table_from) do
139
- Convergence::Table.new('table1').tap do |t|
140
- t.int('id', primary_key: true)
141
- t.varchar('name', limit: 200, null: false)
246
+ subject(:results) { Convergence::Diff.new(**diff_options).diff_table(table_from, table_to) }
247
+ let(:diff_options) { {} }
142
248
 
143
- t.index('name')
144
- t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
249
+ context 'change auto_increment table option' do
250
+ let(:table_from) do
251
+ Convergence::Table.new('table1').tap do |t|
252
+ t.int('id', primary_key: true)
253
+ t.table_options = { auto_increment: 1 }
254
+ end
255
+ end
256
+ let(:table_to) do
257
+ Convergence::Table.new('table1').tap do |t|
258
+ t.int('id', primary_key: true)
259
+ t.table_options = { auto_increment: 5000 }
260
+ end
261
+ end
262
+
263
+ it 'detects the auto_increment change by default' do
264
+ expect(results[:change_table_option][:auto_increment]).to eq(5000)
265
+ end
266
+
267
+ context 'when ignore_auto_increment is enabled' do
268
+ let(:diff_options) { { ignore_auto_increment: true } }
269
+
270
+ it 'ignores the auto_increment change' do
271
+ expect(results[:change_table_option]).not_to have_key(:auto_increment)
272
+ end
145
273
  end
146
274
  end
147
275
 
148
276
  context 'change column options' do
277
+ let(:table_from) do
278
+ Convergence::Table.new('table1').tap do |t|
279
+ t.int('id', primary_key: true)
280
+ t.varchar('name', limit: 200, null: false)
281
+ t.datetime('created_at', default: -> { "CURRENT_TIMESTAMP" })
282
+ end
283
+ end
149
284
  let(:table_to) do
150
285
  Convergence::Table.new('table1').tap do |t|
151
286
  t.int('id', primary_key: true)
152
- t.varchar('name', limit: 300, null: true, unsigned: true)
153
-
154
- t.index('name')
287
+ t.varchar('name', limit: 300, null: true, unsigned: true) # option changed
288
+ t.datetime('created_at', default: -> { "CURRENT_TIMESTAMP" }) # option[:default] not changed
155
289
  end
156
290
  end
157
291
 
158
292
  it do
159
- results = Convergence::Diff.new.diff_table(table_from, table_to)
160
293
  expect(results[:change_column]['name']).not_to be_nil
161
294
  expect(results[:change_column]['name'][:limit]).to eq('300')
162
295
  expect(results[:change_column]['name'][:null]).to eq('true')
163
296
  expect(results[:change_column]['name'][:unsigned]).to eq('true')
297
+ expect(results[:change_column]['created_at']).to be_nil
298
+ end
299
+ end
300
+
301
+ context 'change enum/set column values' do
302
+ let(:table_from) do
303
+ Convergence::Table.new('table1').tap do |t|
304
+ t.int('id', primary_key: true)
305
+ t.enum('status', values: %w(Active Inactive))
306
+ end
307
+ end
308
+
309
+ context 'when values are identical' do
310
+ let(:table_to) do
311
+ Convergence::Table.new('table1').tap do |t|
312
+ t.int('id', primary_key: true)
313
+ t.enum('status', values: %w(Active Inactive))
314
+ end
315
+ end
316
+
317
+ it 'does not detect a change' do
318
+ expect(results[:change_column]['status']).to be_nil
319
+ end
320
+ end
321
+
322
+ context 'when only the letter case differs' do
323
+ let(:table_to) do
324
+ Convergence::Table.new('table1').tap do |t|
325
+ t.int('id', primary_key: true)
326
+ t.enum('status', values: %w(active inactive))
327
+ end
328
+ end
329
+
330
+ it 'detects the change since values are case-sensitive' do
331
+ expect(results[:change_column]['status']).not_to be_nil
332
+ expect(results[:change_column]['status'][:values]).to eq('["active", "inactive"]')
333
+ end
164
334
  end
165
335
  end
166
336
 
167
337
  context 'change column order' do
338
+ let(:table_from) do
339
+ Convergence::Table.new('table1').tap do |t|
340
+ t.int('id', primary_key: true)
341
+ t.varchar('name', limit: 200, null: false)
342
+ end
343
+ end
168
344
  let(:table_to) do
169
345
  Convergence::Table.new('table1').tap do |t|
170
- t.varchar('name', limit: 300, null: true)
346
+ t.varchar('name', limit: 200, null: false)
171
347
  t.int('id', primary_key: true)
172
-
173
- t.index('name')
174
348
  end
175
349
  end
176
350
 
177
351
  it do
178
- results = Convergence::Diff.new.diff_table(table_from, table_to)
179
- expect(results[:change_column]['name']).not_to be_nil
352
+ expect(results[:change_column]['name']).to be_nil
180
353
  expect(results[:change_column]['id']).not_to be_nil
181
354
  expect(results[:change_column]['id'][:after]).to eq('name')
182
355
  end
183
356
  end
184
357
 
185
- context 'remove index' do
358
+ context 'rename column' do
359
+ let(:table_from) do
360
+ Convergence::Table.new('table1').tap do |t|
361
+ t.int('id', primary_key: true)
362
+ t.varchar('name', limit: 200, null: false)
363
+ end
364
+ end
186
365
  let(:table_to) do
187
366
  Convergence::Table.new('table1').tap do |t|
188
367
  t.int('id', primary_key: true)
189
- t.varchar('name', limit: 300, null: true)
368
+ t.varchar('full_name', limit: 200, null: false, renamed_from: 'name')
190
369
  end
191
370
  end
192
371
 
193
- it do
194
- results = Convergence::Diff.new.diff_table(table_from, table_to)
195
- expect(results[:remove_index].values.first.index_columns).to eq(['name'])
372
+ it 'should detect it as a rename, not remove+add' do
373
+ expect(results[:rename_column]).to eq('name' => 'full_name')
374
+ expect(results[:remove_column]).to be_empty
375
+ expect(results[:add_column]).to be_empty
196
376
  end
197
377
  end
198
378
 
199
- context 'add index' do
379
+ context 'rename column together with an unrelated change on another column' do
380
+ let(:table_from) do
381
+ Convergence::Table.new('table1').tap do |t|
382
+ t.int('id', primary_key: true)
383
+ t.varchar('name', limit: 200, null: false)
384
+ end
385
+ end
200
386
  let(:table_to) do
201
387
  Convergence::Table.new('table1').tap do |t|
202
388
  t.int('id', primary_key: true)
203
- t.varchar('name', limit: 300, null: true)
389
+ t.varchar('full_name', limit: 300, null: false, renamed_from: 'name')
390
+ end
391
+ end
204
392
 
205
- t.index('id')
206
- t.index('name')
393
+ it 'should detect only the rename, deferring the other change to the next apply pass' do
394
+ expect(results[:rename_column]).to eq('name' => 'full_name')
395
+ expect(results[:change_column]).to be_empty
396
+ end
397
+ end
398
+
399
+ context 'rename the only column in a table' do
400
+ let(:table_from) do
401
+ Convergence::Table.new('table1').tap do |t|
402
+ t.int('id', primary_key: true)
403
+ end
404
+ end
405
+ let(:table_to) do
406
+ Convergence::Table.new('table1').tap do |t|
407
+ t.int('identifier', primary_key: true, renamed_from: 'id')
207
408
  end
208
409
  end
209
410
 
210
- it do
211
- results = Convergence::Diff.new.diff_table(table_from, table_to)
212
- expect(results[:add_index].values.first.index_columns).to eq(['id'])
411
+ it 'should not be misdetected as removing all columns' do
412
+ expect(results[:rename_column]).to eq('id' => 'identifier')
413
+ expect(results[:remove_column]).to be_empty
414
+ expect(results[:add_column]).to be_empty
213
415
  end
214
416
  end
215
417
 
216
- context 'remove foreign key' do
418
+ context 'remove index' do
419
+ let(:table_from) do
420
+ Convergence::Table.new('table1').tap do |t|
421
+ t.int('id', primary_key: true)
422
+ t.varchar('name', limit: 200, null: false)
423
+
424
+ t.index('name')
425
+ end
426
+ end
427
+ let(:table_to) do
428
+ Convergence::Table.new('table1').tap do |t|
429
+ t.int('id', primary_key: true)
430
+ t.varchar('name', limit: 200, null: false)
431
+ end
432
+ end
433
+
434
+ it { expect(results[:remove_index].values.first.index_columns).to eq(['name']) }
435
+ end
436
+
437
+ context 'add index' do
438
+ let(:table_from) do
439
+ Convergence::Table.new('table1').tap do |t|
440
+ t.int('id', primary_key: true)
441
+ t.varchar('name', limit: 200, null: false)
442
+ end
443
+ end
217
444
  let(:table_to) do
218
445
  Convergence::Table.new('table1').tap do |t|
219
446
  t.int('id', primary_key: true)
220
- t.varchar('name', limit: 300, null: true)
447
+ t.varchar('name', limit: 200, null: false)
221
448
 
222
449
  t.index('name')
223
450
  end
224
451
  end
225
452
 
226
- it do
227
- results = Convergence::Diff.new.diff_table(table_from, table_to)
228
- expect(results[:remove_foreign_key].values.first.from_columns).to eq(['id'])
453
+ it { expect(results[:add_index].values.first.index_columns).to eq(['name']) }
454
+ end
455
+
456
+ context 'remove foreign key' do
457
+ let(:table_from) do
458
+ Convergence::Table.new('table1').tap do |t|
459
+ t.int('id', primary_key: true)
460
+ t.varchar('name', limit: 200, null: false)
461
+
462
+ t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
463
+ end
464
+ end
465
+ let(:table_to) do
466
+ Convergence::Table.new('table1').tap do |t|
467
+ t.int('id', primary_key: true)
468
+ t.varchar('name', limit: 200, null: false)
469
+ end
229
470
  end
471
+
472
+ it { expect(results[:remove_foreign_key].values.first.from_columns).to eq(['id']) }
230
473
  end
231
474
 
232
475
  context 'add foreign key' do
476
+ let(:table_from) do
477
+ Convergence::Table.new('table1').tap do |t|
478
+ t.int('id', primary_key: true)
479
+ t.varchar('name', limit: 200, null: false)
480
+ end
481
+ end
233
482
  let(:table_to) do
234
483
  Convergence::Table.new('table1').tap do |t|
235
484
  t.int('id', primary_key: true)
236
- t.varchar('name', limit: 300, null: true)
485
+ t.varchar('name', limit: 200, null: false)
237
486
 
238
- t.index('name')
239
487
  t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
240
- t.foreign_key('id2', reference: 'ref_tables2', reference_column: 'ref_id2')
241
488
  end
242
489
  end
243
490
 
244
- it do
245
- results = Convergence::Diff.new.diff_table(table_from, table_to)
246
- expect(results[:add_foreign_key].values.first.from_columns).to eq(['id2'])
247
- end
491
+ it { expect(results[:add_foreign_key].values.first.from_columns).to eq(['id']) }
248
492
  end
249
493
 
250
494
  context 'change table options' do
495
+ let(:table_from) do
496
+ Convergence::Table.new('table1').tap do |t|
497
+ t.int('id', primary_key: true)
498
+ t.varchar('name', limit: 200, null: false)
499
+ end
500
+ end
251
501
  let(:table_to) do
252
502
  Convergence::Table.new('table1', engine: 'MyISAM').tap do |t|
253
- t.varchar('name', limit: 300, null: true)
254
503
  t.int('id', primary_key: true)
504
+ t.varchar('name', limit: 200, null: false)
255
505
  end
256
506
  end
257
507
 
258
- it do
259
- results = Convergence::Diff.new.diff_table(table_from, table_to)
260
- expect(results[:change_table_option][:engine]).to eq('MyISAM')
261
- end
508
+ it { expect(results[:change_table_option][:engine]).to eq('MyISAM') }
262
509
  end
263
510
  end
264
511
  end
@@ -89,4 +89,30 @@ describe Convergence::DSL do
89
89
  end
90
90
  end
91
91
  end
92
+
93
+ describe '#parse_dsl' do
94
+ let(:dsl_with_execute) do
95
+ <<-DSL
96
+ create_table "users" do |t|
97
+ t.int "id", primary_key: true, extra: "auto_increment"
98
+ end
99
+
100
+ execute "CREATE OR REPLACE VIEW active_users AS SELECT * FROM users"
101
+ execute "GRANT SELECT ON users TO readonly"
102
+ DSL
103
+ end
104
+
105
+ subject { Convergence::DSL.parse_dsl(dsl_with_execute, '') }
106
+
107
+ it 'should be able to parse tables' do
108
+ expect(subject.tables['users']).not_to be_nil
109
+ end
110
+
111
+ it 'should be able to collect raw sql statements in order' do
112
+ expect(subject.raw_sqls).to eq([
113
+ 'CREATE OR REPLACE VIEW active_users AS SELECT * FROM users',
114
+ 'GRANT SELECT ON users TO readonly'
115
+ ])
116
+ end
117
+ end
92
118
  end
@@ -37,14 +37,17 @@ describe Convergence::Dumper::MysqlSchemaDumper do
37
37
  expect(papers.columns['title1']).not_to be_nil
38
38
  expect(papers.columns['title2']).not_to be_nil
39
39
  expect(papers.columns['description']).not_to be_nil
40
+ expect(papers.columns['edition_number']).not_to be_nil
41
+ expect(papers.columns['published_at']).not_to be_nil
40
42
  end
41
43
 
42
44
  it 'should be dump columns in the correct order' do
43
45
  papers = subject['papers']
44
- expect(papers.columns.keys).to eq(%w(id slug title1 title2 description))
46
+ expect(papers.columns.keys)
47
+ .to eq(%w(id slug title1 title2 description edition_number published_at))
45
48
  end
46
49
 
47
- describe 'table options' do
50
+ describe 'table column options' do
48
51
  it 'should be dump primary key' do
49
52
  expect(subject['papers'].columns['id'].options[:primary_key]).to be_truthy
50
53
  end
@@ -69,6 +72,13 @@ describe Convergence::Dumper::MysqlSchemaDumper do
69
72
  it 'should be dump unsigned definition' do
70
73
  expect(subject['authors'].columns['age'].options[:unsigned]).to be_truthy
71
74
  end
75
+
76
+ it 'should be dump default' do
77
+ expect(subject['papers'].columns['edition_number'].options[:default]).to eq "0"
78
+ expect(subject['papers'].columns['published_at'].options[:default])
79
+ .to be_a(Proc)
80
+ .and have_attributes(call: "CURRENT_TIMESTAMP")
81
+ end
72
82
  end
73
83
  end
74
84
 
@@ -96,6 +106,19 @@ describe Convergence::Dumper::MysqlSchemaDumper do
96
106
  end
97
107
  end
98
108
 
109
+ describe 'enum/set columns' do
110
+ it 'should be dump enum values' do
111
+ expect(subject['enum_set_samples'].columns['status'].options[:values])
112
+ .to eq(%w(active inactive pending))
113
+ expect(subject['enum_set_samples'].columns['status'].options[:default]).to eq('active')
114
+ end
115
+
116
+ it 'should be dump set values, including values containing quotes' do
117
+ expect(subject['enum_set_samples'].columns['flags'].options[:values])
118
+ .to eq(['a', 'b', "it's tricky"])
119
+ end
120
+ end
121
+
99
122
  describe 'foreign keys' do
100
123
  it do
101
124
  foreign_key = subject['paper_authors'].foreign_keys['paper_authors_author_id_fk']
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'convergence/dumper/postgres_schema_dumper'
3
+ require 'convergence/database_connector'
4
+
5
+ describe Convergence::Dumper::PostgresSchemaDumper do
6
+ let(:connector) do
7
+ Convergence::DatabaseConnector.new(postgres_settings)
8
+ end
9
+ let(:dumper) do
10
+ Convergence::Dumper::PostgresSchemaDumper.new(connector)
11
+ end
12
+
13
+ describe '#dump' do
14
+ subject { dumper.dump }
15
+
16
+ it 'should be dump tables' do
17
+ expect(subject['papers']).not_to be_nil
18
+ expect(subject['authors']).not_to be_nil
19
+ expect(subject['paper_authors']).not_to be_nil
20
+ end
21
+
22
+ describe 'table options' do
23
+ it 'should be dump comment' do
24
+ expect(subject['papers'].table_options[:comment]).to eq('Paper')
25
+ end
26
+ end
27
+
28
+ describe 'table columns' do
29
+ it 'should be dump table columns' do
30
+ papers = subject['papers']
31
+ expect(papers.columns['id']).not_to be_nil
32
+ expect(papers.columns['slug']).not_to be_nil
33
+ expect(papers.columns['title1']).not_to be_nil
34
+ expect(papers.columns['title2']).not_to be_nil
35
+ expect(papers.columns['description']).not_to be_nil
36
+ expect(papers.columns['edition_number']).not_to be_nil
37
+ expect(papers.columns['published_at']).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)
43
+ .to eq(%w(id slug title1 title2 description edition_number published_at))
44
+ end
45
+
46
+ describe 'table column options' do
47
+ it 'should be dump primary key' do
48
+ expect(subject['papers'].columns['id'].options[:primary_key]).to be_truthy
49
+ end
50
+
51
+ it 'should be dump extra (identity columns as auto_increment)' do
52
+ expect(subject['papers'].columns['id'].options[:extra]).to eq('auto_increment')
53
+ end
54
+
55
+ it 'should be dump comment' do
56
+ expect(subject['papers'].columns['title1'].options[:comment]).to eq('Title 1')
57
+ end
58
+
59
+ it 'should be dump limit' do
60
+ expect(subject['papers'].columns['title1'].options[:limit]).to eq('300')
61
+ end
62
+
63
+ it 'should be dump not null definition' do
64
+ expect(subject['papers'].columns['title1'].options[:null]).to be_falsy
65
+ expect(subject['authors'].columns['created_at'].options[:null]).to be_truthy
66
+ end
67
+
68
+ it 'should be dump default' do
69
+ expect(subject['papers'].columns['edition_number'].options[:default]).to eq('0')
70
+ expect(subject['papers'].columns['published_at'].options[:default])
71
+ .to be_a(Proc)
72
+ .and have_attributes(call: 'CURRENT_TIMESTAMP')
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'indexes' do
78
+ it 'should be dump index of authors' do
79
+ index = subject['authors'].indexes['index_authors_on_created_at']
80
+ expect(index).not_to be_nil
81
+ expect(index.index_columns).to eq(['created_at'])
82
+ expect(index.options[:unique]).to eq(false)
83
+ end
84
+
85
+ it 'should be dump unique index of papers' do
86
+ index = subject['papers'].indexes['index_papers_on_slug']
87
+ expect(index).not_to be_nil
88
+ expect(index.index_columns).to eq(['slug'])
89
+ expect(index.options[:unique]).to eq(true)
90
+ end
91
+ end
92
+
93
+ describe 'foreign keys' do
94
+ it do
95
+ foreign_key = subject['paper_authors'].foreign_keys['paper_authors_author_id_fk']
96
+ expect(foreign_key).not_to be_nil
97
+ expect(foreign_key.from_columns).to eq(['author_id'])
98
+ expect(foreign_key.to_table).to eq('authors')
99
+ expect(foreign_key.to_columns).to eq(['id'])
100
+ end
101
+ end
102
+ end
103
+ end