activerecord 5.2.8 → 7.0.2

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

Potentially problematic release.


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

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +155 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +42 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record/migration/join_table"
4
3
  require "active_support/core_ext/string/access"
5
- require "digest/sha2"
4
+ require "openssl"
6
5
 
7
6
  module ActiveRecord
8
7
  module ConnectionAdapters # :nodoc:
@@ -30,7 +29,7 @@ module ActiveRecord
30
29
  table_name[0...table_alias_length].tr(".", "_")
31
30
  end
32
31
 
33
- # Returns the relation names useable to back Active Record models.
32
+ # Returns the relation names usable to back Active Record models.
34
33
  # For most adapters this means all #tables and #views.
35
34
  def data_sources
36
35
  query_values(data_source_sql, "SCHEMA")
@@ -97,10 +96,14 @@ module ActiveRecord
97
96
  # # Check an index with a custom name exists
98
97
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
99
98
  #
100
- def index_exists?(table_name, column_name, options = {})
101
- column_names = Array(column_name).map(&:to_s)
99
+ def index_exists?(table_name, column_name, **options)
102
100
  checks = []
103
- checks << lambda { |i| Array(i.columns) == column_names }
101
+
102
+ if column_name.present?
103
+ column_names = Array(column_name).map(&:to_s)
104
+ checks << lambda { |i| Array(i.columns) == column_names }
105
+ end
106
+
104
107
  checks << lambda { |i| i.unique } if options[:unique]
105
108
  checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
106
109
 
@@ -121,6 +124,9 @@ module ActiveRecord
121
124
  # column_exists?(:suppliers, :name)
122
125
  #
123
126
  # # Check a column exists of a particular type
127
+ # #
128
+ # # This works for standard non-casted types (eg. string) but is unreliable
129
+ # # for types that may get cast to something else (eg. char, bigint).
124
130
  # column_exists?(:suppliers, :name, :string)
125
131
  #
126
132
  # # Check a column exists with a specific definition
@@ -129,11 +135,11 @@ module ActiveRecord
129
135
  # column_exists?(:suppliers, :name, :string, null: false)
130
136
  # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
131
137
  #
132
- def column_exists?(table_name, column_name, type = nil, options = {})
138
+ def column_exists?(table_name, column_name, type = nil, **options)
133
139
  column_name = column_name.to_s
134
140
  checks = []
135
141
  checks << lambda { |c| c.name == column_name }
136
- checks << lambda { |c| c.type == type } if type
142
+ checks << lambda { |c| c.type == type.to_sym rescue nil } if type
137
143
  column_options_keys.each do |attr|
138
144
  checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
139
145
  end
@@ -205,19 +211,22 @@ module ActiveRecord
205
211
  # Set to true to drop the table before creating it.
206
212
  # Set to +:cascade+ to drop dependent objects as well.
207
213
  # Defaults to false.
214
+ # [<tt>:if_not_exists</tt>]
215
+ # Set to true to avoid raising an error when the table already exists.
216
+ # Defaults to false.
208
217
  # [<tt>:as</tt>]
209
218
  # SQL to use to generate the table. When this option is used, the block is
210
219
  # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
211
220
  #
212
221
  # ====== Add a backend specific option to the generated SQL (MySQL)
213
222
  #
214
- # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
223
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
215
224
  #
216
225
  # generates:
217
226
  #
218
227
  # CREATE TABLE suppliers (
219
228
  # id bigint auto_increment PRIMARY KEY
220
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
229
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
221
230
  #
222
231
  # ====== Rename the primary key column
223
232
  #
@@ -287,37 +296,44 @@ module ActiveRecord
287
296
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
288
297
  #
289
298
  # See also TableDefinition#column for details on how to create columns.
290
- def create_table(table_name, comment: nil, **options)
291
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
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))
292
301
 
293
- if options[:id] != false && !options[:as]
294
- pk = options.fetch(:primary_key) do
295
- Base.get_primary_key table_name.to_s.singularize
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)
296
308
  end
297
309
 
298
310
  if pk.is_a?(Array)
299
311
  td.primary_keys pk
300
312
  else
301
- td.primary_key pk, options.fetch(:id, :primary_key), options
313
+ td.primary_key pk, id, **options
302
314
  end
303
315
  end
304
316
 
305
317
  yield td if block_given?
306
318
 
307
- if options[:force]
308
- drop_table(table_name, options.merge(if_exists: true))
319
+ if force
320
+ drop_table(table_name, force: force, if_exists: true)
321
+ else
322
+ schema_cache.clear_data_source_cache!(table_name.to_s)
309
323
  end
310
324
 
311
325
  result = execute schema_creation.accept td
312
326
 
313
327
  unless supports_indexes_in_create?
314
328
  td.indexes.each do |column_name, index_options|
315
- add_index(table_name, column_name, index_options)
329
+ add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists)
316
330
  end
317
331
  end
318
332
 
319
333
  if supports_comments? && !supports_comments_in_create?
320
- change_table_comment(table_name, comment) if comment.present?
334
+ if table_comment = td.comment.presence
335
+ change_table_comment(table_name, table_comment)
336
+ end
321
337
 
322
338
  td.columns.each do |column|
323
339
  change_column_comment(table_name, column.name, column.comment) if column.comment.present?
@@ -372,9 +388,9 @@ module ActiveRecord
372
388
 
373
389
  t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
374
390
 
375
- create_table(join_table_name, options.merge!(id: false)) do |td|
376
- td.references t1_ref, column_options
377
- td.references t2_ref, column_options
391
+ create_table(join_table_name, **options.merge!(id: false)) do |td|
392
+ td.references t1_ref, **column_options
393
+ td.references t2_ref, **column_options
378
394
  yield td if block_given?
379
395
  end
380
396
  end
@@ -385,7 +401,7 @@ module ActiveRecord
385
401
  # Although this command ignores the block if one is given, it can be helpful
386
402
  # to provide one in a migration's +change+ method so it can be reverted.
387
403
  # In that case, the block will be used by #create_join_table.
388
- def drop_join_table(table_1, table_2, options = {})
404
+ def drop_join_table(table_1, table_2, **options)
389
405
  join_table_name = find_join_table_name(table_1, table_2, options)
390
406
  drop_table(join_table_name)
391
407
  end
@@ -414,6 +430,12 @@ module ActiveRecord
414
430
  # t.column :name, :string, limit: 60
415
431
  # end
416
432
  #
433
+ # ====== Change type of a column
434
+ #
435
+ # change_table(:suppliers) do |t|
436
+ # t.change :metadata, :json
437
+ # end
438
+ #
417
439
  # ====== Add 2 integer columns
418
440
  #
419
441
  # change_table(:suppliers) do |t|
@@ -462,7 +484,7 @@ module ActiveRecord
462
484
  # end
463
485
  #
464
486
  # See also Table for details on all of the various column transformations.
465
- def change_table(table_name, options = {})
487
+ def change_table(table_name, **options)
466
488
  if supports_bulk_alter? && options[:bulk]
467
489
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
468
490
  yield update_table_definition(table_name, recorder)
@@ -492,38 +514,53 @@ module ActiveRecord
492
514
  # Although this command ignores most +options+ and the block if one is given,
493
515
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
494
516
  # In that case, +options+ and the block will be used by #create_table.
495
- def drop_table(table_name, options = {})
517
+ def drop_table(table_name, **options)
518
+ schema_cache.clear_data_source_cache!(table_name.to_s)
496
519
  execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
497
520
  end
498
521
 
499
522
  # Add a new +type+ column named +column_name+ to +table_name+.
500
523
  #
524
+ # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
525
+ #
501
526
  # The +type+ parameter is normally one of the migrations native types,
502
527
  # which is one of the following:
503
528
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
504
529
  # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
505
530
  # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
506
- # <tt>:binary</tt>, <tt>:boolean</tt>.
531
+ # <tt>:binary</tt>, <tt>:blob</tt>, <tt>:boolean</tt>.
507
532
  #
508
533
  # You may use a type not in this list as long as it is supported by your
509
534
  # database (for example, "polygon" in MySQL), but this will not be database
510
535
  # agnostic and should usually be avoided.
511
536
  #
512
537
  # Available options are (none of these exists by default):
538
+ # * <tt>:comment</tt> -
539
+ # Specifies the comment for the column. This option is ignored by some backends.
540
+ # * <tt>:collation</tt> -
541
+ # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column.
542
+ # If not specified, the column will have the same collation as the table.
543
+ # * <tt>:default</tt> -
544
+ # The column's default value. Use +nil+ for +NULL+.
513
545
  # * <tt>:limit</tt> -
514
546
  # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
515
- # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
547
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, <tt>:blob</tt>, and <tt>:integer</tt> columns.
516
548
  # This option is ignored by some backends.
517
- # * <tt>:default</tt> -
518
- # The column's default value. Use +nil+ for +NULL+.
519
549
  # * <tt>:null</tt> -
520
550
  # Allows or disallows +NULL+ values in the column.
521
551
  # * <tt>:precision</tt> -
522
- # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
552
+ # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
553
+ # <tt>:datetime</tt>, and <tt>:time</tt> columns.
523
554
  # * <tt>:scale</tt> -
524
555
  # 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.
525
559
  # * <tt>:comment</tt> -
526
560
  # Specifies the comment for the column. This option is ignored by some backends.
561
+ # * <tt>:if_not_exists</tt> -
562
+ # Specifies if the column already exists to not try to re-add it. This will avoid
563
+ # duplicate column errors.
527
564
  #
528
565
  # Note: The precision is the total number of significant digits,
529
566
  # and the scale is the number of digits that can be stored following
@@ -544,8 +581,6 @@ module ActiveRecord
544
581
  # but the maximum supported <tt>:precision</tt> is 16. No default.
545
582
  # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
546
583
  # Default is (38,0).
547
- # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
548
- # Default unknown.
549
584
  # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
550
585
  # Default (38,0).
551
586
  #
@@ -575,21 +610,43 @@ module ActiveRecord
575
610
  # # Defines a column with a database-specific type.
576
611
  # add_column(:shapes, :triangle, 'polygon')
577
612
  # # ALTER TABLE "shapes" ADD "triangle" polygon
578
- def add_column(table_name, column_name, type, options = {})
613
+ #
614
+ # # Ignores the method call if the column exists
615
+ # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
616
+ def add_column(table_name, column_name, type, **options)
617
+ return if options[:if_not_exists] == true && column_exists?(table_name, column_name)
618
+
619
+ if supports_datetime_with_precision?
620
+ if type == :datetime && !options.key?(:precision)
621
+ options[:precision] = 6
622
+ end
623
+ end
624
+
579
625
  at = create_alter_table table_name
580
- at.add_column(column_name, type, options)
626
+ at.add_column(column_name, type, **options)
581
627
  execute schema_creation.accept at
582
628
  end
583
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
634
+ end
635
+
584
636
  # Removes the given columns from the table definition.
585
637
  #
586
638
  # remove_columns(:suppliers, :qualification, :experience)
587
639
  #
588
- def remove_columns(table_name, *column_names)
589
- raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
590
- column_names.each do |column_name|
591
- remove_column(table_name, column_name)
640
+ # +type+ and other column options can be passed to make migration reversible.
641
+ #
642
+ # remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
643
+ def remove_columns(table_name, *column_names, type: nil, **options)
644
+ if column_names.empty?
645
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
592
646
  end
647
+
648
+ remove_column_fragments = remove_columns_for_alter(table_name, *column_names, type: type, **options)
649
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_fragments.join(', ')}"
593
650
  end
594
651
 
595
652
  # Removes the column from the table definition.
@@ -599,8 +656,18 @@ module ActiveRecord
599
656
  # The +type+ and +options+ parameters will be ignored if present. It can be helpful
600
657
  # to provide these in a migration's +change+ method so it can be reverted.
601
658
  # In that case, +type+ and +options+ will be used by #add_column.
602
- def remove_column(table_name, column_name, type = nil, options = {})
603
- execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
659
+ # Depending on the database you're using, indexes using this column may be
660
+ # automatically removed or modified to remove this column from the index.
661
+ #
662
+ # If the options provided include an +if_exists+ key, it will be used to check if the
663
+ # column does not exist. This will silently ignore the migration rather than raising
664
+ # if the column was already used.
665
+ #
666
+ # remove_column(:suppliers, :qualification, if_exists: true)
667
+ def remove_column(table_name, column_name, type = nil, **options)
668
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
669
+
670
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}"
604
671
  end
605
672
 
606
673
  # Changes the column's definition according to the new options.
@@ -609,7 +676,7 @@ module ActiveRecord
609
676
  # change_column(:suppliers, :name, :string, limit: 80)
610
677
  # change_column(:accounts, :description, :text)
611
678
  #
612
- def change_column(table_name, column_name, type, options = {})
679
+ def change_column(table_name, column_name, type, **options)
613
680
  raise NotImplementedError, "change_column is not implemented"
614
681
  end
615
682
 
@@ -671,7 +738,17 @@ module ActiveRecord
671
738
  #
672
739
  # generates:
673
740
  #
674
- # CREATE INDEX suppliers_name_index ON suppliers(name)
741
+ # CREATE INDEX index_suppliers_on_name ON suppliers(name)
742
+ #
743
+ # ====== Creating a index which already exists
744
+ #
745
+ # add_index(:suppliers, :name, if_not_exists: true)
746
+ #
747
+ # generates:
748
+ #
749
+ # CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
750
+ #
751
+ # Note: Not supported by MySQL.
675
752
  #
676
753
  # ====== Creating a unique index
677
754
  #
@@ -679,7 +756,7 @@ module ActiveRecord
679
756
  #
680
757
  # generates:
681
758
  #
682
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
759
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
683
760
  #
684
761
  # ====== Creating a named index
685
762
  #
@@ -705,11 +782,11 @@ module ActiveRecord
705
782
  #
706
783
  # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
707
784
  #
708
- # Note: SQLite doesn't support index length.
785
+ # Note: only supported by MySQL
709
786
  #
710
787
  # ====== Creating an index with a sort order (desc or asc, asc is the default)
711
788
  #
712
- # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
789
+ # add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
713
790
  #
714
791
  # generates:
715
792
  #
@@ -725,7 +802,7 @@ module ActiveRecord
725
802
  #
726
803
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
727
804
  #
728
- # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
805
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite.
729
806
  #
730
807
  # ====== Creating an index with a specific method
731
808
  #
@@ -760,9 +837,22 @@ module ActiveRecord
760
837
  # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
761
838
  #
762
839
  # Note: only supported by MySQL.
763
- def add_index(table_name, column_name, options = {})
764
- index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
765
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
840
+ #
841
+ # ====== Creating an index with a specific algorithm
842
+ #
843
+ # add_index(:developers, :name, algorithm: :concurrently)
844
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
845
+ #
846
+ # Note: only supported by PostgreSQL.
847
+ #
848
+ # Concurrently adding an index is not supported in a transaction.
849
+ #
850
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
851
+ 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)
855
+ execute schema_creation.accept(create_index)
766
856
  end
767
857
 
768
858
  # Removes the given index from the table.
@@ -783,8 +873,29 @@ module ActiveRecord
783
873
  #
784
874
  # remove_index :accounts, name: :by_branch_party
785
875
  #
786
- def remove_index(table_name, options = {})
787
- index_name = index_name_for_remove(table_name, options)
876
+ # Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
877
+ #
878
+ # remove_index :accounts, :branch_id, name: :by_branch_party
879
+ #
880
+ # Checks if the index exists before trying to remove it. Will silently ignore indexes that
881
+ # don't exist.
882
+ #
883
+ # remove_index :accounts, if_exists: true
884
+ #
885
+ # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
886
+ #
887
+ # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
888
+ #
889
+ # Note: only supported by PostgreSQL.
890
+ #
891
+ # Concurrently removing an index is not supported in a transaction.
892
+ #
893
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
894
+ def remove_index(table_name, column_name = nil, **options)
895
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
896
+
897
+ index_name = index_name_for_remove(table_name, column_name, options)
898
+
788
899
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
789
900
  end
790
901
 
@@ -795,6 +906,8 @@ module ActiveRecord
795
906
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
796
907
  #
797
908
  def rename_index(table_name, old_name, new_name)
909
+ old_name = old_name.to_s
910
+ new_name = new_name.to_s
798
911
  validate_index_length!(table_name, new_name)
799
912
 
800
913
  # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
@@ -804,7 +917,7 @@ module ActiveRecord
804
917
  remove_index(table_name, name: old_name)
805
918
  end
806
919
 
807
- def index_name(table_name, options) #:nodoc:
920
+ def index_name(table_name, options) # :nodoc:
808
921
  if Hash === options
809
922
  if options[:column]
810
923
  "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
@@ -836,23 +949,25 @@ module ActiveRecord
836
949
  # Add an appropriate index. Defaults to true.
837
950
  # See #add_index for usage of this option.
838
951
  # [<tt>:foreign_key</tt>]
839
- # Add an appropriate foreign key constraint. Defaults to false.
952
+ # Add an appropriate foreign key constraint. Defaults to false, pass true
953
+ # to add. In case the join table can't be inferred from the association
954
+ # pass <tt>:to_table</tt> with the appropriate table name.
840
955
  # [<tt>:polymorphic</tt>]
841
956
  # Whether an additional +_type+ column should be added. Defaults to false.
842
957
  # [<tt>:null</tt>]
843
958
  # Whether the column allows nulls. Defaults to true.
844
959
  #
845
- # ====== Create a user_id bigint column
960
+ # ====== Create a user_id bigint column without an index
846
961
  #
847
- # add_reference(:products, :user)
962
+ # add_reference(:products, :user, index: false)
848
963
  #
849
964
  # ====== Create a user_id string column
850
965
  #
851
966
  # add_reference(:products, :user, type: :string)
852
967
  #
853
- # ====== Create supplier_id, supplier_type columns and appropriate index
968
+ # ====== Create supplier_id, supplier_type columns
854
969
  #
855
- # add_reference(:products, :supplier, polymorphic: true, index: true)
970
+ # add_reference(:products, :supplier, polymorphic: true)
856
971
  #
857
972
  # ====== Create a supplier_id column with a unique index
858
973
  #
@@ -868,10 +983,10 @@ module ActiveRecord
868
983
  #
869
984
  # ====== Create a supplier_id column and a foreign key to the firms table
870
985
  #
871
- # add_reference(:products, :supplier, foreign_key: {to_table: :firms})
986
+ # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
872
987
  #
873
988
  def add_reference(table_name, ref_name, **options)
874
- ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self))
989
+ ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
875
990
  end
876
991
  alias :add_belongs_to :add_reference
877
992
 
@@ -880,7 +995,7 @@ module ActiveRecord
880
995
  #
881
996
  # ====== Remove the reference
882
997
  #
883
- # remove_reference(:products, :user, index: true)
998
+ # remove_reference(:products, :user, index: false)
884
999
  #
885
1000
  # ====== Remove polymorphic reference
886
1001
  #
@@ -888,7 +1003,7 @@ module ActiveRecord
888
1003
  #
889
1004
  # ====== Remove the reference with a foreign key
890
1005
  #
891
- # remove_reference(:products, :user, index: true, foreign_key: true)
1006
+ # remove_reference(:products, :user, foreign_key: true)
892
1007
  #
893
1008
  def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
894
1009
  if foreign_key
@@ -899,7 +1014,7 @@ module ActiveRecord
899
1014
  foreign_key_options = { to_table: reference_name }
900
1015
  end
901
1016
  foreign_key_options[:column] ||= "#{ref_name}_id"
902
- remove_foreign_key(table_name, foreign_key_options)
1017
+ remove_foreign_key(table_name, **foreign_key_options)
903
1018
  end
904
1019
 
905
1020
  remove_column(table_name, "#{ref_name}_id")
@@ -928,6 +1043,10 @@ module ActiveRecord
928
1043
  #
929
1044
  # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
930
1045
  #
1046
+ # ====== Creating a foreign key, ignoring method call if the foreign key exists
1047
+ #
1048
+ # add_foreign_key(:articles, :authors, if_not_exists: true)
1049
+ #
931
1050
  # ====== Creating a foreign key on a specific column
932
1051
  #
933
1052
  # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
@@ -955,10 +1074,17 @@ module ActiveRecord
955
1074
  # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
956
1075
  # [<tt>:on_update</tt>]
957
1076
  # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1077
+ # [<tt>:if_not_exists</tt>]
1078
+ # Specifies if the foreign key already exists to not try to re-add it. This will avoid
1079
+ # duplicate column errors.
958
1080
  # [<tt>:validate</tt>]
959
- # (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+.
960
- def add_foreign_key(from_table, to_table, options = {})
1081
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1082
+ # [<tt>:deferrable</tt>]
1083
+ # (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
1084
+ # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1085
+ def add_foreign_key(from_table, to_table, **options)
961
1086
  return unless supports_foreign_keys?
1087
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
962
1088
 
963
1089
  options = foreign_key_options(from_table, to_table, options)
964
1090
  at = create_alter_table from_table
@@ -980,15 +1106,28 @@ module ActiveRecord
980
1106
  #
981
1107
  # remove_foreign_key :accounts, column: :owner_id
982
1108
  #
1109
+ # Removes the foreign key on +accounts.owner_id+.
1110
+ #
1111
+ # remove_foreign_key :accounts, to_table: :owners
1112
+ #
983
1113
  # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
984
1114
  #
985
1115
  # remove_foreign_key :accounts, name: :special_fk_name
986
1116
  #
987
- # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
988
- def remove_foreign_key(from_table, options_or_to_table = {})
1117
+ # Checks if the foreign key exists before trying to remove it. Will silently ignore indexes that
1118
+ # don't exist.
1119
+ #
1120
+ # remove_foreign_key :accounts, :branches, if_exists: true
1121
+ #
1122
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
1123
+ # with an addition of
1124
+ # [<tt>:to_table</tt>]
1125
+ # The name of the table that contains the referenced primary key.
1126
+ def remove_foreign_key(from_table, to_table = nil, **options)
989
1127
  return unless supports_foreign_keys?
1128
+ return if options[:if_exists] == true && !foreign_key_exists?(from_table, to_table)
990
1129
 
991
- fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
1130
+ fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
992
1131
 
993
1132
  at = create_alter_table from_table
994
1133
  at.drop_foreign_key fk_name_to_delete
@@ -1007,14 +1146,12 @@ module ActiveRecord
1007
1146
  # # Checks to see if a foreign key with a custom name exists.
1008
1147
  # foreign_key_exists?(:accounts, name: "special_fk_name")
1009
1148
  #
1010
- def foreign_key_exists?(from_table, options_or_to_table = {})
1011
- foreign_key_for(from_table, options_or_to_table).present?
1149
+ def foreign_key_exists?(from_table, to_table = nil, **options)
1150
+ foreign_key_for(from_table, to_table: to_table, **options).present?
1012
1151
  end
1013
1152
 
1014
1153
  def foreign_key_column_for(table_name) # :nodoc:
1015
- prefix = Base.table_name_prefix
1016
- suffix = Base.table_name_suffix
1017
- name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
1154
+ name = strip_table_name_prefix_and_suffix(table_name)
1018
1155
  "#{name.singularize}_id"
1019
1156
  end
1020
1157
 
@@ -1025,8 +1162,62 @@ module ActiveRecord
1025
1162
  options
1026
1163
  end
1027
1164
 
1028
- def dump_schema_information #:nodoc:
1029
- versions = ActiveRecord::SchemaMigration.all_versions
1165
+ # Returns an array of check constraints for the given table.
1166
+ # The check constraints are represented as CheckConstraintDefinition objects.
1167
+ def check_constraints(table_name)
1168
+ raise NotImplementedError
1169
+ end
1170
+
1171
+ # Adds a new check constraint to the table. +expression+ is a String
1172
+ # representation of verifiable boolean condition.
1173
+ #
1174
+ # add_check_constraint :products, "price > 0", name: "price_check"
1175
+ #
1176
+ # generates:
1177
+ #
1178
+ # ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
1179
+ #
1180
+ # The +options+ hash can include the following keys:
1181
+ # [<tt>:name</tt>]
1182
+ # The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
1183
+ # [<tt>:validate</tt>]
1184
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1185
+ def add_check_constraint(table_name, expression, **options)
1186
+ return unless supports_check_constraints?
1187
+
1188
+ options = check_constraint_options(table_name, expression, options)
1189
+ at = create_alter_table(table_name)
1190
+ at.add_check_constraint(expression, options)
1191
+
1192
+ execute schema_creation.accept(at)
1193
+ end
1194
+
1195
+ def check_constraint_options(table_name, expression, options) # :nodoc:
1196
+ options = options.dup
1197
+ options[:name] ||= check_constraint_name(table_name, expression: expression, **options)
1198
+ options
1199
+ end
1200
+
1201
+ # Removes the given check constraint from the table.
1202
+ #
1203
+ # remove_check_constraint :products, name: "price_check"
1204
+ #
1205
+ # The +expression+ parameter will be ignored if present. It can be helpful
1206
+ # to provide this in a migration's +change+ method so it can be reverted.
1207
+ # In that case, +expression+ will be used by #add_check_constraint.
1208
+ def remove_check_constraint(table_name, expression = nil, **options)
1209
+ return unless supports_check_constraints?
1210
+
1211
+ chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
1212
+
1213
+ at = create_alter_table(table_name)
1214
+ at.drop_check_constraint(chk_name_to_delete)
1215
+
1216
+ execute schema_creation.accept(at)
1217
+ end
1218
+
1219
+ def dump_schema_information # :nodoc:
1220
+ versions = schema_migration.all_versions
1030
1221
  insert_versions_sql(versions) if versions.any?
1031
1222
  end
1032
1223
 
@@ -1034,15 +1225,12 @@ module ActiveRecord
1034
1225
  { primary_key: true }
1035
1226
  end
1036
1227
 
1037
- def assume_migrated_upto_version(version, migrations_paths)
1038
- migrations_paths = Array(migrations_paths)
1228
+ def assume_migrated_upto_version(version)
1039
1229
  version = version.to_i
1040
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1230
+ sm_table = quote_table_name(schema_migration.table_name)
1041
1231
 
1042
- migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
1043
- versions = migration_context.migration_files.map do |file|
1044
- migration_context.parse_migration_filename(file).first.to_i
1045
- end
1232
+ migrated = migration_context.get_all_versions
1233
+ versions = migration_context.migrations.map(&:version)
1046
1234
 
1047
1235
  unless migrated.include?(version)
1048
1236
  execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
@@ -1053,13 +1241,7 @@ module ActiveRecord
1053
1241
  if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
1054
1242
  raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
1055
1243
  end
1056
- if supports_multi_insert?
1057
- execute insert_versions_sql(inserting)
1058
- else
1059
- inserting.each do |v|
1060
- execute insert_versions_sql(v)
1061
- end
1062
- end
1244
+ execute insert_versions_sql(inserting)
1063
1245
  end
1064
1246
  end
1065
1247
 
@@ -1085,7 +1267,7 @@ module ActiveRecord
1085
1267
  if (0..6) === precision
1086
1268
  column_type_sql << "(#{precision})"
1087
1269
  else
1088
- raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
1270
+ raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6"
1089
1271
  end
1090
1272
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
1091
1273
  column_type_sql << "(#{limit})"
@@ -1107,61 +1289,90 @@ module ActiveRecord
1107
1289
  columns
1108
1290
  end
1109
1291
 
1292
+ def distinct_relation_for_primary_key(relation) # :nodoc:
1293
+ values = columns_for_distinct(
1294
+ visitor.compile(relation.table[relation.primary_key]),
1295
+ relation.order_values
1296
+ )
1297
+
1298
+ limited = relation.reselect(values).distinct!
1299
+ limited_ids = select_rows(limited.arel, "SQL").map(&:last)
1300
+
1301
+ if limited_ids.empty?
1302
+ relation.none!
1303
+ else
1304
+ relation.where!(relation.primary_key => limited_ids)
1305
+ end
1306
+
1307
+ relation.limit_value = relation.offset_value = nil
1308
+ relation
1309
+ end
1310
+
1110
1311
  # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
1111
1312
  # Additional options (like +:null+) are forwarded to #add_column.
1112
1313
  #
1113
1314
  # add_timestamps(:suppliers, null: true)
1114
1315
  #
1115
- def add_timestamps(table_name, options = {})
1316
+ def add_timestamps(table_name, **options)
1116
1317
  options[:null] = false if options[:null].nil?
1117
1318
 
1118
- add_column table_name, :created_at, :datetime, options
1119
- add_column table_name, :updated_at, :datetime, options
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
1120
1325
  end
1121
1326
 
1122
1327
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
1123
1328
  #
1124
1329
  # remove_timestamps(:suppliers)
1125
1330
  #
1126
- def remove_timestamps(table_name, options = {})
1127
- remove_column table_name, :updated_at
1128
- remove_column table_name, :created_at
1331
+ def remove_timestamps(table_name, **options)
1332
+ remove_columns table_name, :updated_at, :created_at
1129
1333
  end
1130
1334
 
1131
- def update_table_definition(table_name, base) #:nodoc:
1335
+ def update_table_definition(table_name, base) # :nodoc:
1132
1336
  Table.new(table_name, base)
1133
1337
  end
1134
1338
 
1135
- def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1136
- column_names = index_column_names(column_name)
1339
+ 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)
1137
1341
 
1138
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
1342
+ column_names = index_column_names(column_name)
1139
1343
 
1140
- index_type = options[:type].to_s if options.key?(:type)
1141
- index_type ||= options[:unique] ? "UNIQUE" : ""
1142
- index_name = options[:name].to_s if options.key?(:name)
1344
+ index_name = name&.to_s
1143
1345
  index_name ||= index_name(table_name, column_names)
1144
1346
 
1145
- if options.key?(:algorithm)
1146
- algorithm = index_algorithms.fetch(options[:algorithm]) {
1147
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
1148
- }
1149
- end
1150
-
1151
- using = "USING #{options[:using]}" if options[:using].present?
1152
-
1153
- if supports_partial_index?
1154
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
1155
- end
1347
+ validate_index_length!(table_name, index_name, internal)
1348
+
1349
+ index = IndexDefinition.new(
1350
+ table_name, index_name,
1351
+ options[:unique],
1352
+ column_names,
1353
+ lengths: options[:length] || {},
1354
+ orders: options[:order] || {},
1355
+ opclasses: options[:opclass] || {},
1356
+ where: options[:where],
1357
+ type: options[:type],
1358
+ using: options[:using],
1359
+ comment: options[:comment]
1360
+ )
1361
+
1362
+ [index, index_algorithm(options[:algorithm]), if_not_exists]
1363
+ end
1156
1364
 
1157
- validate_index_length!(table_name, index_name, options.fetch(:internal, false))
1365
+ def index_algorithm(algorithm) # :nodoc:
1366
+ index_algorithms.fetch(algorithm) do
1367
+ raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}"
1368
+ end if algorithm
1369
+ end
1158
1370
 
1159
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
1160
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1371
+ def quoted_columns_for_index(column_names, options) # :nodoc:
1372
+ quoted_columns = column_names.each_with_object({}) do |name, result|
1373
+ result[name.to_sym] = quote_column_name(name).dup
1161
1374
  end
1162
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
1163
-
1164
- [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1375
+ add_options_for_index_columns(quoted_columns, **options).values.join(", ")
1165
1376
  end
1166
1377
 
1167
1378
  def options_include_default?(options)
@@ -1169,12 +1380,22 @@ module ActiveRecord
1169
1380
  end
1170
1381
 
1171
1382
  # Changes the comment for a table or removes it if +nil+.
1172
- def change_table_comment(table_name, comment)
1383
+ #
1384
+ # Passing a hash containing +:from+ and +:to+ will make this change
1385
+ # reversible in migration:
1386
+ #
1387
+ # change_table_comment(:posts, from: "old_comment", to: "new_comment")
1388
+ def change_table_comment(table_name, comment_or_changes)
1173
1389
  raise NotImplementedError, "#{self.class} does not support changing table comments"
1174
1390
  end
1175
1391
 
1176
1392
  # Changes the comment for a column or removes it if +nil+.
1177
- def change_column_comment(table_name, column_name, comment)
1393
+ #
1394
+ # Passing a hash containing +:from+ and +:to+ will make this change
1395
+ # reversible in migration:
1396
+ #
1397
+ # change_column_comment(:posts, :state, from: "old_comment", to: "new_comment")
1398
+ def change_column_comment(table_name, column_name, comment_or_changes)
1178
1399
  raise NotImplementedError, "#{self.class} does not support changing column comments"
1179
1400
  end
1180
1401
 
@@ -1206,32 +1427,27 @@ module ActiveRecord
1206
1427
  # the PostgreSQL adapter for supporting operator classes.
1207
1428
  def add_options_for_index_columns(quoted_columns, **options)
1208
1429
  if supports_index_sort_order?
1209
- quoted_columns = add_index_sort_order(quoted_columns, options)
1430
+ quoted_columns = add_index_sort_order(quoted_columns, **options)
1210
1431
  end
1211
1432
 
1212
1433
  quoted_columns
1213
1434
  end
1214
1435
 
1215
- def quoted_columns_for_index(column_names, **options)
1216
- return [column_names] if column_names.is_a?(String)
1217
-
1218
- quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]
1219
- add_options_for_index_columns(quoted_columns, options).values
1220
- end
1221
-
1222
- def index_name_for_remove(table_name, options = {})
1223
- return options[:name] if can_remove_index_by_name?(options)
1436
+ def index_name_for_remove(table_name, column_name, options)
1437
+ return options[:name] if can_remove_index_by_name?(column_name, options)
1224
1438
 
1225
1439
  checks = []
1226
1440
 
1227
- if options.is_a?(Hash)
1228
- checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1229
- column_names = index_column_names(options[:column])
1441
+ if !options.key?(:name) && expression_column_name?(column_name)
1442
+ options[:name] = index_name(table_name, column_name)
1443
+ column_names = []
1230
1444
  else
1231
- column_names = index_column_names(options)
1445
+ column_names = index_column_names(column_name || options[:column])
1232
1446
  end
1233
1447
 
1234
- if column_names.present?
1448
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1449
+
1450
+ if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
1235
1451
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1236
1452
  end
1237
1453
 
@@ -1275,14 +1491,18 @@ module ActiveRecord
1275
1491
  SchemaCreation.new(self)
1276
1492
  end
1277
1493
 
1278
- def create_table_definition(*args)
1279
- TableDefinition.new(*args)
1494
+ def create_table_definition(name, **options)
1495
+ TableDefinition.new(self, name, **options)
1280
1496
  end
1281
1497
 
1282
1498
  def create_alter_table(name)
1283
1499
  AlterTable.new create_table_definition(name)
1284
1500
  end
1285
1501
 
1502
+ def extract_table_options!(options)
1503
+ options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
1504
+ end
1505
+
1286
1506
  def fetch_type_metadata(sql_type)
1287
1507
  cast_type = lookup_cast_type(sql_type)
1288
1508
  SqlTypeMetadata.new(
@@ -1295,7 +1515,7 @@ module ActiveRecord
1295
1515
  end
1296
1516
 
1297
1517
  def index_column_names(column_names)
1298
- if column_names.is_a?(String) && /\W/.match?(column_names)
1518
+ if expression_column_name?(column_names)
1299
1519
  column_names
1300
1520
  else
1301
1521
  Array(column_names)
@@ -1303,30 +1523,41 @@ module ActiveRecord
1303
1523
  end
1304
1524
 
1305
1525
  def index_name_options(column_names)
1306
- if column_names.is_a?(String) && /\W/.match?(column_names)
1526
+ if expression_column_name?(column_names)
1307
1527
  column_names = column_names.scan(/\w+/).join("_")
1308
1528
  end
1309
1529
 
1310
1530
  { column: column_names }
1311
1531
  end
1312
1532
 
1533
+ # Try to identify whether the given column name is an expression
1534
+ def expression_column_name?(column_name)
1535
+ column_name.is_a?(String) && /\W/.match?(column_name)
1536
+ end
1537
+
1538
+ def strip_table_name_prefix_and_suffix(table_name)
1539
+ prefix = Base.table_name_prefix
1540
+ suffix = Base.table_name_suffix
1541
+ table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
1542
+ end
1543
+
1313
1544
  def foreign_key_name(table_name, options)
1314
1545
  options.fetch(:name) do
1315
1546
  identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1316
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1547
+ hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1317
1548
 
1318
1549
  "fk_rails_#{hashed_identifier}"
1319
1550
  end
1320
1551
  end
1321
1552
 
1322
- def foreign_key_for(from_table, options_or_to_table = {})
1553
+ def foreign_key_for(from_table, **options)
1323
1554
  return unless supports_foreign_keys?
1324
- foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
1555
+ foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1325
1556
  end
1326
1557
 
1327
- def foreign_key_for!(from_table, options_or_to_table = {})
1328
- foreign_key_for(from_table, options_or_to_table) || \
1329
- raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
1558
+ def foreign_key_for!(from_table, to_table: nil, **options)
1559
+ foreign_key_for(from_table, to_table: to_table, **options) ||
1560
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
1330
1561
  end
1331
1562
 
1332
1563
  def extract_foreign_key_action(specifier)
@@ -1337,11 +1568,30 @@ module ActiveRecord
1337
1568
  end
1338
1569
  end
1339
1570
 
1340
- def validate_index_length!(table_name, new_name, internal = false)
1341
- max_index_length = internal ? index_name_length : allowed_index_name_length
1571
+ def check_constraint_name(table_name, **options)
1572
+ options.fetch(:name) do
1573
+ expression = options.fetch(:expression)
1574
+ identifier = "#{table_name}_#{expression}_chk"
1575
+ hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
1342
1576
 
1343
- if new_name.length > max_index_length
1344
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1577
+ "chk_rails_#{hashed_identifier}"
1578
+ end
1579
+ end
1580
+
1581
+ def check_constraint_for(table_name, **options)
1582
+ return unless supports_check_constraints?
1583
+ chk_name = check_constraint_name(table_name, **options)
1584
+ check_constraints(table_name).detect { |chk| chk.name == chk_name }
1585
+ end
1586
+
1587
+ def check_constraint_for!(table_name, expression: nil, **options)
1588
+ check_constraint_for(table_name, expression: expression, **options) ||
1589
+ raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}")
1590
+ end
1591
+
1592
+ def validate_index_length!(table_name, new_name, internal = false)
1593
+ if new_name.length > index_name_length
1594
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1345
1595
  end
1346
1596
  end
1347
1597
 
@@ -1352,30 +1602,77 @@ module ActiveRecord
1352
1602
  default_or_changes
1353
1603
  end
1354
1604
  end
1605
+ alias :extract_new_comment_value :extract_new_default_value
1606
+
1607
+ def can_remove_index_by_name?(column_name, options)
1608
+ column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1609
+ end
1610
+
1611
+ def bulk_change_table(table_name, operations)
1612
+ sql_fragments = []
1613
+ non_combinable_operations = []
1614
+
1615
+ operations.each do |command, args|
1616
+ table, arguments = args.shift, args
1617
+ method = :"#{command}_for_alter"
1618
+
1619
+ if respond_to?(method, true)
1620
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1621
+ sql_fragments << sqls
1622
+ non_combinable_operations.concat(procs)
1623
+ else
1624
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1625
+ non_combinable_operations.each(&:call)
1626
+ sql_fragments = []
1627
+ non_combinable_operations = []
1628
+ send(command, table, *arguments)
1629
+ end
1630
+ end
1355
1631
 
1356
- def can_remove_index_by_name?(options)
1357
- options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
1632
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1633
+ non_combinable_operations.each(&:call)
1358
1634
  end
1359
1635
 
1360
- def add_column_for_alter(table_name, column_name, type, options = {})
1636
+ def add_column_for_alter(table_name, column_name, type, **options)
1361
1637
  td = create_table_definition(table_name)
1362
- cd = td.new_column_definition(column_name, type, options)
1638
+ cd = td.new_column_definition(column_name, type, **options)
1363
1639
  schema_creation.accept(AddColumnDefinition.new(cd))
1364
1640
  end
1365
1641
 
1366
- def remove_column_for_alter(table_name, column_name, type = nil, options = {})
1642
+ def rename_column_sql(table_name, column_name, new_column_name)
1643
+ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1644
+ end
1645
+
1646
+ def remove_column_for_alter(table_name, column_name, type = nil, **options)
1367
1647
  "DROP COLUMN #{quote_column_name(column_name)}"
1368
1648
  end
1369
1649
 
1370
- def remove_columns_for_alter(table_name, *column_names)
1650
+ def remove_columns_for_alter(table_name, *column_names, **options)
1371
1651
  column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
1372
1652
  end
1373
1653
 
1654
+ def add_timestamps_for_alter(table_name, **options)
1655
+ options[:null] = false if options[:null].nil?
1656
+
1657
+ if !options.key?(:precision) && supports_datetime_with_precision?
1658
+ options[:precision] = 6
1659
+ end
1660
+
1661
+ [
1662
+ add_column_for_alter(table_name, :created_at, :datetime, **options),
1663
+ add_column_for_alter(table_name, :updated_at, :datetime, **options)
1664
+ ]
1665
+ end
1666
+
1667
+ def remove_timestamps_for_alter(table_name, **options)
1668
+ remove_columns_for_alter(table_name, :updated_at, :created_at)
1669
+ end
1670
+
1374
1671
  def insert_versions_sql(versions)
1375
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1672
+ sm_table = quote_table_name(schema_migration.table_name)
1376
1673
 
1377
1674
  if versions.is_a?(Array)
1378
- sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup
1675
+ sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1379
1676
  sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1380
1677
  sql << ";\n\n"
1381
1678
  sql