activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  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 +2 -4
  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 +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. 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,15 @@ 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
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]
309
296
 
310
- if pk.is_a?(Array)
311
- td.primary_keys pk
312
- else
313
- td.primary_key pk, id, **options
314
- end
297
+ if force && options.key?(:if_not_exists)
298
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
315
299
  end
316
300
 
317
- yield td if block_given?
301
+ td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
318
302
 
319
303
  if force
320
304
  drop_table(table_name, force: force, if_exists: true)
@@ -322,7 +306,7 @@ module ActiveRecord
322
306
  schema_cache.clear_data_source_cache!(table_name.to_s)
323
307
  end
324
308
 
325
- result = execute schema_creation.accept td
309
+ result = execute schema_creation.accept(td)
326
310
 
327
311
  unless supports_indexes_in_create?
328
312
  td.indexes.each do |column_name, index_options|
@@ -343,6 +327,18 @@ module ActiveRecord
343
327
  result
344
328
  end
345
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
+
346
342
  # Creates a new join table with the name created using the lexical order of the first two
347
343
  # arguments. These arguments can be a String or a Symbol.
348
344
  #
@@ -386,7 +382,7 @@ module ActiveRecord
386
382
 
387
383
  column_options.reverse_merge!(null: false, index: false)
388
384
 
389
- 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) }
390
386
 
391
387
  create_table(join_table_name, **options.merge!(id: false)) do |td|
392
388
  td.references t1_ref, **column_options
@@ -395,15 +391,33 @@ module ActiveRecord
395
391
  end
396
392
  end
397
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
+
398
412
  # Drops the join table specified by the given arguments.
399
- # See #create_join_table for details.
413
+ # See #create_join_table and #drop_table for details.
400
414
  #
401
415
  # Although this command ignores the block if one is given, it can be helpful
402
416
  # to provide one in a migration's +change+ method so it can be reverted.
403
417
  # In that case, the block will be used by #create_join_table.
404
418
  def drop_join_table(table_1, table_2, **options)
405
419
  join_table_name = find_join_table_name(table_1, table_2, options)
406
- drop_table(join_table_name)
420
+ drop_table(join_table_name, **options)
407
421
  end
408
422
 
409
423
  # A block for changing columns in +table+.
@@ -484,13 +498,13 @@ module ActiveRecord
484
498
  # end
485
499
  #
486
500
  # See also Table for details on all of the various column transformations.
487
- def change_table(table_name, **options)
501
+ def change_table(table_name, base = self, **options)
488
502
  if supports_bulk_alter? && options[:bulk]
489
503
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
490
504
  yield update_table_definition(table_name, recorder)
491
505
  bulk_change_table(table_name, recorder.commands)
492
506
  else
493
- yield update_table_definition(table_name, self)
507
+ yield update_table_definition(table_name, base)
494
508
  end
495
509
  end
496
510
 
@@ -498,7 +512,7 @@ module ActiveRecord
498
512
  #
499
513
  # rename_table('octopuses', 'octopi')
500
514
  #
501
- def rename_table(table_name, new_name)
515
+ def rename_table(table_name, new_name, **)
502
516
  raise NotImplementedError, "rename_table is not implemented"
503
517
  end
504
518
 
@@ -553,11 +567,6 @@ module ActiveRecord
553
567
  # <tt>:datetime</tt>, and <tt>:time</tt> columns.
554
568
  # * <tt>:scale</tt> -
555
569
  # 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
570
  # * <tt>:if_not_exists</tt> -
562
571
  # Specifies if the column already exists to not try to re-add it. This will avoid
563
572
  # duplicate column errors.
@@ -573,7 +582,7 @@ module ActiveRecord
573
582
  # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
574
583
  # <tt>:precision</tt>, and makes no comments about the requirements of
575
584
  # <tt>:precision</tt>.
576
- # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
585
+ # * MySQL: <tt>:precision</tt> [1..65], <tt>:scale</tt> [0..30].
577
586
  # Default is (10,0).
578
587
  # * PostgreSQL: <tt>:precision</tt> [1..infinity],
579
588
  # <tt>:scale</tt> [0..infinity]. No default.
@@ -614,6 +623,24 @@ module ActiveRecord
614
623
  # # Ignores the method call if the column exists
615
624
  # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
616
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:
617
644
  return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
618
645
 
619
646
  if supports_datetime_with_precision?
@@ -622,15 +649,9 @@ module ActiveRecord
622
649
  end
623
650
  end
624
651
 
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
652
+ alter_table = create_alter_table(table_name)
653
+ alter_table.add_column(column_name, type, **options)
654
+ alter_table
634
655
  end
635
656
 
636
657
  # Removes the given columns from the table definition.
@@ -698,6 +719,15 @@ module ActiveRecord
698
719
  raise NotImplementedError, "change_column_default is not implemented"
699
720
  end
700
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
+
701
731
  # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
702
732
  # indicates whether the value can be +NULL+. For example
703
733
  #
@@ -804,6 +834,16 @@ module ActiveRecord
804
834
  #
805
835
  # Note: Partial indexes are only supported for PostgreSQL and SQLite.
806
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
+ #
807
847
  # ====== Creating an index with a specific method
808
848
  #
809
849
  # add_index(:developers, :name, using: 'btree')
@@ -841,20 +881,31 @@ module ActiveRecord
841
881
  # ====== Creating an index with a specific algorithm
842
882
  #
843
883
  # add_index(:developers, :name, algorithm: :concurrently)
844
- # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
884
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
845
885
  #
846
- # 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.
847
890
  #
848
891
  # Concurrently adding an index is not supported in a transaction.
849
892
  #
850
893
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
851
894
  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)
895
+ create_index = build_create_index_definition(table_name, column_name, **options)
855
896
  execute schema_creation.accept(create_index)
856
897
  end
857
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
+
858
909
  # Removes the given index from the table.
859
910
  #
860
911
  # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
@@ -920,7 +971,11 @@ module ActiveRecord
920
971
  def index_name(table_name, options) # :nodoc:
921
972
  if Hash === options
922
973
  if options[:column]
923
- "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
924
979
  elsif options[:name]
925
980
  options[:name]
926
981
  else
@@ -940,7 +995,6 @@ module ActiveRecord
940
995
  # Adds a reference. The reference column is a bigint by default,
941
996
  # the <tt>:type</tt> option can be used to specify a different type.
942
997
  # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
943
- # #add_reference and #add_belongs_to are acceptable.
944
998
  #
945
999
  # The +options+ hash can include the following keys:
946
1000
  # [<tt>:type</tt>]
@@ -986,12 +1040,11 @@ module ActiveRecord
986
1040
  # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
987
1041
  #
988
1042
  def add_reference(table_name, ref_name, **options)
989
- ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
1043
+ ReferenceDefinition.new(ref_name, **options).add(table_name, self)
990
1044
  end
991
1045
  alias :add_belongs_to :add_reference
992
1046
 
993
1047
  # Removes the reference(s). Also removes a +type+ column if one exists.
994
- # #remove_reference and #remove_belongs_to are acceptable.
995
1048
  #
996
1049
  # ====== Remove the reference
997
1050
  #
@@ -1006,19 +1059,21 @@ module ActiveRecord
1006
1059
  # remove_reference(:products, :user, foreign_key: true)
1007
1060
  #
1008
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
+
1009
1064
  if foreign_key
1010
1065
  reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
1011
1066
  if foreign_key.is_a?(Hash)
1012
- foreign_key_options = foreign_key
1067
+ foreign_key_options = foreign_key.merge(conditional_options)
1013
1068
  else
1014
- foreign_key_options = { to_table: reference_name }
1069
+ foreign_key_options = { to_table: reference_name, **conditional_options }
1015
1070
  end
1016
1071
  foreign_key_options[:column] ||= "#{ref_name}_id"
1017
1072
  remove_foreign_key(table_name, **foreign_key_options)
1018
1073
  end
1019
1074
 
1020
- remove_column(table_name, "#{ref_name}_id")
1021
- 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
1022
1077
  end
1023
1078
  alias :remove_belongs_to :remove_reference
1024
1079
 
@@ -1055,6 +1110,16 @@ module ActiveRecord
1055
1110
  #
1056
1111
  # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
1057
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
+ #
1058
1123
  # ====== Creating a cascading foreign key
1059
1124
  #
1060
1125
  # add_foreign_key :articles, :authors, on_delete: :cascade
@@ -1065,15 +1130,17 @@ module ActiveRecord
1065
1130
  #
1066
1131
  # The +options+ hash can include the following keys:
1067
1132
  # [<tt>:column</tt>]
1068
- # 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.
1069
1135
  # [<tt>:primary_key</tt>]
1070
1136
  # The primary key column name on +to_table+. Defaults to +id+.
1137
+ # Pass an array to create a composite foreign key.
1071
1138
  # [<tt>:name</tt>]
1072
1139
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
1073
1140
  # [<tt>:on_delete</tt>]
1074
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1141
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1075
1142
  # [<tt>:on_update</tt>]
1076
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1143
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1077
1144
  # [<tt>:if_not_exists</tt>]
1078
1145
  # Specifies if the foreign key already exists to not try to re-add it. This will avoid
1079
1146
  # duplicate column errors.
@@ -1083,8 +1150,8 @@ module ActiveRecord
1083
1150
  # (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
1084
1151
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1085
1152
  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)
1153
+ return unless use_foreign_keys?
1154
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1088
1155
 
1089
1156
  options = foreign_key_options(from_table, to_table, options)
1090
1157
  at = create_alter_table from_table
@@ -1124,8 +1191,8 @@ module ActiveRecord
1124
1191
  # [<tt>:to_table</tt>]
1125
1192
  # The name of the table that contains the referenced primary key.
1126
1193
  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)
1194
+ return unless use_foreign_keys?
1195
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1129
1196
 
1130
1197
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1131
1198
 
@@ -1150,15 +1217,33 @@ module ActiveRecord
1150
1217
  foreign_key_for(from_table, to_table: to_table, **options).present?
1151
1218
  end
1152
1219
 
1153
- def foreign_key_column_for(table_name) # :nodoc:
1220
+ def foreign_key_column_for(table_name, column_name) # :nodoc:
1154
1221
  name = strip_table_name_prefix_and_suffix(table_name)
1155
- "#{name.singularize}_id"
1222
+ "#{name.singularize}_#{column_name}"
1156
1223
  end
1157
1224
 
1158
1225
  def foreign_key_options(from_table, to_table, options) # :nodoc:
1159
1226
  options = options.dup
1160
- 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
+
1161
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
+
1162
1247
  options
1163
1248
  end
1164
1249
 
@@ -1180,12 +1265,16 @@ module ActiveRecord
1180
1265
  # The +options+ hash can include the following keys:
1181
1266
  # [<tt>:name</tt>]
1182
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.
1183
1270
  # [<tt>:validate</tt>]
1184
1271
  # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1185
- def add_check_constraint(table_name, expression, **options)
1272
+ def add_check_constraint(table_name, expression, if_not_exists: false, **options)
1186
1273
  return unless supports_check_constraints?
1187
1274
 
1188
1275
  options = check_constraint_options(table_name, expression, options)
1276
+ return if if_not_exists && check_constraint_exists?(table_name, **options)
1277
+
1189
1278
  at = create_alter_table(table_name)
1190
1279
  at.add_check_constraint(expression, options)
1191
1280
 
@@ -1198,16 +1287,24 @@ module ActiveRecord
1198
1287
  options
1199
1288
  end
1200
1289
 
1201
- # 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.
1202
1292
  #
1203
1293
  # remove_check_constraint :products, name: "price_check"
1204
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
+ #
1205
1300
  # The +expression+ parameter will be ignored if present. It can be helpful
1206
1301
  # to provide this in a migration's +change+ method so it can be reverted.
1207
1302
  # In that case, +expression+ will be used by #add_check_constraint.
1208
- def remove_check_constraint(table_name, expression = nil, **options)
1303
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
1209
1304
  return unless supports_check_constraints?
1210
1305
 
1306
+ return if if_exists && !check_constraint_exists?(table_name, **options)
1307
+
1211
1308
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
1212
1309
 
1213
1310
  at = create_alter_table(table_name)
@@ -1216,8 +1313,20 @@ module ActiveRecord
1216
1313
  execute schema_creation.accept(at)
1217
1314
  end
1218
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
+
1219
1328
  def dump_schema_information # :nodoc:
1220
- versions = schema_migration.all_versions
1329
+ versions = pool.schema_migration.versions
1221
1330
  insert_versions_sql(versions) if versions.any?
1222
1331
  end
1223
1332
 
@@ -1227,8 +1336,9 @@ module ActiveRecord
1227
1336
 
1228
1337
  def assume_migrated_upto_version(version)
1229
1338
  version = version.to_i
1230
- sm_table = quote_table_name(schema_migration.table_name)
1339
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1231
1340
 
1341
+ migration_context = pool.migration_context
1232
1342
  migrated = migration_context.get_all_versions
1233
1343
  versions = migration_context.migrations.map(&:version)
1234
1344
 
@@ -1290,18 +1400,24 @@ module ActiveRecord
1290
1400
  end
1291
1401
 
1292
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
+
1293
1407
  values = columns_for_distinct(
1294
- visitor.compile(relation.table[relation.primary_key]),
1408
+ primary_key_columns,
1295
1409
  relation.order_values
1296
1410
  )
1297
1411
 
1298
1412
  limited = relation.reselect(values).distinct!
1299
- 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
1300
1416
 
1301
1417
  if limited_ids.empty?
1302
1418
  relation.none!
1303
1419
  else
1304
- relation.where!(relation.primary_key => limited_ids)
1420
+ relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h)
1305
1421
  end
1306
1422
 
1307
1423
  relation.limit_value = relation.offset_value = nil
@@ -1314,14 +1430,8 @@ module ActiveRecord
1314
1430
  # add_timestamps(:suppliers, null: true)
1315
1431
  #
1316
1432
  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
1433
+ fragments = add_timestamps_for_alter(table_name, **options)
1434
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
1325
1435
  end
1326
1436
 
1327
1437
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
@@ -1337,7 +1447,7 @@ module ActiveRecord
1337
1447
  end
1338
1448
 
1339
1449
  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)
1450
+ options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
1341
1451
 
1342
1452
  column_names = index_column_names(column_name)
1343
1453
 
@@ -1356,6 +1466,8 @@ module ActiveRecord
1356
1466
  where: options[:where],
1357
1467
  type: options[:type],
1358
1468
  using: options[:using],
1469
+ include: options[:include],
1470
+ nulls_not_distinct: options[:nulls_not_distinct],
1359
1471
  comment: options[:comment]
1360
1472
  )
1361
1473
 
@@ -1403,7 +1515,79 @@ module ActiveRecord
1403
1515
  SchemaDumper.create(self, options)
1404
1516
  end
1405
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
+
1406
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
+
1407
1591
  def column_options_keys
1408
1592
  [:limit, :precision, :scale, :default, :null, :collation, :comment]
1409
1593
  end
@@ -1438,7 +1622,7 @@ module ActiveRecord
1438
1622
 
1439
1623
  checks = []
1440
1624
 
1441
- if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1625
+ if !options.key?(:name) && expression_column_name?(column_name)
1442
1626
  options[:name] = index_name(table_name, column_name)
1443
1627
  column_names = []
1444
1628
  else
@@ -1447,7 +1631,7 @@ module ActiveRecord
1447
1631
 
1448
1632
  checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1449
1633
 
1450
- if column_names.present?
1634
+ if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
1451
1635
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1452
1636
  end
1453
1637
 
@@ -1457,7 +1641,7 @@ module ActiveRecord
1457
1641
 
1458
1642
  if matching_indexes.count > 1
1459
1643
  raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
1460
- "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1644
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1461
1645
  elsif matching_indexes.none?
1462
1646
  raise ArgumentError, "No indexes found on #{table_name} with the options provided."
1463
1647
  else
@@ -1465,11 +1649,11 @@ module ActiveRecord
1465
1649
  end
1466
1650
  end
1467
1651
 
1468
- def rename_table_indexes(table_name, new_name)
1652
+ def rename_table_indexes(table_name, new_name, **options)
1469
1653
  indexes(new_name).each do |index|
1470
- generated_index_name = index_name(table_name, column: index.columns)
1654
+ generated_index_name = index_name(table_name, column: index.columns, **options)
1471
1655
  if generated_index_name == index.name
1472
- 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)
1473
1657
  end
1474
1658
  end
1475
1659
  end
@@ -1487,10 +1671,6 @@ module ActiveRecord
1487
1671
  end
1488
1672
  end
1489
1673
 
1490
- def schema_creation
1491
- SchemaCreation.new(self)
1492
- end
1493
-
1494
1674
  def create_table_definition(name, **options)
1495
1675
  TableDefinition.new(self, name, **options)
1496
1676
  end
@@ -1499,8 +1679,12 @@ module ActiveRecord
1499
1679
  AlterTable.new create_table_definition(name)
1500
1680
  end
1501
1681
 
1502
- def extract_table_options!(options)
1503
- 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
1504
1688
  end
1505
1689
 
1506
1690
  def fetch_type_metadata(sql_type)
@@ -1515,7 +1699,7 @@ module ActiveRecord
1515
1699
  end
1516
1700
 
1517
1701
  def index_column_names(column_names)
1518
- if column_names.is_a?(String) && /\W/.match?(column_names)
1702
+ if expression_column_name?(column_names)
1519
1703
  column_names
1520
1704
  else
1521
1705
  Array(column_names)
@@ -1523,13 +1707,18 @@ module ActiveRecord
1523
1707
  end
1524
1708
 
1525
1709
  def index_name_options(column_names)
1526
- if column_names.is_a?(String) && /\W/.match?(column_names)
1710
+ if expression_column_name?(column_names)
1527
1711
  column_names = column_names.scan(/\w+/).join("_")
1528
1712
  end
1529
1713
 
1530
1714
  { column: column_names }
1531
1715
  end
1532
1716
 
1717
+ # Try to identify whether the given column name is an expression
1718
+ def expression_column_name?(column_name)
1719
+ column_name.is_a?(String) && /\W/.match?(column_name)
1720
+ end
1721
+
1533
1722
  def strip_table_name_prefix_and_suffix(table_name)
1534
1723
  prefix = Base.table_name_prefix
1535
1724
  suffix = Base.table_name_suffix
@@ -1538,7 +1727,8 @@ module ActiveRecord
1538
1727
 
1539
1728
  def foreign_key_name(table_name, options)
1540
1729
  options.fetch(:name) do
1541
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1730
+ columns = Array(options.fetch(:column)).map(&:to_s)
1731
+ identifier = "#{table_name}_#{columns * '_and_'}_fk"
1542
1732
  hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1543
1733
 
1544
1734
  "fk_rails_#{hashed_identifier}"
@@ -1546,7 +1736,7 @@ module ActiveRecord
1546
1736
  end
1547
1737
 
1548
1738
  def foreign_key_for(from_table, **options)
1549
- return unless supports_foreign_keys?
1739
+ return unless use_foreign_keys?
1550
1740
  foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1551
1741
  end
1552
1742
 
@@ -1563,6 +1753,10 @@ module ActiveRecord
1563
1753
  end
1564
1754
  end
1565
1755
 
1756
+ def foreign_keys_enabled?
1757
+ @config.fetch(:foreign_keys, true)
1758
+ end
1759
+
1566
1760
  def check_constraint_name(table_name, **options)
1567
1761
  options.fetch(:name) do
1568
1762
  expression = options.fetch(:expression)
@@ -1576,7 +1770,7 @@ module ActiveRecord
1576
1770
  def check_constraint_for(table_name, **options)
1577
1771
  return unless supports_check_constraints?
1578
1772
  chk_name = check_constraint_name(table_name, **options)
1579
- check_constraints(table_name).detect { |chk| chk.name == chk_name }
1773
+ check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) }
1580
1774
  end
1581
1775
 
1582
1776
  def check_constraint_for!(table_name, expression: nil, **options)
@@ -1590,6 +1784,12 @@ module ActiveRecord
1590
1784
  end
1591
1785
  end
1592
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
+
1593
1793
  def extract_new_default_value(default_or_changes)
1594
1794
  if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
1595
1795
  default_or_changes[:to]
@@ -1603,29 +1803,8 @@ module ActiveRecord
1603
1803
  column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1604
1804
  end
1605
1805
 
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)
1806
+ def reference_name_for_table(table_name)
1807
+ table_name.to_s.singularize
1629
1808
  end
1630
1809
 
1631
1810
  def add_column_for_alter(table_name, column_name, type, **options)
@@ -1634,6 +1813,11 @@ module ActiveRecord
1634
1813
  schema_creation.accept(AddColumnDefinition.new(cd))
1635
1814
  end
1636
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
+
1637
1821
  def rename_column_sql(table_name, column_name, new_column_name)
1638
1822
  "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1639
1823
  end
@@ -1664,12 +1848,12 @@ module ActiveRecord
1664
1848
  end
1665
1849
 
1666
1850
  def insert_versions_sql(versions)
1667
- sm_table = quote_table_name(schema_migration.table_name)
1851
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1668
1852
 
1669
1853
  if versions.is_a?(Array)
1670
1854
  sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1671
- sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1672
- sql << ";\n\n"
1855
+ sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1856
+ sql << ";"
1673
1857
  sql
1674
1858
  else
1675
1859
  "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"