activerecord 6.0.3.4 → 6.1.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +891 -695
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +5 -5
  7. data/lib/active_record/association_relation.rb +30 -12
  8. data/lib/active_record/associations.rb +118 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +44 -28
  11. data/lib/active_record/associations/association_scope.rb +19 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +22 -8
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  14. data/lib/active_record/associations/builder/association.rb +32 -5
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +72 -50
  28. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +11 -5
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -8
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +33 -8
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +152 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -96
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  90. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  91. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  94. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -58
  96. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  97. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  103. data/lib/active_record/connection_handling.rb +218 -71
  104. data/lib/active_record/core.rb +245 -61
  105. data/lib/active_record/database_configurations.rb +124 -85
  106. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  107. data/lib/active_record/database_configurations/database_config.rb +52 -9
  108. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  109. data/lib/active_record/database_configurations/url_config.rb +15 -40
  110. data/lib/active_record/delegated_type.rb +209 -0
  111. data/lib/active_record/destroy_association_async_job.rb +36 -0
  112. data/lib/active_record/enum.rb +82 -38
  113. data/lib/active_record/errors.rb +47 -12
  114. data/lib/active_record/explain.rb +9 -4
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +10 -17
  117. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  118. data/lib/active_record/fixture_set/render_context.rb +1 -1
  119. data/lib/active_record/fixture_set/table_row.rb +2 -2
  120. data/lib/active_record/fixtures.rb +58 -9
  121. data/lib/active_record/gem_version.rb +3 -3
  122. data/lib/active_record/inheritance.rb +40 -18
  123. data/lib/active_record/insert_all.rb +35 -6
  124. data/lib/active_record/integration.rb +3 -5
  125. data/lib/active_record/internal_metadata.rb +16 -7
  126. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  127. data/lib/active_record/locking/optimistic.rb +33 -17
  128. data/lib/active_record/locking/pessimistic.rb +6 -2
  129. data/lib/active_record/log_subscriber.rb +27 -8
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/migration/command_recorder.rb +47 -27
  135. data/lib/active_record/migration/compatibility.rb +68 -17
  136. data/lib/active_record/model_schema.rb +117 -13
  137. data/lib/active_record/nested_attributes.rb +2 -3
  138. data/lib/active_record/no_touching.rb +1 -1
  139. data/lib/active_record/persistence.rb +50 -45
  140. data/lib/active_record/query_cache.rb +15 -5
  141. data/lib/active_record/querying.rb +11 -6
  142. data/lib/active_record/railtie.rb +64 -44
  143. data/lib/active_record/railties/console_sandbox.rb +2 -4
  144. data/lib/active_record/railties/databases.rake +276 -99
  145. data/lib/active_record/readonly_attributes.rb +4 -0
  146. data/lib/active_record/reflection.rb +71 -57
  147. data/lib/active_record/relation.rb +96 -67
  148. data/lib/active_record/relation/batches.rb +38 -31
  149. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  150. data/lib/active_record/relation/calculations.rb +101 -44
  151. data/lib/active_record/relation/delegation.rb +2 -1
  152. data/lib/active_record/relation/finder_methods.rb +45 -15
  153. data/lib/active_record/relation/from_clause.rb +1 -1
  154. data/lib/active_record/relation/merger.rb +27 -25
  155. data/lib/active_record/relation/predicate_builder.rb +59 -38
  156. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  157. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  158. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  159. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  160. data/lib/active_record/relation/query_methods.rb +333 -195
  161. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  162. data/lib/active_record/relation/spawn_methods.rb +8 -7
  163. data/lib/active_record/relation/where_clause.rb +104 -57
  164. data/lib/active_record/result.rb +41 -33
  165. data/lib/active_record/runtime_registry.rb +2 -2
  166. data/lib/active_record/sanitization.rb +6 -17
  167. data/lib/active_record/schema_dumper.rb +34 -4
  168. data/lib/active_record/schema_migration.rb +2 -8
  169. data/lib/active_record/scoping/named.rb +6 -17
  170. data/lib/active_record/secure_token.rb +16 -8
  171. data/lib/active_record/serialization.rb +5 -3
  172. data/lib/active_record/signed_id.rb +116 -0
  173. data/lib/active_record/statement_cache.rb +20 -4
  174. data/lib/active_record/store.rb +2 -2
  175. data/lib/active_record/suppressor.rb +2 -2
  176. data/lib/active_record/table_metadata.rb +42 -51
  177. data/lib/active_record/tasks/database_tasks.rb +140 -113
  178. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  179. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  180. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  181. data/lib/active_record/test_databases.rb +5 -4
  182. data/lib/active_record/test_fixtures.rb +37 -16
  183. data/lib/active_record/timestamp.rb +4 -6
  184. data/lib/active_record/touch_later.rb +21 -21
  185. data/lib/active_record/transactions.rb +19 -66
  186. data/lib/active_record/type.rb +8 -1
  187. data/lib/active_record/type/serialized.rb +6 -2
  188. data/lib/active_record/type/time.rb +10 -0
  189. data/lib/active_record/type_caster/connection.rb +0 -1
  190. data/lib/active_record/type_caster/map.rb +8 -5
  191. data/lib/active_record/validations.rb +1 -0
  192. data/lib/active_record/validations/numericality.rb +35 -0
  193. data/lib/active_record/validations/uniqueness.rb +24 -4
  194. data/lib/arel.rb +5 -13
  195. data/lib/arel/attributes/attribute.rb +4 -0
  196. data/lib/arel/collectors/bind.rb +5 -0
  197. data/lib/arel/collectors/composite.rb +8 -0
  198. data/lib/arel/collectors/sql_string.rb +7 -0
  199. data/lib/arel/collectors/substitute_binds.rb +7 -0
  200. data/lib/arel/nodes.rb +3 -1
  201. data/lib/arel/nodes/binary.rb +82 -8
  202. data/lib/arel/nodes/bind_param.rb +8 -0
  203. data/lib/arel/nodes/casted.rb +21 -9
  204. data/lib/arel/nodes/equality.rb +6 -9
  205. data/lib/arel/nodes/grouping.rb +3 -0
  206. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  207. data/lib/arel/nodes/in.rb +8 -1
  208. data/lib/arel/nodes/infix_operation.rb +13 -1
  209. data/lib/arel/nodes/join_source.rb +1 -1
  210. data/lib/arel/nodes/node.rb +7 -6
  211. data/lib/arel/nodes/ordering.rb +27 -0
  212. data/lib/arel/nodes/sql_literal.rb +3 -0
  213. data/lib/arel/nodes/table_alias.rb +7 -3
  214. data/lib/arel/nodes/unary.rb +0 -1
  215. data/lib/arel/predications.rb +12 -18
  216. data/lib/arel/select_manager.rb +1 -2
  217. data/lib/arel/table.rb +13 -5
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel/visitors/dot.rb +14 -2
  220. data/lib/arel/visitors/mysql.rb +11 -1
  221. data/lib/arel/visitors/postgresql.rb +15 -4
  222. data/lib/arel/visitors/to_sql.rb +89 -78
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  225. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  226. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  227. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  228. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  229. metadata +25 -26
  230. data/lib/active_record/advisory_lock_base.rb +0 -18
  231. data/lib/active_record/attribute_decorators.rb +0 -88
  232. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  233. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  234. data/lib/active_record/define_callbacks.rb +0 -22
  235. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  236. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  237. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  238. data/lib/arel/attributes.rb +0 -22
  239. data/lib/arel/visitors/depth_first.rb +0 -203
  240. data/lib/arel/visitors/ibm_db.rb +0 -34
  241. data/lib/arel/visitors/informix.rb +0 -62
  242. data/lib/arel/visitors/mssql.rb +0 -156
  243. data/lib/arel/visitors/oracle.rb +0 -158
  244. data/lib/arel/visitors/oracle12.rb +0 -65
  245. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -13,9 +13,9 @@ module ActiveRecord
13
13
  end
14
14
 
15
15
  def column_spec_for_primary_key(column)
16
- return {} if default_primary_key?(column)
17
- spec = { id: schema_type(column).inspect }
18
- spec.merge!(prepare_column_options(column).except!(:null, :comment))
16
+ spec = {}
17
+ spec[:id] = schema_type(column).inspect unless default_primary_key?(column)
18
+ spec.merge!(prepare_column_options(column).except!(:null))
19
19
  spec[:default] ||= "nil" if explicit_primary_key_default?(column)
20
20
  spec
21
21
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record/migration/join_table"
4
3
  require "active_support/core_ext/string/access"
5
- require "active_support/deprecation"
6
4
  require "digest/sha2"
7
5
 
8
6
  module ActiveRecord
@@ -98,10 +96,14 @@ module ActiveRecord
98
96
  # # Check an index with a custom name exists
99
97
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
100
98
  #
101
- def index_exists?(table_name, column_name, options = {})
102
- column_names = Array(column_name).map(&:to_s)
99
+ def index_exists?(table_name, column_name, **options)
103
100
  checks = []
104
- checks << lambda { |i| Array(i.columns) == column_names }
101
+
102
+ if column_name.present?
103
+ column_names = Array(column_name).map(&:to_s)
104
+ checks << lambda { |i| Array(i.columns) == column_names }
105
+ end
106
+
105
107
  checks << lambda { |i| i.unique } if options[:unique]
106
108
  checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
107
109
 
@@ -291,37 +293,42 @@ module ActiveRecord
291
293
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
292
294
  #
293
295
  # See also TableDefinition#column for details on how to create columns.
294
- def create_table(table_name, **options)
295
- td = create_table_definition(table_name, **options)
296
+ def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
297
+ td = create_table_definition(table_name, **extract_table_options!(options))
296
298
 
297
- if options[:id] != false && !options[:as]
298
- pk = options.fetch(:primary_key) do
299
- Base.get_primary_key table_name.to_s.singularize
299
+ if id && !td.as
300
+ pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
301
+
302
+ if id.is_a?(Hash)
303
+ options.merge!(id.except(:type))
304
+ id = id.fetch(:type, :primary_key)
300
305
  end
301
306
 
302
307
  if pk.is_a?(Array)
303
308
  td.primary_keys pk
304
309
  else
305
- td.primary_key pk, options.fetch(:id, :primary_key), **options.except(:comment)
310
+ td.primary_key pk, id, **options
306
311
  end
307
312
  end
308
313
 
309
314
  yield td if block_given?
310
315
 
311
- if options[:force]
312
- drop_table(table_name, **options, if_exists: true)
316
+ if force
317
+ drop_table(table_name, force: force, if_exists: true)
318
+ else
319
+ schema_cache.clear_data_source_cache!(table_name.to_s)
313
320
  end
314
321
 
315
322
  result = execute schema_creation.accept td
316
323
 
317
324
  unless supports_indexes_in_create?
318
325
  td.indexes.each do |column_name, index_options|
319
- add_index(table_name, column_name, index_options)
326
+ add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists)
320
327
  end
321
328
  end
322
329
 
323
330
  if supports_comments? && !supports_comments_in_create?
324
- if table_comment = options[:comment].presence
331
+ if table_comment = td.comment.presence
325
332
  change_table_comment(table_name, table_comment)
326
333
  end
327
334
 
@@ -420,6 +427,12 @@ module ActiveRecord
420
427
  # t.column :name, :string, limit: 60
421
428
  # end
422
429
  #
430
+ # ====== Change type of a column
431
+ #
432
+ # change_table(:suppliers) do |t|
433
+ # t.change :metadata, :json
434
+ # end
435
+ #
423
436
  # ====== Add 2 integer columns
424
437
  #
425
438
  # change_table(:suppliers) do |t|
@@ -499,6 +512,7 @@ module ActiveRecord
499
512
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
500
513
  # In that case, +options+ and the block will be used by #create_table.
501
514
  def drop_table(table_name, **options)
515
+ schema_cache.clear_data_source_cache!(table_name.to_s)
502
516
  execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
503
517
  end
504
518
 
@@ -534,6 +548,9 @@ module ActiveRecord
534
548
  # column will have the same collation as the table.
535
549
  # * <tt>:comment</tt> -
536
550
  # Specifies the comment for the column. This option is ignored by some backends.
551
+ # * <tt>:if_not_exists</tt> -
552
+ # Specifies if the column already exists to not try to re-add it. This will avoid
553
+ # duplicate column errors.
537
554
  #
538
555
  # Note: The precision is the total number of significant digits,
539
556
  # and the scale is the number of digits that can be stored following
@@ -554,8 +571,6 @@ module ActiveRecord
554
571
  # but the maximum supported <tt>:precision</tt> is 16. No default.
555
572
  # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
556
573
  # Default is (38,0).
557
- # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
558
- # Default unknown.
559
574
  # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
560
575
  # Default (38,0).
561
576
  #
@@ -585,20 +600,37 @@ module ActiveRecord
585
600
  # # Defines a column with a database-specific type.
586
601
  # add_column(:shapes, :triangle, 'polygon')
587
602
  # # ALTER TABLE "shapes" ADD "triangle" polygon
603
+ #
604
+ # # Ignores the method call if the column exists
605
+ # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
588
606
  def add_column(table_name, column_name, type, **options)
607
+ return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
608
+
589
609
  at = create_alter_table table_name
590
610
  at.add_column(column_name, type, **options)
591
611
  execute schema_creation.accept at
592
612
  end
593
613
 
614
+ def add_columns(table_name, *column_names, type:, **options) # :nodoc:
615
+ column_names.each do |column_name|
616
+ add_column(table_name, column_name, type, **options)
617
+ end
618
+ end
619
+
594
620
  # Removes the given columns from the table definition.
595
621
  #
596
622
  # remove_columns(:suppliers, :qualification, :experience)
597
623
  #
598
- def remove_columns(table_name, *column_names)
599
- raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
624
+ # +type+ and other column options can be passed to make migration reversible.
625
+ #
626
+ # remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
627
+ def remove_columns(table_name, *column_names, type: nil, **options)
628
+ if column_names.empty?
629
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
630
+ end
631
+
600
632
  column_names.each do |column_name|
601
- remove_column(table_name, column_name)
633
+ remove_column(table_name, column_name, type, **options)
602
634
  end
603
635
  end
604
636
 
@@ -610,7 +642,15 @@ module ActiveRecord
610
642
  # to provide these in a migration's +change+ method so it can be reverted.
611
643
  # In that case, +type+ and +options+ will be used by #add_column.
612
644
  # Indexes on the column are automatically removed.
645
+ #
646
+ # If the options provided include an +if_exists+ key, it will be used to check if the
647
+ # column does not exist. This will silently ignore the migration rather than raising
648
+ # if the column was already used.
649
+ #
650
+ # remove_column(:suppliers, :qualification, if_exists: true)
613
651
  def remove_column(table_name, column_name, type = nil, **options)
652
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
653
+
614
654
  execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}"
615
655
  end
616
656
 
@@ -620,7 +660,7 @@ module ActiveRecord
620
660
  # change_column(:suppliers, :name, :string, limit: 80)
621
661
  # change_column(:accounts, :description, :text)
622
662
  #
623
- def change_column(table_name, column_name, type, options = {})
663
+ def change_column(table_name, column_name, type, **options)
624
664
  raise NotImplementedError, "change_column is not implemented"
625
665
  end
626
666
 
@@ -682,7 +722,17 @@ module ActiveRecord
682
722
  #
683
723
  # generates:
684
724
  #
685
- # CREATE INDEX suppliers_name_index ON suppliers(name)
725
+ # CREATE INDEX index_suppliers_on_name ON suppliers(name)
726
+ #
727
+ # ====== Creating a index which already exists
728
+ #
729
+ # add_index(:suppliers, :name, if_not_exists: true)
730
+ #
731
+ # generates:
732
+ #
733
+ # CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
734
+ #
735
+ # Note: Not supported by MySQL.
686
736
  #
687
737
  # ====== Creating a unique index
688
738
  #
@@ -690,7 +740,7 @@ module ActiveRecord
690
740
  #
691
741
  # generates:
692
742
  #
693
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
743
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
694
744
  #
695
745
  # ====== Creating a named index
696
746
  #
@@ -720,7 +770,7 @@ module ActiveRecord
720
770
  #
721
771
  # ====== Creating an index with a sort order (desc or asc, asc is the default)
722
772
  #
723
- # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
773
+ # add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
724
774
  #
725
775
  # generates:
726
776
  #
@@ -736,7 +786,7 @@ module ActiveRecord
736
786
  #
737
787
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
738
788
  #
739
- # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
789
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite.
740
790
  #
741
791
  # ====== Creating an index with a specific method
742
792
  #
@@ -782,9 +832,11 @@ module ActiveRecord
782
832
  # Concurrently adding an index is not supported in a transaction.
783
833
  #
784
834
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
785
- def add_index(table_name, column_name, options = {})
786
- index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, **options)
787
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
835
+ def add_index(table_name, column_name, **options)
836
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
837
+
838
+ create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
839
+ execute schema_creation.accept(create_index)
788
840
  end
789
841
 
790
842
  # Removes the given index from the table.
@@ -805,6 +857,15 @@ module ActiveRecord
805
857
  #
806
858
  # remove_index :accounts, name: :by_branch_party
807
859
  #
860
+ # Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
861
+ #
862
+ # remove_index :accounts, :branch_id, name: :by_branch_party
863
+ #
864
+ # Checks if the index exists before trying to remove it. Will silently ignore indexes that
865
+ # don't exist.
866
+ #
867
+ # remove_index :accounts, if_exists: true
868
+ #
808
869
  # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
809
870
  #
810
871
  # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
@@ -814,8 +875,11 @@ module ActiveRecord
814
875
  # Concurrently removing an index is not supported in a transaction.
815
876
  #
816
877
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
817
- def remove_index(table_name, options = {})
818
- index_name = index_name_for_remove(table_name, options)
878
+ def remove_index(table_name, column_name = nil, **options)
879
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
880
+
881
+ index_name = index_name_for_remove(table_name, column_name, options)
882
+
819
883
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
820
884
  end
821
885
 
@@ -826,6 +890,8 @@ module ActiveRecord
826
890
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
827
891
  #
828
892
  def rename_index(table_name, old_name, new_name)
893
+ old_name = old_name.to_s
894
+ new_name = new_name.to_s
829
895
  validate_index_length!(table_name, new_name)
830
896
 
831
897
  # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
@@ -867,7 +933,9 @@ module ActiveRecord
867
933
  # Add an appropriate index. Defaults to true.
868
934
  # See #add_index for usage of this option.
869
935
  # [<tt>:foreign_key</tt>]
870
- # Add an appropriate foreign key constraint. Defaults to false.
936
+ # Add an appropriate foreign key constraint. Defaults to false, pass true
937
+ # to add. In case the join table can't be inferred from the association
938
+ # pass <tt>:to_table</tt> with the appropriate table name.
871
939
  # [<tt>:polymorphic</tt>]
872
940
  # Whether an additional +_type+ column should be added. Defaults to false.
873
941
  # [<tt>:null</tt>]
@@ -899,7 +967,7 @@ module ActiveRecord
899
967
  #
900
968
  # ====== Create a supplier_id column and a foreign key to the firms table
901
969
  #
902
- # add_reference(:products, :supplier, foreign_key: {to_table: :firms})
970
+ # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
903
971
  #
904
972
  def add_reference(table_name, ref_name, **options)
905
973
  ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
@@ -1061,6 +1129,60 @@ module ActiveRecord
1061
1129
  options
1062
1130
  end
1063
1131
 
1132
+ # Returns an array of check constraints for the given table.
1133
+ # The check constraints are represented as CheckConstraintDefinition objects.
1134
+ def check_constraints(table_name)
1135
+ raise NotImplementedError
1136
+ end
1137
+
1138
+ # Adds a new check constraint to the table. +expression+ is a String
1139
+ # representation of verifiable boolean condition.
1140
+ #
1141
+ # add_check_constraint :products, "price > 0", name: "price_check"
1142
+ #
1143
+ # generates:
1144
+ #
1145
+ # ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
1146
+ #
1147
+ # The +options+ hash can include the following keys:
1148
+ # [<tt>:name</tt>]
1149
+ # The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
1150
+ # [<tt>:validate</tt>]
1151
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1152
+ def add_check_constraint(table_name, expression, **options)
1153
+ return unless supports_check_constraints?
1154
+
1155
+ options = check_constraint_options(table_name, expression, options)
1156
+ at = create_alter_table(table_name)
1157
+ at.add_check_constraint(expression, options)
1158
+
1159
+ execute schema_creation.accept(at)
1160
+ end
1161
+
1162
+ def check_constraint_options(table_name, expression, options) # :nodoc:
1163
+ options = options.dup
1164
+ options[:name] ||= check_constraint_name(table_name, expression: expression, **options)
1165
+ options
1166
+ end
1167
+
1168
+ # Removes the given check constraint from the table.
1169
+ #
1170
+ # remove_check_constraint :products, name: "price_check"
1171
+ #
1172
+ # The +expression+ parameter will be ignored if present. It can be helpful
1173
+ # to provide this in a migration's +change+ method so it can be reverted.
1174
+ # In that case, +expression+ will be used by #add_check_constraint.
1175
+ def remove_check_constraint(table_name, expression = nil, **options)
1176
+ return unless supports_check_constraints?
1177
+
1178
+ chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
1179
+
1180
+ at = create_alter_table(table_name)
1181
+ at.drop_check_constraint(chk_name_to_delete)
1182
+
1183
+ execute schema_creation.accept(at)
1184
+ end
1185
+
1064
1186
  def dump_schema_information # :nodoc:
1065
1187
  versions = schema_migration.all_versions
1066
1188
  insert_versions_sql(versions) if versions.any?
@@ -1070,13 +1192,7 @@ module ActiveRecord
1070
1192
  { primary_key: true }
1071
1193
  end
1072
1194
 
1073
- def assume_migrated_upto_version(version, migrations_paths = nil)
1074
- unless migrations_paths.nil?
1075
- ActiveSupport::Deprecation.warn(<<~MSG.squish)
1076
- Passing migrations_paths to #assume_migrated_upto_version is deprecated and will be removed in Rails 6.1.
1077
- MSG
1078
- end
1079
-
1195
+ def assume_migrated_upto_version(version)
1080
1196
  version = version.to_i
1081
1197
  sm_table = quote_table_name(schema_migration.table_name)
1082
1198
 
@@ -1169,36 +1285,43 @@ module ActiveRecord
1169
1285
  Table.new(table_name, base)
1170
1286
  end
1171
1287
 
1172
- def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1173
- column_names = index_column_names(column_name)
1288
+ def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1289
+ options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
1174
1290
 
1175
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
1291
+ column_names = index_column_names(column_name)
1176
1292
 
1177
- index_type = options[:type].to_s if options.key?(:type)
1178
- index_type ||= options[:unique] ? "UNIQUE" : ""
1179
- index_name = options[:name].to_s if options.key?(:name)
1293
+ index_name = name&.to_s
1180
1294
  index_name ||= index_name(table_name, column_names)
1181
1295
 
1182
- if options.key?(:algorithm)
1183
- algorithm = index_algorithms.fetch(options[:algorithm]) {
1184
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
1185
- }
1186
- end
1187
-
1188
- using = "USING #{options[:using]}" if options[:using].present?
1189
-
1190
- if supports_partial_index?
1191
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
1192
- end
1296
+ validate_index_length!(table_name, index_name, internal)
1297
+
1298
+ index = IndexDefinition.new(
1299
+ table_name, index_name,
1300
+ options[:unique],
1301
+ column_names,
1302
+ lengths: options[:length] || {},
1303
+ orders: options[:order] || {},
1304
+ opclasses: options[:opclass] || {},
1305
+ where: options[:where],
1306
+ type: options[:type],
1307
+ using: options[:using],
1308
+ comment: options[:comment]
1309
+ )
1310
+
1311
+ [index, index_algorithm(options[:algorithm]), if_not_exists]
1312
+ end
1193
1313
 
1194
- validate_index_length!(table_name, index_name, options.fetch(:internal, false))
1314
+ def index_algorithm(algorithm) # :nodoc:
1315
+ index_algorithms.fetch(algorithm) do
1316
+ raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}"
1317
+ end if algorithm
1318
+ end
1195
1319
 
1196
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
1197
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1320
+ def quoted_columns_for_index(column_names, options) # :nodoc:
1321
+ quoted_columns = column_names.each_with_object({}) do |name, result|
1322
+ result[name.to_sym] = quote_column_name(name).dup
1198
1323
  end
1199
- index_columns = quoted_columns_for_index(column_names, **options).join(", ")
1200
-
1201
- [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1324
+ add_options_for_index_columns(quoted_columns, **options).values.join(", ")
1202
1325
  end
1203
1326
 
1204
1327
  def options_include_default?(options)
@@ -1259,25 +1382,20 @@ module ActiveRecord
1259
1382
  quoted_columns
1260
1383
  end
1261
1384
 
1262
- def quoted_columns_for_index(column_names, **options)
1263
- return [column_names] if column_names.is_a?(String)
1264
-
1265
- quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]
1266
- add_options_for_index_columns(quoted_columns, **options).values
1267
- end
1268
-
1269
- def index_name_for_remove(table_name, options = {})
1270
- return options[:name] if can_remove_index_by_name?(options)
1385
+ def index_name_for_remove(table_name, column_name, options)
1386
+ return options[:name] if can_remove_index_by_name?(column_name, options)
1271
1387
 
1272
1388
  checks = []
1273
1389
 
1274
- if options.is_a?(Hash)
1275
- checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1276
- column_names = index_column_names(options[:column])
1390
+ if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1391
+ options[:name] = index_name(table_name, column_name)
1392
+ column_names = []
1277
1393
  else
1278
- column_names = index_column_names(options)
1394
+ column_names = index_column_names(column_name || options[:column])
1279
1395
  end
1280
1396
 
1397
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1398
+
1281
1399
  if column_names.present?
1282
1400
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1283
1401
  end
@@ -1322,14 +1440,18 @@ module ActiveRecord
1322
1440
  SchemaCreation.new(self)
1323
1441
  end
1324
1442
 
1325
- def create_table_definition(*args, **options)
1326
- TableDefinition.new(self, *args, **options)
1443
+ def create_table_definition(name, **options)
1444
+ TableDefinition.new(self, name, **options)
1327
1445
  end
1328
1446
 
1329
1447
  def create_alter_table(name)
1330
1448
  AlterTable.new create_table_definition(name)
1331
1449
  end
1332
1450
 
1451
+ def extract_table_options!(options)
1452
+ options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
1453
+ end
1454
+
1333
1455
  def fetch_type_metadata(sql_type)
1334
1456
  cast_type = lookup_cast_type(sql_type)
1335
1457
  SqlTypeMetadata.new(
@@ -1390,11 +1512,30 @@ module ActiveRecord
1390
1512
  end
1391
1513
  end
1392
1514
 
1393
- def validate_index_length!(table_name, new_name, internal = false)
1394
- max_index_length = internal ? index_name_length : allowed_index_name_length
1515
+ def check_constraint_name(table_name, **options)
1516
+ options.fetch(:name) do
1517
+ expression = options.fetch(:expression)
1518
+ identifier = "#{table_name}_#{expression}_chk"
1519
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1395
1520
 
1396
- if new_name.length > max_index_length
1397
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1521
+ "chk_rails_#{hashed_identifier}"
1522
+ end
1523
+ end
1524
+
1525
+ def check_constraint_for(table_name, **options)
1526
+ return unless supports_check_constraints?
1527
+ chk_name = check_constraint_name(table_name, **options)
1528
+ check_constraints(table_name).detect { |chk| chk.name == chk_name }
1529
+ end
1530
+
1531
+ def check_constraint_for!(table_name, expression: nil, **options)
1532
+ check_constraint_for(table_name, expression: expression, **options) ||
1533
+ raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}")
1534
+ end
1535
+
1536
+ def validate_index_length!(table_name, new_name, internal = false)
1537
+ if new_name.length > index_name_length
1538
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1398
1539
  end
1399
1540
  end
1400
1541
 
@@ -1407,8 +1548,8 @@ module ActiveRecord
1407
1548
  end
1408
1549
  alias :extract_new_comment_value :extract_new_default_value
1409
1550
 
1410
- def can_remove_index_by_name?(options)
1411
- options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
1551
+ def can_remove_index_by_name?(column_name, options)
1552
+ column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1412
1553
  end
1413
1554
 
1414
1555
  def bulk_change_table(table_name, operations)
@@ -1442,6 +1583,10 @@ module ActiveRecord
1442
1583
  schema_creation.accept(AddColumnDefinition.new(cd))
1443
1584
  end
1444
1585
 
1586
+ def rename_column_sql(table_name, column_name, new_column_name)
1587
+ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1588
+ end
1589
+
1445
1590
  def remove_column_for_alter(table_name, column_name, type = nil, **options)
1446
1591
  "DROP COLUMN #{quote_column_name(column_name)}"
1447
1592
  end