activerecord 4.2.9 → 6.1.4.1

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 (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +964 -1382
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +15 -14
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +266 -251
  8. data/lib/active_record/association_relation.rb +40 -15
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +162 -69
  11. data/lib/active_record/associations/association_scope.rb +105 -130
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -65
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  14. data/lib/active_record/associations/builder/association.rb +57 -43
  15. data/lib/active_record/associations/builder/belongs_to.rb +74 -57
  16. data/lib/active_record/associations/builder/collection_association.rb +15 -37
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
  18. data/lib/active_record/associations/builder/has_many.rb +13 -5
  19. data/lib/active_record/associations/builder/has_one.rb +44 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +148 -287
  22. data/lib/active_record/associations/collection_proxy.rb +252 -150
  23. data/lib/active_record/associations/foreign_association.rb +23 -1
  24. data/lib/active_record/associations/has_many_association.rb +56 -98
  25. data/lib/active_record/associations/has_many_through_association.rb +68 -89
  26. data/lib/active_record/associations/has_one_association.rb +73 -47
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +174 -169
  32. data/lib/active_record/associations/preloader/association.rb +108 -115
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +97 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -39
  36. data/lib/active_record/associations/through_association.rb +39 -19
  37. data/lib/active_record/associations.rb +1845 -1598
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -148
  41. data/lib/active_record/attribute_methods/primary_key.rb +93 -83
  42. data/lib/active_record/attribute_methods/query.rb +8 -10
  43. data/lib/active_record/attribute_methods/read.rb +19 -79
  44. data/lib/active_record/attribute_methods/serialization.rb +49 -24
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
  46. data/lib/active_record/attribute_methods/write.rb +24 -55
  47. data/lib/active_record/attribute_methods.rb +149 -154
  48. data/lib/active_record/attributes.rb +234 -78
  49. data/lib/active_record/autosave_association.rb +133 -60
  50. data/lib/active_record/base.rb +46 -46
  51. data/lib/active_record/callbacks.rb +234 -79
  52. data/lib/active_record/coders/json.rb +3 -1
  53. data/lib/active_record/coders/yaml_column.rb +34 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
  67. data/lib/active_record/connection_adapters/column.rb +67 -40
  68. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  69. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
  72. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
  80. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  81. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  101. data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  109. data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
  110. data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
  111. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  112. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  114. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
  116. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  117. data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
  118. data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
  119. data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
  120. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  121. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  122. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  123. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -373
  129. data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
  130. data/lib/active_record/connection_adapters.rb +52 -0
  131. data/lib/active_record/connection_handling.rb +314 -41
  132. data/lib/active_record/core.rb +458 -241
  133. data/lib/active_record/counter_cache.rb +70 -49
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  135. data/lib/active_record/database_configurations/database_config.rb +80 -0
  136. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  137. data/lib/active_record/database_configurations/url_config.rb +53 -0
  138. data/lib/active_record/database_configurations.rb +272 -0
  139. data/lib/active_record/delegated_type.rb +209 -0
  140. data/lib/active_record/destroy_association_async_job.rb +36 -0
  141. data/lib/active_record/dynamic_matchers.rb +87 -106
  142. data/lib/active_record/enum.rb +211 -92
  143. data/lib/active_record/errors.rb +224 -54
  144. data/lib/active_record/explain.rb +27 -11
  145. data/lib/active_record/explain_registry.rb +4 -2
  146. data/lib/active_record/explain_subscriber.rb +10 -5
  147. data/lib/active_record/fixture_set/file.rb +33 -14
  148. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  149. data/lib/active_record/fixture_set/render_context.rb +17 -0
  150. data/lib/active_record/fixture_set/table_row.rb +152 -0
  151. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  152. data/lib/active_record/fixtures.rb +275 -500
  153. data/lib/active_record/gem_version.rb +6 -4
  154. data/lib/active_record/inheritance.rb +175 -110
  155. data/lib/active_record/insert_all.rb +212 -0
  156. data/lib/active_record/integration.rb +121 -29
  157. data/lib/active_record/internal_metadata.rb +62 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +27 -5
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +98 -92
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +93 -31
  163. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector.rb +77 -0
  166. data/lib/active_record/migration/command_recorder.rb +185 -90
  167. data/lib/active_record/migration/compatibility.rb +295 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +673 -325
  170. data/lib/active_record/model_schema.rb +418 -113
  171. data/lib/active_record/nested_attributes.rb +263 -224
  172. data/lib/active_record/no_touching.rb +15 -2
  173. data/lib/active_record/null_relation.rb +24 -38
  174. data/lib/active_record/persistence.rb +572 -136
  175. data/lib/active_record/query_cache.rb +29 -23
  176. data/lib/active_record/querying.rb +50 -31
  177. data/lib/active_record/railtie.rb +170 -51
  178. data/lib/active_record/railties/console_sandbox.rb +3 -3
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +523 -199
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +454 -291
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
  184. data/lib/active_record/relation/batches.rb +217 -59
  185. data/lib/active_record/relation/calculations.rb +324 -249
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +316 -242
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +95 -103
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  192. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  193. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +57 -0
  194. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  195. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  196. data/lib/active_record/relation/predicate_builder.rb +136 -122
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -413
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +18 -20
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -343
  203. data/lib/active_record/result.rb +91 -47
  204. data/lib/active_record/runtime_registry.rb +6 -4
  205. data/lib/active_record/sanitization.rb +134 -122
  206. data/lib/active_record/schema.rb +21 -24
  207. data/lib/active_record/schema_dumper.rb +141 -92
  208. data/lib/active_record/schema_migration.rb +24 -23
  209. data/lib/active_record/scoping/default.rb +96 -83
  210. data/lib/active_record/scoping/named.rb +78 -36
  211. data/lib/active_record/scoping.rb +45 -27
  212. data/lib/active_record/secure_token.rb +48 -0
  213. data/lib/active_record/serialization.rb +8 -6
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +89 -36
  216. data/lib/active_record/store.rb +128 -43
  217. data/lib/active_record/suppressor.rb +61 -0
  218. data/lib/active_record/table_metadata.rb +81 -0
  219. data/lib/active_record/tasks/database_tasks.rb +364 -130
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +287 -0
  225. data/lib/active_record/timestamp.rb +86 -43
  226. data/lib/active_record/touch_later.rb +65 -0
  227. data/lib/active_record/transactions.rb +182 -163
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type/adapter_specific_registry.rb +126 -0
  230. data/lib/active_record/type/date.rb +4 -45
  231. data/lib/active_record/type/date_time.rb +4 -49
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  234. data/lib/active_record/type/internal/timezone.rb +17 -0
  235. data/lib/active_record/type/json.rb +30 -0
  236. data/lib/active_record/type/serialized.rb +27 -15
  237. data/lib/active_record/type/text.rb +2 -2
  238. data/lib/active_record/type/time.rb +21 -16
  239. data/lib/active_record/type/type_map.rb +16 -19
  240. data/lib/active_record/type/unsigned_integer.rb +9 -8
  241. data/lib/active_record/type.rb +84 -23
  242. data/lib/active_record/type_caster/connection.rb +33 -0
  243. data/lib/active_record/type_caster/map.rb +23 -0
  244. data/lib/active_record/type_caster.rb +9 -0
  245. data/lib/active_record/validations/absence.rb +25 -0
  246. data/lib/active_record/validations/associated.rb +12 -4
  247. data/lib/active_record/validations/length.rb +26 -0
  248. data/lib/active_record/validations/numericality.rb +35 -0
  249. data/lib/active_record/validations/presence.rb +14 -13
  250. data/lib/active_record/validations/uniqueness.rb +63 -56
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +42 -29
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes/and.rb +32 -0
  269. data/lib/arel/nodes/ascending.rb +23 -0
  270. data/lib/arel/nodes/binary.rb +126 -0
  271. data/lib/arel/nodes/bind_param.rb +44 -0
  272. data/lib/arel/nodes/case.rb +55 -0
  273. data/lib/arel/nodes/casted.rb +62 -0
  274. data/lib/arel/nodes/comment.rb +29 -0
  275. data/lib/arel/nodes/count.rb +12 -0
  276. data/lib/arel/nodes/delete_statement.rb +45 -0
  277. data/lib/arel/nodes/descending.rb +23 -0
  278. data/lib/arel/nodes/equality.rb +15 -0
  279. data/lib/arel/nodes/extract.rb +24 -0
  280. data/lib/arel/nodes/false.rb +16 -0
  281. data/lib/arel/nodes/full_outer_join.rb +8 -0
  282. data/lib/arel/nodes/function.rb +44 -0
  283. data/lib/arel/nodes/grouping.rb +11 -0
  284. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  285. data/lib/arel/nodes/in.rb +15 -0
  286. data/lib/arel/nodes/infix_operation.rb +92 -0
  287. data/lib/arel/nodes/inner_join.rb +8 -0
  288. data/lib/arel/nodes/insert_statement.rb +37 -0
  289. data/lib/arel/nodes/join_source.rb +20 -0
  290. data/lib/arel/nodes/matches.rb +18 -0
  291. data/lib/arel/nodes/named_function.rb +23 -0
  292. data/lib/arel/nodes/node.rb +51 -0
  293. data/lib/arel/nodes/node_expression.rb +13 -0
  294. data/lib/arel/nodes/ordering.rb +27 -0
  295. data/lib/arel/nodes/outer_join.rb +8 -0
  296. data/lib/arel/nodes/over.rb +15 -0
  297. data/lib/arel/nodes/regexp.rb +16 -0
  298. data/lib/arel/nodes/right_outer_join.rb +8 -0
  299. data/lib/arel/nodes/select_core.rb +67 -0
  300. data/lib/arel/nodes/select_statement.rb +41 -0
  301. data/lib/arel/nodes/sql_literal.rb +19 -0
  302. data/lib/arel/nodes/string_join.rb +11 -0
  303. data/lib/arel/nodes/table_alias.rb +31 -0
  304. data/lib/arel/nodes/terminal.rb +16 -0
  305. data/lib/arel/nodes/true.rb +16 -0
  306. data/lib/arel/nodes/unary.rb +44 -0
  307. data/lib/arel/nodes/unary_operation.rb +20 -0
  308. data/lib/arel/nodes/unqualified_column.rb +22 -0
  309. data/lib/arel/nodes/update_statement.rb +41 -0
  310. data/lib/arel/nodes/values_list.rb +9 -0
  311. data/lib/arel/nodes/window.rb +126 -0
  312. data/lib/arel/nodes/with.rb +11 -0
  313. data/lib/arel/nodes.rb +70 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors/dot.rb +308 -0
  321. data/lib/arel/visitors/mysql.rb +93 -0
  322. data/lib/arel/visitors/postgresql.rb +120 -0
  323. data/lib/arel/visitors/sqlite.rb +38 -0
  324. data/lib/arel/visitors/to_sql.rb +899 -0
  325. data/lib/arel/visitors/visitor.rb +45 -0
  326. data/lib/arel/visitors.rb +13 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/arel.rb +54 -0
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  331. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
  332. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
  333. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
  334. data/lib/rails/generators/active_record/migration.rb +35 -1
  335. data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. data/lib/rails/generators/active_record.rb +7 -5
  340. metadata +172 -65
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_decorators.rb +0 -66
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/attribute_set.rb +0 -81
  352. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  353. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  354. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  355. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  356. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  357. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  358. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  359. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  360. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  361. data/lib/active_record/type/big_integer.rb +0 -13
  362. data/lib/active_record/type/binary.rb +0 -50
  363. data/lib/active_record/type/boolean.rb +0 -31
  364. data/lib/active_record/type/decimal.rb +0 -64
  365. data/lib/active_record/type/decorator.rb +0 -14
  366. data/lib/active_record/type/float.rb +0 -19
  367. data/lib/active_record/type/integer.rb +0 -59
  368. data/lib/active_record/type/mutable.rb +0 -16
  369. data/lib/active_record/type/numeric.rb +0 -36
  370. data/lib/active_record/type/string.rb +0 -40
  371. data/lib/active_record/type/time_value.rb +0 -38
  372. data/lib/active_record/type/value.rb +0 -110
  373. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  374. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,6 +1,7 @@
1
- require 'active_record/migration/join_table'
2
- require 'active_support/core_ext/string/access'
3
- require 'digest'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/access"
4
+ require "digest/sha2"
4
5
 
5
6
  module ActiveRecord
6
7
  module ConnectionAdapters # :nodoc:
@@ -14,15 +15,26 @@ module ActiveRecord
14
15
  {}
15
16
  end
16
17
 
18
+ def table_options(table_name)
19
+ nil
20
+ end
21
+
22
+ # Returns the table comment that's stored in database metadata.
23
+ def table_comment(table_name)
24
+ nil
25
+ end
26
+
17
27
  # Truncates a table alias according to the limits of the current adapter.
18
28
  def table_alias_for(table_name)
19
- table_name[0...table_alias_length].tr('.', '_')
29
+ table_name[0...table_alias_length].tr(".", "_")
20
30
  end
21
31
 
22
32
  # Returns the relation names useable to back Active Record models.
23
- # For most adapters this means all tables and views.
33
+ # For most adapters this means all #tables and #views.
24
34
  def data_sources
25
- tables
35
+ query_values(data_source_sql, "SCHEMA")
36
+ rescue NotImplementedError
37
+ tables | views
26
38
  end
27
39
 
28
40
  # Checks to see if the data source +name+ exists on the database.
@@ -30,19 +42,45 @@ module ActiveRecord
30
42
  # data_source_exists?(:ebooks)
31
43
  #
32
44
  def data_source_exists?(name)
45
+ query_values(data_source_sql(name), "SCHEMA").any? if name.present?
46
+ rescue NotImplementedError
33
47
  data_sources.include?(name.to_s)
34
48
  end
35
49
 
50
+ # Returns an array of table names defined in the database.
51
+ def tables
52
+ query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
53
+ end
54
+
36
55
  # Checks to see if the table +table_name+ exists on the database.
37
56
  #
38
57
  # table_exists?(:developers)
39
58
  #
40
59
  def table_exists?(table_name)
60
+ query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
61
+ rescue NotImplementedError
41
62
  tables.include?(table_name.to_s)
42
63
  end
43
64
 
65
+ # Returns an array of view names defined in the database.
66
+ def views
67
+ query_values(data_source_sql(type: "VIEW"), "SCHEMA")
68
+ end
69
+
70
+ # Checks to see if the view +view_name+ exists on the database.
71
+ #
72
+ # view_exists?(:ebooks)
73
+ #
74
+ def view_exists?(view_name)
75
+ query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
76
+ rescue NotImplementedError
77
+ views.include?(view_name.to_s)
78
+ end
79
+
44
80
  # Returns an array of indexes for the given table.
45
- # def indexes(table_name, name = nil) end
81
+ def indexes(table_name)
82
+ raise NotImplementedError, "#indexes is not implemented"
83
+ end
46
84
 
47
85
  # Checks to see if an index exists on a table for a given index definition.
48
86
  #
@@ -58,20 +96,27 @@ module ActiveRecord
58
96
  # # Check an index with a custom name exists
59
97
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
60
98
  #
61
- def index_exists?(table_name, column_name, options = {})
62
- column_names = Array(column_name).map(&:to_s)
63
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
99
+ def index_exists?(table_name, column_name, **options)
64
100
  checks = []
65
- checks << lambda { |i| i.name == index_name }
66
- checks << lambda { |i| 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
+
67
107
  checks << lambda { |i| i.unique } if options[:unique]
108
+ checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
68
109
 
69
110
  indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
70
111
  end
71
112
 
72
- # Returns an array of Column objects for the table specified by +table_name+.
73
- # See the concrete implementation for details on the expected parameter values.
74
- def columns(table_name) end
113
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
114
+ def columns(table_name)
115
+ table_name = table_name.to_s
116
+ column_definitions(table_name).map do |field|
117
+ new_column_from_field(table_name, field)
118
+ end
119
+ end
75
120
 
76
121
  # Checks to see if a column exists in a given table.
77
122
  #
@@ -87,21 +132,29 @@ module ActiveRecord
87
132
  # column_exists?(:suppliers, :name, :string, null: false)
88
133
  # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
89
134
  #
90
- def column_exists?(table_name, column_name, type = nil, options = {})
135
+ def column_exists?(table_name, column_name, type = nil, **options)
91
136
  column_name = column_name.to_s
92
- columns(table_name).any?{ |c| c.name == column_name &&
93
- (!type || c.type == type) &&
94
- (!options.key?(:limit) || c.limit == options[:limit]) &&
95
- (!options.key?(:precision) || c.precision == options[:precision]) &&
96
- (!options.key?(:scale) || c.scale == options[:scale]) &&
97
- (!options.key?(:default) || c.default == options[:default]) &&
98
- (!options.key?(:null) || c.null == options[:null]) }
137
+ checks = []
138
+ checks << lambda { |c| c.name == column_name }
139
+ checks << lambda { |c| c.type == type.to_sym rescue nil } if type
140
+ column_options_keys.each do |attr|
141
+ checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
142
+ end
143
+
144
+ columns(table_name).any? { |c| checks.all? { |check| check[c] } }
145
+ end
146
+
147
+ # Returns just a table's primary key
148
+ def primary_key(table_name)
149
+ pk = primary_keys(table_name)
150
+ pk = pk.first unless pk.size > 1
151
+ pk
99
152
  end
100
153
 
101
154
  # Creates a new table with the name +table_name+. +table_name+ may either
102
155
  # be a String or a Symbol.
103
156
  #
104
- # There are two ways to work with +create_table+. You can use the block
157
+ # There are two ways to work with #create_table. You can use the block
105
158
  # form or the regular form, like this:
106
159
  #
107
160
  # === Block form
@@ -133,13 +186,18 @@ module ActiveRecord
133
186
  # The +options+ hash can include the following keys:
134
187
  # [<tt>:id</tt>]
135
188
  # Whether to automatically add a primary key column. Defaults to true.
136
- # Join tables for +has_and_belongs_to_many+ should set it to false.
189
+ # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
190
+ #
191
+ # A Symbol can be used to specify the type of the generated primary key column.
137
192
  # [<tt>:primary_key</tt>]
138
193
  # The name of the primary key, if one is to be added automatically.
139
- # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
194
+ # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
195
+ #
196
+ # If an array is passed, a composite primary key will be created.
140
197
  #
141
198
  # Note that Active Record models will automatically detect their
142
- # primary key. This can be avoided by using +self.primary_key=+ on the model
199
+ # primary key. This can be avoided by using
200
+ # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
143
201
  # to define the key explicitly.
144
202
  #
145
203
  # [<tt>:options</tt>]
@@ -150,19 +208,22 @@ module ActiveRecord
150
208
  # Set to true to drop the table before creating it.
151
209
  # Set to +:cascade+ to drop dependent objects as well.
152
210
  # Defaults to false.
211
+ # [<tt>:if_not_exists</tt>]
212
+ # Set to true to avoid raising an error when the table already exists.
213
+ # Defaults to false.
153
214
  # [<tt>:as</tt>]
154
215
  # SQL to use to generate the table. When this option is used, the block is
155
216
  # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
156
217
  #
157
218
  # ====== Add a backend specific option to the generated SQL (MySQL)
158
219
  #
159
- # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
220
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
160
221
  #
161
222
  # generates:
162
223
  #
163
224
  # CREATE TABLE suppliers (
164
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
165
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
225
+ # id bigint auto_increment PRIMARY KEY
226
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
166
227
  #
167
228
  # ====== Rename the primary key column
168
229
  #
@@ -173,22 +234,52 @@ module ActiveRecord
173
234
  # generates:
174
235
  #
175
236
  # CREATE TABLE objects (
176
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
237
+ # guid bigint auto_increment PRIMARY KEY,
177
238
  # name varchar(80)
178
239
  # )
179
240
  #
241
+ # ====== Change the primary key column type
242
+ #
243
+ # create_table(:tags, id: :string) do |t|
244
+ # t.column :label, :string
245
+ # end
246
+ #
247
+ # generates:
248
+ #
249
+ # CREATE TABLE tags (
250
+ # id varchar PRIMARY KEY,
251
+ # label varchar
252
+ # )
253
+ #
254
+ # ====== Create a composite primary key
255
+ #
256
+ # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
257
+ # t.belongs_to :product
258
+ # t.belongs_to :client
259
+ # end
260
+ #
261
+ # generates:
262
+ #
263
+ # CREATE TABLE order (
264
+ # product_id bigint NOT NULL,
265
+ # client_id bigint NOT NULL
266
+ # );
267
+ #
268
+ # ALTER TABLE ONLY "orders"
269
+ # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
270
+ #
180
271
  # ====== Do not add a primary key column
181
272
  #
182
273
  # create_table(:categories_suppliers, id: false) do |t|
183
- # t.column :category_id, :integer
184
- # t.column :supplier_id, :integer
274
+ # t.column :category_id, :bigint
275
+ # t.column :supplier_id, :bigint
185
276
  # end
186
277
  #
187
278
  # generates:
188
279
  #
189
280
  # CREATE TABLE categories_suppliers (
190
- # category_id int,
191
- # supplier_id int
281
+ # category_id bigint,
282
+ # supplier_id bigint
192
283
  # )
193
284
  #
194
285
  # ====== Create a temporary table based on a query
@@ -202,33 +293,48 @@ module ActiveRecord
202
293
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
203
294
  #
204
295
  # See also TableDefinition#column for details on how to create columns.
205
- def create_table(table_name, options = {})
206
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
296
+ def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
297
+ td = create_table_definition(table_name, **extract_table_options!(options))
298
+
299
+ if id && !td.as
300
+ pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
207
301
 
208
- if options[:id] != false && !options[:as]
209
- pk = options.fetch(:primary_key) do
210
- Base.get_primary_key table_name.to_s.singularize
302
+ if id.is_a?(Hash)
303
+ options.merge!(id.except(:type))
304
+ id = id.fetch(:type, :primary_key)
211
305
  end
212
306
 
213
- td.primary_key pk, options.fetch(:id, :primary_key), options
307
+ if pk.is_a?(Array)
308
+ td.primary_keys pk
309
+ else
310
+ td.primary_key pk, id, **options
311
+ end
214
312
  end
215
313
 
216
314
  yield td if block_given?
217
315
 
218
- if options[:force] && table_exists?(table_name)
219
- drop_table(table_name, options)
316
+ if force
317
+ drop_table(table_name, force: force, if_exists: true)
318
+ else
319
+ schema_cache.clear_data_source_cache!(table_name.to_s)
220
320
  end
221
321
 
222
322
  result = execute schema_creation.accept td
223
323
 
224
324
  unless supports_indexes_in_create?
225
- td.indexes.each_pair do |column_name, index_options|
226
- add_index(table_name, column_name, index_options)
325
+ td.indexes.each do |column_name, index_options|
326
+ add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists)
227
327
  end
228
328
  end
229
329
 
230
- td.foreign_keys.each do |other_table_name, foreign_key_options|
231
- add_foreign_key(table_name, other_table_name, foreign_key_options)
330
+ if supports_comments? && !supports_comments_in_create?
331
+ if table_comment = td.comment.presence
332
+ change_table_comment(table_name, table_comment)
333
+ end
334
+
335
+ td.columns.each do |column|
336
+ change_column_comment(table_name, column.name, column.comment) if column.comment.present?
337
+ end
232
338
  end
233
339
 
234
340
  result
@@ -240,9 +346,9 @@ module ActiveRecord
240
346
  # # Creates a table called 'assemblies_parts' with no id.
241
347
  # create_join_table(:assemblies, :parts)
242
348
  #
243
- # You can pass a +options+ hash can include the following keys:
349
+ # You can pass an +options+ hash which can include the following keys:
244
350
  # [<tt>:table_name</tt>]
245
- # Sets the table name overriding the default
351
+ # Sets the table name, overriding the default.
246
352
  # [<tt>:column_options</tt>]
247
353
  # Any extra options you want appended to the columns definition.
248
354
  # [<tt>:options</tt>]
@@ -253,7 +359,7 @@ module ActiveRecord
253
359
  # Set to true to drop the table before creating it.
254
360
  # Defaults to false.
255
361
  #
256
- # Note that +create_join_table+ does not create any indices by default; you can use
362
+ # Note that #create_join_table does not create any indices by default; you can use
257
363
  # its block form to do so yourself:
258
364
  #
259
365
  # create_join_table :products, :categories do |t|
@@ -268,32 +374,31 @@ module ActiveRecord
268
374
  # generates:
269
375
  #
270
376
  # CREATE TABLE assemblies_parts (
271
- # assembly_id int NOT NULL,
272
- # part_id int NOT NULL,
377
+ # assembly_id bigint NOT NULL,
378
+ # part_id bigint NOT NULL,
273
379
  # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
274
380
  #
275
- def create_join_table(table_1, table_2, options = {})
381
+ def create_join_table(table_1, table_2, column_options: {}, **options)
276
382
  join_table_name = find_join_table_name(table_1, table_2, options)
277
383
 
278
- column_options = options.delete(:column_options) || {}
279
- column_options.reverse_merge!(null: false)
384
+ column_options.reverse_merge!(null: false, index: false)
280
385
 
281
- t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
386
+ t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
282
387
 
283
- create_table(join_table_name, options.merge!(id: false)) do |td|
284
- td.integer t1_column, column_options
285
- td.integer t2_column, column_options
388
+ create_table(join_table_name, **options.merge!(id: false)) do |td|
389
+ td.references t1_ref, **column_options
390
+ td.references t2_ref, **column_options
286
391
  yield td if block_given?
287
392
  end
288
393
  end
289
394
 
290
395
  # Drops the join table specified by the given arguments.
291
- # See +create_join_table+ for details.
396
+ # See #create_join_table for details.
292
397
  #
293
398
  # Although this command ignores the block if one is given, it can be helpful
294
399
  # to provide one in a migration's +change+ method so it can be reverted.
295
- # In that case, the block will be used by create_join_table.
296
- def drop_join_table(table_1, table_2, options = {})
400
+ # In that case, the block will be used by #create_join_table.
401
+ def drop_join_table(table_1, table_2, **options)
297
402
  join_table_name = find_join_table_name(table_1, table_2, options)
298
403
  drop_table(join_table_name)
299
404
  end
@@ -310,16 +415,24 @@ module ActiveRecord
310
415
  # [<tt>:bulk</tt>]
311
416
  # Set this to true to make this a bulk alter query, such as
312
417
  #
313
- # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
418
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
314
419
  #
315
420
  # Defaults to false.
316
421
  #
422
+ # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
423
+ #
317
424
  # ====== Add a column
318
425
  #
319
426
  # change_table(:suppliers) do |t|
320
427
  # t.column :name, :string, limit: 60
321
428
  # end
322
429
  #
430
+ # ====== Change type of a column
431
+ #
432
+ # change_table(:suppliers) do |t|
433
+ # t.change :metadata, :json
434
+ # end
435
+ #
323
436
  # ====== Add 2 integer columns
324
437
  #
325
438
  # change_table(:suppliers) do |t|
@@ -338,7 +451,7 @@ module ActiveRecord
338
451
  # t.references :company
339
452
  # end
340
453
  #
341
- # Creates a <tt>company_id(integer)</tt> column.
454
+ # Creates a <tt>company_id(bigint)</tt> column.
342
455
  #
343
456
  # ====== Add a polymorphic foreign key column
344
457
  #
@@ -346,7 +459,7 @@ module ActiveRecord
346
459
  # t.belongs_to :company, polymorphic: true
347
460
  # end
348
461
  #
349
- # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns.
462
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
350
463
  #
351
464
  # ====== Remove a column
352
465
  #
@@ -367,8 +480,8 @@ module ActiveRecord
367
480
  # t.remove_index :company_id
368
481
  # end
369
482
  #
370
- # See also Table for details on all of the various column transformation.
371
- def change_table(table_name, options = {})
483
+ # See also Table for details on all of the various column transformations.
484
+ def change_table(table_name, **options)
372
485
  if supports_bulk_alter? && options[:bulk]
373
486
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
374
487
  yield update_table_definition(table_name, recorder)
@@ -391,30 +504,133 @@ module ActiveRecord
391
504
  # [<tt>:force</tt>]
392
505
  # Set to +:cascade+ to drop dependent objects as well.
393
506
  # Defaults to false.
507
+ # [<tt>:if_exists</tt>]
508
+ # Set to +true+ to only drop the table if it exists.
509
+ # Defaults to false.
394
510
  #
395
511
  # Although this command ignores most +options+ and the block if one is given,
396
512
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
397
- # In that case, +options+ and the block will be used by create_table.
398
- def drop_table(table_name, options = {})
399
- execute "DROP TABLE #{quote_table_name(table_name)}"
513
+ # In that case, +options+ and the block will be used by #create_table.
514
+ def drop_table(table_name, **options)
515
+ schema_cache.clear_data_source_cache!(table_name.to_s)
516
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
400
517
  end
401
518
 
402
- # Adds a new column to the named table.
403
- # See TableDefinition#column for details of the options you can use.
404
- def add_column(table_name, column_name, type, options = {})
519
+ # Add a new +type+ column named +column_name+ to +table_name+.
520
+ #
521
+ # The +type+ parameter is normally one of the migrations native types,
522
+ # which is one of the following:
523
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
524
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
525
+ # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
526
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
527
+ #
528
+ # You may use a type not in this list as long as it is supported by your
529
+ # database (for example, "polygon" in MySQL), but this will not be database
530
+ # agnostic and should usually be avoided.
531
+ #
532
+ # Available options are (none of these exists by default):
533
+ # * <tt>:limit</tt> -
534
+ # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
535
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
536
+ # This option is ignored by some backends.
537
+ # * <tt>:default</tt> -
538
+ # The column's default value. Use +nil+ for +NULL+.
539
+ # * <tt>:null</tt> -
540
+ # Allows or disallows +NULL+ values in the column.
541
+ # * <tt>:precision</tt> -
542
+ # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
543
+ # <tt>:datetime</tt>, and <tt>:time</tt> columns.
544
+ # * <tt>:scale</tt> -
545
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
546
+ # * <tt>:collation</tt> -
547
+ # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
548
+ # column will have the same collation as the table.
549
+ # * <tt>:comment</tt> -
550
+ # Specifies the comment for the column. This option is ignored by some backends.
551
+ # * <tt>:if_not_exists</tt> -
552
+ # Specifies if the column already exists to not try to re-add it. This will avoid
553
+ # duplicate column errors.
554
+ #
555
+ # Note: The precision is the total number of significant digits,
556
+ # and the scale is the number of digits that can be stored following
557
+ # the decimal point. For example, the number 123.45 has a precision of 5
558
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
559
+ # range from -999.99 to 999.99.
560
+ #
561
+ # Please be aware of different RDBMS implementations behavior with
562
+ # <tt>:decimal</tt> columns:
563
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
564
+ # <tt>:precision</tt>, and makes no comments about the requirements of
565
+ # <tt>:precision</tt>.
566
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
567
+ # Default is (10,0).
568
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
569
+ # <tt>:scale</tt> [0..infinity]. No default.
570
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
571
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
572
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
573
+ # Default is (38,0).
574
+ # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
575
+ # Default (38,0).
576
+ #
577
+ # == Examples
578
+ #
579
+ # add_column(:users, :picture, :binary, limit: 2.megabytes)
580
+ # # ALTER TABLE "users" ADD "picture" blob(2097152)
581
+ #
582
+ # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
583
+ # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
584
+ #
585
+ # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
586
+ # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
587
+ #
588
+ # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
589
+ # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
590
+ #
591
+ # # While :scale defaults to zero on most databases, it
592
+ # # probably wouldn't hurt to include it.
593
+ # add_column(:measurements, :huge_integer, :decimal, precision: 30)
594
+ # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
595
+ #
596
+ # # Defines a column that stores an array of a type.
597
+ # add_column(:users, :skills, :text, array: true)
598
+ # # ALTER TABLE "users" ADD "skills" text[]
599
+ #
600
+ # # Defines a column with a database-specific type.
601
+ # add_column(:shapes, :triangle, 'polygon')
602
+ # # ALTER TABLE "shapes" ADD "triangle" polygon
603
+ #
604
+ # # Ignores the method call if the column exists
605
+ # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
606
+ def add_column(table_name, column_name, type, **options)
607
+ return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
608
+
405
609
  at = create_alter_table table_name
406
- at.add_column(column_name, type, options)
610
+ at.add_column(column_name, type, **options)
407
611
  execute schema_creation.accept at
408
612
  end
409
613
 
614
+ def add_columns(table_name, *column_names, type:, **options) # :nodoc:
615
+ column_names.each do |column_name|
616
+ add_column(table_name, column_name, type, **options)
617
+ end
618
+ end
619
+
410
620
  # Removes the given columns from the table definition.
411
621
  #
412
622
  # remove_columns(:suppliers, :qualification, :experience)
413
623
  #
414
- def remove_columns(table_name, *column_names)
415
- raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
624
+ # +type+ and other column options can be passed to make migration reversible.
625
+ #
626
+ # remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
627
+ def remove_columns(table_name, *column_names, type: nil, **options)
628
+ if column_names.empty?
629
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
630
+ end
631
+
416
632
  column_names.each do |column_name|
417
- remove_column(table_name, column_name)
633
+ remove_column(table_name, column_name, type, **options)
418
634
  end
419
635
  end
420
636
 
@@ -424,9 +640,18 @@ module ActiveRecord
424
640
  #
425
641
  # The +type+ and +options+ parameters will be ignored if present. It can be helpful
426
642
  # to provide these in a migration's +change+ method so it can be reverted.
427
- # In that case, +type+ and +options+ will be used by add_column.
428
- def remove_column(table_name, column_name, type = nil, options = {})
429
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
643
+ # In that case, +type+ and +options+ will be used by #add_column.
644
+ # Indexes on the column are automatically removed.
645
+ #
646
+ # If the options provided include an +if_exists+ key, it will be used to check if the
647
+ # column does not exist. This will silently ignore the migration rather than raising
648
+ # if the column was already used.
649
+ #
650
+ # remove_column(:suppliers, :qualification, if_exists: true)
651
+ def remove_column(table_name, column_name, type = nil, **options)
652
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
653
+
654
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}"
430
655
  end
431
656
 
432
657
  # Changes the column's definition according to the new options.
@@ -435,7 +660,7 @@ module ActiveRecord
435
660
  # change_column(:suppliers, :name, :string, limit: 80)
436
661
  # change_column(:accounts, :description, :text)
437
662
  #
438
- def change_column(table_name, column_name, type, options = {})
663
+ def change_column(table_name, column_name, type, **options)
439
664
  raise NotImplementedError, "change_column is not implemented"
440
665
  end
441
666
 
@@ -448,11 +673,16 @@ module ActiveRecord
448
673
  #
449
674
  # change_column_default(:users, :email, nil)
450
675
  #
451
- def change_column_default(table_name, column_name, default)
676
+ # Passing a hash containing +:from+ and +:to+ will make this change
677
+ # reversible in migration:
678
+ #
679
+ # change_column_default(:posts, :state, from: nil, to: "draft")
680
+ #
681
+ def change_column_default(table_name, column_name, default_or_changes)
452
682
  raise NotImplementedError, "change_column_default is not implemented"
453
683
  end
454
684
 
455
- # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
685
+ # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
456
686
  # indicates whether the value can be +NULL+. For example
457
687
  #
458
688
  # change_column_null(:users, :nickname, false)
@@ -464,7 +694,7 @@ module ActiveRecord
464
694
  # allows them to be +NULL+ (drops the constraint).
465
695
  #
466
696
  # The method accepts an optional fourth argument to replace existing
467
- # +NULL+s with some other value. Use that one when enabling the
697
+ # <tt>NULL</tt>s with some other value. Use that one when enabling the
468
698
  # constraint if needed, since otherwise those rows would not be valid.
469
699
  #
470
700
  # Please note the fourth argument does not set a column's default.
@@ -492,7 +722,17 @@ module ActiveRecord
492
722
  #
493
723
  # generates:
494
724
  #
495
- # CREATE INDEX suppliers_name_index ON suppliers(name)
725
+ # CREATE INDEX index_suppliers_on_name ON suppliers(name)
726
+ #
727
+ # ====== Creating a index which already exists
728
+ #
729
+ # add_index(:suppliers, :name, if_not_exists: true)
730
+ #
731
+ # generates:
732
+ #
733
+ # CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
734
+ #
735
+ # Note: Not supported by MySQL.
496
736
  #
497
737
  # ====== Creating a unique index
498
738
  #
@@ -500,7 +740,7 @@ module ActiveRecord
500
740
  #
501
741
  # generates:
502
742
  #
503
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
743
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
504
744
  #
505
745
  # ====== Creating a named index
506
746
  #
@@ -518,6 +758,8 @@ module ActiveRecord
518
758
  #
519
759
  # CREATE INDEX by_name ON accounts(name(10))
520
760
  #
761
+ # ====== Creating an index with specific key lengths for multiple keys
762
+ #
521
763
  # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
522
764
  #
523
765
  # generates:
@@ -528,13 +770,13 @@ module ActiveRecord
528
770
  #
529
771
  # ====== Creating an index with a sort order (desc or asc, asc is the default)
530
772
  #
531
- # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
773
+ # add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
532
774
  #
533
775
  # generates:
534
776
  #
535
777
  # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
536
778
  #
537
- # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it).
779
+ # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
538
780
  #
539
781
  # ====== Creating a partial index
540
782
  #
@@ -544,7 +786,7 @@ module ActiveRecord
544
786
  #
545
787
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
546
788
  #
547
- # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
789
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite.
548
790
  #
549
791
  # ====== Creating an index with a specific method
550
792
  #
@@ -557,6 +799,19 @@ module ActiveRecord
557
799
  #
558
800
  # Note: only supported by PostgreSQL and MySQL
559
801
  #
802
+ # ====== Creating an index with a specific operator class
803
+ #
804
+ # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
805
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
806
+ #
807
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
808
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
809
+ #
810
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
811
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
812
+ #
813
+ # Note: only supported by PostgreSQL
814
+ #
560
815
  # ====== Creating an index with a specific type
561
816
  #
562
817
  # add_index(:developers, :name, type: :fulltext)
@@ -565,23 +820,36 @@ module ActiveRecord
565
820
  #
566
821
  # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
567
822
  #
568
- # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
569
- def add_index(table_name, column_name, options = {})
570
- index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
571
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
823
+ # Note: only supported by MySQL.
824
+ #
825
+ # ====== Creating an index with a specific algorithm
826
+ #
827
+ # add_index(:developers, :name, algorithm: :concurrently)
828
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
829
+ #
830
+ # Note: only supported by PostgreSQL.
831
+ #
832
+ # Concurrently adding an index is not supported in a transaction.
833
+ #
834
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
835
+ def add_index(table_name, column_name, **options)
836
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
837
+
838
+ create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
839
+ execute schema_creation.accept(create_index)
572
840
  end
573
841
 
574
842
  # Removes the given index from the table.
575
843
  #
576
- # Removes the +index_accounts_on_column+ in the +accounts+ table.
844
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
577
845
  #
578
- # remove_index :accounts, :column
846
+ # remove_index :accounts, :branch_id
579
847
  #
580
- # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
848
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
581
849
  #
582
850
  # remove_index :accounts, column: :branch_id
583
851
  #
584
- # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
852
+ # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
585
853
  #
586
854
  # remove_index :accounts, column: [:branch_id, :party_id]
587
855
  #
@@ -589,11 +857,29 @@ module ActiveRecord
589
857
  #
590
858
  # remove_index :accounts, name: :by_branch_party
591
859
  #
592
- def remove_index(table_name, options = {})
593
- remove_index!(table_name, index_name_for_remove(table_name, options))
594
- end
860
+ # Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
861
+ #
862
+ # remove_index :accounts, :branch_id, name: :by_branch_party
863
+ #
864
+ # Checks if the index exists before trying to remove it. Will silently ignore indexes that
865
+ # don't exist.
866
+ #
867
+ # remove_index :accounts, if_exists: true
868
+ #
869
+ # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
870
+ #
871
+ # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
872
+ #
873
+ # Note: only supported by PostgreSQL.
874
+ #
875
+ # Concurrently removing an index is not supported in a transaction.
876
+ #
877
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
878
+ def remove_index(table_name, column_name = nil, **options)
879
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
880
+
881
+ index_name = index_name_for_remove(table_name, column_name, options)
595
882
 
596
- def remove_index!(table_name, index_name) #:nodoc:
597
883
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
598
884
  end
599
885
 
@@ -604,9 +890,11 @@ module ActiveRecord
604
890
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
605
891
  #
606
892
  def rename_index(table_name, old_name, new_name)
893
+ old_name = old_name.to_s
894
+ new_name = new_name.to_s
607
895
  validate_index_length!(table_name, new_name)
608
896
 
609
- # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
897
+ # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
610
898
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
611
899
  return unless old_index_def
612
900
  add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
@@ -623,73 +911,75 @@ module ActiveRecord
623
911
  raise ArgumentError, "You must specify the index name"
624
912
  end
625
913
  else
626
- index_name(table_name, :column => options)
914
+ index_name(table_name, index_name_options(options))
627
915
  end
628
916
  end
629
917
 
630
918
  # Verifies the existence of an index with a given name.
631
- #
632
- # The default argument is returned if the underlying implementation does not define the indexes method,
633
- # as there's no way to determine the correct answer in that case.
634
- def index_name_exists?(table_name, index_name, default)
635
- return default unless respond_to?(:indexes)
919
+ def index_name_exists?(table_name, index_name)
636
920
  index_name = index_name.to_s
637
921
  indexes(table_name).detect { |i| i.name == index_name }
638
922
  end
639
923
 
640
- # Adds a reference. The reference column is an integer by default,
924
+ # Adds a reference. The reference column is a bigint by default,
641
925
  # the <tt>:type</tt> option can be used to specify a different type.
642
926
  # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
643
- # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
927
+ # #add_reference and #add_belongs_to are acceptable.
644
928
  #
645
929
  # The +options+ hash can include the following keys:
646
930
  # [<tt>:type</tt>]
647
- # The reference column type. Defaults to +:integer+.
931
+ # The reference column type. Defaults to +:bigint+.
648
932
  # [<tt>:index</tt>]
649
- # Add an appropriate index. Defaults to false.
933
+ # Add an appropriate index. Defaults to true.
934
+ # See #add_index for usage of this option.
650
935
  # [<tt>:foreign_key</tt>]
651
- # Add an appropriate foreign key. Defaults to false.
936
+ # Add an appropriate foreign key constraint. Defaults to false, pass true
937
+ # to add. In case the join table can't be inferred from the association
938
+ # pass <tt>:to_table</tt> with the appropriate table name.
652
939
  # [<tt>:polymorphic</tt>]
653
- # Wether an additional +_type+ column should be added. Defaults to false.
940
+ # Whether an additional +_type+ column should be added. Defaults to false.
941
+ # [<tt>:null</tt>]
942
+ # Whether the column allows nulls. Defaults to true.
654
943
  #
655
- # ====== Create a user_id integer column
944
+ # ====== Create a user_id bigint column without an index
656
945
  #
657
- # add_reference(:products, :user)
946
+ # add_reference(:products, :user, index: false)
658
947
  #
659
948
  # ====== Create a user_id string column
660
949
  #
661
950
  # add_reference(:products, :user, type: :string)
662
951
  #
663
- # ====== Create supplier_id, supplier_type columns and appropriate index
952
+ # ====== Create supplier_id, supplier_type columns
664
953
  #
665
- # add_reference(:products, :supplier, polymorphic: true, index: true)
954
+ # add_reference(:products, :supplier, polymorphic: true)
666
955
  #
667
- def add_reference(table_name, ref_name, options = {})
668
- polymorphic = options.delete(:polymorphic)
669
- index_options = options.delete(:index)
670
- type = options.delete(:type) || :integer
671
- foreign_key_options = options.delete(:foreign_key)
672
-
673
- if polymorphic && foreign_key_options
674
- raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
675
- end
676
-
677
- add_column(table_name, "#{ref_name}_id", type, options)
678
- add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
679
- add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
680
- if foreign_key_options
681
- to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
682
- add_foreign_key(table_name, to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {})
683
- end
956
+ # ====== Create a supplier_id column with a unique index
957
+ #
958
+ # add_reference(:products, :supplier, index: { unique: true })
959
+ #
960
+ # ====== Create a supplier_id column with a named index
961
+ #
962
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
963
+ #
964
+ # ====== Create a supplier_id column and appropriate foreign key
965
+ #
966
+ # add_reference(:products, :supplier, foreign_key: true)
967
+ #
968
+ # ====== Create a supplier_id column and a foreign key to the firms table
969
+ #
970
+ # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
971
+ #
972
+ def add_reference(table_name, ref_name, **options)
973
+ ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
684
974
  end
685
975
  alias :add_belongs_to :add_reference
686
976
 
687
977
  # Removes the reference(s). Also removes a +type+ column if one exists.
688
- # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
978
+ # #remove_reference and #remove_belongs_to are acceptable.
689
979
  #
690
980
  # ====== Remove the reference
691
981
  #
692
- # remove_reference(:products, :user, index: true)
982
+ # remove_reference(:products, :user, index: false)
693
983
  #
694
984
  # ====== Remove polymorphic reference
695
985
  #
@@ -697,21 +987,27 @@ module ActiveRecord
697
987
  #
698
988
  # ====== Remove the reference with a foreign key
699
989
  #
700
- # remove_reference(:products, :user, index: true, foreign_key: true)
990
+ # remove_reference(:products, :user, foreign_key: true)
701
991
  #
702
- def remove_reference(table_name, ref_name, options = {})
703
- if options[:foreign_key]
704
- to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
705
- remove_foreign_key(table_name, to_table)
992
+ def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
993
+ if foreign_key
994
+ reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
995
+ if foreign_key.is_a?(Hash)
996
+ foreign_key_options = foreign_key
997
+ else
998
+ foreign_key_options = { to_table: reference_name }
999
+ end
1000
+ foreign_key_options[:column] ||= "#{ref_name}_id"
1001
+ remove_foreign_key(table_name, **foreign_key_options)
706
1002
  end
707
1003
 
708
1004
  remove_column(table_name, "#{ref_name}_id")
709
- remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
1005
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
710
1006
  end
711
1007
  alias :remove_belongs_to :remove_reference
712
1008
 
713
1009
  # Returns an array of foreign keys for the given table.
714
- # The foreign keys are represented as +ForeignKeyDefinition+ objects.
1010
+ # The foreign keys are represented as ForeignKeyDefinition objects.
715
1011
  def foreign_keys(table_name)
716
1012
  raise NotImplementedError, "foreign_keys is not implemented"
717
1013
  end
@@ -729,11 +1025,11 @@ module ActiveRecord
729
1025
  #
730
1026
  # generates:
731
1027
  #
732
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
1028
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
733
1029
  #
734
1030
  # ====== Creating a foreign key on a specific column
735
1031
  #
736
- # add_foreign_key :articles, :users, column: :author_id, primary_key: :lng_id
1032
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
737
1033
  #
738
1034
  # generates:
739
1035
  #
@@ -745,7 +1041,7 @@ module ActiveRecord
745
1041
  #
746
1042
  # generates:
747
1043
  #
748
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
1044
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
749
1045
  #
750
1046
  # The +options+ hash can include the following keys:
751
1047
  # [<tt>:column</tt>]
@@ -755,28 +1051,25 @@ module ActiveRecord
755
1051
  # [<tt>:name</tt>]
756
1052
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
757
1053
  # [<tt>:on_delete</tt>]
758
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
1054
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
759
1055
  # [<tt>:on_update</tt>]
760
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
761
- def add_foreign_key(from_table, to_table, options = {})
1056
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1057
+ # [<tt>:validate</tt>]
1058
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1059
+ def add_foreign_key(from_table, to_table, **options)
762
1060
  return unless supports_foreign_keys?
763
1061
 
764
- options[:column] ||= foreign_key_column_for(to_table)
765
-
766
- options = {
767
- column: options[:column],
768
- primary_key: options[:primary_key],
769
- name: foreign_key_name(from_table, options),
770
- on_delete: options[:on_delete],
771
- on_update: options[:on_update]
772
- }
1062
+ options = foreign_key_options(from_table, to_table, options)
773
1063
  at = create_alter_table from_table
774
1064
  at.add_foreign_key to_table, options
775
1065
 
776
1066
  execute schema_creation.accept(at)
777
1067
  end
778
1068
 
779
- # Removes the given foreign key from the table.
1069
+ # Removes the given foreign key from the table. Any option parameters provided
1070
+ # will be used to re-add the foreign key in case of a migration rollback.
1071
+ # It is recommended that you provide any options used when creating the foreign
1072
+ # key so that the migration can be reverted properly.
780
1073
  #
781
1074
  # Removes the foreign key on +accounts.branch_id+.
782
1075
  #
@@ -786,28 +1079,22 @@ module ActiveRecord
786
1079
  #
787
1080
  # remove_foreign_key :accounts, column: :owner_id
788
1081
  #
1082
+ # Removes the foreign key on +accounts.owner_id+.
1083
+ #
1084
+ # remove_foreign_key :accounts, to_table: :owners
1085
+ #
789
1086
  # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
790
1087
  #
791
1088
  # remove_foreign_key :accounts, name: :special_fk_name
792
1089
  #
793
- def remove_foreign_key(from_table, options_or_to_table = {})
1090
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
1091
+ # with an addition of
1092
+ # [<tt>:to_table</tt>]
1093
+ # The name of the table that contains the referenced primary key.
1094
+ def remove_foreign_key(from_table, to_table = nil, **options)
794
1095
  return unless supports_foreign_keys?
795
1096
 
796
- if options_or_to_table.is_a?(Hash)
797
- options = options_or_to_table
798
- else
799
- options = { column: foreign_key_column_for(options_or_to_table) }
800
- end
801
-
802
- fk_name_to_delete = options.fetch(:name) do
803
- fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
804
-
805
- if fk_to_delete
806
- fk_to_delete.name
807
- else
808
- raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
809
- end
810
- end
1097
+ fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
811
1098
 
812
1099
  at = create_alter_table from_table
813
1100
  at.drop_foreign_key fk_name_to_delete
@@ -815,54 +1102,119 @@ module ActiveRecord
815
1102
  execute schema_creation.accept(at)
816
1103
  end
817
1104
 
1105
+ # Checks to see if a foreign key exists on a table for a given foreign key definition.
1106
+ #
1107
+ # # Checks to see if a foreign key exists.
1108
+ # foreign_key_exists?(:accounts, :branches)
1109
+ #
1110
+ # # Checks to see if a foreign key on a specified column exists.
1111
+ # foreign_key_exists?(:accounts, column: :owner_id)
1112
+ #
1113
+ # # Checks to see if a foreign key with a custom name exists.
1114
+ # foreign_key_exists?(:accounts, name: "special_fk_name")
1115
+ #
1116
+ def foreign_key_exists?(from_table, to_table = nil, **options)
1117
+ foreign_key_for(from_table, to_table: to_table, **options).present?
1118
+ end
1119
+
818
1120
  def foreign_key_column_for(table_name) # :nodoc:
819
- prefix = Base.table_name_prefix
820
- suffix = Base.table_name_suffix
821
- name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
1121
+ name = strip_table_name_prefix_and_suffix(table_name)
822
1122
  "#{name.singularize}_id"
823
1123
  end
824
1124
 
825
- def dump_schema_information #:nodoc:
826
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
1125
+ def foreign_key_options(from_table, to_table, options) # :nodoc:
1126
+ options = options.dup
1127
+ options[:column] ||= foreign_key_column_for(to_table)
1128
+ options[:name] ||= foreign_key_name(from_table, options)
1129
+ options
1130
+ end
1131
+
1132
+ # Returns an array of check constraints for the given table.
1133
+ # The check constraints are represented as CheckConstraintDefinition objects.
1134
+ def check_constraints(table_name)
1135
+ raise NotImplementedError
1136
+ end
1137
+
1138
+ # Adds a new check constraint to the table. +expression+ is a String
1139
+ # representation of verifiable boolean condition.
1140
+ #
1141
+ # add_check_constraint :products, "price > 0", name: "price_check"
1142
+ #
1143
+ # generates:
1144
+ #
1145
+ # ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
1146
+ #
1147
+ # The +options+ hash can include the following keys:
1148
+ # [<tt>:name</tt>]
1149
+ # The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
1150
+ # [<tt>:validate</tt>]
1151
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1152
+ def add_check_constraint(table_name, expression, **options)
1153
+ return unless supports_check_constraints?
1154
+
1155
+ options = check_constraint_options(table_name, expression, options)
1156
+ at = create_alter_table(table_name)
1157
+ at.add_check_constraint(expression, options)
1158
+
1159
+ execute schema_creation.accept(at)
1160
+ end
1161
+
1162
+ def check_constraint_options(table_name, expression, options) # :nodoc:
1163
+ options = options.dup
1164
+ options[:name] ||= check_constraint_name(table_name, expression: expression, **options)
1165
+ options
1166
+ end
1167
+
1168
+ # Removes the given check constraint from the table.
1169
+ #
1170
+ # remove_check_constraint :products, name: "price_check"
1171
+ #
1172
+ # The +expression+ parameter will be ignored if present. It can be helpful
1173
+ # to provide this in a migration's +change+ method so it can be reverted.
1174
+ # In that case, +expression+ will be used by #add_check_constraint.
1175
+ def remove_check_constraint(table_name, expression = nil, **options)
1176
+ return unless supports_check_constraints?
1177
+
1178
+ chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
827
1179
 
828
- ActiveRecord::SchemaMigration.order('version').map { |sm|
829
- "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
830
- }.join "\n\n"
1180
+ at = create_alter_table(table_name)
1181
+ at.drop_check_constraint(chk_name_to_delete)
1182
+
1183
+ execute schema_creation.accept(at)
831
1184
  end
832
1185
 
833
- # Should not be called normally, but this operation is non-destructive.
834
- # The migrations module handles this automatically.
835
- def initialize_schema_migrations_table
836
- ActiveRecord::SchemaMigration.create_table
1186
+ def dump_schema_information # :nodoc:
1187
+ versions = schema_migration.all_versions
1188
+ insert_versions_sql(versions) if versions.any?
837
1189
  end
838
1190
 
839
- def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
840
- migrations_paths = Array(migrations_paths)
1191
+ def internal_string_options_for_primary_key # :nodoc:
1192
+ { primary_key: true }
1193
+ end
1194
+
1195
+ def assume_migrated_upto_version(version)
841
1196
  version = version.to_i
842
- sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
1197
+ sm_table = quote_table_name(schema_migration.table_name)
843
1198
 
844
- migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
845
- versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
846
- ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
847
- end
1199
+ migrated = migration_context.get_all_versions
1200
+ versions = migration_context.migrations.map(&:version)
848
1201
 
849
1202
  unless migrated.include?(version)
850
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
1203
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
851
1204
  end
852
1205
 
853
- inserted = Set.new
854
- (versions - migrated).each do |v|
855
- if inserted.include?(v)
856
- raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
857
- elsif v < version
858
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
859
- inserted << v
1206
+ inserting = (versions - migrated).select { |v| v < version }
1207
+ if inserting.any?
1208
+ if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
1209
+ raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
860
1210
  end
1211
+ execute insert_versions_sql(inserting)
861
1212
  end
862
1213
  end
863
1214
 
864
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
865
- if native = native_database_types[type.to_sym]
1215
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
1216
+ type = type.to_sym if type
1217
+ if native = native_database_types[type]
866
1218
  column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
867
1219
 
868
1220
  if type == :decimal # ignore limit, use precision and scale
@@ -878,6 +1230,12 @@ module ActiveRecord
878
1230
  raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
879
1231
  end
880
1232
 
1233
+ elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
1234
+ if (0..6) === precision
1235
+ column_type_sql << "(#{precision})"
1236
+ else
1237
+ raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6"
1238
+ end
881
1239
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
882
1240
  column_type_sql << "(#{limit})"
883
1241
  end
@@ -889,7 +1247,7 @@ module ActiveRecord
889
1247
  end
890
1248
 
891
1249
  # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
892
- # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
1250
+ # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
893
1251
  # require the order columns appear in the SELECT.
894
1252
  #
895
1253
  # columns_for_distinct("posts.id", ["posts.created_at desc"])
@@ -898,23 +1256,27 @@ module ActiveRecord
898
1256
  columns
899
1257
  end
900
1258
 
901
- include TimestampDefaultDeprecation
902
1259
  # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
903
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
1260
+ # Additional options (like +:null+) are forwarded to #add_column.
904
1261
  #
905
- # add_timestamps(:suppliers, null: false)
1262
+ # add_timestamps(:suppliers, null: true)
906
1263
  #
907
- def add_timestamps(table_name, options = {})
908
- emit_warning_if_null_unspecified(:add_timestamps, options)
909
- add_column table_name, :created_at, :datetime, options
910
- add_column table_name, :updated_at, :datetime, options
1264
+ def add_timestamps(table_name, **options)
1265
+ options[:null] = false if options[:null].nil?
1266
+
1267
+ if !options.key?(:precision) && supports_datetime_with_precision?
1268
+ options[:precision] = 6
1269
+ end
1270
+
1271
+ add_column table_name, :created_at, :datetime, **options
1272
+ add_column table_name, :updated_at, :datetime, **options
911
1273
  end
912
1274
 
913
1275
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
914
1276
  #
915
1277
  # remove_timestamps(:suppliers)
916
1278
  #
917
- def remove_timestamps(table_name, options = {})
1279
+ def remove_timestamps(table_name, **options)
918
1280
  remove_column table_name, :updated_at
919
1281
  remove_column table_name, :created_at
920
1282
  end
@@ -923,86 +1285,133 @@ module ActiveRecord
923
1285
  Table.new(table_name, base)
924
1286
  end
925
1287
 
926
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
927
- column_names = Array(column_name)
928
- index_name = index_name(table_name, column: column_names)
1288
+ def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1289
+ options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
929
1290
 
930
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
1291
+ column_names = index_column_names(column_name)
931
1292
 
932
- index_type = options[:unique] ? "UNIQUE" : ""
933
- index_type = options[:type].to_s if options.key?(:type)
934
- index_name = options[:name].to_s if options.key?(:name)
935
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
1293
+ index_name = name&.to_s
1294
+ index_name ||= index_name(table_name, column_names)
936
1295
 
937
- if options.key?(:algorithm)
938
- algorithm = index_algorithms.fetch(options[:algorithm]) {
939
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
940
- }
941
- end
1296
+ validate_index_length!(table_name, index_name, internal)
942
1297
 
943
- using = "USING #{options[:using]}" if options[:using].present?
1298
+ index = IndexDefinition.new(
1299
+ table_name, index_name,
1300
+ options[:unique],
1301
+ column_names,
1302
+ lengths: options[:length] || {},
1303
+ orders: options[:order] || {},
1304
+ opclasses: options[:opclass] || {},
1305
+ where: options[:where],
1306
+ type: options[:type],
1307
+ using: options[:using],
1308
+ comment: options[:comment]
1309
+ )
944
1310
 
945
- if supports_partial_index?
946
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
947
- end
1311
+ [index, index_algorithm(options[:algorithm]), if_not_exists]
1312
+ end
948
1313
 
949
- if index_name.length > max_index_length
950
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
951
- end
952
- if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
953
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1314
+ def index_algorithm(algorithm) # :nodoc:
1315
+ index_algorithms.fetch(algorithm) do
1316
+ raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}"
1317
+ end if algorithm
1318
+ end
1319
+
1320
+ def quoted_columns_for_index(column_names, options) # :nodoc:
1321
+ quoted_columns = column_names.each_with_object({}) do |name, result|
1322
+ result[name.to_sym] = quote_column_name(name).dup
954
1323
  end
955
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
1324
+ add_options_for_index_columns(quoted_columns, **options).values.join(", ")
1325
+ end
956
1326
 
957
- [index_name, index_type, index_columns, index_options, algorithm, using]
1327
+ def options_include_default?(options)
1328
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
958
1329
  end
959
1330
 
960
- protected
961
- def add_index_sort_order(option_strings, column_names, options = {})
962
- if options.is_a?(Hash) && order = options[:order]
963
- case order
964
- when Hash
965
- column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
966
- when String
967
- column_names.each {|name| option_strings[name] += " #{order.upcase}"}
968
- end
969
- end
1331
+ # Changes the comment for a table or removes it if +nil+.
1332
+ #
1333
+ # Passing a hash containing +:from+ and +:to+ will make this change
1334
+ # reversible in migration:
1335
+ #
1336
+ # change_table_comment(:posts, from: "old_comment", to: "new_comment")
1337
+ def change_table_comment(table_name, comment_or_changes)
1338
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
1339
+ end
970
1340
 
971
- return option_strings
1341
+ # Changes the comment for a column or removes it if +nil+.
1342
+ #
1343
+ # Passing a hash containing +:from+ and +:to+ will make this change
1344
+ # reversible in migration:
1345
+ #
1346
+ # change_column_comment(:posts, :state, from: "old_comment", to: "new_comment")
1347
+ def change_column_comment(table_name, column_name, comment_or_changes)
1348
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
1349
+ end
1350
+
1351
+ def create_schema_dumper(options) # :nodoc:
1352
+ SchemaDumper.create(self, options)
1353
+ end
1354
+
1355
+ private
1356
+ def column_options_keys
1357
+ [:limit, :precision, :scale, :default, :null, :collation, :comment]
1358
+ end
1359
+
1360
+ def add_index_sort_order(quoted_columns, **options)
1361
+ orders = options_for_index_columns(options[:order])
1362
+ quoted_columns.each do |name, column|
1363
+ column << " #{orders[name].upcase}" if orders[name].present?
1364
+ end
972
1365
  end
973
1366
 
974
- # Overridden by the MySQL adapter for supporting index lengths
975
- def quoted_columns_for_index(column_names, options = {})
976
- option_strings = Hash[column_names.map {|name| [name, '']}]
1367
+ def options_for_index_columns(options)
1368
+ if options.is_a?(Hash)
1369
+ options.symbolize_keys
1370
+ else
1371
+ Hash.new { |hash, column| hash[column] = options }
1372
+ end
1373
+ end
977
1374
 
978
- # add index sort order if supported
1375
+ # Overridden by the MySQL adapter for supporting index lengths and by
1376
+ # the PostgreSQL adapter for supporting operator classes.
1377
+ def add_options_for_index_columns(quoted_columns, **options)
979
1378
  if supports_index_sort_order?
980
- option_strings = add_index_sort_order(option_strings, column_names, options)
1379
+ quoted_columns = add_index_sort_order(quoted_columns, **options)
981
1380
  end
982
1381
 
983
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
1382
+ quoted_columns
984
1383
  end
985
1384
 
986
- def options_include_default?(options)
987
- options.include?(:default) && !(options[:null] == false && options[:default].nil?)
988
- end
1385
+ def index_name_for_remove(table_name, column_name, options)
1386
+ return options[:name] if can_remove_index_by_name?(column_name, options)
989
1387
 
990
- def index_name_for_remove(table_name, options = {})
991
- index_name = index_name(table_name, options)
1388
+ checks = []
992
1389
 
993
- unless index_name_exists?(table_name, index_name, true)
994
- if options.is_a?(Hash) && options.has_key?(:name)
995
- options_without_column = options.dup
996
- options_without_column.delete :column
997
- index_name_without_column = index_name(table_name, options_without_column)
1390
+ if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1391
+ options[:name] = index_name(table_name, column_name)
1392
+ column_names = []
1393
+ else
1394
+ column_names = index_column_names(column_name || options[:column])
1395
+ end
998
1396
 
999
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
1000
- end
1397
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1001
1398
 
1002
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
1399
+ if column_names.present?
1400
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1003
1401
  end
1004
1402
 
1005
- index_name
1403
+ raise ArgumentError, "No name or columns specified" if checks.none?
1404
+
1405
+ matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }
1406
+
1407
+ if matching_indexes.count > 1
1408
+ raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
1409
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1410
+ elsif matching_indexes.none?
1411
+ raise ArgumentError, "No indexes found on #{table_name} with the options provided."
1412
+ else
1413
+ matching_indexes.first.name
1414
+ end
1006
1415
  end
1007
1416
 
1008
1417
  def rename_table_indexes(table_name, new_name)
@@ -1027,28 +1436,202 @@ module ActiveRecord
1027
1436
  end
1028
1437
  end
1029
1438
 
1030
- private
1031
- def create_table_definition(name, temporary, options, as = nil)
1032
- TableDefinition.new native_database_types, name, temporary, options, as
1033
- end
1439
+ def schema_creation
1440
+ SchemaCreation.new(self)
1441
+ end
1034
1442
 
1035
- def create_alter_table(name)
1036
- AlterTable.new create_table_definition(name, false, {})
1037
- end
1443
+ def create_table_definition(name, **options)
1444
+ TableDefinition.new(self, name, **options)
1445
+ end
1038
1446
 
1039
- def foreign_key_name(table_name, options) # :nodoc:
1040
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1041
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1042
- options.fetch(:name) do
1043
- "fk_rails_#{hashed_identifier}"
1447
+ def create_alter_table(name)
1448
+ AlterTable.new create_table_definition(name)
1044
1449
  end
1045
- end
1046
1450
 
1047
- def validate_index_length!(table_name, new_name)
1048
- if new_name.length > allowed_index_name_length
1049
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1451
+ def extract_table_options!(options)
1452
+ options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
1453
+ end
1454
+
1455
+ def fetch_type_metadata(sql_type)
1456
+ cast_type = lookup_cast_type(sql_type)
1457
+ SqlTypeMetadata.new(
1458
+ sql_type: sql_type,
1459
+ type: cast_type.type,
1460
+ limit: cast_type.limit,
1461
+ precision: cast_type.precision,
1462
+ scale: cast_type.scale,
1463
+ )
1464
+ end
1465
+
1466
+ def index_column_names(column_names)
1467
+ if column_names.is_a?(String) && /\W/.match?(column_names)
1468
+ column_names
1469
+ else
1470
+ Array(column_names)
1471
+ end
1472
+ end
1473
+
1474
+ def index_name_options(column_names)
1475
+ if column_names.is_a?(String) && /\W/.match?(column_names)
1476
+ column_names = column_names.scan(/\w+/).join("_")
1477
+ end
1478
+
1479
+ { column: column_names }
1480
+ end
1481
+
1482
+ def strip_table_name_prefix_and_suffix(table_name)
1483
+ prefix = Base.table_name_prefix
1484
+ suffix = Base.table_name_suffix
1485
+ table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
1486
+ end
1487
+
1488
+ def foreign_key_name(table_name, options)
1489
+ options.fetch(:name) do
1490
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1491
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1492
+
1493
+ "fk_rails_#{hashed_identifier}"
1494
+ end
1495
+ end
1496
+
1497
+ def foreign_key_for(from_table, **options)
1498
+ return unless supports_foreign_keys?
1499
+ foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1500
+ end
1501
+
1502
+ def foreign_key_for!(from_table, to_table: nil, **options)
1503
+ foreign_key_for(from_table, to_table: to_table, **options) ||
1504
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
1505
+ end
1506
+
1507
+ def extract_foreign_key_action(specifier)
1508
+ case specifier
1509
+ when "CASCADE"; :cascade
1510
+ when "SET NULL"; :nullify
1511
+ when "RESTRICT"; :restrict
1512
+ end
1513
+ end
1514
+
1515
+ def check_constraint_name(table_name, **options)
1516
+ options.fetch(:name) do
1517
+ expression = options.fetch(:expression)
1518
+ identifier = "#{table_name}_#{expression}_chk"
1519
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1520
+
1521
+ "chk_rails_#{hashed_identifier}"
1522
+ end
1523
+ end
1524
+
1525
+ def check_constraint_for(table_name, **options)
1526
+ return unless supports_check_constraints?
1527
+ chk_name = check_constraint_name(table_name, **options)
1528
+ check_constraints(table_name).detect { |chk| chk.name == chk_name }
1529
+ end
1530
+
1531
+ def check_constraint_for!(table_name, expression: nil, **options)
1532
+ check_constraint_for(table_name, expression: expression, **options) ||
1533
+ raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}")
1534
+ end
1535
+
1536
+ def validate_index_length!(table_name, new_name, internal = false)
1537
+ if new_name.length > index_name_length
1538
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1539
+ end
1540
+ end
1541
+
1542
+ def extract_new_default_value(default_or_changes)
1543
+ if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
1544
+ default_or_changes[:to]
1545
+ else
1546
+ default_or_changes
1547
+ end
1548
+ end
1549
+ alias :extract_new_comment_value :extract_new_default_value
1550
+
1551
+ def can_remove_index_by_name?(column_name, options)
1552
+ column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1553
+ end
1554
+
1555
+ def bulk_change_table(table_name, operations)
1556
+ sql_fragments = []
1557
+ non_combinable_operations = []
1558
+
1559
+ operations.each do |command, args|
1560
+ table, arguments = args.shift, args
1561
+ method = :"#{command}_for_alter"
1562
+
1563
+ if respond_to?(method, true)
1564
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1565
+ sql_fragments << sqls
1566
+ non_combinable_operations.concat(procs)
1567
+ else
1568
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1569
+ non_combinable_operations.each(&:call)
1570
+ sql_fragments = []
1571
+ non_combinable_operations = []
1572
+ send(command, table, *arguments)
1573
+ end
1574
+ end
1575
+
1576
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
1577
+ non_combinable_operations.each(&:call)
1578
+ end
1579
+
1580
+ def add_column_for_alter(table_name, column_name, type, **options)
1581
+ td = create_table_definition(table_name)
1582
+ cd = td.new_column_definition(column_name, type, **options)
1583
+ schema_creation.accept(AddColumnDefinition.new(cd))
1584
+ end
1585
+
1586
+ def rename_column_sql(table_name, column_name, new_column_name)
1587
+ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1588
+ end
1589
+
1590
+ def remove_column_for_alter(table_name, column_name, type = nil, **options)
1591
+ "DROP COLUMN #{quote_column_name(column_name)}"
1592
+ end
1593
+
1594
+ def remove_columns_for_alter(table_name, *column_names, **options)
1595
+ column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
1596
+ end
1597
+
1598
+ def add_timestamps_for_alter(table_name, **options)
1599
+ options[:null] = false if options[:null].nil?
1600
+
1601
+ if !options.key?(:precision) && supports_datetime_with_precision?
1602
+ options[:precision] = 6
1603
+ end
1604
+
1605
+ [
1606
+ add_column_for_alter(table_name, :created_at, :datetime, **options),
1607
+ add_column_for_alter(table_name, :updated_at, :datetime, **options)
1608
+ ]
1609
+ end
1610
+
1611
+ def remove_timestamps_for_alter(table_name, **options)
1612
+ remove_columns_for_alter(table_name, :updated_at, :created_at)
1613
+ end
1614
+
1615
+ def insert_versions_sql(versions)
1616
+ sm_table = quote_table_name(schema_migration.table_name)
1617
+
1618
+ if versions.is_a?(Array)
1619
+ sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1620
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1621
+ sql << ";\n\n"
1622
+ sql
1623
+ else
1624
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1625
+ end
1626
+ end
1627
+
1628
+ def data_source_sql(name = nil, type: nil)
1629
+ raise NotImplementedError
1630
+ end
1631
+
1632
+ def quoted_scope(name = nil, type: nil)
1633
+ raise NotImplementedError
1050
1634
  end
1051
- end
1052
1635
  end
1053
1636
  end
1054
1637
  end