activerecord 7.0.0 → 7.1.2

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 +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -96,25 +96,19 @@ module ActiveRecord
96
96
  # # Check an index with a custom name exists
97
97
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
98
98
  #
99
+ # # Check a valid index exists (PostgreSQL only)
100
+ # index_exists?(:suppliers, :company_id, valid: true)
101
+ #
99
102
  def index_exists?(table_name, column_name, **options)
100
- checks = []
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
-
107
- checks << lambda { |i| i.unique } if options[:unique]
108
- checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
109
-
110
- indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
103
+ indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
111
104
  end
112
105
 
113
106
  # Returns an array of +Column+ objects for the table specified by +table_name+.
114
107
  def columns(table_name)
115
108
  table_name = table_name.to_s
116
- column_definitions(table_name).map do |field|
117
- new_column_from_field(table_name, field)
109
+ definitions = column_definitions(table_name)
110
+ definitions.map do |field|
111
+ new_column_from_field(table_name, field, definitions)
118
112
  end
119
113
  end
120
114
 
@@ -263,7 +257,7 @@ module ActiveRecord
263
257
  #
264
258
  # generates:
265
259
  #
266
- # CREATE TABLE order (
260
+ # CREATE TABLE orders (
267
261
  # product_id bigint NOT NULL,
268
262
  # client_id bigint NOT NULL
269
263
  # );
@@ -296,25 +290,10 @@ module ActiveRecord
296
290
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
297
291
  #
298
292
  # See also TableDefinition#column for details on how to create columns.
299
- def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
300
- td = create_table_definition(table_name, **extract_table_options!(options))
301
-
302
- if id && !td.as
303
- pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
304
-
305
- if id.is_a?(Hash)
306
- options.merge!(id.except(:type))
307
- id = id.fetch(:type, :primary_key)
308
- end
309
-
310
- if pk.is_a?(Array)
311
- td.primary_keys pk
312
- else
313
- td.primary_key pk, id, **options
314
- end
315
- end
316
-
317
- yield td if block_given?
293
+ def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block)
294
+ validate_create_table_options!(options)
295
+ validate_table_length!(table_name) unless options[:_uses_legacy_table_name]
296
+ td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
318
297
 
319
298
  if force
320
299
  drop_table(table_name, force: force, if_exists: true)
@@ -322,7 +301,7 @@ module ActiveRecord
322
301
  schema_cache.clear_data_source_cache!(table_name.to_s)
323
302
  end
324
303
 
325
- result = execute schema_creation.accept td
304
+ result = execute schema_creation.accept(td)
326
305
 
327
306
  unless supports_indexes_in_create?
328
307
  td.indexes.each do |column_name, index_options|
@@ -343,6 +322,18 @@ module ActiveRecord
343
322
  result
344
323
  end
345
324
 
325
+ # Returns a TableDefinition object containing information about the table that would be created
326
+ # if the same arguments were passed to #create_table. See #create_table for information about
327
+ # passing a +table_name+, and other additional options that can be passed.
328
+ def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
329
+ table_definition = create_table_definition(table_name, **options.extract!(*valid_table_definition_options, :_skip_validate_options))
330
+ table_definition.set_primary_key(table_name, id, primary_key, **options.extract!(*valid_primary_key_options, :_skip_validate_options))
331
+
332
+ yield table_definition if block_given?
333
+
334
+ table_definition
335
+ end
336
+
346
337
  # Creates a new join table with the name created using the lexical order of the first two
347
338
  # arguments. These arguments can be a String or a Symbol.
348
339
  #
@@ -386,7 +377,7 @@ module ActiveRecord
386
377
 
387
378
  column_options.reverse_merge!(null: false, index: false)
388
379
 
389
- t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
380
+ t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
390
381
 
391
382
  create_table(join_table_name, **options.merge!(id: false)) do |td|
392
383
  td.references t1_ref, **column_options
@@ -395,15 +386,33 @@ module ActiveRecord
395
386
  end
396
387
  end
397
388
 
389
+ # Builds a TableDefinition object for a join table.
390
+ #
391
+ # This definition object contains information about the table that would be created
392
+ # if the same arguments were passed to #create_join_table. See #create_join_table for
393
+ # information about what arguments should be passed.
394
+ def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc:
395
+ join_table_name = find_join_table_name(table_1, table_2, options)
396
+ column_options.reverse_merge!(null: false, index: false)
397
+
398
+ t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
399
+
400
+ build_create_table_definition(join_table_name, **options.merge!(id: false)) do |td|
401
+ td.references t1_ref, **column_options
402
+ td.references t2_ref, **column_options
403
+ yield td if block_given?
404
+ end
405
+ end
406
+
398
407
  # Drops the join table specified by the given arguments.
399
- # See #create_join_table for details.
408
+ # See #create_join_table and #drop_table for details.
400
409
  #
401
410
  # Although this command ignores the block if one is given, it can be helpful
402
411
  # to provide one in a migration's +change+ method so it can be reverted.
403
412
  # In that case, the block will be used by #create_join_table.
404
413
  def drop_join_table(table_1, table_2, **options)
405
414
  join_table_name = find_join_table_name(table_1, table_2, options)
406
- drop_table(join_table_name)
415
+ drop_table(join_table_name, **options)
407
416
  end
408
417
 
409
418
  # A block for changing columns in +table+.
@@ -484,13 +493,13 @@ module ActiveRecord
484
493
  # end
485
494
  #
486
495
  # See also Table for details on all of the various column transformations.
487
- def change_table(table_name, **options)
496
+ def change_table(table_name, base = self, **options)
488
497
  if supports_bulk_alter? && options[:bulk]
489
498
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
490
499
  yield update_table_definition(table_name, recorder)
491
500
  bulk_change_table(table_name, recorder.commands)
492
501
  else
493
- yield update_table_definition(table_name, self)
502
+ yield update_table_definition(table_name, base)
494
503
  end
495
504
  end
496
505
 
@@ -498,7 +507,7 @@ module ActiveRecord
498
507
  #
499
508
  # rename_table('octopuses', 'octopi')
500
509
  #
501
- def rename_table(table_name, new_name)
510
+ def rename_table(table_name, new_name, **)
502
511
  raise NotImplementedError, "rename_table is not implemented"
503
512
  end
504
513
 
@@ -553,11 +562,6 @@ module ActiveRecord
553
562
  # <tt>:datetime</tt>, and <tt>:time</tt> columns.
554
563
  # * <tt>:scale</tt> -
555
564
  # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
556
- # * <tt>:collation</tt> -
557
- # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
558
- # column will have the same collation as the table.
559
- # * <tt>:comment</tt> -
560
- # Specifies the comment for the column. This option is ignored by some backends.
561
565
  # * <tt>:if_not_exists</tt> -
562
566
  # Specifies if the column already exists to not try to re-add it. This will avoid
563
567
  # duplicate column errors.
@@ -573,7 +577,7 @@ module ActiveRecord
573
577
  # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
574
578
  # <tt>:precision</tt>, and makes no comments about the requirements of
575
579
  # <tt>:precision</tt>.
576
- # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
580
+ # * MySQL: <tt>:precision</tt> [1..65], <tt>:scale</tt> [0..30].
577
581
  # Default is (10,0).
578
582
  # * PostgreSQL: <tt>:precision</tt> [1..infinity],
579
583
  # <tt>:scale</tt> [0..infinity]. No default.
@@ -614,6 +618,24 @@ module ActiveRecord
614
618
  # # Ignores the method call if the column exists
615
619
  # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
616
620
  def add_column(table_name, column_name, type, **options)
621
+ add_column_def = build_add_column_definition(table_name, column_name, type, **options)
622
+ return unless add_column_def
623
+
624
+ execute schema_creation.accept(add_column_def)
625
+ end
626
+
627
+ def add_columns(table_name, *column_names, type:, **options) # :nodoc:
628
+ column_names.each do |column_name|
629
+ add_column(table_name, column_name, type, **options)
630
+ end
631
+ end
632
+
633
+ # Builds an AlterTable object for adding a column to a table.
634
+ #
635
+ # This definition object contains information about the column that would be created
636
+ # if the same arguments were passed to #add_column. See #add_column for information about
637
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
638
+ def build_add_column_definition(table_name, column_name, type, **options) # :nodoc:
617
639
  return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
618
640
 
619
641
  if supports_datetime_with_precision?
@@ -622,15 +644,9 @@ module ActiveRecord
622
644
  end
623
645
  end
624
646
 
625
- at = create_alter_table table_name
626
- at.add_column(column_name, type, **options)
627
- execute schema_creation.accept at
628
- end
629
-
630
- def add_columns(table_name, *column_names, type:, **options) # :nodoc:
631
- column_names.each do |column_name|
632
- add_column(table_name, column_name, type, **options)
633
- end
647
+ alter_table = create_alter_table(table_name)
648
+ alter_table.add_column(column_name, type, **options)
649
+ alter_table
634
650
  end
635
651
 
636
652
  # Removes the given columns from the table definition.
@@ -698,6 +714,15 @@ module ActiveRecord
698
714
  raise NotImplementedError, "change_column_default is not implemented"
699
715
  end
700
716
 
717
+ # Builds a ChangeColumnDefaultDefinition object.
718
+ #
719
+ # This definition object contains information about the column change that would occur
720
+ # if the same arguments were passed to #change_column_default. See #change_column_default for
721
+ # information about passing a +table_name+, +column_name+, +type+ and other options that can be passed.
722
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
723
+ raise NotImplementedError, "build_change_column_default_definition is not implemented"
724
+ end
725
+
701
726
  # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
702
727
  # indicates whether the value can be +NULL+. For example
703
728
  #
@@ -804,6 +829,16 @@ module ActiveRecord
804
829
  #
805
830
  # Note: Partial indexes are only supported for PostgreSQL and SQLite.
806
831
  #
832
+ # ====== Creating an index that includes additional columns
833
+ #
834
+ # add_index(:accounts, :branch_id, include: :party_id)
835
+ #
836
+ # generates:
837
+ #
838
+ # CREATE INDEX index_accounts_on_branch_id ON accounts USING btree(branch_id) INCLUDE (party_id)
839
+ #
840
+ # Note: only supported by PostgreSQL.
841
+ #
807
842
  # ====== Creating an index with a specific method
808
843
  #
809
844
  # add_index(:developers, :name, using: 'btree')
@@ -849,12 +884,20 @@ module ActiveRecord
849
884
  #
850
885
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
851
886
  def add_index(table_name, column_name, **options)
852
- index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
853
-
854
- create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
887
+ create_index = build_create_index_definition(table_name, column_name, **options)
855
888
  execute schema_creation.accept(create_index)
856
889
  end
857
890
 
891
+ # Builds a CreateIndexDefinition object.
892
+ #
893
+ # This definition object contains information about the index that would be created
894
+ # if the same arguments were passed to #add_index. See #add_index for information about
895
+ # passing a +table_name+, +column_name+, and other additional options that can be passed.
896
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
897
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
898
+ CreateIndexDefinition.new(index, algorithm, if_not_exists)
899
+ end
900
+
858
901
  # Removes the given index from the table.
859
902
  #
860
903
  # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
@@ -920,7 +963,7 @@ module ActiveRecord
920
963
  def index_name(table_name, options) # :nodoc:
921
964
  if Hash === options
922
965
  if options[:column]
923
- "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
966
+ generate_index_name(table_name, options[:column])
924
967
  elsif options[:name]
925
968
  options[:name]
926
969
  else
@@ -940,7 +983,6 @@ module ActiveRecord
940
983
  # Adds a reference. The reference column is a bigint by default,
941
984
  # the <tt>:type</tt> option can be used to specify a different type.
942
985
  # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
943
- # #add_reference and #add_belongs_to are acceptable.
944
986
  #
945
987
  # The +options+ hash can include the following keys:
946
988
  # [<tt>:type</tt>]
@@ -986,12 +1028,11 @@ module ActiveRecord
986
1028
  # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
987
1029
  #
988
1030
  def add_reference(table_name, ref_name, **options)
989
- ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
1031
+ ReferenceDefinition.new(ref_name, **options).add(table_name, self)
990
1032
  end
991
1033
  alias :add_belongs_to :add_reference
992
1034
 
993
1035
  # Removes the reference(s). Also removes a +type+ column if one exists.
994
- # #remove_reference and #remove_belongs_to are acceptable.
995
1036
  #
996
1037
  # ====== Remove the reference
997
1038
  #
@@ -1006,19 +1047,21 @@ module ActiveRecord
1006
1047
  # remove_reference(:products, :user, foreign_key: true)
1007
1048
  #
1008
1049
  def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
1050
+ conditional_options = options.slice(:if_exists, :if_not_exists)
1051
+
1009
1052
  if foreign_key
1010
1053
  reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
1011
1054
  if foreign_key.is_a?(Hash)
1012
- foreign_key_options = foreign_key
1055
+ foreign_key_options = foreign_key.merge(conditional_options)
1013
1056
  else
1014
- foreign_key_options = { to_table: reference_name }
1057
+ foreign_key_options = { to_table: reference_name, **conditional_options }
1015
1058
  end
1016
1059
  foreign_key_options[:column] ||= "#{ref_name}_id"
1017
1060
  remove_foreign_key(table_name, **foreign_key_options)
1018
1061
  end
1019
1062
 
1020
- remove_column(table_name, "#{ref_name}_id")
1021
- remove_column(table_name, "#{ref_name}_type") if polymorphic
1063
+ remove_column(table_name, "#{ref_name}_id", **conditional_options)
1064
+ remove_column(table_name, "#{ref_name}_type", **conditional_options) if polymorphic
1022
1065
  end
1023
1066
  alias :remove_belongs_to :remove_reference
1024
1067
 
@@ -1055,6 +1098,16 @@ module ActiveRecord
1055
1098
  #
1056
1099
  # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
1057
1100
  #
1101
+ # ====== Creating a composite foreign key
1102
+ #
1103
+ # Assuming "carts" table has "(shop_id, user_id)" as a primary key.
1104
+ #
1105
+ # add_foreign_key :orders, :carts, primary_key: [:shop_id, :user_id]
1106
+ #
1107
+ # generates:
1108
+ #
1109
+ # ALTER TABLE "orders" ADD CONSTRAINT fk_rails_6f5e4cb3a4 FOREIGN KEY ("cart_shop_id", "cart_user_id") REFERENCES "carts" ("shop_id", "user_id")
1110
+ #
1058
1111
  # ====== Creating a cascading foreign key
1059
1112
  #
1060
1113
  # add_foreign_key :articles, :authors, on_delete: :cascade
@@ -1065,15 +1118,17 @@ module ActiveRecord
1065
1118
  #
1066
1119
  # The +options+ hash can include the following keys:
1067
1120
  # [<tt>:column</tt>]
1068
- # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
1121
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>.
1122
+ # Pass an array to create a composite foreign key.
1069
1123
  # [<tt>:primary_key</tt>]
1070
1124
  # The primary key column name on +to_table+. Defaults to +id+.
1125
+ # Pass an array to create a composite foreign key.
1071
1126
  # [<tt>:name</tt>]
1072
1127
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
1073
1128
  # [<tt>:on_delete</tt>]
1074
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1129
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1075
1130
  # [<tt>:on_update</tt>]
1076
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1131
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1077
1132
  # [<tt>:if_not_exists</tt>]
1078
1133
  # Specifies if the foreign key already exists to not try to re-add it. This will avoid
1079
1134
  # duplicate column errors.
@@ -1083,8 +1138,8 @@ module ActiveRecord
1083
1138
  # (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
1084
1139
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1085
1140
  def add_foreign_key(from_table, to_table, **options)
1086
- return unless supports_foreign_keys?
1087
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
1141
+ return unless use_foreign_keys?
1142
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1088
1143
 
1089
1144
  options = foreign_key_options(from_table, to_table, options)
1090
1145
  at = create_alter_table from_table
@@ -1124,8 +1179,8 @@ module ActiveRecord
1124
1179
  # [<tt>:to_table</tt>]
1125
1180
  # The name of the table that contains the referenced primary key.
1126
1181
  def remove_foreign_key(from_table, to_table = nil, **options)
1127
- return unless supports_foreign_keys?
1128
- return if options[:if_exists] == true && !foreign_key_exists?(from_table, to_table)
1182
+ return unless use_foreign_keys?
1183
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1129
1184
 
1130
1185
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1131
1186
 
@@ -1150,15 +1205,33 @@ module ActiveRecord
1150
1205
  foreign_key_for(from_table, to_table: to_table, **options).present?
1151
1206
  end
1152
1207
 
1153
- def foreign_key_column_for(table_name) # :nodoc:
1208
+ def foreign_key_column_for(table_name, column_name) # :nodoc:
1154
1209
  name = strip_table_name_prefix_and_suffix(table_name)
1155
- "#{name.singularize}_id"
1210
+ "#{name.singularize}_#{column_name}"
1156
1211
  end
1157
1212
 
1158
1213
  def foreign_key_options(from_table, to_table, options) # :nodoc:
1159
1214
  options = options.dup
1160
- options[:column] ||= foreign_key_column_for(to_table)
1215
+
1216
+ if options[:primary_key].is_a?(Array)
1217
+ options[:column] ||= options[:primary_key].map do |pk_column|
1218
+ foreign_key_column_for(to_table, pk_column)
1219
+ end
1220
+ else
1221
+ options[:column] ||= foreign_key_column_for(to_table, "id")
1222
+ end
1223
+
1161
1224
  options[:name] ||= foreign_key_name(from_table, options)
1225
+
1226
+ if options[:column].is_a?(Array) || options[:primary_key].is_a?(Array)
1227
+ if Array(options[:primary_key]).size != Array(options[:column]).size
1228
+ raise ArgumentError, <<~MSG.squish
1229
+ For composite primary keys, specify :column and :primary_key, where
1230
+ :column must reference all the :primary_key columns from #{to_table.inspect}
1231
+ MSG
1232
+ end
1233
+ end
1234
+
1162
1235
  options
1163
1236
  end
1164
1237
 
@@ -1180,12 +1253,16 @@ module ActiveRecord
1180
1253
  # The +options+ hash can include the following keys:
1181
1254
  # [<tt>:name</tt>]
1182
1255
  # The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
1256
+ # [<tt>:if_not_exists</tt>]
1257
+ # Silently ignore if the constraint already exists, rather than raise an error.
1183
1258
  # [<tt>:validate</tt>]
1184
1259
  # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1185
- def add_check_constraint(table_name, expression, **options)
1260
+ def add_check_constraint(table_name, expression, if_not_exists: false, **options)
1186
1261
  return unless supports_check_constraints?
1187
1262
 
1188
1263
  options = check_constraint_options(table_name, expression, options)
1264
+ return if if_not_exists && check_constraint_exists?(table_name, **options)
1265
+
1189
1266
  at = create_alter_table(table_name)
1190
1267
  at.add_check_constraint(expression, options)
1191
1268
 
@@ -1198,16 +1275,24 @@ module ActiveRecord
1198
1275
  options
1199
1276
  end
1200
1277
 
1201
- # Removes the given check constraint from the table.
1278
+ # Removes the given check constraint from the table. Removing a check constraint
1279
+ # that does not exist will raise an error.
1202
1280
  #
1203
1281
  # remove_check_constraint :products, name: "price_check"
1204
1282
  #
1283
+ # To silently ignore a non-existent check constraint rather than raise an error,
1284
+ # use the +if_exists+ option.
1285
+ #
1286
+ # remove_check_constraint :products, name: "price_check", if_exists: true
1287
+ #
1205
1288
  # The +expression+ parameter will be ignored if present. It can be helpful
1206
1289
  # to provide this in a migration's +change+ method so it can be reverted.
1207
1290
  # In that case, +expression+ will be used by #add_check_constraint.
1208
- def remove_check_constraint(table_name, expression = nil, **options)
1291
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
1209
1292
  return unless supports_check_constraints?
1210
1293
 
1294
+ return if if_exists && !check_constraint_exists?(table_name, **options)
1295
+
1211
1296
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
1212
1297
 
1213
1298
  at = create_alter_table(table_name)
@@ -1216,8 +1301,20 @@ module ActiveRecord
1216
1301
  execute schema_creation.accept(at)
1217
1302
  end
1218
1303
 
1304
+
1305
+ # Checks to see if a check constraint exists on a table for a given check constraint definition.
1306
+ #
1307
+ # check_constraint_exists?(:products, name: "price_check")
1308
+ #
1309
+ def check_constraint_exists?(table_name, **options)
1310
+ if !options.key?(:name) && !options.key?(:expression)
1311
+ raise ArgumentError, "At least one of :name or :expression must be supplied"
1312
+ end
1313
+ check_constraint_for(table_name, **options).present?
1314
+ end
1315
+
1219
1316
  def dump_schema_information # :nodoc:
1220
- versions = schema_migration.all_versions
1317
+ versions = schema_migration.versions
1221
1318
  insert_versions_sql(versions) if versions.any?
1222
1319
  end
1223
1320
 
@@ -1290,18 +1387,24 @@ module ActiveRecord
1290
1387
  end
1291
1388
 
1292
1389
  def distinct_relation_for_primary_key(relation) # :nodoc:
1390
+ primary_key_columns = Array(relation.primary_key).map do |column|
1391
+ visitor.compile(relation.table[column])
1392
+ end
1393
+
1293
1394
  values = columns_for_distinct(
1294
- visitor.compile(relation.table[relation.primary_key]),
1395
+ primary_key_columns,
1295
1396
  relation.order_values
1296
1397
  )
1297
1398
 
1298
1399
  limited = relation.reselect(values).distinct!
1299
- limited_ids = select_rows(limited.arel, "SQL").map(&:last)
1400
+ limited_ids = select_rows(limited.arel, "SQL").map do |results|
1401
+ results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL
1402
+ end
1300
1403
 
1301
1404
  if limited_ids.empty?
1302
1405
  relation.none!
1303
1406
  else
1304
- relation.where!(relation.primary_key => limited_ids)
1407
+ relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h)
1305
1408
  end
1306
1409
 
1307
1410
  relation.limit_value = relation.offset_value = nil
@@ -1314,14 +1417,8 @@ module ActiveRecord
1314
1417
  # add_timestamps(:suppliers, null: true)
1315
1418
  #
1316
1419
  def add_timestamps(table_name, **options)
1317
- options[:null] = false if options[:null].nil?
1318
-
1319
- if !options.key?(:precision) && supports_datetime_with_precision?
1320
- options[:precision] = 6
1321
- end
1322
-
1323
- add_column table_name, :created_at, :datetime, **options
1324
- add_column table_name, :updated_at, :datetime, **options
1420
+ fragments = add_timestamps_for_alter(table_name, **options)
1421
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
1325
1422
  end
1326
1423
 
1327
1424
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
@@ -1337,7 +1434,7 @@ module ActiveRecord
1337
1434
  end
1338
1435
 
1339
1436
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1340
- options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
1437
+ options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
1341
1438
 
1342
1439
  column_names = index_column_names(column_name)
1343
1440
 
@@ -1356,6 +1453,8 @@ module ActiveRecord
1356
1453
  where: options[:where],
1357
1454
  type: options[:type],
1358
1455
  using: options[:using],
1456
+ include: options[:include],
1457
+ nulls_not_distinct: options[:nulls_not_distinct],
1359
1458
  comment: options[:comment]
1360
1459
  )
1361
1460
 
@@ -1403,7 +1502,79 @@ module ActiveRecord
1403
1502
  SchemaDumper.create(self, options)
1404
1503
  end
1405
1504
 
1505
+ def use_foreign_keys?
1506
+ supports_foreign_keys? && foreign_keys_enabled?
1507
+ end
1508
+
1509
+ # Returns an instance of SchemaCreation, which can be used to visit a schema definition
1510
+ # object and return DDL.
1511
+ def schema_creation # :nodoc:
1512
+ SchemaCreation.new(self)
1513
+ end
1514
+
1515
+ def bulk_change_table(table_name, operations) # :nodoc:
1516
+ sql_fragments = []
1517
+ non_combinable_operations = []
1518
+
1519
+ operations.each do |command, args|
1520
+ table, arguments = args.shift, args
1521
+ method = :"#{command}_for_alter"
1522
+
1523
+ if respond_to?(method, true)
1524
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1525
+ sql_fragments.concat(sqls)
1526
+ non_combinable_operations.concat(procs)
1527
+ else
1528
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1529
+ non_combinable_operations.each(&:call)
1530
+ sql_fragments = []
1531
+ non_combinable_operations = []
1532
+ send(command, table, *arguments)
1533
+ end
1534
+ end
1535
+
1536
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1537
+ non_combinable_operations.each(&:call)
1538
+ end
1539
+
1540
+ def valid_table_definition_options # :nodoc:
1541
+ [:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation]
1542
+ end
1543
+
1544
+ def valid_column_definition_options # :nodoc:
1545
+ ColumnDefinition::OPTION_NAMES
1546
+ end
1547
+
1548
+ def valid_primary_key_options # :nodoc:
1549
+ [:limit, :default, :precision]
1550
+ end
1551
+
1552
+ # Returns the maximum length of an index name in bytes.
1553
+ def max_index_name_size
1554
+ 62
1555
+ end
1556
+
1406
1557
  private
1558
+ def generate_index_name(table_name, column)
1559
+ name = "index_#{table_name}_on_#{Array(column) * '_and_'}"
1560
+ return name if name.bytesize <= max_index_name_size
1561
+
1562
+ # Fallback to short version, add hash to ensure uniqueness
1563
+ hashed_identifier = "_" + OpenSSL::Digest::SHA256.hexdigest(name).first(10)
1564
+ name = "idx_on_#{Array(column) * '_'}"
1565
+
1566
+ short_limit = max_index_name_size - hashed_identifier.bytesize
1567
+ short_name = name.mb_chars.limit(short_limit).to_s
1568
+
1569
+ "#{short_name}#{hashed_identifier}"
1570
+ end
1571
+
1572
+ def validate_change_column_null_argument!(value)
1573
+ unless value == true || value == false
1574
+ raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}"
1575
+ end
1576
+ end
1577
+
1407
1578
  def column_options_keys
1408
1579
  [:limit, :precision, :scale, :default, :null, :collation, :comment]
1409
1580
  end
@@ -1438,7 +1609,7 @@ module ActiveRecord
1438
1609
 
1439
1610
  checks = []
1440
1611
 
1441
- if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1612
+ if !options.key?(:name) && expression_column_name?(column_name)
1442
1613
  options[:name] = index_name(table_name, column_name)
1443
1614
  column_names = []
1444
1615
  else
@@ -1447,7 +1618,7 @@ module ActiveRecord
1447
1618
 
1448
1619
  checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1449
1620
 
1450
- if column_names.present?
1621
+ if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
1451
1622
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1452
1623
  end
1453
1624
 
@@ -1457,7 +1628,7 @@ module ActiveRecord
1457
1628
 
1458
1629
  if matching_indexes.count > 1
1459
1630
  raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
1460
- "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1631
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1461
1632
  elsif matching_indexes.none?
1462
1633
  raise ArgumentError, "No indexes found on #{table_name} with the options provided."
1463
1634
  else
@@ -1487,10 +1658,6 @@ module ActiveRecord
1487
1658
  end
1488
1659
  end
1489
1660
 
1490
- def schema_creation
1491
- SchemaCreation.new(self)
1492
- end
1493
-
1494
1661
  def create_table_definition(name, **options)
1495
1662
  TableDefinition.new(self, name, **options)
1496
1663
  end
@@ -1499,8 +1666,12 @@ module ActiveRecord
1499
1666
  AlterTable.new create_table_definition(name)
1500
1667
  end
1501
1668
 
1502
- def extract_table_options!(options)
1503
- options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
1669
+ def validate_create_table_options!(options)
1670
+ unless options[:_skip_validate_options]
1671
+ options
1672
+ .except(:_uses_legacy_table_name, :_skip_validate_options)
1673
+ .assert_valid_keys(valid_table_definition_options, valid_primary_key_options)
1674
+ end
1504
1675
  end
1505
1676
 
1506
1677
  def fetch_type_metadata(sql_type)
@@ -1515,7 +1686,7 @@ module ActiveRecord
1515
1686
  end
1516
1687
 
1517
1688
  def index_column_names(column_names)
1518
- if column_names.is_a?(String) && /\W/.match?(column_names)
1689
+ if expression_column_name?(column_names)
1519
1690
  column_names
1520
1691
  else
1521
1692
  Array(column_names)
@@ -1523,13 +1694,18 @@ module ActiveRecord
1523
1694
  end
1524
1695
 
1525
1696
  def index_name_options(column_names)
1526
- if column_names.is_a?(String) && /\W/.match?(column_names)
1697
+ if expression_column_name?(column_names)
1527
1698
  column_names = column_names.scan(/\w+/).join("_")
1528
1699
  end
1529
1700
 
1530
1701
  { column: column_names }
1531
1702
  end
1532
1703
 
1704
+ # Try to identify whether the given column name is an expression
1705
+ def expression_column_name?(column_name)
1706
+ column_name.is_a?(String) && /\W/.match?(column_name)
1707
+ end
1708
+
1533
1709
  def strip_table_name_prefix_and_suffix(table_name)
1534
1710
  prefix = Base.table_name_prefix
1535
1711
  suffix = Base.table_name_suffix
@@ -1538,7 +1714,8 @@ module ActiveRecord
1538
1714
 
1539
1715
  def foreign_key_name(table_name, options)
1540
1716
  options.fetch(:name) do
1541
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1717
+ columns = Array(options.fetch(:column)).map(&:to_s)
1718
+ identifier = "#{table_name}_#{columns * '_and_'}_fk"
1542
1719
  hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1543
1720
 
1544
1721
  "fk_rails_#{hashed_identifier}"
@@ -1546,7 +1723,7 @@ module ActiveRecord
1546
1723
  end
1547
1724
 
1548
1725
  def foreign_key_for(from_table, **options)
1549
- return unless supports_foreign_keys?
1726
+ return unless use_foreign_keys?
1550
1727
  foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1551
1728
  end
1552
1729
 
@@ -1563,6 +1740,10 @@ module ActiveRecord
1563
1740
  end
1564
1741
  end
1565
1742
 
1743
+ def foreign_keys_enabled?
1744
+ @config.fetch(:foreign_keys, true)
1745
+ end
1746
+
1566
1747
  def check_constraint_name(table_name, **options)
1567
1748
  options.fetch(:name) do
1568
1749
  expression = options.fetch(:expression)
@@ -1576,7 +1757,7 @@ module ActiveRecord
1576
1757
  def check_constraint_for(table_name, **options)
1577
1758
  return unless supports_check_constraints?
1578
1759
  chk_name = check_constraint_name(table_name, **options)
1579
- check_constraints(table_name).detect { |chk| chk.name == chk_name }
1760
+ check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) }
1580
1761
  end
1581
1762
 
1582
1763
  def check_constraint_for!(table_name, expression: nil, **options)
@@ -1590,6 +1771,12 @@ module ActiveRecord
1590
1771
  end
1591
1772
  end
1592
1773
 
1774
+ def validate_table_length!(table_name)
1775
+ if table_name.length > table_name_length
1776
+ raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters"
1777
+ end
1778
+ end
1779
+
1593
1780
  def extract_new_default_value(default_or_changes)
1594
1781
  if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
1595
1782
  default_or_changes[:to]
@@ -1603,29 +1790,8 @@ module ActiveRecord
1603
1790
  column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1604
1791
  end
1605
1792
 
1606
- def bulk_change_table(table_name, operations)
1607
- sql_fragments = []
1608
- non_combinable_operations = []
1609
-
1610
- operations.each do |command, args|
1611
- table, arguments = args.shift, args
1612
- method = :"#{command}_for_alter"
1613
-
1614
- if respond_to?(method, true)
1615
- sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1616
- sql_fragments << sqls
1617
- non_combinable_operations.concat(procs)
1618
- else
1619
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1620
- non_combinable_operations.each(&:call)
1621
- sql_fragments = []
1622
- non_combinable_operations = []
1623
- send(command, table, *arguments)
1624
- end
1625
- end
1626
-
1627
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1628
- non_combinable_operations.each(&:call)
1793
+ def reference_name_for_table(table_name)
1794
+ table_name.to_s.singularize
1629
1795
  end
1630
1796
 
1631
1797
  def add_column_for_alter(table_name, column_name, type, **options)
@@ -1634,6 +1800,11 @@ module ActiveRecord
1634
1800
  schema_creation.accept(AddColumnDefinition.new(cd))
1635
1801
  end
1636
1802
 
1803
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
1804
+ cd = build_change_column_default_definition(table_name, column_name, default_or_changes)
1805
+ schema_creation.accept(cd)
1806
+ end
1807
+
1637
1808
  def rename_column_sql(table_name, column_name, new_column_name)
1638
1809
  "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1639
1810
  end
@@ -1668,8 +1839,8 @@ module ActiveRecord
1668
1839
 
1669
1840
  if versions.is_a?(Array)
1670
1841
  sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1671
- sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1672
- sql << ";\n\n"
1842
+ sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1843
+ sql << ";"
1673
1844
  sql
1674
1845
  else
1675
1846
  "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"