activerecord 6.1.7.6 → 7.0.8

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1570 -1016
  3. data/README.rdoc +3 -3
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +20 -22
  17. data/lib/active_record/associations/collection_proxy.rb +15 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +138 -100
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +8 -6
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +19 -22
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +14 -16
  47. data/lib/active_record/coders/yaml_column.rb +4 -8
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +52 -23
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +82 -25
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +144 -82
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +115 -85
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +37 -25
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -23
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +4 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  70. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -1
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -17
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/quoting.rb +76 -73
  83. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  86. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +40 -21
  88. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  89. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -106
  90. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +33 -18
  93. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -0
  94. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +19 -17
  95. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +98 -36
  96. data/lib/active_record/connection_adapters.rb +6 -5
  97. data/lib/active_record/connection_handling.rb +49 -55
  98. data/lib/active_record/core.rb +123 -148
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  100. data/lib/active_record/database_configurations/database_config.rb +12 -9
  101. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  102. data/lib/active_record/database_configurations/url_config.rb +2 -2
  103. data/lib/active_record/database_configurations.rb +15 -32
  104. data/lib/active_record/delegated_type.rb +53 -12
  105. data/lib/active_record/destroy_association_async_job.rb +1 -1
  106. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  107. data/lib/active_record/dynamic_matchers.rb +1 -1
  108. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  109. data/lib/active_record/encryption/cipher.rb +53 -0
  110. data/lib/active_record/encryption/config.rb +44 -0
  111. data/lib/active_record/encryption/configurable.rb +67 -0
  112. data/lib/active_record/encryption/context.rb +35 -0
  113. data/lib/active_record/encryption/contexts.rb +72 -0
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  116. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  118. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  119. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  120. data/lib/active_record/encryption/encryptor.rb +155 -0
  121. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  122. data/lib/active_record/encryption/errors.rb +15 -0
  123. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  124. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  125. data/lib/active_record/encryption/key.rb +28 -0
  126. data/lib/active_record/encryption/key_generator.rb +42 -0
  127. data/lib/active_record/encryption/key_provider.rb +46 -0
  128. data/lib/active_record/encryption/message.rb +33 -0
  129. data/lib/active_record/encryption/message_serializer.rb +90 -0
  130. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  131. data/lib/active_record/encryption/properties.rb +76 -0
  132. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  133. data/lib/active_record/encryption/scheme.rb +99 -0
  134. data/lib/active_record/encryption.rb +55 -0
  135. data/lib/active_record/enum.rb +50 -43
  136. data/lib/active_record/errors.rb +67 -4
  137. data/lib/active_record/explain_registry.rb +11 -6
  138. data/lib/active_record/explain_subscriber.rb +1 -1
  139. data/lib/active_record/fixture_set/file.rb +15 -1
  140. data/lib/active_record/fixture_set/table_row.rb +41 -6
  141. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  142. data/lib/active_record/fixtures.rb +20 -23
  143. data/lib/active_record/future_result.rb +139 -0
  144. data/lib/active_record/gem_version.rb +5 -5
  145. data/lib/active_record/inheritance.rb +55 -17
  146. data/lib/active_record/insert_all.rb +80 -14
  147. data/lib/active_record/integration.rb +4 -3
  148. data/lib/active_record/internal_metadata.rb +1 -5
  149. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  150. data/lib/active_record/locking/optimistic.rb +36 -21
  151. data/lib/active_record/locking/pessimistic.rb +10 -4
  152. data/lib/active_record/log_subscriber.rb +23 -7
  153. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  154. data/lib/active_record/middleware/database_selector.rb +18 -6
  155. data/lib/active_record/middleware/shard_selector.rb +60 -0
  156. data/lib/active_record/migration/command_recorder.rb +8 -9
  157. data/lib/active_record/migration/compatibility.rb +93 -46
  158. data/lib/active_record/migration/join_table.rb +1 -1
  159. data/lib/active_record/migration.rb +167 -87
  160. data/lib/active_record/model_schema.rb +58 -59
  161. data/lib/active_record/nested_attributes.rb +13 -12
  162. data/lib/active_record/no_touching.rb +3 -3
  163. data/lib/active_record/null_relation.rb +2 -6
  164. data/lib/active_record/persistence.rb +231 -61
  165. data/lib/active_record/query_cache.rb +2 -2
  166. data/lib/active_record/query_logs.rb +149 -0
  167. data/lib/active_record/querying.rb +16 -6
  168. data/lib/active_record/railtie.rb +136 -22
  169. data/lib/active_record/railties/controller_runtime.rb +4 -5
  170. data/lib/active_record/railties/databases.rake +78 -136
  171. data/lib/active_record/readonly_attributes.rb +11 -0
  172. data/lib/active_record/reflection.rb +80 -49
  173. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  174. data/lib/active_record/relation/batches.rb +6 -6
  175. data/lib/active_record/relation/calculations.rb +92 -60
  176. data/lib/active_record/relation/delegation.rb +7 -7
  177. data/lib/active_record/relation/finder_methods.rb +31 -35
  178. data/lib/active_record/relation/merger.rb +20 -13
  179. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  180. data/lib/active_record/relation/predicate_builder.rb +1 -6
  181. data/lib/active_record/relation/query_attribute.rb +28 -11
  182. data/lib/active_record/relation/query_methods.rb +304 -68
  183. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  184. data/lib/active_record/relation/spawn_methods.rb +2 -2
  185. data/lib/active_record/relation/where_clause.rb +10 -19
  186. data/lib/active_record/relation.rb +189 -88
  187. data/lib/active_record/result.rb +23 -11
  188. data/lib/active_record/runtime_registry.rb +9 -13
  189. data/lib/active_record/sanitization.rb +17 -12
  190. data/lib/active_record/schema.rb +38 -23
  191. data/lib/active_record/schema_dumper.rb +29 -19
  192. data/lib/active_record/schema_migration.rb +4 -4
  193. data/lib/active_record/scoping/default.rb +60 -13
  194. data/lib/active_record/scoping/named.rb +3 -11
  195. data/lib/active_record/scoping.rb +64 -34
  196. data/lib/active_record/serialization.rb +6 -1
  197. data/lib/active_record/signed_id.rb +3 -3
  198. data/lib/active_record/store.rb +2 -2
  199. data/lib/active_record/suppressor.rb +11 -15
  200. data/lib/active_record/table_metadata.rb +6 -2
  201. data/lib/active_record/tasks/database_tasks.rb +127 -60
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  204. data/lib/active_record/test_databases.rb +1 -1
  205. data/lib/active_record/test_fixtures.rb +9 -6
  206. data/lib/active_record/timestamp.rb +3 -4
  207. data/lib/active_record/transactions.rb +12 -17
  208. data/lib/active_record/translation.rb +3 -3
  209. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  210. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  211. data/lib/active_record/type/internal/timezone.rb +2 -2
  212. data/lib/active_record/type/serialized.rb +9 -5
  213. data/lib/active_record/type/type_map.rb +17 -20
  214. data/lib/active_record/type.rb +1 -2
  215. data/lib/active_record/validations/associated.rb +4 -4
  216. data/lib/active_record/validations/presence.rb +2 -2
  217. data/lib/active_record/validations/uniqueness.rb +4 -4
  218. data/lib/active_record/version.rb +1 -1
  219. data/lib/active_record.rb +225 -27
  220. data/lib/arel/attributes/attribute.rb +0 -8
  221. data/lib/arel/crud.rb +28 -22
  222. data/lib/arel/delete_manager.rb +18 -4
  223. data/lib/arel/filter_predications.rb +9 -0
  224. data/lib/arel/insert_manager.rb +2 -3
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/casted.rb +1 -1
  227. data/lib/arel/nodes/delete_statement.rb +12 -13
  228. data/lib/arel/nodes/filter.rb +10 -0
  229. data/lib/arel/nodes/function.rb +1 -0
  230. data/lib/arel/nodes/insert_statement.rb +2 -2
  231. data/lib/arel/nodes/select_core.rb +2 -2
  232. data/lib/arel/nodes/select_statement.rb +2 -2
  233. data/lib/arel/nodes/update_statement.rb +8 -3
  234. data/lib/arel/nodes.rb +1 -0
  235. data/lib/arel/predications.rb +11 -3
  236. data/lib/arel/select_manager.rb +10 -4
  237. data/lib/arel/table.rb +0 -1
  238. data/lib/arel/tree_manager.rb +0 -12
  239. data/lib/arel/update_manager.rb +18 -4
  240. data/lib/arel/visitors/dot.rb +80 -90
  241. data/lib/arel/visitors/mysql.rb +8 -2
  242. data/lib/arel/visitors/postgresql.rb +0 -10
  243. data/lib/arel/visitors/to_sql.rb +58 -2
  244. data/lib/arel.rb +2 -1
  245. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  247. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  248. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  249. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  250. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  251. metadata +55 -11
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/access"
4
- require "digest/sha2"
4
+ require "openssl"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters # :nodoc:
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  table_name[0...table_alias_length].tr(".", "_")
30
30
  end
31
31
 
32
- # Returns the relation names useable to back Active Record models.
32
+ # Returns the relation names usable to back Active Record models.
33
33
  # For most adapters this means all #tables and #views.
34
34
  def data_sources
35
35
  query_values(data_source_sql, "SCHEMA")
@@ -98,6 +98,7 @@ module ActiveRecord
98
98
  #
99
99
  def index_exists?(table_name, column_name, **options)
100
100
  checks = []
101
+ column_name = options[:column] if column_name.nil?
101
102
 
102
103
  if column_name.present?
103
104
  column_names = Array(column_name).map(&:to_s)
@@ -124,6 +125,9 @@ module ActiveRecord
124
125
  # column_exists?(:suppliers, :name)
125
126
  #
126
127
  # # Check a column exists of a particular type
128
+ # #
129
+ # # This works for standard non-casted types (eg. string) but is unreliable
130
+ # # for types that may get cast to something else (eg. char, bigint).
127
131
  # column_exists?(:suppliers, :name, :string)
128
132
  #
129
133
  # # Check a column exists with a specific definition
@@ -260,7 +264,7 @@ module ActiveRecord
260
264
  #
261
265
  # generates:
262
266
  #
263
- # CREATE TABLE order (
267
+ # CREATE TABLE orders (
264
268
  # product_id bigint NOT NULL,
265
269
  # client_id bigint NOT NULL
266
270
  # );
@@ -518,24 +522,31 @@ module ActiveRecord
518
522
 
519
523
  # Add a new +type+ column named +column_name+ to +table_name+.
520
524
  #
525
+ # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
526
+ #
521
527
  # The +type+ parameter is normally one of the migrations native types,
522
528
  # which is one of the following:
523
529
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
524
530
  # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
525
531
  # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
526
- # <tt>:binary</tt>, <tt>:boolean</tt>.
532
+ # <tt>:binary</tt>, <tt>:blob</tt>, <tt>:boolean</tt>.
527
533
  #
528
534
  # You may use a type not in this list as long as it is supported by your
529
535
  # database (for example, "polygon" in MySQL), but this will not be database
530
536
  # agnostic and should usually be avoided.
531
537
  #
532
538
  # Available options are (none of these exists by default):
539
+ # * <tt>:comment</tt> -
540
+ # Specifies the comment for the column. This option is ignored by some backends.
541
+ # * <tt>:collation</tt> -
542
+ # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column.
543
+ # If not specified, the column will have the same collation as the table.
544
+ # * <tt>:default</tt> -
545
+ # The column's default value. Use +nil+ for +NULL+.
533
546
  # * <tt>:limit</tt> -
534
547
  # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
535
- # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
548
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, <tt>:blob</tt>, and <tt>:integer</tt> columns.
536
549
  # This option is ignored by some backends.
537
- # * <tt>:default</tt> -
538
- # The column's default value. Use +nil+ for +NULL+.
539
550
  # * <tt>:null</tt> -
540
551
  # Allows or disallows +NULL+ values in the column.
541
552
  # * <tt>:precision</tt> -
@@ -604,7 +615,13 @@ module ActiveRecord
604
615
  # # Ignores the method call if the column exists
605
616
  # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
606
617
  def add_column(table_name, column_name, type, **options)
607
- return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
618
+ return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
619
+
620
+ if supports_datetime_with_precision?
621
+ if type == :datetime && !options.key?(:precision)
622
+ options[:precision] = 6
623
+ end
624
+ end
608
625
 
609
626
  at = create_alter_table table_name
610
627
  at.add_column(column_name, type, **options)
@@ -629,9 +646,8 @@ module ActiveRecord
629
646
  raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
630
647
  end
631
648
 
632
- column_names.each do |column_name|
633
- remove_column(table_name, column_name, type, **options)
634
- end
649
+ remove_column_fragments = remove_columns_for_alter(table_name, *column_names, type: type, **options)
650
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_fragments.join(', ')}"
635
651
  end
636
652
 
637
653
  # Removes the column from the table definition.
@@ -641,7 +657,8 @@ module ActiveRecord
641
657
  # The +type+ and +options+ parameters will be ignored if present. It can be helpful
642
658
  # to provide these in a migration's +change+ method so it can be reverted.
643
659
  # In that case, +type+ and +options+ will be used by #add_column.
644
- # Indexes on the column are automatically removed.
660
+ # Depending on the database you're using, indexes using this column may be
661
+ # automatically removed or modified to remove this column from the index.
645
662
  #
646
663
  # If the options provided include an +if_exists+ key, it will be used to check if the
647
664
  # column does not exist. This will silently ignore the migration rather than raising
@@ -766,7 +783,7 @@ module ActiveRecord
766
783
  #
767
784
  # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
768
785
  #
769
- # Note: SQLite doesn't support index length.
786
+ # Note: only supported by MySQL
770
787
  #
771
788
  # ====== Creating an index with a sort order (desc or asc, asc is the default)
772
789
  #
@@ -901,7 +918,7 @@ module ActiveRecord
901
918
  remove_index(table_name, name: old_name)
902
919
  end
903
920
 
904
- def index_name(table_name, options) #:nodoc:
921
+ def index_name(table_name, options) # :nodoc:
905
922
  if Hash === options
906
923
  if options[:column]
907
924
  "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
@@ -1027,6 +1044,10 @@ module ActiveRecord
1027
1044
  #
1028
1045
  # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
1029
1046
  #
1047
+ # ====== Creating a foreign key, ignoring method call if the foreign key exists
1048
+ #
1049
+ # add_foreign_key(:articles, :authors, if_not_exists: true)
1050
+ #
1030
1051
  # ====== Creating a foreign key on a specific column
1031
1052
  #
1032
1053
  # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
@@ -1051,13 +1072,20 @@ module ActiveRecord
1051
1072
  # [<tt>:name</tt>]
1052
1073
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
1053
1074
  # [<tt>:on_delete</tt>]
1054
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1075
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1055
1076
  # [<tt>:on_update</tt>]
1056
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1077
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1078
+ # [<tt>:if_not_exists</tt>]
1079
+ # Specifies if the foreign key already exists to not try to re-add it. This will avoid
1080
+ # duplicate column errors.
1057
1081
  # [<tt>:validate</tt>]
1058
1082
  # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1083
+ # [<tt>:deferrable</tt>]
1084
+ # (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
1085
+ # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1059
1086
  def add_foreign_key(from_table, to_table, **options)
1060
1087
  return unless supports_foreign_keys?
1088
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
1061
1089
 
1062
1090
  options = foreign_key_options(from_table, to_table, options)
1063
1091
  at = create_alter_table from_table
@@ -1087,12 +1115,18 @@ module ActiveRecord
1087
1115
  #
1088
1116
  # remove_foreign_key :accounts, name: :special_fk_name
1089
1117
  #
1118
+ # Checks if the foreign key exists before trying to remove it. Will silently ignore indexes that
1119
+ # don't exist.
1120
+ #
1121
+ # remove_foreign_key :accounts, :branches, if_exists: true
1122
+ #
1090
1123
  # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
1091
1124
  # with an addition of
1092
1125
  # [<tt>:to_table</tt>]
1093
1126
  # The name of the table that contains the referenced primary key.
1094
1127
  def remove_foreign_key(from_table, to_table = nil, **options)
1095
1128
  return unless supports_foreign_keys?
1129
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1096
1130
 
1097
1131
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1098
1132
 
@@ -1256,6 +1290,25 @@ module ActiveRecord
1256
1290
  columns
1257
1291
  end
1258
1292
 
1293
+ def distinct_relation_for_primary_key(relation) # :nodoc:
1294
+ values = columns_for_distinct(
1295
+ visitor.compile(relation.table[relation.primary_key]),
1296
+ relation.order_values
1297
+ )
1298
+
1299
+ limited = relation.reselect(values).distinct!
1300
+ limited_ids = select_rows(limited.arel, "SQL").map(&:last)
1301
+
1302
+ if limited_ids.empty?
1303
+ relation.none!
1304
+ else
1305
+ relation.where!(relation.primary_key => limited_ids)
1306
+ end
1307
+
1308
+ relation.limit_value = relation.offset_value = nil
1309
+ relation
1310
+ end
1311
+
1259
1312
  # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
1260
1313
  # Additional options (like +:null+) are forwarded to #add_column.
1261
1314
  #
@@ -1277,11 +1330,10 @@ module ActiveRecord
1277
1330
  # remove_timestamps(:suppliers)
1278
1331
  #
1279
1332
  def remove_timestamps(table_name, **options)
1280
- remove_column table_name, :updated_at
1281
- remove_column table_name, :created_at
1333
+ remove_columns table_name, :updated_at, :created_at
1282
1334
  end
1283
1335
 
1284
- def update_table_definition(table_name, base) #:nodoc:
1336
+ def update_table_definition(table_name, base) # :nodoc:
1285
1337
  Table.new(table_name, base)
1286
1338
  end
1287
1339
 
@@ -1387,7 +1439,7 @@ module ActiveRecord
1387
1439
 
1388
1440
  checks = []
1389
1441
 
1390
- if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1442
+ if !options.key?(:name) && expression_column_name?(column_name)
1391
1443
  options[:name] = index_name(table_name, column_name)
1392
1444
  column_names = []
1393
1445
  else
@@ -1396,7 +1448,7 @@ module ActiveRecord
1396
1448
 
1397
1449
  checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1398
1450
 
1399
- if column_names.present?
1451
+ if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
1400
1452
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1401
1453
  end
1402
1454
 
@@ -1464,7 +1516,7 @@ module ActiveRecord
1464
1516
  end
1465
1517
 
1466
1518
  def index_column_names(column_names)
1467
- if column_names.is_a?(String) && /\W/.match?(column_names)
1519
+ if expression_column_name?(column_names)
1468
1520
  column_names
1469
1521
  else
1470
1522
  Array(column_names)
@@ -1472,13 +1524,18 @@ module ActiveRecord
1472
1524
  end
1473
1525
 
1474
1526
  def index_name_options(column_names)
1475
- if column_names.is_a?(String) && /\W/.match?(column_names)
1527
+ if expression_column_name?(column_names)
1476
1528
  column_names = column_names.scan(/\w+/).join("_")
1477
1529
  end
1478
1530
 
1479
1531
  { column: column_names }
1480
1532
  end
1481
1533
 
1534
+ # Try to identify whether the given column name is an expression
1535
+ def expression_column_name?(column_name)
1536
+ column_name.is_a?(String) && /\W/.match?(column_name)
1537
+ end
1538
+
1482
1539
  def strip_table_name_prefix_and_suffix(table_name)
1483
1540
  prefix = Base.table_name_prefix
1484
1541
  suffix = Base.table_name_suffix
@@ -1488,7 +1545,7 @@ module ActiveRecord
1488
1545
  def foreign_key_name(table_name, options)
1489
1546
  options.fetch(:name) do
1490
1547
  identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1491
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1548
+ hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1492
1549
 
1493
1550
  "fk_rails_#{hashed_identifier}"
1494
1551
  end
@@ -1516,7 +1573,7 @@ module ActiveRecord
1516
1573
  options.fetch(:name) do
1517
1574
  expression = options.fetch(:expression)
1518
1575
  identifier = "#{table_name}_#{expression}_chk"
1519
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1576
+ hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1520
1577
 
1521
1578
  "chk_rails_#{hashed_identifier}"
1522
1579
  end
@@ -73,7 +73,7 @@ module ActiveRecord
73
73
  end
74
74
  end
75
75
 
76
- class NullTransaction #:nodoc:
76
+ class NullTransaction # :nodoc:
77
77
  def initialize; end
78
78
  def state; end
79
79
  def closed?; true; end
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  def add_record(record, _ = true); end
83
83
  end
84
84
 
85
- class Transaction #:nodoc:
85
+ class Transaction # :nodoc:
86
86
  attr_reader :connection, :state, :savepoint_name, :isolation_level
87
87
  attr_accessor :written
88
88
 
@@ -221,7 +221,7 @@ module ActiveRecord
221
221
  end
222
222
  end
223
223
 
224
- class TransactionManager #:nodoc:
224
+ class TransactionManager # :nodoc:
225
225
  def initialize(connection)
226
226
  @stack = []
227
227
  @connection = connection
@@ -333,26 +333,19 @@ module ActiveRecord
333
333
  # @connection still holds an open or invalid transaction, so we must not
334
334
  # put it back in the pool for reuse.
335
335
  @connection.throw_away! unless transaction.state.rolledback?
336
+ elsif Thread.current.status == "aborting" || (!completed && transaction.written)
337
+ # The transaction is still open but the block returned earlier.
338
+ #
339
+ # The block could return early because of a timeout or because the thread is aborting,
340
+ # so we are rolling back to make sure the timeout didn't caused the transaction to be
341
+ # committed incompletely.
342
+ rollback_transaction
336
343
  else
337
- if Thread.current.status == "aborting"
338
- rollback_transaction
339
- else
340
- if !completed && transaction.written
341
- ActiveSupport::Deprecation.warn(<<~EOW)
342
- Using `return`, `break` or `throw` to exit a transaction block is
343
- deprecated without replacement. If the `throw` came from
344
- `Timeout.timeout(duration)`, pass an exception class as a second
345
- argument so it doesn't use `throw` to abort its block. This results
346
- in the transaction being committed, but in the next release of Rails
347
- it will rollback.
348
- EOW
349
- end
350
- begin
351
- commit_transaction
352
- rescue Exception
353
- rollback_transaction(transaction) unless transaction.state.completed?
354
- raise
355
- end
344
+ begin
345
+ commit_transaction
346
+ rescue Exception
347
+ rollback_transaction(transaction) unless transaction.state.completed?
348
+ raise
356
349
  end
357
350
  end
358
351
  end