activerecord 5.0.7.2 → 6.1.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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +829 -2015
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +11 -9
  5. data/examples/performance.rb +31 -29
  6. data/examples/simple.rb +5 -3
  7. data/lib/active_record.rb +37 -29
  8. data/lib/active_record/aggregations.rb +249 -247
  9. data/lib/active_record/association_relation.rb +30 -18
  10. data/lib/active_record/associations.rb +1714 -1596
  11. data/lib/active_record/associations/alias_tracker.rb +36 -42
  12. data/lib/active_record/associations/association.rb +143 -68
  13. data/lib/active_record/associations/association_scope.rb +98 -94
  14. data/lib/active_record/associations/belongs_to_association.rb +76 -46
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -28
  17. data/lib/active_record/associations/builder/belongs_to.rb +52 -60
  18. data/lib/active_record/associations/builder/collection_association.rb +12 -22
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
  20. data/lib/active_record/associations/builder/has_many.rb +10 -2
  21. data/lib/active_record/associations/builder/has_one.rb +35 -2
  22. data/lib/active_record/associations/builder/singular_association.rb +5 -1
  23. data/lib/active_record/associations/collection_association.rb +104 -259
  24. data/lib/active_record/associations/collection_proxy.rb +169 -125
  25. data/lib/active_record/associations/foreign_association.rb +22 -0
  26. data/lib/active_record/associations/has_many_association.rb +46 -31
  27. data/lib/active_record/associations/has_many_through_association.rb +66 -46
  28. data/lib/active_record/associations/has_one_association.rb +71 -52
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +169 -180
  31. data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +97 -104
  35. data/lib/active_record/associations/preloader/association.rb +109 -97
  36. data/lib/active_record/associations/preloader/through_association.rb +77 -76
  37. data/lib/active_record/associations/singular_association.rb +12 -45
  38. data/lib/active_record/associations/through_association.rb +27 -15
  39. data/lib/active_record/attribute_assignment.rb +55 -60
  40. data/lib/active_record/attribute_methods.rb +111 -141
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
  42. data/lib/active_record/attribute_methods/dirty.rb +172 -112
  43. data/lib/active_record/attribute_methods/primary_key.rb +88 -91
  44. data/lib/active_record/attribute_methods/query.rb +6 -8
  45. data/lib/active_record/attribute_methods/read.rb +18 -50
  46. data/lib/active_record/attribute_methods/serialization.rb +38 -10
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
  48. data/lib/active_record/attribute_methods/write.rb +25 -32
  49. data/lib/active_record/attributes.rb +69 -31
  50. data/lib/active_record/autosave_association.rb +102 -66
  51. data/lib/active_record/base.rb +16 -25
  52. data/lib/active_record/callbacks.rb +202 -43
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +11 -12
  55. data/lib/active_record/connection_adapters.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
  69. data/lib/active_record/connection_adapters/column.rb +55 -13
  70. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  71. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
  82. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  83. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
  85. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
  86. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
  93. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
  95. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
  98. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
  106. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
  107. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  109. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  110. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
  121. data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
  123. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  124. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
  131. data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
  132. data/lib/active_record/connection_handling.rb +287 -45
  133. data/lib/active_record/core.rb +385 -181
  134. data/lib/active_record/counter_cache.rb +60 -28
  135. data/lib/active_record/database_configurations.rb +272 -0
  136. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  137. data/lib/active_record/database_configurations/database_config.rb +80 -0
  138. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  139. data/lib/active_record/database_configurations/url_config.rb +53 -0
  140. data/lib/active_record/delegated_type.rb +209 -0
  141. data/lib/active_record/destroy_association_async_job.rb +36 -0
  142. data/lib/active_record/dynamic_matchers.rb +87 -87
  143. data/lib/active_record/enum.rb +122 -47
  144. data/lib/active_record/errors.rb +153 -22
  145. data/lib/active_record/explain.rb +13 -8
  146. data/lib/active_record/explain_registry.rb +3 -1
  147. data/lib/active_record/explain_subscriber.rb +9 -4
  148. data/lib/active_record/fixture_set/file.rb +20 -22
  149. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  150. data/lib/active_record/fixture_set/render_context.rb +17 -0
  151. data/lib/active_record/fixture_set/table_row.rb +152 -0
  152. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  153. data/lib/active_record/fixtures.rb +246 -507
  154. data/lib/active_record/gem_version.rb +6 -4
  155. data/lib/active_record/inheritance.rb +168 -95
  156. data/lib/active_record/insert_all.rb +208 -0
  157. data/lib/active_record/integration.rb +114 -25
  158. data/lib/active_record/internal_metadata.rb +30 -24
  159. data/lib/active_record/legacy_yaml_adapter.rb +11 -5
  160. data/lib/active_record/locking/optimistic.rb +81 -85
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +68 -31
  163. data/lib/active_record/middleware/database_selector.rb +77 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  166. data/lib/active_record/migration.rb +439 -342
  167. data/lib/active_record/migration/command_recorder.rb +152 -98
  168. data/lib/active_record/migration/compatibility.rb +229 -60
  169. data/lib/active_record/migration/join_table.rb +8 -7
  170. data/lib/active_record/model_schema.rb +230 -122
  171. data/lib/active_record/nested_attributes.rb +213 -203
  172. data/lib/active_record/no_touching.rb +11 -2
  173. data/lib/active_record/null_relation.rb +12 -34
  174. data/lib/active_record/persistence.rb +471 -97
  175. data/lib/active_record/query_cache.rb +23 -12
  176. data/lib/active_record/querying.rb +43 -25
  177. data/lib/active_record/railtie.rb +155 -43
  178. data/lib/active_record/railties/console_sandbox.rb +2 -0
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +507 -195
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +245 -269
  183. data/lib/active_record/relation.rb +475 -324
  184. data/lib/active_record/relation/batches.rb +125 -72
  185. data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
  186. data/lib/active_record/relation/calculations.rb +267 -171
  187. data/lib/active_record/relation/delegation.rb +73 -69
  188. data/lib/active_record/relation/finder_methods.rb +238 -248
  189. data/lib/active_record/relation/from_clause.rb +7 -9
  190. data/lib/active_record/relation/merger.rb +95 -77
  191. data/lib/active_record/relation/predicate_builder.rb +109 -110
  192. data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
  193. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  194. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
  195. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
  196. data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
  197. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  198. data/lib/active_record/relation/query_attribute.rb +33 -2
  199. data/lib/active_record/relation/query_methods.rb +654 -374
  200. data/lib/active_record/relation/record_fetch_warning.rb +8 -6
  201. data/lib/active_record/relation/spawn_methods.rb +15 -14
  202. data/lib/active_record/relation/where_clause.rb +171 -109
  203. data/lib/active_record/result.rb +88 -51
  204. data/lib/active_record/runtime_registry.rb +5 -3
  205. data/lib/active_record/sanitization.rb +73 -100
  206. data/lib/active_record/schema.rb +7 -14
  207. data/lib/active_record/schema_dumper.rb +101 -69
  208. data/lib/active_record/schema_migration.rb +16 -12
  209. data/lib/active_record/scoping.rb +20 -20
  210. data/lib/active_record/scoping/default.rb +92 -95
  211. data/lib/active_record/scoping/named.rb +39 -30
  212. data/lib/active_record/secure_token.rb +19 -9
  213. data/lib/active_record/serialization.rb +7 -3
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +80 -29
  216. data/lib/active_record/store.rb +122 -42
  217. data/lib/active_record/suppressor.rb +6 -3
  218. data/lib/active_record/table_metadata.rb +51 -39
  219. data/lib/active_record/tasks/database_tasks.rb +332 -115
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +246 -0
  225. data/lib/active_record/timestamp.rb +70 -38
  226. data/lib/active_record/touch_later.rb +26 -24
  227. data/lib/active_record/transactions.rb +121 -184
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type.rb +29 -17
  230. data/lib/active_record/type/adapter_specific_registry.rb +44 -48
  231. data/lib/active_record/type/date.rb +2 -0
  232. data/lib/active_record/type/date_time.rb +2 -0
  233. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  234. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  235. data/lib/active_record/type/internal/timezone.rb +2 -0
  236. data/lib/active_record/type/json.rb +30 -0
  237. data/lib/active_record/type/serialized.rb +20 -9
  238. data/lib/active_record/type/text.rb +11 -0
  239. data/lib/active_record/type/time.rb +12 -1
  240. data/lib/active_record/type/type_map.rb +14 -17
  241. data/lib/active_record/type/unsigned_integer.rb +16 -0
  242. data/lib/active_record/type_caster.rb +4 -2
  243. data/lib/active_record/type_caster/connection.rb +17 -13
  244. data/lib/active_record/type_caster/map.rb +10 -6
  245. data/lib/active_record/validations.rb +8 -5
  246. data/lib/active_record/validations/absence.rb +2 -0
  247. data/lib/active_record/validations/associated.rb +4 -3
  248. data/lib/active_record/validations/length.rb +2 -0
  249. data/lib/active_record/validations/numericality.rb +35 -0
  250. data/lib/active_record/validations/presence.rb +4 -2
  251. data/lib/active_record/validations/uniqueness.rb +52 -45
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/arel.rb +54 -0
  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.rb +70 -0
  269. data/lib/arel/nodes/and.rb +32 -0
  270. data/lib/arel/nodes/ascending.rb +23 -0
  271. data/lib/arel/nodes/binary.rb +126 -0
  272. data/lib/arel/nodes/bind_param.rb +44 -0
  273. data/lib/arel/nodes/case.rb +55 -0
  274. data/lib/arel/nodes/casted.rb +62 -0
  275. data/lib/arel/nodes/comment.rb +29 -0
  276. data/lib/arel/nodes/count.rb +12 -0
  277. data/lib/arel/nodes/delete_statement.rb +45 -0
  278. data/lib/arel/nodes/descending.rb +23 -0
  279. data/lib/arel/nodes/equality.rb +15 -0
  280. data/lib/arel/nodes/extract.rb +24 -0
  281. data/lib/arel/nodes/false.rb +16 -0
  282. data/lib/arel/nodes/full_outer_join.rb +8 -0
  283. data/lib/arel/nodes/function.rb +44 -0
  284. data/lib/arel/nodes/grouping.rb +11 -0
  285. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  286. data/lib/arel/nodes/in.rb +15 -0
  287. data/lib/arel/nodes/infix_operation.rb +92 -0
  288. data/lib/arel/nodes/inner_join.rb +8 -0
  289. data/lib/arel/nodes/insert_statement.rb +37 -0
  290. data/lib/arel/nodes/join_source.rb +20 -0
  291. data/lib/arel/nodes/matches.rb +18 -0
  292. data/lib/arel/nodes/named_function.rb +23 -0
  293. data/lib/arel/nodes/node.rb +51 -0
  294. data/lib/arel/nodes/node_expression.rb +13 -0
  295. data/lib/arel/nodes/ordering.rb +27 -0
  296. data/lib/arel/nodes/outer_join.rb +8 -0
  297. data/lib/arel/nodes/over.rb +15 -0
  298. data/lib/arel/nodes/regexp.rb +16 -0
  299. data/lib/arel/nodes/right_outer_join.rb +8 -0
  300. data/lib/arel/nodes/select_core.rb +67 -0
  301. data/lib/arel/nodes/select_statement.rb +41 -0
  302. data/lib/arel/nodes/sql_literal.rb +19 -0
  303. data/lib/arel/nodes/string_join.rb +11 -0
  304. data/lib/arel/nodes/table_alias.rb +31 -0
  305. data/lib/arel/nodes/terminal.rb +16 -0
  306. data/lib/arel/nodes/true.rb +16 -0
  307. data/lib/arel/nodes/unary.rb +44 -0
  308. data/lib/arel/nodes/unary_operation.rb +20 -0
  309. data/lib/arel/nodes/unqualified_column.rb +22 -0
  310. data/lib/arel/nodes/update_statement.rb +41 -0
  311. data/lib/arel/nodes/values_list.rb +9 -0
  312. data/lib/arel/nodes/window.rb +126 -0
  313. data/lib/arel/nodes/with.rb +11 -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.rb +13 -0
  321. data/lib/arel/visitors/dot.rb +308 -0
  322. data/lib/arel/visitors/mysql.rb +93 -0
  323. data/lib/arel/visitors/postgresql.rb +120 -0
  324. data/lib/arel/visitors/sqlite.rb +38 -0
  325. data/lib/arel/visitors/to_sql.rb +899 -0
  326. data/lib/arel/visitors/visitor.rb +45 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/rails/generators/active_record.rb +7 -5
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  331. data/lib/rails/generators/active_record/migration.rb +22 -3
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
  333. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
  335. data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
  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 → model.rb.tt} +10 -1
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. metadata +141 -57
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -20
  347. data/lib/active_record/attribute.rb +0 -213
  348. data/lib/active_record/attribute/user_provided_default.rb +0 -28
  349. data/lib/active_record/attribute_decorators.rb +0 -67
  350. data/lib/active_record/attribute_mutation_tracker.rb +0 -70
  351. data/lib/active_record/attribute_set.rb +0 -110
  352. data/lib/active_record/attribute_set/builder.rb +0 -132
  353. data/lib/active_record/collection_cache_key.rb +0 -50
  354. data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
  355. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
  356. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  359. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
  360. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
  361. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
  362. data/lib/active_record/relation/where_clause_factory.rb +0 -38
  363. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,32 +1,21 @@
1
- require 'active_record/connection_adapters/abstract_adapter'
2
- require 'active_record/connection_adapters/statement_pool'
3
- require 'active_record/connection_adapters/mysql/column'
4
- require 'active_record/connection_adapters/mysql/explain_pretty_printer'
5
- require 'active_record/connection_adapters/mysql/quoting'
6
- require 'active_record/connection_adapters/mysql/schema_creation'
7
- require 'active_record/connection_adapters/mysql/schema_definitions'
8
- require 'active_record/connection_adapters/mysql/schema_dumper'
9
- require 'active_record/connection_adapters/mysql/type_metadata'
10
-
11
- require 'active_support/core_ext/string/strip'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/abstract_adapter"
4
+ require "active_record/connection_adapters/statement_pool"
5
+ require "active_record/connection_adapters/mysql/column"
6
+ require "active_record/connection_adapters/mysql/explain_pretty_printer"
7
+ require "active_record/connection_adapters/mysql/quoting"
8
+ require "active_record/connection_adapters/mysql/schema_creation"
9
+ require "active_record/connection_adapters/mysql/schema_definitions"
10
+ require "active_record/connection_adapters/mysql/schema_dumper"
11
+ require "active_record/connection_adapters/mysql/schema_statements"
12
+ require "active_record/connection_adapters/mysql/type_metadata"
12
13
 
13
14
  module ActiveRecord
14
15
  module ConnectionAdapters
15
16
  class AbstractMysqlAdapter < AbstractAdapter
16
17
  include MySQL::Quoting
17
- include MySQL::ColumnDumper
18
-
19
- def update_table_definition(table_name, base) # :nodoc:
20
- MySQL::Table.new(table_name, base)
21
- end
22
-
23
- def schema_creation # :nodoc:
24
- MySQL::SchemaCreation.new(self)
25
- end
26
-
27
- def arel_visitor # :nodoc:
28
- Arel::Visitors::MySQL.new(self)
29
- end
18
+ include MySQL::SchemaStatements
30
19
 
31
20
  ##
32
21
  # :singleton-method:
@@ -35,82 +24,56 @@ module ActiveRecord
35
24
  # to your application.rb file:
36
25
  #
37
26
  # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
38
- class_attribute :emulate_booleans
39
- self.emulate_booleans = true
27
+ class_attribute :emulate_booleans, default: true
40
28
 
41
29
  NATIVE_DATABASE_TYPES = {
42
- primary_key: "int auto_increment PRIMARY KEY",
30
+ primary_key: "bigint auto_increment PRIMARY KEY",
43
31
  string: { name: "varchar", limit: 255 },
44
32
  text: { name: "text" },
45
33
  integer: { name: "int", limit: 4 },
46
- float: { name: "float" },
34
+ float: { name: "float", limit: 24 },
47
35
  decimal: { name: "decimal" },
48
36
  datetime: { name: "datetime" },
37
+ timestamp: { name: "timestamp" },
49
38
  time: { name: "time" },
50
39
  date: { name: "date" },
51
40
  binary: { name: "blob" },
41
+ blob: { name: "blob" },
52
42
  boolean: { name: "tinyint", limit: 1 },
53
43
  json: { name: "json" },
54
44
  }
55
45
 
56
- INDEX_TYPES = [:fulltext, :spatial]
57
- INDEX_USINGS = [:btree, :hash]
58
-
59
- class StatementPool < ConnectionAdapters::StatementPool
60
- private def dealloc(stmt)
61
- stmt[:stmt].close
62
- end
46
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
47
+ private
48
+ def dealloc(stmt)
49
+ stmt.close
50
+ end
63
51
  end
64
52
 
65
53
  def initialize(connection, logger, connection_options, config)
66
54
  super(connection, logger, config)
67
-
68
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
69
-
70
- if version < '5.0.0'
71
- raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.0."
72
- end
73
55
  end
74
56
 
75
- CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
76
-
77
- def internal_string_options_for_primary_key # :nodoc:
78
- super.tap { |options|
79
- options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
80
- }
81
- end
82
-
83
- def version #:nodoc:
84
- @version ||= Version.new(version_string)
57
+ def get_database_version #:nodoc:
58
+ full_version_string = get_full_version
59
+ version_string = version_string(full_version_string)
60
+ Version.new(version_string, full_version_string)
85
61
  end
86
62
 
87
63
  def mariadb? # :nodoc:
88
- full_version =~ /mariadb/i
89
- end
90
-
91
- # Returns true, since this connection adapter supports migrations.
92
- def supports_migrations?
93
- true
94
- end
95
-
96
- def supports_primary_key?
97
- true
64
+ /mariadb/i.match?(full_version)
98
65
  end
99
66
 
100
- def supports_bulk_alter? #:nodoc:
67
+ def supports_bulk_alter?
101
68
  true
102
69
  end
103
70
 
104
- # Returns true, since this connection adapter supports prepared statement
105
- # caching.
106
- def supports_statement_cache?
107
- true
71
+ def supports_index_sort_order?
72
+ !mariadb? && database_version >= "8.0.1"
108
73
  end
109
74
 
110
- # Technically MySQL allows to create indexes with the sort order syntax
111
- # but at the moment (5.5) it doesn't yet implement them
112
- def supports_index_sort_order?
113
- true
75
+ def supports_expression_index?
76
+ !mariadb? && database_version >= "8.0.13"
114
77
  end
115
78
 
116
79
  def supports_transaction_isolation?
@@ -129,15 +92,36 @@ module ActiveRecord
129
92
  true
130
93
  end
131
94
 
95
+ def supports_check_constraints?
96
+ if mariadb?
97
+ database_version >= "10.2.1"
98
+ else
99
+ database_version >= "8.0.16"
100
+ end
101
+ end
102
+
132
103
  def supports_views?
133
104
  true
134
105
  end
135
106
 
136
107
  def supports_datetime_with_precision?
108
+ mariadb? || database_version >= "5.6.4"
109
+ end
110
+
111
+ def supports_virtual_columns?
112
+ mariadb? || database_version >= "5.7.5"
113
+ end
114
+
115
+ # See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
116
+ def supports_optimizer_hints?
117
+ !mariadb? && database_version >= "5.7.7"
118
+ end
119
+
120
+ def supports_common_table_expressions?
137
121
  if mariadb?
138
- version >= '5.3.0'
122
+ database_version >= "10.2.1"
139
123
  else
140
- version >= '5.6.4'
124
+ database_version >= "8.0.1"
141
125
  end
142
126
  end
143
127
 
@@ -145,12 +129,20 @@ module ActiveRecord
145
129
  true
146
130
  end
147
131
 
132
+ def supports_insert_on_duplicate_skip?
133
+ true
134
+ end
135
+
136
+ def supports_insert_on_duplicate_update?
137
+ true
138
+ end
139
+
148
140
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
149
- select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
141
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
150
142
  end
151
143
 
152
144
  def release_advisory_lock(lock_name) # :nodoc:
153
- select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
145
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
154
146
  end
155
147
 
156
148
  def native_database_types
@@ -158,7 +150,12 @@ module ActiveRecord
158
150
  end
159
151
 
160
152
  def index_algorithms
161
- { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
153
+ {
154
+ default: "ALGORITHM = DEFAULT",
155
+ copy: "ALGORITHM = COPY",
156
+ inplace: "ALGORITHM = INPLACE",
157
+ instant: "ALGORITHM = INSTANT",
158
+ }
162
159
  end
163
160
 
164
161
  # HELPER METHODS ===========================================
@@ -169,10 +166,6 @@ module ActiveRecord
169
166
  raise NotImplementedError
170
167
  end
171
168
 
172
- def new_column(*args) #:nodoc:
173
- MySQL::Column.new(*args)
174
- end
175
-
176
169
  # Must return the MySQL error number from the exception, if the exception has an
177
170
  # error number.
178
171
  def error_number(exception) # :nodoc:
@@ -182,7 +175,7 @@ module ActiveRecord
182
175
  # REFERENTIAL INTEGRITY ====================================
183
176
 
184
177
  def disable_referential_integrity #:nodoc:
185
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
178
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
186
179
 
187
180
  begin
188
181
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -194,28 +187,25 @@ module ActiveRecord
194
187
 
195
188
  # CONNECTION MANAGEMENT ====================================
196
189
 
197
- # Clears the prepared statements cache.
198
- def clear_cache!
190
+ def clear_cache! # :nodoc:
199
191
  reload_type_map
200
- @statements.clear
192
+ super
201
193
  end
202
194
 
203
195
  #--
204
196
  # DATABASE STATEMENTS ======================================
205
197
  #++
206
198
 
207
- def explain(arel, binds = [])
208
- sql = "EXPLAIN #{to_sql(arel, binds)}"
209
- start = Time.now
210
- result = exec_query(sql, 'EXPLAIN', binds)
211
- elapsed = Time.now - start
212
-
213
- MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
214
- end
215
-
216
199
  # Executes the SQL statement in the context of this connection.
217
200
  def execute(sql, name = nil)
218
- log(sql, name) { @connection.query(sql) }
201
+ materialize_transactions
202
+ mark_transaction_written_if_write(sql)
203
+
204
+ log(sql, name) do
205
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
206
+ @connection.query(sql)
207
+ end
208
+ end
219
209
  end
220
210
 
221
211
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
@@ -226,7 +216,7 @@ module ActiveRecord
226
216
  end
227
217
 
228
218
  def begin_db_transaction
229
- execute "BEGIN"
219
+ execute("BEGIN", "TRANSACTION")
230
220
  end
231
221
 
232
222
  def begin_isolated_db_transaction(isolation)
@@ -235,26 +225,14 @@ module ActiveRecord
235
225
  end
236
226
 
237
227
  def commit_db_transaction #:nodoc:
238
- execute "COMMIT"
228
+ execute("COMMIT", "TRANSACTION")
239
229
  end
240
230
 
241
231
  def exec_rollback_db_transaction #:nodoc:
242
- execute "ROLLBACK"
232
+ execute("ROLLBACK", "TRANSACTION")
243
233
  end
244
234
 
245
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
246
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
247
- # these, we must use a subquery.
248
- def join_to_update(update, select, key) # :nodoc:
249
- if select.limit || select.offset || select.orders.any?
250
- super
251
- else
252
- update.table select.source
253
- update.wheres = select.constraints
254
- end
255
- end
256
-
257
- def empty_insert_statement_value
235
+ def empty_insert_statement_value(primary_key = nil)
258
236
  "VALUES ()"
259
237
  end
260
238
 
@@ -270,7 +248,7 @@ module ActiveRecord
270
248
  end
271
249
 
272
250
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
273
- # Charset defaults to utf8.
251
+ # Charset defaults to utf8mb4.
274
252
  #
275
253
  # Example:
276
254
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -278,9 +256,13 @@ module ActiveRecord
278
256
  # create_database 'matt_development', charset: :big5
279
257
  def create_database(name, options = {})
280
258
  if options[:collation]
281
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
259
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
260
+ elsif options[:charset]
261
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
262
+ elsif row_format_dynamic_by_default?
263
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
282
264
  else
283
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
265
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
284
266
  end
285
267
  end
286
268
 
@@ -293,149 +275,34 @@ module ActiveRecord
293
275
  end
294
276
 
295
277
  def current_database
296
- select_value 'SELECT DATABASE() as db'
278
+ query_value("SELECT database()", "SCHEMA")
297
279
  end
298
280
 
299
281
  # Returns the database character set.
300
282
  def charset
301
- show_variable 'character_set_database'
283
+ show_variable "character_set_database"
302
284
  end
303
285
 
304
286
  # Returns the database collation strategy.
305
287
  def collation
306
- show_variable 'collation_database'
307
- end
308
-
309
- def tables(name = nil) # :nodoc:
310
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
311
- #tables currently returns both tables and views.
312
- This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
313
- Use #data_sources instead.
314
- MSG
315
-
316
- if name
317
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
318
- Passing arguments to #tables is deprecated without replacement.
319
- MSG
320
- end
321
-
322
- data_sources
323
- end
324
-
325
- def data_sources
326
- sql = "SELECT table_name FROM information_schema.tables "
327
- sql << "WHERE table_schema = DATABASE()"
328
-
329
- select_values(sql, 'SCHEMA')
330
- end
331
-
332
- def truncate(table_name, name = nil)
333
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
334
- end
335
-
336
- def table_exists?(table_name)
337
- # Update lib/active_record/internal_metadata.rb when this gets removed
338
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
339
- #table_exists? currently checks both tables and views.
340
- This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
341
- Use #data_source_exists? instead.
342
- MSG
343
-
344
- data_source_exists?(table_name)
345
- end
346
-
347
- def data_source_exists?(table_name)
348
- return false unless table_name.present?
349
-
350
- schema, name = extract_schema_qualified_name(table_name)
351
-
352
- sql = "SELECT table_name FROM information_schema.tables "
353
- sql << "WHERE table_schema = #{schema} AND table_name = #{name}"
354
-
355
- select_values(sql, 'SCHEMA').any?
356
- end
357
-
358
- def views # :nodoc:
359
- select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
360
- end
361
-
362
- def view_exists?(view_name) # :nodoc:
363
- return false unless view_name.present?
364
-
365
- schema, name = extract_schema_qualified_name(view_name)
366
-
367
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
368
- sql << " AND table_schema = #{schema} AND table_name = #{name}"
369
-
370
- select_values(sql, 'SCHEMA').any?
371
- end
372
-
373
- # Returns an array of indexes for the given table.
374
- def indexes(table_name, name = nil) #:nodoc:
375
- indexes = []
376
- current_index = nil
377
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
378
- each_hash(result) do |row|
379
- if current_index != row[:Key_name]
380
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
381
- current_index = row[:Key_name]
382
-
383
- mysql_index_type = row[:Index_type].downcase.to_sym
384
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
385
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
386
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
387
- end
388
-
389
- indexes.last.columns << row[:Column_name]
390
- indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
391
- end
392
- end
393
-
394
- indexes
395
- end
396
-
397
- # Returns an array of +Column+ objects for the table specified by +table_name+.
398
- def columns(table_name) # :nodoc:
399
- table_name = table_name.to_s
400
- column_definitions(table_name).map do |field|
401
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
402
- if type_metadata.type == :datetime && field[:Default] =~ /\ACURRENT_TIMESTAMP(?:\(\))?\z/i
403
- default, default_function = nil, "CURRENT_TIMESTAMP"
404
- else
405
- default, default_function = field[:Default], nil
406
- end
407
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
408
- end
288
+ show_variable "collation_database"
409
289
  end
410
290
 
411
291
  def table_comment(table_name) # :nodoc:
412
- schema, name = extract_schema_qualified_name(table_name)
292
+ scope = quoted_scope(table_name)
413
293
 
414
- select_value(<<-SQL.strip_heredoc, 'SCHEMA')
294
+ query_value(<<~SQL, "SCHEMA").presence
415
295
  SELECT table_comment
416
296
  FROM information_schema.tables
417
- WHERE table_schema = #{schema}
418
- AND table_name = #{name}
297
+ WHERE table_schema = #{scope[:schema]}
298
+ AND table_name = #{scope[:name]}
419
299
  SQL
420
300
  end
421
301
 
422
- def create_table(table_name, **options) #:nodoc:
423
- super(table_name, options: 'ENGINE=InnoDB', **options)
424
- end
425
-
426
- def bulk_change_table(table_name, operations) #:nodoc:
427
- sqls = operations.flat_map do |command, args|
428
- table, arguments = args.shift, args
429
- method = :"#{command}_sql"
430
-
431
- if respond_to?(method, true)
432
- send(method, table, *arguments)
433
- else
434
- raise "Unknown method called : #{method}(#{arguments.inspect})"
435
- end
436
- end.join(", ")
437
-
438
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
302
+ def change_table_comment(table_name, comment_or_changes) # :nodoc:
303
+ comment = extract_new_comment_value(comment_or_changes)
304
+ comment = "" if comment.nil?
305
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
439
306
  end
440
307
 
441
308
  # Renames a table.
@@ -443,6 +310,8 @@ module ActiveRecord
443
310
  # Example:
444
311
  # rename_table('octopuses', 'octopi')
445
312
  def rename_table(table_name, new_name)
313
+ schema_cache.clear_data_source_cache!(table_name.to_s)
314
+ schema_cache.clear_data_source_cache!(new_name.to_s)
446
315
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
447
316
  rename_table_indexes(table_name, new_name)
448
317
  end
@@ -462,7 +331,8 @@ module ActiveRecord
462
331
  # Although this command ignores most +options+ and the block if one is given,
463
332
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
464
333
  # In that case, +options+ and the block will be used by create_table.
465
- def drop_table(table_name, options = {})
334
+ def drop_table(table_name, **options)
335
+ schema_cache.clear_data_source_cache!(table_name.to_s)
466
336
  execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
467
337
  end
468
338
 
@@ -478,33 +348,38 @@ module ActiveRecord
478
348
 
479
349
  def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
480
350
  default = extract_new_default_value(default_or_changes)
481
- column = column_for(table_name, column_name)
482
- change_column table_name, column_name, column.sql_type, :default => default
351
+ change_column table_name, column_name, nil, default: default
483
352
  end
484
353
 
485
354
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
486
- column = column_for(table_name, column_name)
487
-
488
355
  unless null || default.nil?
489
356
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
490
357
  end
491
358
 
492
- change_column table_name, column_name, column.sql_type, :null => null
359
+ change_column table_name, column_name, nil, null: null
493
360
  end
494
361
 
495
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
496
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
362
+ def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
363
+ comment = extract_new_comment_value(comment_or_changes)
364
+ change_column table_name, column_name, nil, comment: comment
365
+ end
366
+
367
+ def change_column(table_name, column_name, type, **options) #:nodoc:
368
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
497
369
  end
498
370
 
499
371
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
500
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
372
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
501
373
  rename_column_indexes(table_name, column_name, new_column_name)
502
374
  end
503
375
 
504
- def add_index(table_name, column_name, options = {}) #:nodoc:
505
- index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
506
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
507
- execute add_sql_comment!(sql, comment)
376
+ def add_index(table_name, column_name, **options) #:nodoc:
377
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
378
+
379
+ return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
380
+
381
+ create_index = CreateIndexDefinition.new(index, algorithm)
382
+ execute schema_creation.accept(create_index)
508
383
  end
509
384
 
510
385
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -515,85 +390,99 @@ module ActiveRecord
515
390
  def foreign_keys(table_name)
516
391
  raise ArgumentError unless table_name.present?
517
392
 
518
- schema, name = extract_schema_qualified_name(table_name)
393
+ scope = quoted_scope(table_name)
519
394
 
520
- fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
395
+ fk_info = exec_query(<<~SQL, "SCHEMA")
521
396
  SELECT fk.referenced_table_name AS 'to_table',
522
397
  fk.referenced_column_name AS 'primary_key',
523
398
  fk.column_name AS 'column',
524
399
  fk.constraint_name AS 'name',
525
400
  rc.update_rule AS 'on_update',
526
401
  rc.delete_rule AS 'on_delete'
527
- FROM information_schema.key_column_usage fk
528
- JOIN information_schema.referential_constraints rc
402
+ FROM information_schema.referential_constraints rc
403
+ JOIN information_schema.key_column_usage fk
529
404
  USING (constraint_schema, constraint_name)
530
405
  WHERE fk.referenced_column_name IS NOT NULL
531
- AND fk.table_schema = #{schema}
532
- AND fk.table_name = #{name}
533
- AND rc.table_name = #{name}
406
+ AND fk.table_schema = #{scope[:schema]}
407
+ AND fk.table_name = #{scope[:name]}
408
+ AND rc.constraint_schema = #{scope[:schema]}
409
+ AND rc.table_name = #{scope[:name]}
534
410
  SQL
535
411
 
536
412
  fk_info.map do |row|
537
413
  options = {
538
- column: row['column'],
539
- name: row['name'],
540
- primary_key: row['primary_key']
414
+ column: row["column"],
415
+ name: row["name"],
416
+ primary_key: row["primary_key"]
541
417
  }
542
418
 
543
- options[:on_update] = extract_foreign_key_action(row['on_update'])
544
- options[:on_delete] = extract_foreign_key_action(row['on_delete'])
419
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
420
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
545
421
 
546
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
422
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
547
423
  end
548
424
  end
549
425
 
550
- def table_options(table_name) # :nodoc:
551
- table_options = {}
426
+ def check_constraints(table_name)
427
+ if supports_check_constraints?
428
+ scope = quoted_scope(table_name)
552
429
 
430
+ chk_info = exec_query(<<~SQL, "SCHEMA")
431
+ SELECT cc.constraint_name AS 'name',
432
+ cc.check_clause AS 'expression'
433
+ FROM information_schema.check_constraints cc
434
+ JOIN information_schema.table_constraints tc
435
+ USING (constraint_schema, constraint_name)
436
+ WHERE tc.table_schema = #{scope[:schema]}
437
+ AND tc.table_name = #{scope[:name]}
438
+ AND cc.constraint_schema = #{scope[:schema]}
439
+ SQL
440
+
441
+ chk_info.map do |row|
442
+ options = {
443
+ name: row["name"]
444
+ }
445
+ expression = row["expression"]
446
+ expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
447
+ CheckConstraintDefinition.new(table_name, expression, options)
448
+ end
449
+ else
450
+ raise NotImplementedError
451
+ end
452
+ end
453
+
454
+ def table_options(table_name) # :nodoc:
553
455
  create_table_info = create_table_info(table_name)
554
456
 
555
457
  # strip create_definitions and partition_options
556
- raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
458
+ # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
459
+ raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
460
+
461
+ return if raw_table_options.empty?
462
+
463
+ table_options = {}
464
+
465
+ if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
466
+ raw_table_options = $` + $' # before part + after part
467
+ table_options[:charset] = charset
468
+ table_options[:collation] = collation if collation
469
+ end
557
470
 
558
471
  # strip AUTO_INCREMENT
559
472
  raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
560
473
 
561
- table_options[:options] = raw_table_options
562
-
563
474
  # strip COMMENT
564
- if raw_table_options.sub!(/ COMMENT='.+'/, '')
475
+ if raw_table_options.sub!(/ COMMENT='.+'/, "")
565
476
  table_options[:comment] = table_comment(table_name)
566
477
  end
567
478
 
479
+ table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
568
480
  table_options
569
481
  end
570
482
 
571
- # Maps logical Rails types to MySQL-specific data types.
572
- def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
573
- sql = case type.to_s
574
- when 'integer'
575
- integer_to_sql(limit)
576
- when 'text'
577
- text_to_sql(limit)
578
- when 'blob'
579
- binary_to_sql(limit)
580
- when 'binary'
581
- if (0..0xfff) === limit
582
- "varbinary(#{limit})"
583
- else
584
- binary_to_sql(limit)
585
- end
586
- else
587
- super(type, limit, precision, scale)
588
- end
589
-
590
- sql << ' unsigned' if unsigned && type != :primary_key
591
- sql
592
- end
593
-
594
483
  # SHOW VARIABLES LIKE 'name'
595
484
  def show_variable(name)
596
- select_value("SELECT @@#{name}", 'SCHEMA')
485
+ query_value("SELECT @@#{name}", "SCHEMA")
597
486
  rescue ActiveRecord::StatementInvalid
598
487
  nil
599
488
  end
@@ -601,21 +490,23 @@ module ActiveRecord
601
490
  def primary_keys(table_name) # :nodoc:
602
491
  raise ArgumentError unless table_name.present?
603
492
 
604
- schema, name = extract_schema_qualified_name(table_name)
493
+ scope = quoted_scope(table_name)
605
494
 
606
- select_values(<<-SQL.strip_heredoc, 'SCHEMA')
495
+ query_values(<<~SQL, "SCHEMA")
607
496
  SELECT column_name
608
- FROM information_schema.key_column_usage
609
- WHERE constraint_name = 'PRIMARY'
610
- AND table_schema = #{schema}
611
- AND table_name = #{name}
612
- ORDER BY ordinal_position
497
+ FROM information_schema.statistics
498
+ WHERE index_name = 'PRIMARY'
499
+ AND table_schema = #{scope[:schema]}
500
+ AND table_name = #{scope[:name]}
501
+ ORDER BY seq_in_index
613
502
  SQL
614
503
  end
615
504
 
616
- def case_sensitive_comparison(table, attribute, column, value)
617
- if !value.nil? && column.collation && !column.case_sensitive?
618
- table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
505
+ def case_sensitive_comparison(attribute, value) # :nodoc:
506
+ column = column_for_attribute(attribute)
507
+
508
+ if column.collation && !column.case_sensitive?
509
+ attribute.eq(Arel::Nodes::Bin.new(value))
619
510
  else
620
511
  super
621
512
  end
@@ -629,352 +520,336 @@ module ActiveRecord
629
520
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
630
521
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
631
522
  # distinct queries, and requires that the ORDER BY include the distinct column.
632
- # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
523
+ # See https://dev.mysql.com/doc/refman/en/group-by-handling.html
633
524
  def columns_for_distinct(columns, orders) # :nodoc:
634
- order_columns = orders.reject(&:blank?).map { |s|
525
+ order_columns = orders.compact_blank.map { |s|
635
526
  # Convert Arel node to string
636
- s = s.to_sql unless s.is_a?(String)
527
+ s = visitor.compile(s) unless s.is_a?(String)
637
528
  # Remove any ASC/DESC modifiers
638
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
639
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
529
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
530
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
640
531
 
641
- [super, *order_columns].join(', ')
532
+ (order_columns << super).join(", ")
642
533
  end
643
534
 
644
535
  def strict_mode?
645
536
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
646
537
  end
647
538
 
648
- def valid_type?(type)
649
- !native_database_types[type].nil?
539
+ def default_index_type?(index) # :nodoc:
540
+ index.using == :btree || super
650
541
  end
651
542
 
652
- protected
653
-
654
- def initialize_type_map(m) # :nodoc:
655
- super
656
-
657
- register_class_with_limit m, %r(char)i, MysqlString
658
-
659
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
660
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
661
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
662
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
663
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
664
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
665
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
666
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
667
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
668
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
669
- m.register_type %r(^json)i, MysqlJson.new
670
-
671
- register_integer_type m, %r(^bigint)i, limit: 8
672
- register_integer_type m, %r(^int)i, limit: 4
673
- register_integer_type m, %r(^mediumint)i, limit: 3
674
- register_integer_type m, %r(^smallint)i, limit: 2
675
- register_integer_type m, %r(^tinyint)i, limit: 1
543
+ def build_insert_sql(insert) # :nodoc:
544
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
676
545
 
677
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
678
- m.alias_type %r(year)i, 'integer'
679
- m.alias_type %r(bit)i, 'binary'
680
-
681
- m.register_type(%r(enum)i) do |sql_type|
682
- limit = sql_type[/^enum\((.+)\)/i, 1]
683
- .split(',').map{|enum| enum.strip.length - 2}.max
684
- MysqlString.new(limit: limit)
546
+ if insert.skip_duplicates?
547
+ no_op_column = quote_column_name(insert.keys.first)
548
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
549
+ elsif insert.update_duplicates?
550
+ sql << " ON DUPLICATE KEY UPDATE "
551
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
552
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
685
553
  end
686
554
 
687
- m.register_type(%r(^set)i) do |sql_type|
688
- limit = sql_type[/^set\((.+)\)/i, 1]
689
- .split(',').map{|set| set.strip.length - 1}.sum - 1
690
- MysqlString.new(limit: limit)
691
- end
555
+ sql
692
556
  end
693
557
 
694
- def register_integer_type(mapping, key, options) # :nodoc:
695
- mapping.register_type(key) do |sql_type|
696
- if /\bunsigned\b/ === sql_type
697
- Type::UnsignedInteger.new(options)
698
- else
699
- Type::Integer.new(options)
700
- end
558
+ def check_version # :nodoc:
559
+ if database_version < "5.5.8"
560
+ raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
701
561
  end
702
562
  end
703
563
 
704
- def extract_precision(sql_type)
705
- if /time/ === sql_type
706
- super || 0
707
- else
564
+ private
565
+ def initialize_type_map(m = type_map)
708
566
  super
709
- end
710
- end
711
-
712
- def fetch_type_metadata(sql_type, extra = "")
713
- MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
714
- end
715
567
 
716
- def add_index_length(quoted_columns, **options)
717
- if length = options[:length]
718
- case length
719
- when Hash
720
- length = length.symbolize_keys
721
- quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
722
- when Integer
723
- quoted_columns.each { |name, column| column << "(#{length})" }
568
+ m.register_type(%r(char)i) do |sql_type|
569
+ limit = extract_limit(sql_type)
570
+ Type.lookup(:string, adapter: :mysql2, limit: limit)
724
571
  end
725
- end
726
-
727
- quoted_columns
728
- end
729
572
 
730
- def add_options_for_index_columns(quoted_columns, **options)
731
- quoted_columns = add_index_length(quoted_columns, options)
732
- super
733
- end
734
-
735
- def translate_exception(exception, message)
736
- case error_number(exception)
737
- when 1062
738
- RecordNotUnique.new(message)
739
- when 1452
740
- InvalidForeignKey.new(message)
741
- when 1406
742
- ValueTooLong.new(message)
743
- else
744
- super
573
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
574
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
575
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
576
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
577
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
578
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
579
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
580
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
581
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
582
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
583
+
584
+ register_integer_type m, %r(^bigint)i, limit: 8
585
+ register_integer_type m, %r(^int)i, limit: 4
586
+ register_integer_type m, %r(^mediumint)i, limit: 3
587
+ register_integer_type m, %r(^smallint)i, limit: 2
588
+ register_integer_type m, %r(^tinyint)i, limit: 1
589
+
590
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
591
+ m.alias_type %r(year)i, "integer"
592
+ m.alias_type %r(bit)i, "binary"
593
+
594
+ m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
595
+ m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
596
+ end
597
+
598
+ def register_integer_type(mapping, key, **options)
599
+ mapping.register_type(key) do |sql_type|
600
+ if /\bunsigned\b/.match?(sql_type)
601
+ Type::UnsignedInteger.new(**options)
602
+ else
603
+ Type::Integer.new(**options)
604
+ end
605
+ end
745
606
  end
746
- end
747
-
748
- def add_column_sql(table_name, column_name, type, options = {})
749
- td = create_table_definition(table_name)
750
- cd = td.new_column_definition(column_name, type, options)
751
- schema_creation.accept(AddColumnDefinition.new(cd))
752
- end
753
-
754
- def change_column_sql(table_name, column_name, type, options = {})
755
- column = column_for(table_name, column_name)
756
607
 
757
- unless options_include_default?(options)
758
- options[:default] = column.default
608
+ def extract_precision(sql_type)
609
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
610
+ super || 0
611
+ else
612
+ super
613
+ end
759
614
  end
760
615
 
761
- unless options.has_key?(:null)
762
- options[:null] = column.null
616
+ # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
617
+ ER_DB_CREATE_EXISTS = 1007
618
+ ER_FILSORT_ABORT = 1028
619
+ ER_DUP_ENTRY = 1062
620
+ ER_NOT_NULL_VIOLATION = 1048
621
+ ER_NO_REFERENCED_ROW = 1216
622
+ ER_ROW_IS_REFERENCED = 1217
623
+ ER_DO_NOT_HAVE_DEFAULT = 1364
624
+ ER_ROW_IS_REFERENCED_2 = 1451
625
+ ER_NO_REFERENCED_ROW_2 = 1452
626
+ ER_DATA_TOO_LONG = 1406
627
+ ER_OUT_OF_RANGE = 1264
628
+ ER_LOCK_DEADLOCK = 1213
629
+ ER_CANNOT_ADD_FOREIGN = 1215
630
+ ER_CANNOT_CREATE_TABLE = 1005
631
+ ER_LOCK_WAIT_TIMEOUT = 1205
632
+ ER_QUERY_INTERRUPTED = 1317
633
+ ER_QUERY_TIMEOUT = 3024
634
+ ER_FK_INCOMPATIBLE_COLUMNS = 3780
635
+
636
+ def translate_exception(exception, message:, sql:, binds:)
637
+ case error_number(exception)
638
+ when nil
639
+ if exception.message.match?(/MySQL client is not connected/i)
640
+ ConnectionNotEstablished.new(exception)
641
+ else
642
+ super
643
+ end
644
+ when ER_DB_CREATE_EXISTS
645
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
646
+ when ER_DUP_ENTRY
647
+ RecordNotUnique.new(message, sql: sql, binds: binds)
648
+ when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
649
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
650
+ when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
651
+ mismatched_foreign_key(message, sql: sql, binds: binds)
652
+ when ER_CANNOT_CREATE_TABLE
653
+ if message.include?("errno: 150")
654
+ mismatched_foreign_key(message, sql: sql, binds: binds)
655
+ else
656
+ super
657
+ end
658
+ when ER_DATA_TOO_LONG
659
+ ValueTooLong.new(message, sql: sql, binds: binds)
660
+ when ER_OUT_OF_RANGE
661
+ RangeError.new(message, sql: sql, binds: binds)
662
+ when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
663
+ NotNullViolation.new(message, sql: sql, binds: binds)
664
+ when ER_LOCK_DEADLOCK
665
+ Deadlocked.new(message, sql: sql, binds: binds)
666
+ when ER_LOCK_WAIT_TIMEOUT
667
+ LockWaitTimeout.new(message, sql: sql, binds: binds)
668
+ when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
669
+ StatementTimeout.new(message, sql: sql, binds: binds)
670
+ when ER_QUERY_INTERRUPTED
671
+ QueryCanceled.new(message, sql: sql, binds: binds)
672
+ else
673
+ super
674
+ end
763
675
  end
764
676
 
765
- td = create_table_definition(table_name)
766
- cd = td.new_column_definition(column.name, type, options)
767
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
768
- end
677
+ def change_column_for_alter(table_name, column_name, type, **options)
678
+ column = column_for(table_name, column_name)
679
+ type ||= column.sql_type
769
680
 
770
- def rename_column_sql(table_name, column_name, new_column_name)
771
- column = column_for(table_name, column_name)
772
- options = {
773
- default: column.default,
774
- null: column.null,
775
- auto_increment: column.auto_increment?
776
- }
777
-
778
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
779
- td = create_table_definition(table_name)
780
- cd = td.new_column_definition(new_column_name, current_type, options)
781
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
782
- end
681
+ unless options.key?(:default)
682
+ options[:default] = column.default
683
+ end
783
684
 
784
- def remove_column_sql(table_name, column_name, type = nil, options = {})
785
- "DROP #{quote_column_name(column_name)}"
786
- end
685
+ unless options.key?(:null)
686
+ options[:null] = column.null
687
+ end
787
688
 
788
- def remove_columns_sql(table_name, *column_names)
789
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
790
- end
689
+ unless options.key?(:comment)
690
+ options[:comment] = column.comment
691
+ end
791
692
 
792
- def add_index_sql(table_name, column_name, options = {})
793
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
794
- index_algorithm[0, 0] = ", " if index_algorithm.present?
795
- "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
796
- end
693
+ td = create_table_definition(table_name)
694
+ cd = td.new_column_definition(column.name, type, **options)
695
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
696
+ end
797
697
 
798
- def remove_index_sql(table_name, options = {})
799
- index_name = index_name_for_remove(table_name, options)
800
- "DROP INDEX #{index_name}"
801
- end
698
+ def rename_column_for_alter(table_name, column_name, new_column_name)
699
+ return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
802
700
 
803
- def add_timestamps_sql(table_name, options = {})
804
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
805
- end
701
+ column = column_for(table_name, column_name)
702
+ options = {
703
+ default: column.default,
704
+ null: column.null,
705
+ auto_increment: column.auto_increment?,
706
+ comment: column.comment
707
+ }
806
708
 
807
- def remove_timestamps_sql(table_name, options = {})
808
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
809
- end
709
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
710
+ td = create_table_definition(table_name)
711
+ cd = td.new_column_definition(new_column_name, current_type, **options)
712
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
713
+ end
810
714
 
811
- private
715
+ def add_index_for_alter(table_name, column_name, **options)
716
+ index, algorithm, _ = add_index_options(table_name, column_name, **options)
717
+ algorithm = ", #{algorithm}" if algorithm
812
718
 
813
- # MySQL is too stupid to create a temporary table for use subquery, so we have
814
- # to give it some prompting in the form of a subsubquery. Ugh!
815
- def subquery_for(key, select)
816
- subsubselect = select.clone
817
- subsubselect.projections = [key]
719
+ "ADD #{schema_creation.accept(index)}#{algorithm}"
720
+ end
818
721
 
819
- # Materialize subquery by adding distinct
820
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
821
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
722
+ def remove_index_for_alter(table_name, column_name = nil, **options)
723
+ index_name = index_name_for_remove(table_name, column_name, options)
724
+ "DROP INDEX #{quote_column_name(index_name)}"
725
+ end
822
726
 
823
- subselect = Arel::SelectManager.new(select.engine)
824
- subselect.project Arel.sql(key.name)
825
- subselect.from subsubselect.as('__active_record_temp')
826
- end
727
+ def supports_rename_index?
728
+ if mariadb?
729
+ database_version >= "10.5.2"
730
+ else
731
+ database_version >= "5.7.6"
732
+ end
733
+ end
827
734
 
828
- def supports_rename_index?
829
- mariadb? ? false : version >= '5.7.6'
830
- end
735
+ def supports_rename_column?
736
+ if mariadb?
737
+ database_version >= "10.5.2"
738
+ else
739
+ database_version >= "8.0.3"
740
+ end
741
+ end
831
742
 
832
- def configure_connection
833
- variables = @config.fetch(:variables, {}).stringify_keys
743
+ def configure_connection
744
+ variables = @config.fetch(:variables, {}).stringify_keys
834
745
 
835
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
836
- variables['sql_auto_is_null'] = 0
746
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
747
+ variables["sql_auto_is_null"] = 0
837
748
 
838
- # Increase timeout so the server doesn't disconnect us.
839
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
840
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
841
- variables["wait_timeout"] = wait_timeout
749
+ # Increase timeout so the server doesn't disconnect us.
750
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
751
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
752
+ variables["wait_timeout"] = wait_timeout
842
753
 
843
- defaults = [':default', :default].to_set
754
+ defaults = [":default", :default].to_set
844
755
 
845
- # Make MySQL reject illegal values rather than truncating or blanking them, see
846
- # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
847
- # If the user has provided another value for sql_mode, don't replace it.
848
- if sql_mode = variables.delete('sql_mode')
849
- sql_mode = quote(sql_mode)
850
- elsif !defaults.include?(strict_mode?)
851
- if strict_mode?
852
- sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
853
- else
854
- sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
855
- sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
856
- sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
756
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
757
+ # https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
758
+ # If the user has provided another value for sql_mode, don't replace it.
759
+ if sql_mode = variables.delete("sql_mode")
760
+ sql_mode = quote(sql_mode)
761
+ elsif !defaults.include?(strict_mode?)
762
+ if strict_mode?
763
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
764
+ else
765
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
766
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
767
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
768
+ end
769
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
857
770
  end
858
- sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
859
- end
860
- sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
861
-
862
- # NAMES does not have an equals sign, see
863
- # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
864
- # (trailing comma because variable_assignments will always have content)
865
- if @config[:encoding]
866
- encoding = "NAMES #{@config[:encoding]}"
867
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
868
- encoding << ", "
869
- end
870
-
871
- # Gather up all of the SET variables...
872
- variable_assignments = variables.map do |k, v|
873
- if defaults.include?(v)
874
- "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
875
- elsif !v.nil?
876
- "@@SESSION.#{k} = #{quote(v)}"
771
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
772
+
773
+ # NAMES does not have an equals sign, see
774
+ # https://dev.mysql.com/doc/refman/en/set-names.html
775
+ # (trailing comma because variable_assignments will always have content)
776
+ if @config[:encoding]
777
+ encoding = +"NAMES #{@config[:encoding]}"
778
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
779
+ encoding << ", "
877
780
  end
878
- # or else nil; compact to clear nils out
879
- end.compact.join(', ')
880
781
 
881
- # ...and send them all in one query
882
- @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
883
- end
782
+ # Gather up all of the SET variables...
783
+ variable_assignments = variables.map do |k, v|
784
+ if defaults.include?(v)
785
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
786
+ elsif !v.nil?
787
+ "@@SESSION.#{k} = #{quote(v)}"
788
+ end
789
+ # or else nil; compact to clear nils out
790
+ end.compact.join(", ")
884
791
 
885
- def column_definitions(table_name) # :nodoc:
886
- execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
887
- each_hash(result)
792
+ # ...and send them all in one query
793
+ execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
888
794
  end
889
- end
890
795
 
891
- def extract_foreign_key_action(specifier) # :nodoc:
892
- case specifier
893
- when 'CASCADE'; :cascade
894
- when 'SET NULL'; :nullify
796
+ def column_definitions(table_name) # :nodoc:
797
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
798
+ each_hash(result)
799
+ end
895
800
  end
896
- end
897
-
898
- def create_table_info(table_name) # :nodoc:
899
- select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
900
- end
901
-
902
- def create_table_definition(*args) # :nodoc:
903
- MySQL::TableDefinition.new(*args)
904
- end
905
801
 
906
- def extract_schema_qualified_name(string) # :nodoc:
907
- schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/).map { |s| quote(s) }
908
- schema, name = "DATABASE()", schema unless name
909
- [schema, name]
910
- end
911
-
912
- def integer_to_sql(limit) # :nodoc:
913
- case limit
914
- when 1; 'tinyint'
915
- when 2; 'smallint'
916
- when 3; 'mediumint'
917
- when nil, 4; 'int'
918
- when 5..8; 'bigint'
919
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
802
+ def create_table_info(table_name) # :nodoc:
803
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
920
804
  end
921
- end
922
805
 
923
- def text_to_sql(limit) # :nodoc:
924
- case limit
925
- when 0..0xff; 'tinytext'
926
- when nil, 0x100..0xffff; 'text'
927
- when 0x10000..0xffffff; 'mediumtext'
928
- when 0x1000000..0xffffffff; 'longtext'
929
- else raise(ActiveRecordError, "No text type has byte length #{limit}")
806
+ def arel_visitor
807
+ Arel::Visitors::MySQL.new(self)
930
808
  end
931
- end
932
809
 
933
- def binary_to_sql(limit) # :nodoc:
934
- case limit
935
- when 0..0xff; 'tinyblob'
936
- when nil, 0x100..0xffff; 'blob'
937
- when 0x10000..0xffffff; 'mediumblob'
938
- when 0x1000000..0xffffffff; 'longblob'
939
- else raise(ActiveRecordError, "No binary type has byte length #{limit}")
810
+ def build_statement_pool
811
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
940
812
  end
941
- end
942
813
 
943
- def version_string
944
- full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
945
- end
814
+ def mismatched_foreign_key(message, sql:, binds:)
815
+ match = %r/
816
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
817
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
818
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
819
+ /xmi.match(sql)
946
820
 
947
- class MysqlJson < Type::Internal::AbstractJson # :nodoc:
948
- def changed_in_place?(raw_old_value, new_value)
949
- # Normalization is required because MySQL JSON data format includes
950
- # the space between the elements.
951
- super(serialize(deserialize(raw_old_value)), new_value)
952
- end
953
- end
821
+ options = {
822
+ message: message,
823
+ sql: sql,
824
+ binds: binds,
825
+ }
954
826
 
955
- class MysqlString < Type::String # :nodoc:
956
- def serialize(value)
957
- case value
958
- when true then MySQL::Quoting::QUOTED_TRUE
959
- when false then MySQL::Quoting::QUOTED_FALSE
960
- else super
827
+ if match
828
+ options[:table] = match[:table]
829
+ options[:foreign_key] = match[:foreign_key]
830
+ options[:target_table] = match[:target_table]
831
+ options[:primary_key] = match[:primary_key]
832
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
961
833
  end
962
- end
963
834
 
964
- private
835
+ MismatchedForeignKey.new(**options)
836
+ end
965
837
 
966
- def cast_value(value)
967
- case value
968
- when true then MySQL::Quoting::QUOTED_TRUE
969
- when false then MySQL::Quoting::QUOTED_FALSE
970
- else super
971
- end
838
+ def version_string(full_version_string)
839
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
972
840
  end
973
- end
974
841
 
975
- ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
976
- ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
977
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
842
+ # Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")).
843
+ # TODO: Remove the constant alias once Rails 6.1 has released.
844
+ MysqlString = Type::String # :nodoc:
845
+
846
+ ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
847
+ Type::ImmutableString.new(true: "1", false: "0", **args)
848
+ end
849
+ ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
850
+ Type::String.new(true: "1", false: "0", **args)
851
+ end
852
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
978
853
  end
979
854
  end
980
855
  end