activerecord 7.0.8.7 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -96,26 +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
- column_name = options[:column] if column_name.nil?
102
-
103
- if column_name.present?
104
- column_names = Array(column_name).map(&:to_s)
105
- checks << lambda { |i| Array(i.columns) == column_names }
106
- end
107
-
108
- checks << lambda { |i| i.unique } if options[:unique]
109
- checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
110
-
111
- indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
103
+ indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
112
104
  end
113
105
 
114
106
  # Returns an array of +Column+ objects for the table specified by +table_name+.
115
107
  def columns(table_name)
116
108
  table_name = table_name.to_s
117
- column_definitions(table_name).map do |field|
118
- 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)
119
112
  end
120
113
  end
121
114
 
@@ -297,25 +290,15 @@ module ActiveRecord
297
290
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
298
291
  #
299
292
  # See also TableDefinition#column for details on how to create columns.
300
- def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
301
- td = create_table_definition(table_name, **extract_table_options!(options))
302
-
303
- if id && !td.as
304
- pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
305
-
306
- if id.is_a?(Hash)
307
- options.merge!(id.except(:type))
308
- id = id.fetch(:type, :primary_key)
309
- end
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]
310
296
 
311
- if pk.is_a?(Array)
312
- td.primary_keys pk
313
- else
314
- td.primary_key pk, id, **options
315
- end
297
+ if force && options.key?(:if_not_exists)
298
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
316
299
  end
317
300
 
318
- yield td if block_given?
301
+ td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
319
302
 
320
303
  if force
321
304
  drop_table(table_name, force: force, if_exists: true)
@@ -323,7 +306,7 @@ module ActiveRecord
323
306
  schema_cache.clear_data_source_cache!(table_name.to_s)
324
307
  end
325
308
 
326
- result = execute schema_creation.accept td
309
+ result = execute schema_creation.accept(td)
327
310
 
328
311
  unless supports_indexes_in_create?
329
312
  td.indexes.each do |column_name, index_options|
@@ -344,6 +327,18 @@ module ActiveRecord
344
327
  result
345
328
  end
346
329
 
330
+ # Returns a TableDefinition object containing information about the table that would be created
331
+ # if the same arguments were passed to #create_table. See #create_table for information about
332
+ # passing a +table_name+, and other additional options that can be passed.
333
+ def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
334
+ table_definition = create_table_definition(table_name, **options.extract!(*valid_table_definition_options, :_skip_validate_options))
335
+ table_definition.set_primary_key(table_name, id, primary_key, **options.extract!(*valid_primary_key_options, :_skip_validate_options))
336
+
337
+ yield table_definition if block_given?
338
+
339
+ table_definition
340
+ end
341
+
347
342
  # Creates a new join table with the name created using the lexical order of the first two
348
343
  # arguments. These arguments can be a String or a Symbol.
349
344
  #
@@ -387,7 +382,7 @@ module ActiveRecord
387
382
 
388
383
  column_options.reverse_merge!(null: false, index: false)
389
384
 
390
- t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
385
+ t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
391
386
 
392
387
  create_table(join_table_name, **options.merge!(id: false)) do |td|
393
388
  td.references t1_ref, **column_options
@@ -396,15 +391,33 @@ module ActiveRecord
396
391
  end
397
392
  end
398
393
 
394
+ # Builds a TableDefinition object for a join table.
395
+ #
396
+ # This definition object contains information about the table that would be created
397
+ # if the same arguments were passed to #create_join_table. See #create_join_table for
398
+ # information about what arguments should be passed.
399
+ def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc:
400
+ join_table_name = find_join_table_name(table_1, table_2, options)
401
+ column_options.reverse_merge!(null: false, index: false)
402
+
403
+ t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }
404
+
405
+ build_create_table_definition(join_table_name, **options.merge!(id: false)) do |td|
406
+ td.references t1_ref, **column_options
407
+ td.references t2_ref, **column_options
408
+ yield td if block_given?
409
+ end
410
+ end
411
+
399
412
  # Drops the join table specified by the given arguments.
400
- # See #create_join_table for details.
413
+ # See #create_join_table and #drop_table for details.
401
414
  #
402
415
  # Although this command ignores the block if one is given, it can be helpful
403
416
  # to provide one in a migration's +change+ method so it can be reverted.
404
417
  # In that case, the block will be used by #create_join_table.
405
418
  def drop_join_table(table_1, table_2, **options)
406
419
  join_table_name = find_join_table_name(table_1, table_2, options)
407
- drop_table(join_table_name)
420
+ drop_table(join_table_name, **options)
408
421
  end
409
422
 
410
423
  # A block for changing columns in +table+.
@@ -485,13 +498,13 @@ module ActiveRecord
485
498
  # end
486
499
  #
487
500
  # See also Table for details on all of the various column transformations.
488
- def change_table(table_name, **options)
501
+ def change_table(table_name, base = self, **options)
489
502
  if supports_bulk_alter? && options[:bulk]
490
503
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
491
504
  yield update_table_definition(table_name, recorder)
492
505
  bulk_change_table(table_name, recorder.commands)
493
506
  else
494
- yield update_table_definition(table_name, self)
507
+ yield update_table_definition(table_name, base)
495
508
  end
496
509
  end
497
510
 
@@ -499,7 +512,7 @@ module ActiveRecord
499
512
  #
500
513
  # rename_table('octopuses', 'octopi')
501
514
  #
502
- def rename_table(table_name, new_name)
515
+ def rename_table(table_name, new_name, **)
503
516
  raise NotImplementedError, "rename_table is not implemented"
504
517
  end
505
518
 
@@ -554,11 +567,6 @@ module ActiveRecord
554
567
  # <tt>:datetime</tt>, and <tt>:time</tt> columns.
555
568
  # * <tt>:scale</tt> -
556
569
  # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
557
- # * <tt>:collation</tt> -
558
- # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
559
- # column will have the same collation as the table.
560
- # * <tt>:comment</tt> -
561
- # Specifies the comment for the column. This option is ignored by some backends.
562
570
  # * <tt>:if_not_exists</tt> -
563
571
  # Specifies if the column already exists to not try to re-add it. This will avoid
564
572
  # duplicate column errors.
@@ -574,7 +582,7 @@ module ActiveRecord
574
582
  # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
575
583
  # <tt>:precision</tt>, and makes no comments about the requirements of
576
584
  # <tt>:precision</tt>.
577
- # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
585
+ # * MySQL: <tt>:precision</tt> [1..65], <tt>:scale</tt> [0..30].
578
586
  # Default is (10,0).
579
587
  # * PostgreSQL: <tt>:precision</tt> [1..infinity],
580
588
  # <tt>:scale</tt> [0..infinity]. No default.
@@ -615,6 +623,24 @@ module ActiveRecord
615
623
  # # Ignores the method call if the column exists
616
624
  # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
617
625
  def add_column(table_name, column_name, type, **options)
626
+ add_column_def = build_add_column_definition(table_name, column_name, type, **options)
627
+ return unless add_column_def
628
+
629
+ execute schema_creation.accept(add_column_def)
630
+ end
631
+
632
+ def add_columns(table_name, *column_names, type:, **options) # :nodoc:
633
+ column_names.each do |column_name|
634
+ add_column(table_name, column_name, type, **options)
635
+ end
636
+ end
637
+
638
+ # Builds an AlterTable object for adding a column to a table.
639
+ #
640
+ # This definition object contains information about the column that would be created
641
+ # if the same arguments were passed to #add_column. See #add_column for information about
642
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
643
+ def build_add_column_definition(table_name, column_name, type, **options) # :nodoc:
618
644
  return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
619
645
 
620
646
  if supports_datetime_with_precision?
@@ -623,15 +649,9 @@ module ActiveRecord
623
649
  end
624
650
  end
625
651
 
626
- at = create_alter_table table_name
627
- at.add_column(column_name, type, **options)
628
- execute schema_creation.accept at
629
- end
630
-
631
- def add_columns(table_name, *column_names, type:, **options) # :nodoc:
632
- column_names.each do |column_name|
633
- add_column(table_name, column_name, type, **options)
634
- end
652
+ alter_table = create_alter_table(table_name)
653
+ alter_table.add_column(column_name, type, **options)
654
+ alter_table
635
655
  end
636
656
 
637
657
  # Removes the given columns from the table definition.
@@ -662,7 +682,7 @@ module ActiveRecord
662
682
  #
663
683
  # If the options provided include an +if_exists+ key, it will be used to check if the
664
684
  # column does not exist. This will silently ignore the migration rather than raising
665
- # if the column was already used.
685
+ # if the column was already removed.
666
686
  #
667
687
  # remove_column(:suppliers, :qualification, if_exists: true)
668
688
  def remove_column(table_name, column_name, type = nil, **options)
@@ -699,6 +719,15 @@ module ActiveRecord
699
719
  raise NotImplementedError, "change_column_default is not implemented"
700
720
  end
701
721
 
722
+ # Builds a ChangeColumnDefaultDefinition object.
723
+ #
724
+ # This definition object contains information about the column change that would occur
725
+ # if the same arguments were passed to #change_column_default. See #change_column_default for
726
+ # information about passing a +table_name+, +column_name+, +type+ and other options that can be passed.
727
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
728
+ raise NotImplementedError, "build_change_column_default_definition is not implemented"
729
+ end
730
+
702
731
  # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
703
732
  # indicates whether the value can be +NULL+. For example
704
733
  #
@@ -805,6 +834,16 @@ module ActiveRecord
805
834
  #
806
835
  # Note: Partial indexes are only supported for PostgreSQL and SQLite.
807
836
  #
837
+ # ====== Creating an index that includes additional columns
838
+ #
839
+ # add_index(:accounts, :branch_id, include: :party_id)
840
+ #
841
+ # generates:
842
+ #
843
+ # CREATE INDEX index_accounts_on_branch_id ON accounts USING btree(branch_id) INCLUDE (party_id)
844
+ #
845
+ # Note: only supported by PostgreSQL.
846
+ #
808
847
  # ====== Creating an index with a specific method
809
848
  #
810
849
  # add_index(:developers, :name, using: 'btree')
@@ -842,20 +881,31 @@ module ActiveRecord
842
881
  # ====== Creating an index with a specific algorithm
843
882
  #
844
883
  # add_index(:developers, :name, algorithm: :concurrently)
845
- # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
884
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
846
885
  #
847
- # Note: only supported by PostgreSQL.
886
+ # add_index(:developers, :name, algorithm: :inplace)
887
+ # # CREATE INDEX `index_developers_on_name` ON `developers` (`name`) ALGORITHM = INPLACE -- MySQL
888
+ #
889
+ # Note: only supported by PostgreSQL and MySQL.
848
890
  #
849
891
  # Concurrently adding an index is not supported in a transaction.
850
892
  #
851
893
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
852
894
  def add_index(table_name, column_name, **options)
853
- index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
854
-
855
- create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
895
+ create_index = build_create_index_definition(table_name, column_name, **options)
856
896
  execute schema_creation.accept(create_index)
857
897
  end
858
898
 
899
+ # Builds a CreateIndexDefinition object.
900
+ #
901
+ # This definition object contains information about the index that would be created
902
+ # if the same arguments were passed to #add_index. See #add_index for information about
903
+ # passing a +table_name+, +column_name+, and other additional options that can be passed.
904
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
905
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
906
+ CreateIndexDefinition.new(index, algorithm, if_not_exists)
907
+ end
908
+
859
909
  # Removes the given index from the table.
860
910
  #
861
911
  # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
@@ -921,7 +971,11 @@ module ActiveRecord
921
971
  def index_name(table_name, options) # :nodoc:
922
972
  if Hash === options
923
973
  if options[:column]
924
- "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
974
+ if options[:_uses_legacy_index_name]
975
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
976
+ else
977
+ generate_index_name(table_name, options[:column])
978
+ end
925
979
  elsif options[:name]
926
980
  options[:name]
927
981
  else
@@ -941,7 +995,6 @@ module ActiveRecord
941
995
  # Adds a reference. The reference column is a bigint by default,
942
996
  # the <tt>:type</tt> option can be used to specify a different type.
943
997
  # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
944
- # #add_reference and #add_belongs_to are acceptable.
945
998
  #
946
999
  # The +options+ hash can include the following keys:
947
1000
  # [<tt>:type</tt>]
@@ -987,12 +1040,11 @@ module ActiveRecord
987
1040
  # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
988
1041
  #
989
1042
  def add_reference(table_name, ref_name, **options)
990
- ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
1043
+ ReferenceDefinition.new(ref_name, **options).add(table_name, self)
991
1044
  end
992
1045
  alias :add_belongs_to :add_reference
993
1046
 
994
1047
  # Removes the reference(s). Also removes a +type+ column if one exists.
995
- # #remove_reference and #remove_belongs_to are acceptable.
996
1048
  #
997
1049
  # ====== Remove the reference
998
1050
  #
@@ -1007,19 +1059,21 @@ module ActiveRecord
1007
1059
  # remove_reference(:products, :user, foreign_key: true)
1008
1060
  #
1009
1061
  def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
1062
+ conditional_options = options.slice(:if_exists, :if_not_exists)
1063
+
1010
1064
  if foreign_key
1011
1065
  reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
1012
1066
  if foreign_key.is_a?(Hash)
1013
- foreign_key_options = foreign_key
1067
+ foreign_key_options = foreign_key.merge(conditional_options)
1014
1068
  else
1015
- foreign_key_options = { to_table: reference_name }
1069
+ foreign_key_options = { to_table: reference_name, **conditional_options }
1016
1070
  end
1017
1071
  foreign_key_options[:column] ||= "#{ref_name}_id"
1018
1072
  remove_foreign_key(table_name, **foreign_key_options)
1019
1073
  end
1020
1074
 
1021
- remove_column(table_name, "#{ref_name}_id")
1022
- remove_column(table_name, "#{ref_name}_type") if polymorphic
1075
+ remove_column(table_name, "#{ref_name}_id", **conditional_options)
1076
+ remove_column(table_name, "#{ref_name}_type", **conditional_options) if polymorphic
1023
1077
  end
1024
1078
  alias :remove_belongs_to :remove_reference
1025
1079
 
@@ -1056,6 +1110,16 @@ module ActiveRecord
1056
1110
  #
1057
1111
  # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
1058
1112
  #
1113
+ # ====== Creating a composite foreign key
1114
+ #
1115
+ # Assuming "carts" table has "(shop_id, user_id)" as a primary key.
1116
+ #
1117
+ # add_foreign_key :orders, :carts, primary_key: [:shop_id, :user_id]
1118
+ #
1119
+ # generates:
1120
+ #
1121
+ # ALTER TABLE "orders" ADD CONSTRAINT fk_rails_6f5e4cb3a4 FOREIGN KEY ("cart_shop_id", "cart_user_id") REFERENCES "carts" ("shop_id", "user_id")
1122
+ #
1059
1123
  # ====== Creating a cascading foreign key
1060
1124
  #
1061
1125
  # add_foreign_key :articles, :authors, on_delete: :cascade
@@ -1066,9 +1130,11 @@ module ActiveRecord
1066
1130
  #
1067
1131
  # The +options+ hash can include the following keys:
1068
1132
  # [<tt>:column</tt>]
1069
- # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
1133
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>.
1134
+ # Pass an array to create a composite foreign key.
1070
1135
  # [<tt>:primary_key</tt>]
1071
1136
  # The primary key column name on +to_table+. Defaults to +id+.
1137
+ # Pass an array to create a composite foreign key.
1072
1138
  # [<tt>:name</tt>]
1073
1139
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
1074
1140
  # [<tt>:on_delete</tt>]
@@ -1084,8 +1150,8 @@ module ActiveRecord
1084
1150
  # (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
1085
1151
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1086
1152
  def add_foreign_key(from_table, to_table, **options)
1087
- return unless supports_foreign_keys?
1088
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
1153
+ return unless use_foreign_keys?
1154
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1089
1155
 
1090
1156
  options = foreign_key_options(from_table, to_table, options)
1091
1157
  at = create_alter_table from_table
@@ -1125,7 +1191,7 @@ module ActiveRecord
1125
1191
  # [<tt>:to_table</tt>]
1126
1192
  # The name of the table that contains the referenced primary key.
1127
1193
  def remove_foreign_key(from_table, to_table = nil, **options)
1128
- return unless supports_foreign_keys?
1194
+ return unless use_foreign_keys?
1129
1195
  return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1130
1196
 
1131
1197
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
@@ -1151,15 +1217,33 @@ module ActiveRecord
1151
1217
  foreign_key_for(from_table, to_table: to_table, **options).present?
1152
1218
  end
1153
1219
 
1154
- def foreign_key_column_for(table_name) # :nodoc:
1220
+ def foreign_key_column_for(table_name, column_name) # :nodoc:
1155
1221
  name = strip_table_name_prefix_and_suffix(table_name)
1156
- "#{name.singularize}_id"
1222
+ "#{name.singularize}_#{column_name}"
1157
1223
  end
1158
1224
 
1159
1225
  def foreign_key_options(from_table, to_table, options) # :nodoc:
1160
1226
  options = options.dup
1161
- options[:column] ||= foreign_key_column_for(to_table)
1227
+
1228
+ if options[:primary_key].is_a?(Array)
1229
+ options[:column] ||= options[:primary_key].map do |pk_column|
1230
+ foreign_key_column_for(to_table, pk_column)
1231
+ end
1232
+ else
1233
+ options[:column] ||= foreign_key_column_for(to_table, "id")
1234
+ end
1235
+
1162
1236
  options[:name] ||= foreign_key_name(from_table, options)
1237
+
1238
+ if options[:column].is_a?(Array) || options[:primary_key].is_a?(Array)
1239
+ if Array(options[:primary_key]).size != Array(options[:column]).size
1240
+ raise ArgumentError, <<~MSG.squish
1241
+ For composite primary keys, specify :column and :primary_key, where
1242
+ :column must reference all the :primary_key columns from #{to_table.inspect}
1243
+ MSG
1244
+ end
1245
+ end
1246
+
1163
1247
  options
1164
1248
  end
1165
1249
 
@@ -1181,12 +1265,16 @@ module ActiveRecord
1181
1265
  # The +options+ hash can include the following keys:
1182
1266
  # [<tt>:name</tt>]
1183
1267
  # The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
1268
+ # [<tt>:if_not_exists</tt>]
1269
+ # Silently ignore if the constraint already exists, rather than raise an error.
1184
1270
  # [<tt>:validate</tt>]
1185
1271
  # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1186
- def add_check_constraint(table_name, expression, **options)
1272
+ def add_check_constraint(table_name, expression, if_not_exists: false, **options)
1187
1273
  return unless supports_check_constraints?
1188
1274
 
1189
1275
  options = check_constraint_options(table_name, expression, options)
1276
+ return if if_not_exists && check_constraint_exists?(table_name, **options)
1277
+
1190
1278
  at = create_alter_table(table_name)
1191
1279
  at.add_check_constraint(expression, options)
1192
1280
 
@@ -1199,16 +1287,24 @@ module ActiveRecord
1199
1287
  options
1200
1288
  end
1201
1289
 
1202
- # Removes the given check constraint from the table.
1290
+ # Removes the given check constraint from the table. Removing a check constraint
1291
+ # that does not exist will raise an error.
1203
1292
  #
1204
1293
  # remove_check_constraint :products, name: "price_check"
1205
1294
  #
1295
+ # To silently ignore a non-existent check constraint rather than raise an error,
1296
+ # use the +if_exists+ option.
1297
+ #
1298
+ # remove_check_constraint :products, name: "price_check", if_exists: true
1299
+ #
1206
1300
  # The +expression+ parameter will be ignored if present. It can be helpful
1207
1301
  # to provide this in a migration's +change+ method so it can be reverted.
1208
1302
  # In that case, +expression+ will be used by #add_check_constraint.
1209
- def remove_check_constraint(table_name, expression = nil, **options)
1303
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
1210
1304
  return unless supports_check_constraints?
1211
1305
 
1306
+ return if if_exists && !check_constraint_exists?(table_name, **options)
1307
+
1212
1308
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
1213
1309
 
1214
1310
  at = create_alter_table(table_name)
@@ -1217,8 +1313,20 @@ module ActiveRecord
1217
1313
  execute schema_creation.accept(at)
1218
1314
  end
1219
1315
 
1316
+
1317
+ # Checks to see if a check constraint exists on a table for a given check constraint definition.
1318
+ #
1319
+ # check_constraint_exists?(:products, name: "price_check")
1320
+ #
1321
+ def check_constraint_exists?(table_name, **options)
1322
+ if !options.key?(:name) && !options.key?(:expression)
1323
+ raise ArgumentError, "At least one of :name or :expression must be supplied"
1324
+ end
1325
+ check_constraint_for(table_name, **options).present?
1326
+ end
1327
+
1220
1328
  def dump_schema_information # :nodoc:
1221
- versions = schema_migration.all_versions
1329
+ versions = pool.schema_migration.versions
1222
1330
  insert_versions_sql(versions) if versions.any?
1223
1331
  end
1224
1332
 
@@ -1228,8 +1336,9 @@ module ActiveRecord
1228
1336
 
1229
1337
  def assume_migrated_upto_version(version)
1230
1338
  version = version.to_i
1231
- sm_table = quote_table_name(schema_migration.table_name)
1339
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1232
1340
 
1341
+ migration_context = pool.migration_context
1233
1342
  migrated = migration_context.get_all_versions
1234
1343
  versions = migration_context.migrations.map(&:version)
1235
1344
 
@@ -1291,18 +1400,24 @@ module ActiveRecord
1291
1400
  end
1292
1401
 
1293
1402
  def distinct_relation_for_primary_key(relation) # :nodoc:
1403
+ primary_key_columns = Array(relation.primary_key).map do |column|
1404
+ visitor.compile(relation.table[column])
1405
+ end
1406
+
1294
1407
  values = columns_for_distinct(
1295
- visitor.compile(relation.table[relation.primary_key]),
1408
+ primary_key_columns,
1296
1409
  relation.order_values
1297
1410
  )
1298
1411
 
1299
1412
  limited = relation.reselect(values).distinct!
1300
- limited_ids = select_rows(limited.arel, "SQL").map(&:last)
1413
+ limited_ids = select_rows(limited.arel, "SQL").map do |results|
1414
+ results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL
1415
+ end
1301
1416
 
1302
1417
  if limited_ids.empty?
1303
1418
  relation.none!
1304
1419
  else
1305
- relation.where!(relation.primary_key => limited_ids)
1420
+ relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h)
1306
1421
  end
1307
1422
 
1308
1423
  relation.limit_value = relation.offset_value = nil
@@ -1315,14 +1430,8 @@ module ActiveRecord
1315
1430
  # add_timestamps(:suppliers, null: true)
1316
1431
  #
1317
1432
  def add_timestamps(table_name, **options)
1318
- options[:null] = false if options[:null].nil?
1319
-
1320
- if !options.key?(:precision) && supports_datetime_with_precision?
1321
- options[:precision] = 6
1322
- end
1323
-
1324
- add_column table_name, :created_at, :datetime, **options
1325
- add_column table_name, :updated_at, :datetime, **options
1433
+ fragments = add_timestamps_for_alter(table_name, **options)
1434
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
1326
1435
  end
1327
1436
 
1328
1437
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
@@ -1338,7 +1447,7 @@ module ActiveRecord
1338
1447
  end
1339
1448
 
1340
1449
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1341
- options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
1450
+ options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
1342
1451
 
1343
1452
  column_names = index_column_names(column_name)
1344
1453
 
@@ -1357,6 +1466,8 @@ module ActiveRecord
1357
1466
  where: options[:where],
1358
1467
  type: options[:type],
1359
1468
  using: options[:using],
1469
+ include: options[:include],
1470
+ nulls_not_distinct: options[:nulls_not_distinct],
1360
1471
  comment: options[:comment]
1361
1472
  )
1362
1473
 
@@ -1404,7 +1515,79 @@ module ActiveRecord
1404
1515
  SchemaDumper.create(self, options)
1405
1516
  end
1406
1517
 
1518
+ def use_foreign_keys?
1519
+ supports_foreign_keys? && foreign_keys_enabled?
1520
+ end
1521
+
1522
+ # Returns an instance of SchemaCreation, which can be used to visit a schema definition
1523
+ # object and return DDL.
1524
+ def schema_creation # :nodoc:
1525
+ SchemaCreation.new(self)
1526
+ end
1527
+
1528
+ def bulk_change_table(table_name, operations) # :nodoc:
1529
+ sql_fragments = []
1530
+ non_combinable_operations = []
1531
+
1532
+ operations.each do |command, args|
1533
+ table, arguments = args.shift, args
1534
+ method = :"#{command}_for_alter"
1535
+
1536
+ if respond_to?(method, true)
1537
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1538
+ sql_fragments.concat(sqls)
1539
+ non_combinable_operations.concat(procs)
1540
+ else
1541
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1542
+ non_combinable_operations.each(&:call)
1543
+ sql_fragments = []
1544
+ non_combinable_operations = []
1545
+ send(command, table, *arguments)
1546
+ end
1547
+ end
1548
+
1549
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1550
+ non_combinable_operations.each(&:call)
1551
+ end
1552
+
1553
+ def valid_table_definition_options # :nodoc:
1554
+ [:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation]
1555
+ end
1556
+
1557
+ def valid_column_definition_options # :nodoc:
1558
+ ColumnDefinition::OPTION_NAMES
1559
+ end
1560
+
1561
+ def valid_primary_key_options # :nodoc:
1562
+ [:limit, :default, :precision]
1563
+ end
1564
+
1565
+ # Returns the maximum length of an index name in bytes.
1566
+ def max_index_name_size
1567
+ 62
1568
+ end
1569
+
1407
1570
  private
1571
+ def generate_index_name(table_name, column)
1572
+ name = "index_#{table_name}_on_#{Array(column) * '_and_'}"
1573
+ return name if name.bytesize <= max_index_name_size
1574
+
1575
+ # Fallback to short version, add hash to ensure uniqueness
1576
+ hashed_identifier = "_" + OpenSSL::Digest::SHA256.hexdigest(name).first(10)
1577
+ name = "idx_on_#{Array(column) * '_'}"
1578
+
1579
+ short_limit = max_index_name_size - hashed_identifier.bytesize
1580
+ short_name = name.mb_chars.limit(short_limit).to_s
1581
+
1582
+ "#{short_name}#{hashed_identifier}"
1583
+ end
1584
+
1585
+ def validate_change_column_null_argument!(value)
1586
+ unless value == true || value == false
1587
+ raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}"
1588
+ end
1589
+ end
1590
+
1408
1591
  def column_options_keys
1409
1592
  [:limit, :precision, :scale, :default, :null, :collation, :comment]
1410
1593
  end
@@ -1458,7 +1641,7 @@ module ActiveRecord
1458
1641
 
1459
1642
  if matching_indexes.count > 1
1460
1643
  raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
1461
- "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1644
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1462
1645
  elsif matching_indexes.none?
1463
1646
  raise ArgumentError, "No indexes found on #{table_name} with the options provided."
1464
1647
  else
@@ -1466,11 +1649,11 @@ module ActiveRecord
1466
1649
  end
1467
1650
  end
1468
1651
 
1469
- def rename_table_indexes(table_name, new_name)
1652
+ def rename_table_indexes(table_name, new_name, **options)
1470
1653
  indexes(new_name).each do |index|
1471
- generated_index_name = index_name(table_name, column: index.columns)
1654
+ generated_index_name = index_name(table_name, column: index.columns, **options)
1472
1655
  if generated_index_name == index.name
1473
- rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
1656
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns, **options)
1474
1657
  end
1475
1658
  end
1476
1659
  end
@@ -1488,10 +1671,6 @@ module ActiveRecord
1488
1671
  end
1489
1672
  end
1490
1673
 
1491
- def schema_creation
1492
- SchemaCreation.new(self)
1493
- end
1494
-
1495
1674
  def create_table_definition(name, **options)
1496
1675
  TableDefinition.new(self, name, **options)
1497
1676
  end
@@ -1500,8 +1679,12 @@ module ActiveRecord
1500
1679
  AlterTable.new create_table_definition(name)
1501
1680
  end
1502
1681
 
1503
- def extract_table_options!(options)
1504
- options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
1682
+ def validate_create_table_options!(options)
1683
+ unless options[:_skip_validate_options]
1684
+ options
1685
+ .except(:_uses_legacy_table_name, :_skip_validate_options)
1686
+ .assert_valid_keys(valid_table_definition_options, valid_primary_key_options)
1687
+ end
1505
1688
  end
1506
1689
 
1507
1690
  def fetch_type_metadata(sql_type)
@@ -1544,7 +1727,8 @@ module ActiveRecord
1544
1727
 
1545
1728
  def foreign_key_name(table_name, options)
1546
1729
  options.fetch(:name) do
1547
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1730
+ columns = Array(options.fetch(:column)).map(&:to_s)
1731
+ identifier = "#{table_name}_#{columns * '_and_'}_fk"
1548
1732
  hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1549
1733
 
1550
1734
  "fk_rails_#{hashed_identifier}"
@@ -1552,7 +1736,7 @@ module ActiveRecord
1552
1736
  end
1553
1737
 
1554
1738
  def foreign_key_for(from_table, **options)
1555
- return unless supports_foreign_keys?
1739
+ return unless use_foreign_keys?
1556
1740
  foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1557
1741
  end
1558
1742
 
@@ -1569,6 +1753,10 @@ module ActiveRecord
1569
1753
  end
1570
1754
  end
1571
1755
 
1756
+ def foreign_keys_enabled?
1757
+ @config.fetch(:foreign_keys, true)
1758
+ end
1759
+
1572
1760
  def check_constraint_name(table_name, **options)
1573
1761
  options.fetch(:name) do
1574
1762
  expression = options.fetch(:expression)
@@ -1582,7 +1770,7 @@ module ActiveRecord
1582
1770
  def check_constraint_for(table_name, **options)
1583
1771
  return unless supports_check_constraints?
1584
1772
  chk_name = check_constraint_name(table_name, **options)
1585
- check_constraints(table_name).detect { |chk| chk.name == chk_name }
1773
+ check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) }
1586
1774
  end
1587
1775
 
1588
1776
  def check_constraint_for!(table_name, expression: nil, **options)
@@ -1596,6 +1784,12 @@ module ActiveRecord
1596
1784
  end
1597
1785
  end
1598
1786
 
1787
+ def validate_table_length!(table_name)
1788
+ if table_name.length > table_name_length
1789
+ raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters"
1790
+ end
1791
+ end
1792
+
1599
1793
  def extract_new_default_value(default_or_changes)
1600
1794
  if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
1601
1795
  default_or_changes[:to]
@@ -1609,29 +1803,8 @@ module ActiveRecord
1609
1803
  column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1610
1804
  end
1611
1805
 
1612
- def bulk_change_table(table_name, operations)
1613
- sql_fragments = []
1614
- non_combinable_operations = []
1615
-
1616
- operations.each do |command, args|
1617
- table, arguments = args.shift, args
1618
- method = :"#{command}_for_alter"
1619
-
1620
- if respond_to?(method, true)
1621
- sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1622
- sql_fragments << sqls
1623
- non_combinable_operations.concat(procs)
1624
- else
1625
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1626
- non_combinable_operations.each(&:call)
1627
- sql_fragments = []
1628
- non_combinable_operations = []
1629
- send(command, table, *arguments)
1630
- end
1631
- end
1632
-
1633
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1634
- non_combinable_operations.each(&:call)
1806
+ def reference_name_for_table(table_name)
1807
+ table_name.to_s.singularize
1635
1808
  end
1636
1809
 
1637
1810
  def add_column_for_alter(table_name, column_name, type, **options)
@@ -1640,6 +1813,11 @@ module ActiveRecord
1640
1813
  schema_creation.accept(AddColumnDefinition.new(cd))
1641
1814
  end
1642
1815
 
1816
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
1817
+ cd = build_change_column_default_definition(table_name, column_name, default_or_changes)
1818
+ schema_creation.accept(cd)
1819
+ end
1820
+
1643
1821
  def rename_column_sql(table_name, column_name, new_column_name)
1644
1822
  "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1645
1823
  end
@@ -1670,12 +1848,12 @@ module ActiveRecord
1670
1848
  end
1671
1849
 
1672
1850
  def insert_versions_sql(versions)
1673
- sm_table = quote_table_name(schema_migration.table_name)
1851
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1674
1852
 
1675
1853
  if versions.is_a?(Array)
1676
1854
  sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1677
- sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1678
- sql << ";\n\n"
1855
+ sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1856
+ sql << ";"
1679
1857
  sql
1680
1858
  else
1681
1859
  "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"