ridgepole 1.0.1 → 3.1.5

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.
@@ -4,6 +4,10 @@ module Ridgepole
4
4
  class Delta
5
5
  SCRIPT_NAME = '<Schema>'
6
6
 
7
+ RIDGEPOLE_OPTIONS = %i[
8
+ renamed_from
9
+ ].freeze
10
+
7
11
  def initialize(delta, options = {})
8
12
  @delta = delta
9
13
  @options = options
@@ -70,8 +74,8 @@ module Ridgepole
70
74
  ActiveRecord::Migration.disable_logging = true
71
75
  buf = StringIO.new
72
76
 
73
- callback = proc do |sql, _name|
74
- buf.puts sql if sql =~ /\A(CREATE|ALTER|DROP|RENAME)\b/i
77
+ callback = proc do |sql|
78
+ buf.puts sql if sql =~ /\A(CREATE|ALTER|DROP|RENAME|COMMENT)\b/i
75
79
  end
76
80
 
77
81
  eval_script_block = proc do
@@ -235,13 +239,39 @@ create_table(#{table_name.inspect}, #{inspect_options_include_default_proc(optio
235
239
  normalize_limit(column_type, column_options)
236
240
 
237
241
  buf.puts(<<-RUBY)
238
- t.column(#{column_name.inspect}, :#{column_type.to_s.inspect}, #{inspect_options_include_default_proc(column_options)})
242
+ t.column(#{column_name.inspect}, :#{column_type.to_s.inspect}, #{inspect_options_include_default_proc(normalize_options(column_options))})
239
243
  RUBY
240
244
  end
241
245
 
242
- if @options[:create_table_with_index] && !indices.empty?
243
- indices.each do |index_name, index_attrs|
244
- append_add_index(table_name, index_name, index_attrs, buf, true)
246
+ # Partition indices: indices referencing auto_increment columns must be
247
+ # inside CREATE TABLE on MySQL to avoid "must be defined as a key" error.
248
+ # cf. https://github.com/ridgepole/ridgepole/issues/494
249
+ if @options[:create_table_with_index]
250
+ indices_in_create = indices
251
+ indices_after_create = {}
252
+ else
253
+ indices_in_create, indices_after_create = partition_indices_for_create(definition, indices)
254
+ end
255
+
256
+ indices_in_create.each do |index_name, index_attrs|
257
+ append_add_index(table_name, index_name, index_attrs, buf, true)
258
+ end
259
+
260
+ unless (check_constraints = attrs[:check_constraints] || {}).empty?
261
+ check_constraints.each_value do |check_constraint_attrs|
262
+ append_add_check_constraint(table_name, check_constraint_attrs, buf, true)
263
+ end
264
+ end
265
+
266
+ unless (exclusion_constraints = attrs[:exclusion_constraints] || {}).empty?
267
+ exclusion_constraints.each_value do |exclusion_constraint_attrs|
268
+ append_add_exclusion_constraint(table_name, exclusion_constraint_attrs, buf, true)
269
+ end
270
+ end
271
+
272
+ unless (unique_constraints = attrs[:unique_constraints] || {}).empty?
273
+ unique_constraints.each_value do |unique_constraint_attrs|
274
+ append_add_unique_constraint(table_name, unique_constraint_attrs, buf, true)
245
275
  end
246
276
  end
247
277
 
@@ -249,16 +279,16 @@ create_table(#{table_name.inspect}, #{inspect_options_include_default_proc(optio
249
279
  end
250
280
  RUBY
251
281
 
252
- if !(@options[:create_table_with_index]) && !indices.empty?
282
+ unless indices_after_create.empty?
253
283
  append_change_table(table_name, buf) do
254
- indices.each do |index_name, index_attrs|
284
+ indices_after_create.each do |index_name, index_attrs|
255
285
  append_add_index(table_name, index_name, index_attrs, buf)
256
286
  end
257
287
  end
258
288
  end
259
289
 
260
290
  unless (foreign_keys = attrs[:foreign_keys] || {}).empty?
261
- foreign_keys.each do |_, foreign_key_attrs|
291
+ foreign_keys.each_value do |foreign_key_attrs|
262
292
  append_add_foreign_key(table_name, foreign_key_attrs, post_buf_for_fk, @options)
263
293
  end
264
294
  end
@@ -267,6 +297,30 @@ end
267
297
  post_buf_for_fk.puts
268
298
  end
269
299
 
300
+ def partition_indices_for_create(definition, indices)
301
+ return [{}, indices] unless Ridgepole::ConnectionAdapters.mysql?
302
+
303
+ auto_increment_columns = definition.select do |_col_name, col_attrs|
304
+ col_attrs.dig(:options, :auto_increment)
305
+ end.keys
306
+
307
+ return [{}, indices] if auto_increment_columns.empty?
308
+
309
+ in_create = {}
310
+ after_create = {}
311
+
312
+ indices.each do |idx_name, idx_attrs|
313
+ columns = Array(idx_attrs[:column_name])
314
+ if (columns & auto_increment_columns).any?
315
+ in_create[idx_name] = idx_attrs
316
+ else
317
+ after_create[idx_name] = idx_attrs
318
+ end
319
+ end
320
+
321
+ [in_create, after_create]
322
+ end
323
+
270
324
  def append_rename_table(to_table_name, from_table_name, buf)
271
325
  buf.puts(<<-RUBY)
272
326
  rename_table(#{from_table_name.inspect}, #{to_table_name.inspect})
@@ -286,14 +340,14 @@ drop_table(#{table_name.inspect})
286
340
  def append_change_table_options(table_name, table_options, buf)
287
341
  # XXX: MySQL only
288
342
  buf.puts(<<-RUBY)
289
- execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} #{table_options}"
343
+ execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} #{table_options.dump[1..-2]}"
290
344
  RUBY
291
345
 
292
346
  buf.puts
293
347
  end
294
348
 
295
349
  def append_change_table_raw_options(table_name, raw_table_options, table_charset, table_collation, buf)
296
- if raw_table_options.blank? && ActiveRecord.gem_version >= Gem::Version.new('6.1.0')
350
+ if raw_table_options.blank?
297
351
  # Implicit engine is InnoDB in 6.1.0
298
352
  # related: https://github.com/rails/rails/pull/39365/files#diff-868f1dccfcbed26a288bf9f3fd8a39c863a4413ab0075e12b6805d9798f556d1R441
299
353
  raw_table_options = +'ENGINE=InnoDB'
@@ -306,8 +360,14 @@ execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name
306
360
  end
307
361
 
308
362
  def append_change_table_comment(table_name, table_comment, buf)
309
- comment_literal = "COMMENT=#{ActiveRecord::Base.connection.quote(table_comment)}"
310
- append_change_table_options(table_name, comment_literal, buf)
363
+ if Ridgepole::ConnectionAdapters.postgresql?
364
+ buf.puts(<<-RUBY)
365
+ change_table_comment(#{table_name.inspect}, #{table_comment.inspect})
366
+ RUBY
367
+ else
368
+ comment_literal = "COMMENT=#{ActiveRecord::Base.connection.quote(table_comment)}"
369
+ append_change_table_options(table_name, comment_literal, buf)
370
+ end
311
371
  end
312
372
 
313
373
  def append_change(table_name, attrs, buf, pre_buf_for_fk, post_buf_for_fk)
@@ -315,10 +375,14 @@ execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name
315
375
  primary_key_definition = attrs[:primary_key_definition] || {}
316
376
  indices = attrs[:indices] || {}
317
377
  foreign_keys = attrs[:foreign_keys] || {}
378
+ check_constraints = attrs[:check_constraints] || {}
379
+ exclusion_constraints = attrs[:exclusion_constraints] || {}
380
+ unique_constraints = attrs[:unique_constraints] || {}
318
381
  table_options = attrs[:table_options]
319
382
  table_charset = attrs[:table_charset]
320
383
  table_collation = attrs[:table_collation]
321
384
  table_comment = attrs[:table_comment]
385
+ column_comments = attrs[:column_comments] || {}
322
386
 
323
387
  if !definition.empty? || !indices.empty? || !primary_key_definition.empty?
324
388
  append_change_table(table_name, buf) do
@@ -330,16 +394,31 @@ execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name
330
394
  end
331
395
 
332
396
  append_change_foreign_keys(table_name, foreign_keys, pre_buf_for_fk, post_buf_for_fk, @options) unless foreign_keys.empty?
397
+ append_change_check_constraints(table_name, check_constraints, buf) unless check_constraints.empty?
398
+ append_change_exclusion_constraints(table_name, exclusion_constraints, buf) unless exclusion_constraints.empty?
399
+ append_change_unique_constraints(table_name, unique_constraints, buf) unless unique_constraints.empty?
333
400
 
334
- append_change_table_raw_options(table_name, table_options, table_charset, table_collation, buf) if table_options || table_charset || table_collation
401
+ if table_options || table_charset || table_collation
402
+ append_change_table_raw_options(table_name, table_options, table_charset, table_collation,
403
+ buf)
404
+ end
335
405
 
336
406
  append_change_table_comment(table_name, table_comment, buf) if table_comment
407
+ append_change_column_comments(table_name, column_comments, buf) unless column_comments.empty?
337
408
 
338
409
  buf.puts
339
410
  pre_buf_for_fk.puts
340
411
  post_buf_for_fk.puts
341
412
  end
342
413
 
414
+ def append_change_column_comments(table_name, column_comments, buf)
415
+ column_comments.each do |column_name, comment|
416
+ buf.puts(<<-RUBY)
417
+ change_column_comment(#{table_name.inspect}, #{column_name.inspect}, #{comment.inspect})
418
+ RUBY
419
+ end
420
+ end
421
+
343
422
  def append_change_table(table_name, buf)
344
423
  buf.puts "change_table(#{table_name.inspect}, bulk: true) do |t|" if @options[:bulk_change]
345
424
  yield
@@ -397,8 +476,15 @@ rename_column(#{table_name.inspect}, #{from_column_name.inspect}, #{to_column_na
397
476
  options = attrs[:options] || {}
398
477
 
399
478
  # Fix for https://github.com/rails/rails/commit/7f0567b43b73b1bd1a16bfac9cd32fcbf1321b51
400
- if Ridgepole::ConnectionAdapters.mysql? && ActiveRecord::VERSION::STRING !~ /\A5\.0\./
479
+ if Ridgepole::ConnectionAdapters.mysql?
401
480
  options[:comment] = nil unless options.key?(:comment)
481
+
482
+ # Generated/virtual columns cannot have DEFAULT values in MySQL.
483
+ # cf. https://github.com/ridgepole/ridgepole/issues/482
484
+ if type == :virtual
485
+ options.delete(:default)
486
+ options.delete(:unsigned)
487
+ end
402
488
  end
403
489
 
404
490
  if @options[:bulk_change]
@@ -475,11 +561,11 @@ remove_index(#{table_name.inspect}, #{target})
475
561
  end
476
562
 
477
563
  def append_change_foreign_keys(table_name, delta, pre_buf_for_fk, post_buf_for_fk, options)
478
- (delta[:delete] || {}).each do |_, attrs|
564
+ (delta[:delete] || {}).each_value do |attrs|
479
565
  append_remove_foreign_key(table_name, attrs, pre_buf_for_fk, options)
480
566
  end
481
567
 
482
- (delta[:add] || {}).each do |_, attrs|
568
+ (delta[:add] || {}).each_value do |attrs|
483
569
  append_add_foreign_key(table_name, attrs, post_buf_for_fk, options)
484
570
  end
485
571
  end
@@ -508,6 +594,108 @@ remove_foreign_key(#{table_name.inspect}, #{target})
508
594
  RUBY
509
595
  end
510
596
 
597
+ def append_change_check_constraints(table_name, delta, buf)
598
+ (delta[:delete] || {}).each_value do |attrs|
599
+ append_remove_check_constraint(table_name, attrs, buf)
600
+ end
601
+
602
+ (delta[:add] || {}).each_value do |attrs|
603
+ append_add_check_constraint(table_name, attrs, buf)
604
+ end
605
+ end
606
+
607
+ def append_add_check_constraint(table_name, attrs, buf, force_bulk_change = false)
608
+ expression = attrs.fetch(:expression)
609
+ attrs_options = attrs[:options] || {}
610
+
611
+ if force_bulk_change
612
+ buf.puts(<<-RUBY)
613
+ t.check_constraint(#{expression.inspect}, **#{attrs_options.inspect})
614
+ RUBY
615
+ else
616
+ buf.puts(<<-RUBY)
617
+ add_check_constraint(#{table_name.inspect}, #{expression.inspect}, **#{attrs_options.inspect})
618
+ RUBY
619
+ end
620
+ end
621
+
622
+ def append_remove_check_constraint(table_name, attrs, buf)
623
+ expression = attrs.fetch(:expression)
624
+ attrs_options = attrs[:options] || {}
625
+
626
+ buf.puts(<<-RUBY)
627
+ remove_check_constraint(#{table_name.inspect}, #{expression.inspect}, **#{attrs_options.inspect})
628
+ RUBY
629
+ end
630
+
631
+ def append_change_exclusion_constraints(table_name, delta, buf)
632
+ (delta[:delete] || {}).each_value do |attrs|
633
+ append_remove_exclusion_constraint(table_name, attrs, buf)
634
+ end
635
+
636
+ (delta[:add] || {}).each_value do |attrs|
637
+ append_add_exclusion_constraint(table_name, attrs, buf)
638
+ end
639
+ end
640
+
641
+ def append_add_exclusion_constraint(table_name, attrs, buf, force_bulk_change = false)
642
+ expression = attrs.fetch(:expression)
643
+ attrs_options = attrs[:options] || {}
644
+
645
+ if force_bulk_change
646
+ buf.puts(<<-RUBY)
647
+ t.exclusion_constraint(#{expression.inspect}, **#{attrs_options.inspect})
648
+ RUBY
649
+ else
650
+ buf.puts(<<-RUBY)
651
+ add_exclusion_constraint(#{table_name.inspect}, #{expression.inspect}, **#{attrs_options.inspect})
652
+ RUBY
653
+ end
654
+ end
655
+
656
+ def append_remove_exclusion_constraint(table_name, attrs, buf)
657
+ expression = attrs.fetch(:expression)
658
+ attrs_options = attrs[:options] || {}
659
+
660
+ buf.puts(<<-RUBY)
661
+ remove_exclusion_constraint(#{table_name.inspect}, #{expression.inspect}, **#{attrs_options.inspect})
662
+ RUBY
663
+ end
664
+
665
+ def append_change_unique_constraints(table_name, delta, buf)
666
+ (delta[:delete] || {}).each_value do |attrs|
667
+ append_remove_unique_constraint(table_name, attrs, buf)
668
+ end
669
+
670
+ (delta[:add] || {}).each_value do |attrs|
671
+ append_add_unique_constraint(table_name, attrs, buf)
672
+ end
673
+ end
674
+
675
+ def append_add_unique_constraint(table_name, attrs, buf, force_bulk_change = false)
676
+ column_name = attrs.fetch(:column_name)
677
+ attrs_options = attrs[:options] || {}
678
+
679
+ if force_bulk_change
680
+ buf.puts(<<-RUBY)
681
+ t.unique_constraint(#{column_name.inspect}, **#{attrs_options.inspect})
682
+ RUBY
683
+ else
684
+ buf.puts(<<-RUBY)
685
+ add_unique_constraint(#{table_name.inspect}, #{column_name.inspect}, **#{attrs_options.inspect})
686
+ RUBY
687
+ end
688
+ end
689
+
690
+ def append_remove_unique_constraint(table_name, attrs, buf)
691
+ column_name = attrs.fetch(:column_name)
692
+ attrs_options = attrs[:options] || {}
693
+
694
+ buf.puts(<<-RUBY)
695
+ remove_unique_constraint(#{table_name.inspect}, #{column_name.inspect}, **#{attrs_options.inspect})
696
+ RUBY
697
+ end
698
+
511
699
  def delta_execute
512
700
  @delta[:execute] || []
513
701
  end
@@ -517,6 +705,12 @@ remove_foreign_key(#{table_name.inspect}, #{target})
517
705
  column_options[:limit] ||= default_limit if default_limit
518
706
  end
519
707
 
708
+ def normalize_options(options)
709
+ opts = options.dup
710
+ RIDGEPOLE_OPTIONS.each { opts.delete(_1) }
711
+ opts
712
+ end
713
+
520
714
  def inspect_options_include_default_proc(options)
521
715
  options = options.dup
522
716