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,209 +1,87 @@
1
- require 'arel/visitors/bind_visitor'
2
- 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"
3
13
 
4
14
  module ActiveRecord
5
15
  module ConnectionAdapters
6
16
  class AbstractMysqlAdapter < AbstractAdapter
7
- include Savepoints
8
-
9
- class SchemaCreation < AbstractAdapter::SchemaCreation
10
- def visit_AddColumn(o)
11
- add_column_position!(super, column_options(o))
12
- end
13
-
14
- private
15
-
16
- def visit_DropForeignKey(name)
17
- "DROP FOREIGN KEY #{name}"
18
- end
19
-
20
- def visit_TableDefinition(o)
21
- name = o.name
22
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
23
-
24
- statements = o.columns.map { |c| accept c }
25
- statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
26
-
27
- create_sql << "(#{statements.join(', ')}) " if statements.present?
28
- create_sql << "#{o.options}"
29
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
30
- create_sql
31
- end
32
-
33
- def visit_ChangeColumnDefinition(o)
34
- column = o.column
35
- options = o.options
36
- sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
37
- change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
38
- add_column_options!(change_column_sql, options.merge(column: column))
39
- add_column_position!(change_column_sql, options)
40
- end
41
-
42
- def add_column_position!(sql, options)
43
- if options[:first]
44
- sql << " FIRST"
45
- elsif options[:after]
46
- sql << " AFTER #{quote_column_name(options[:after])}"
47
- end
48
- sql
49
- end
50
-
51
- def index_in_create(table_name, column_name, options)
52
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
53
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
54
- end
55
- end
56
-
57
- def schema_creation
58
- SchemaCreation.new self
59
- end
60
-
61
- def prepare_column_options(column, types) # :nodoc:
62
- spec = super
63
- spec.delete(:limit) if :boolean === column.type
64
- spec
65
- end
66
-
67
- class Column < ConnectionAdapters::Column # :nodoc:
68
- attr_reader :collation, :strict, :extra
69
-
70
- def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
71
- @strict = strict
72
- @collation = collation
73
- @extra = extra
74
- super(name, default, cast_type, sql_type, null)
75
- assert_valid_default(default)
76
- extract_default
77
- end
78
-
79
- def extract_default
80
- if blob_or_text_column?
81
- @default = null || strict ? nil : ''
82
- elsif missing_default_forged_as_empty_string?(@default)
83
- @default = nil
84
- end
85
- end
86
-
87
- def has_default?
88
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
89
- super
90
- end
91
-
92
- def blob_or_text_column?
93
- sql_type =~ /blob/i || type == :text
94
- end
95
-
96
- def case_sensitive?
97
- collation && !collation.match(/_ci$/)
98
- end
99
-
100
- def ==(other)
101
- super &&
102
- collation == other.collation &&
103
- strict == other.strict &&
104
- extra == other.extra
105
- end
106
-
107
- private
108
-
109
- # MySQL misreports NOT NULL column default when none is given.
110
- # We can't detect this for columns which may have a legitimate ''
111
- # default (string) but we can for others (integer, datetime, boolean,
112
- # and the rest).
113
- #
114
- # Test whether the column has default '', is not null, and is not
115
- # a type allowing default ''.
116
- def missing_default_forged_as_empty_string?(default)
117
- type != :string && !null && default == ''
118
- end
119
-
120
- def assert_valid_default(default)
121
- if blob_or_text_column? && default.present?
122
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
123
- end
124
- end
125
-
126
- def attributes_for_hash
127
- super + [collation, strict, extra]
128
- end
129
- end
17
+ include MySQL::Quoting
18
+ include MySQL::SchemaStatements
130
19
 
131
20
  ##
132
21
  # :singleton-method:
133
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
134
- # as boolean. If you wish to disable this emulation (which was the default
135
- # behavior in versions 0.13.1 and earlier) you can add the following line
22
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
23
+ # as boolean. If you wish to disable this emulation you can add the following line
136
24
  # to your application.rb file:
137
25
  #
138
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
139
- class_attribute :emulate_booleans
140
- self.emulate_booleans = true
141
-
142
- LOST_CONNECTION_ERROR_MESSAGES = [
143
- "Server shutdown in progress",
144
- "Broken pipe",
145
- "Lost connection to MySQL server during query",
146
- "MySQL server has gone away" ]
147
-
148
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
26
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
27
+ class_attribute :emulate_booleans, default: true
149
28
 
150
29
  NATIVE_DATABASE_TYPES = {
151
- :primary_key => "int(11) auto_increment PRIMARY KEY",
152
- :string => { :name => "varchar", :limit => 255 },
153
- :text => { :name => "text" },
154
- :integer => { :name => "int", :limit => 4 },
155
- :float => { :name => "float" },
156
- :decimal => { :name => "decimal" },
157
- :datetime => { :name => "datetime" },
158
- :time => { :name => "time" },
159
- :date => { :name => "date" },
160
- :binary => { :name => "blob" },
161
- :boolean => { :name => "tinyint", :limit => 1 }
30
+ primary_key: "bigint auto_increment PRIMARY KEY",
31
+ string: { name: "varchar", limit: 255 },
32
+ text: { name: "text" },
33
+ integer: { name: "int", limit: 4 },
34
+ float: { name: "float", limit: 24 },
35
+ decimal: { name: "decimal" },
36
+ datetime: { name: "datetime" },
37
+ timestamp: { name: "timestamp" },
38
+ time: { name: "time" },
39
+ date: { name: "date" },
40
+ binary: { name: "blob" },
41
+ blob: { name: "blob" },
42
+ boolean: { name: "tinyint", limit: 1 },
43
+ json: { name: "json" },
162
44
  }
163
45
 
164
- INDEX_TYPES = [:fulltext, :spatial]
165
- INDEX_USINGS = [:btree, :hash]
46
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
47
+ private
48
+ def dealloc(stmt)
49
+ stmt.close
50
+ end
51
+ end
166
52
 
167
- # FIXME: Make the first parameter more similar for the two adapters
168
53
  def initialize(connection, logger, connection_options, config)
169
- super(connection, logger)
170
- @connection_options, @config = connection_options, config
171
- @quoted_column_names, @quoted_table_names = {}, {}
54
+ super(connection, logger, config)
55
+ end
172
56
 
173
- @visitor = Arel::Visitors::MySQL.new self
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)
61
+ end
174
62
 
175
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
176
- @prepared_statements = true
177
- else
178
- @prepared_statements = false
179
- end
63
+ def mariadb? # :nodoc:
64
+ /mariadb/i.match?(full_version)
180
65
  end
181
66
 
182
- # Returns true, since this connection adapter supports migrations.
183
- def supports_migrations?
67
+ def supports_bulk_alter?
184
68
  true
185
69
  end
186
70
 
187
- def supports_primary_key?
188
- true
71
+ def supports_index_sort_order?
72
+ !mariadb? && database_version >= "8.0.1"
189
73
  end
190
74
 
191
- def supports_bulk_alter? #:nodoc:
192
- true
75
+ def supports_expression_index?
76
+ !mariadb? && database_version >= "8.0.13"
193
77
  end
194
78
 
195
- # Technically MySQL allows to create indexes with the sort order syntax
196
- # but at the moment (5.5) it doesn't yet implement them
197
- def supports_index_sort_order?
79
+ def supports_transaction_isolation?
198
80
  true
199
81
  end
200
82
 
201
- # MySQL 4 technically support transaction isolation, but it is affected by a bug
202
- # where the transaction level gets persisted for the whole session:
203
- #
204
- # http://bugs.mysql.com/bug.php?id=39170
205
- def supports_transaction_isolation?
206
- version >= '5.0.0'
83
+ def supports_explain?
84
+ true
207
85
  end
208
86
 
209
87
  def supports_indexes_in_create?
@@ -214,78 +92,90 @@ module ActiveRecord
214
92
  true
215
93
  end
216
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
+
217
103
  def supports_views?
218
- version >= '5.0.0'
104
+ true
219
105
  end
220
106
 
221
107
  def supports_datetime_with_precision?
222
- version >= '5.6.4'
108
+ mariadb? || database_version >= "5.6.4"
223
109
  end
224
110
 
225
- def native_database_types
226
- NATIVE_DATABASE_TYPES
111
+ def supports_virtual_columns?
112
+ mariadb? || database_version >= "5.7.5"
227
113
  end
228
114
 
229
- def index_algorithms
230
- { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
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"
231
118
  end
232
119
 
233
- # HELPER METHODS ===========================================
234
-
235
- # The two drivers have slightly different ways of yielding hashes of results, so
236
- # this method must be implemented to provide a uniform interface.
237
- def each_hash(result) # :nodoc:
238
- raise NotImplementedError
120
+ def supports_common_table_expressions?
121
+ if mariadb?
122
+ database_version >= "10.2.1"
123
+ else
124
+ database_version >= "8.0.1"
125
+ end
239
126
  end
240
127
 
241
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
242
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
128
+ def supports_advisory_locks?
129
+ true
243
130
  end
244
131
 
245
- # Must return the MySQL error number from the exception, if the exception has an
246
- # error number.
247
- def error_number(exception) # :nodoc:
248
- raise NotImplementedError
132
+ def supports_insert_on_duplicate_skip?
133
+ true
249
134
  end
250
135
 
251
- # QUOTING ==================================================
252
-
253
- def _quote(value) # :nodoc:
254
- if value.is_a?(Type::Binary::Data)
255
- "x'#{value.hex}'"
256
- else
257
- super
258
- end
136
+ def supports_insert_on_duplicate_update?
137
+ true
259
138
  end
260
139
 
261
- def quote_column_name(name) #:nodoc:
262
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
140
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
141
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
263
142
  end
264
143
 
265
- def quote_table_name(name) #:nodoc:
266
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
144
+ def release_advisory_lock(lock_name) # :nodoc:
145
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
267
146
  end
268
147
 
269
- def quoted_true
270
- QUOTED_TRUE
148
+ def native_database_types
149
+ NATIVE_DATABASE_TYPES
271
150
  end
272
151
 
273
- def unquoted_true
274
- 1
152
+ def index_algorithms
153
+ {
154
+ default: "ALGORITHM = DEFAULT",
155
+ copy: "ALGORITHM = COPY",
156
+ inplace: "ALGORITHM = INPLACE",
157
+ instant: "ALGORITHM = INSTANT",
158
+ }
275
159
  end
276
160
 
277
- def quoted_false
278
- QUOTED_FALSE
161
+ # HELPER METHODS ===========================================
162
+
163
+ # The two drivers have slightly different ways of yielding hashes of results, so
164
+ # this method must be implemented to provide a uniform interface.
165
+ def each_hash(result) # :nodoc:
166
+ raise NotImplementedError
279
167
  end
280
168
 
281
- def unquoted_false
282
- 0
169
+ # Must return the MySQL error number from the exception, if the exception has an
170
+ # error number.
171
+ def error_number(exception) # :nodoc:
172
+ raise NotImplementedError
283
173
  end
284
174
 
285
175
  # REFERENTIAL INTEGRITY ====================================
286
176
 
287
177
  def disable_referential_integrity #:nodoc:
288
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
178
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
289
179
 
290
180
  begin
291
181
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -295,34 +185,38 @@ module ActiveRecord
295
185
  end
296
186
  end
297
187
 
298
- #--
299
- # DATABASE STATEMENTS ======================================
300
- #++
188
+ # CONNECTION MANAGEMENT ====================================
301
189
 
302
- def clear_cache!
303
- super
190
+ def clear_cache! # :nodoc:
304
191
  reload_type_map
192
+ super
305
193
  end
306
194
 
195
+ #--
196
+ # DATABASE STATEMENTS ======================================
197
+ #++
198
+
307
199
  # Executes the SQL statement in the context of this connection.
308
200
  def execute(sql, name = nil)
309
- log(sql, name) { @connection.query(sql) }
310
- end
201
+ materialize_transactions
202
+ mark_transaction_written_if_write(sql)
311
203
 
312
- # MysqlAdapter has to free a result after using it, so we use this method to write
313
- # stuff in an abstract way without concerning ourselves about whether it needs to be
314
- # explicitly freed or not.
315
- def execute_and_free(sql, name = nil) #:nodoc:
316
- yield execute(sql, name)
204
+ log(sql, name) do
205
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
206
+ @connection.query(sql)
207
+ end
208
+ end
317
209
  end
318
210
 
319
- def update_sql(sql, name = nil) #:nodoc:
320
- super
321
- @connection.affected_rows
211
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
212
+ # to write stuff in an abstract way without concerning ourselves about whether it
213
+ # needs to be explicitly freed or not.
214
+ def execute_and_free(sql, name = nil) # :nodoc:
215
+ yield execute(sql, name)
322
216
  end
323
217
 
324
218
  def begin_db_transaction
325
- execute "BEGIN"
219
+ execute("BEGIN", "TRANSACTION")
326
220
  end
327
221
 
328
222
  def begin_isolated_db_transaction(isolation)
@@ -331,26 +225,14 @@ module ActiveRecord
331
225
  end
332
226
 
333
227
  def commit_db_transaction #:nodoc:
334
- execute "COMMIT"
228
+ execute("COMMIT", "TRANSACTION")
335
229
  end
336
230
 
337
231
  def exec_rollback_db_transaction #:nodoc:
338
- execute "ROLLBACK"
339
- end
340
-
341
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
342
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
343
- # these, we must use a subquery.
344
- def join_to_update(update, select) #:nodoc:
345
- if select.limit || select.offset || select.orders.any?
346
- super
347
- else
348
- update.table select.source
349
- update.wheres = select.constraints
350
- end
232
+ execute("ROLLBACK", "TRANSACTION")
351
233
  end
352
234
 
353
- def empty_insert_statement_value
235
+ def empty_insert_statement_value(primary_key = nil)
354
236
  "VALUES ()"
355
237
  end
356
238
 
@@ -366,7 +248,7 @@ module ActiveRecord
366
248
  end
367
249
 
368
250
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
369
- # Charset defaults to utf8.
251
+ # Charset defaults to utf8mb4.
370
252
  #
371
253
  # Example:
372
254
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -374,9 +256,13 @@ module ActiveRecord
374
256
  # create_database 'matt_development', charset: :big5
375
257
  def create_database(name, options = {})
376
258
  if options[:collation]
377
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{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`"
378
264
  else
379
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
265
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
380
266
  end
381
267
  end
382
268
 
@@ -385,108 +271,38 @@ module ActiveRecord
385
271
  # Example:
386
272
  # drop_database('sebastian_development')
387
273
  def drop_database(name) #:nodoc:
388
- execute "DROP DATABASE IF EXISTS `#{name}`"
274
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
389
275
  end
390
276
 
391
277
  def current_database
392
- select_value 'SELECT DATABASE() as db'
278
+ query_value("SELECT database()", "SCHEMA")
393
279
  end
394
280
 
395
281
  # Returns the database character set.
396
282
  def charset
397
- show_variable 'character_set_database'
283
+ show_variable "character_set_database"
398
284
  end
399
285
 
400
286
  # Returns the database collation strategy.
401
287
  def collation
402
- show_variable 'collation_database'
403
- end
404
-
405
- def tables(name = nil, database = nil, like = nil) #:nodoc:
406
- sql = "SHOW TABLES "
407
- sql << "IN #{quote_table_name(database)} " if database
408
- sql << "LIKE #{quote(like)}" if like
409
-
410
- execute_and_free(sql, 'SCHEMA') do |result|
411
- result.collect { |field| field.first }
412
- end
413
- end
414
- alias data_sources tables
415
-
416
- def truncate(table_name, name = nil)
417
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
288
+ show_variable "collation_database"
418
289
  end
419
290
 
420
- def table_exists?(name)
421
- return false unless name.present?
422
- return true if tables(nil, nil, name).any?
291
+ def table_comment(table_name) # :nodoc:
292
+ scope = quoted_scope(table_name)
423
293
 
424
- name = name.to_s
425
- schema, table = name.split('.', 2)
426
-
427
- unless table # A table was provided without a schema
428
- table = schema
429
- schema = nil
430
- end
431
-
432
- tables(nil, schema, table).any?
433
- end
434
- alias data_source_exists? table_exists?
435
-
436
- # Returns an array of indexes for the given table.
437
- def indexes(table_name, name = nil) #:nodoc:
438
- indexes = []
439
- current_index = nil
440
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
441
- each_hash(result) do |row|
442
- if current_index != row[:Key_name]
443
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
444
- current_index = row[:Key_name]
445
-
446
- mysql_index_type = row[:Index_type].downcase.to_sym
447
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
448
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
449
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
450
- end
451
-
452
- indexes.last.columns << row[:Column_name]
453
- indexes.last.lengths << row[:Sub_part]
454
- end
455
- end
456
-
457
- indexes
458
- end
459
-
460
- # Returns an array of +Column+ objects for the table specified by +table_name+.
461
- def columns(table_name)#:nodoc:
462
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
463
- execute_and_free(sql, 'SCHEMA') do |result|
464
- each_hash(result).map do |field|
465
- field_name = set_field_encoding(field[:Field])
466
- sql_type = field[:Type]
467
- cast_type = lookup_cast_type(sql_type)
468
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
469
- end
470
- end
471
- end
472
-
473
- def create_table(table_name, options = {}) #:nodoc:
474
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
294
+ query_value(<<~SQL, "SCHEMA").presence
295
+ SELECT table_comment
296
+ FROM information_schema.tables
297
+ WHERE table_schema = #{scope[:schema]}
298
+ AND table_name = #{scope[:name]}
299
+ SQL
475
300
  end
476
301
 
477
- def bulk_change_table(table_name, operations) #:nodoc:
478
- sqls = operations.flat_map do |command, args|
479
- table, arguments = args.shift, args
480
- method = :"#{command}_sql"
481
-
482
- if respond_to?(method, true)
483
- send(method, table, *arguments)
484
- else
485
- raise "Unknown method called : #{method}(#{arguments.inspect})"
486
- end
487
- end.join(", ")
488
-
489
- 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)}")
490
306
  end
491
307
 
492
308
  # Renames a table.
@@ -494,12 +310,30 @@ module ActiveRecord
494
310
  # Example:
495
311
  # rename_table('octopuses', 'octopi')
496
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)
497
315
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
498
316
  rename_table_indexes(table_name, new_name)
499
317
  end
500
318
 
501
- def drop_table(table_name, options = {})
502
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
319
+ # Drops a table from the database.
320
+ #
321
+ # [<tt>:force</tt>]
322
+ # Set to +:cascade+ to drop dependent objects as well.
323
+ # Defaults to false.
324
+ # [<tt>:if_exists</tt>]
325
+ # Set to +true+ to only drop the table if it exists.
326
+ # Defaults to false.
327
+ # [<tt>:temporary</tt>]
328
+ # Set to +true+ to drop temporary table.
329
+ # Defaults to false.
330
+ #
331
+ # Although this command ignores most +options+ and the block if one is given,
332
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
333
+ # In that case, +options+ and the block will be used by create_table.
334
+ def drop_table(table_name, **options)
335
+ schema_cache.clear_data_source_cache!(table_name.to_s)
336
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
503
337
  end
504
338
 
505
339
  def rename_index(table_name, old_name, new_name)
@@ -512,427 +346,510 @@ module ActiveRecord
512
346
  end
513
347
  end
514
348
 
515
- def change_column_default(table_name, column_name, default) #:nodoc:
516
- column = column_for(table_name, column_name)
517
- change_column table_name, column_name, column.sql_type, :default => default
349
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
350
+ default = extract_new_default_value(default_or_changes)
351
+ change_column table_name, column_name, nil, default: default
518
352
  end
519
353
 
520
- def change_column_null(table_name, column_name, null, default = nil)
521
- column = column_for(table_name, column_name)
522
-
354
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
523
355
  unless null || default.nil?
524
356
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
525
357
  end
526
358
 
527
- change_column table_name, column_name, column.sql_type, :null => null
359
+ change_column table_name, column_name, nil, null: null
528
360
  end
529
361
 
530
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
531
- 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)}")
532
369
  end
533
370
 
534
371
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
535
- 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)}")
536
373
  rename_column_indexes(table_name, column_name, new_column_name)
537
374
  end
538
375
 
539
- def add_index(table_name, column_name, options = {}) #:nodoc:
540
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
541
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
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)
383
+ end
384
+
385
+ def add_sql_comment!(sql, comment) # :nodoc:
386
+ sql << " COMMENT #{quote(comment)}" if comment.present?
387
+ sql
542
388
  end
543
389
 
544
390
  def foreign_keys(table_name)
545
- fk_info = select_all <<-SQL.strip_heredoc
546
- SELECT fk.referenced_table_name as 'to_table'
547
- ,fk.referenced_column_name as 'primary_key'
548
- ,fk.column_name as 'column'
549
- ,fk.constraint_name as 'name'
550
- FROM information_schema.key_column_usage fk
551
- WHERE fk.referenced_column_name is not null
552
- AND fk.table_schema = '#{@config[:database]}'
553
- AND fk.table_name = '#{table_name}'
391
+ raise ArgumentError unless table_name.present?
392
+
393
+ scope = quoted_scope(table_name)
394
+
395
+ fk_info = exec_query(<<~SQL, "SCHEMA")
396
+ SELECT fk.referenced_table_name AS 'to_table',
397
+ fk.referenced_column_name AS 'primary_key',
398
+ fk.column_name AS 'column',
399
+ fk.constraint_name AS 'name',
400
+ rc.update_rule AS 'on_update',
401
+ rc.delete_rule AS 'on_delete'
402
+ FROM information_schema.referential_constraints rc
403
+ JOIN information_schema.key_column_usage fk
404
+ USING (constraint_schema, constraint_name)
405
+ WHERE fk.referenced_column_name IS NOT NULL
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]}
554
410
  SQL
555
411
 
556
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
557
-
558
412
  fk_info.map do |row|
559
413
  options = {
560
- column: row['column'],
561
- name: row['name'],
562
- primary_key: row['primary_key']
414
+ column: row["column"],
415
+ name: row["name"],
416
+ primary_key: row["primary_key"]
563
417
  }
564
418
 
565
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
566
- options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
419
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
420
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
567
421
 
568
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
422
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
569
423
  end
570
424
  end
571
425
 
572
- # Maps logical Rails types to MySQL-specific data types.
573
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
574
- case type.to_s
575
- when 'binary'
576
- case limit
577
- when 0..0xfff; "varbinary(#{limit})"
578
- when nil; "blob"
579
- when 0x1000..0xffffffff; "blob(#{limit})"
580
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
581
- end
582
- when 'integer'
583
- case limit
584
- when 1; 'tinyint'
585
- when 2; 'smallint'
586
- when 3; 'mediumint'
587
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
588
- when 5..8; 'bigint'
589
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
590
- end
591
- when 'text'
592
- case limit
593
- when 0..0xff; 'tinytext'
594
- when nil, 0x100..0xffff; 'text'
595
- when 0x10000..0xffffff; 'mediumtext'
596
- when 0x1000000..0xffffffff; 'longtext'
597
- else raise(ActiveRecordError, "No text type has character length #{limit}")
598
- end
599
- when 'datetime'
600
- return super unless precision
426
+ def check_constraints(table_name)
427
+ if supports_check_constraints?
428
+ scope = quoted_scope(table_name)
601
429
 
602
- case precision
603
- when 0..6; "datetime(#{precision})"
604
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
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)
605
448
  end
606
449
  else
607
- super
450
+ raise NotImplementedError
608
451
  end
609
452
  end
610
453
 
454
+ def table_options(table_name) # :nodoc:
455
+ create_table_info = create_table_info(table_name)
456
+
457
+ # strip create_definitions and partition_options
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
470
+
471
+ # strip AUTO_INCREMENT
472
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
473
+
474
+ # strip COMMENT
475
+ if raw_table_options.sub!(/ COMMENT='.+'/, "")
476
+ table_options[:comment] = table_comment(table_name)
477
+ end
478
+
479
+ table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
480
+ table_options
481
+ end
482
+
611
483
  # SHOW VARIABLES LIKE 'name'
612
484
  def show_variable(name)
613
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
614
- variables.first['Value'] unless variables.empty?
485
+ query_value("SELECT @@#{name}", "SCHEMA")
615
486
  rescue ActiveRecord::StatementInvalid
616
487
  nil
617
488
  end
618
489
 
619
- # Returns a table's primary key and belonging sequence.
620
- def pk_and_sequence_for(table)
621
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
622
- create_table = each_hash(result).first[:"Create Table"]
623
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
624
- keys = $1.split(",").map { |key| key.delete('`"') }
625
- keys.length == 1 ? [keys.first, nil] : nil
626
- else
627
- nil
628
- end
629
- end
630
- end
490
+ def primary_keys(table_name) # :nodoc:
491
+ raise ArgumentError unless table_name.present?
631
492
 
632
- # Returns just a table's primary key
633
- def primary_key(table)
634
- pk_and_sequence = pk_and_sequence_for(table)
635
- pk_and_sequence && pk_and_sequence.first
636
- end
493
+ scope = quoted_scope(table_name)
637
494
 
638
- def case_sensitive_modifier(node, table_attribute)
639
- node = Arel::Nodes.build_quoted node, table_attribute
640
- Arel::Nodes::Bin.new(node)
495
+ query_values(<<~SQL, "SCHEMA")
496
+ SELECT column_name
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
502
+ SQL
641
503
  end
642
504
 
643
- def case_sensitive_comparison(table, attribute, column, value)
644
- if column.case_sensitive?
645
- table[attribute].eq(value)
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))
646
510
  else
647
511
  super
648
512
  end
649
513
  end
650
514
 
651
- def case_insensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- super
654
- else
655
- table[attribute].eq(value)
656
- end
515
+ def can_perform_case_insensitive_comparison_for?(column)
516
+ column.case_sensitive?
657
517
  end
518
+ private :can_perform_case_insensitive_comparison_for?
658
519
 
659
520
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
660
521
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
661
522
  # distinct queries, and requires that the ORDER BY include the distinct column.
662
- # 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
663
524
  def columns_for_distinct(columns, orders) # :nodoc:
664
- order_columns = orders.reject(&:blank?).map { |s|
525
+ order_columns = orders.compact_blank.map { |s|
665
526
  # Convert Arel node to string
666
- s = s.to_sql unless s.is_a?(String)
527
+ s = visitor.compile(s) unless s.is_a?(String)
667
528
  # Remove any ASC/DESC modifiers
668
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
669
- }.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}" }
670
531
 
671
- [super, *order_columns].join(', ')
532
+ (order_columns << super).join(", ")
672
533
  end
673
534
 
674
535
  def strict_mode?
675
536
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
676
537
  end
677
538
 
678
- def valid_type?(type)
679
- !native_database_types[type].nil?
539
+ def default_index_type?(index) # :nodoc:
540
+ index.using == :btree || super
680
541
  end
681
542
 
682
- protected
543
+ def build_insert_sql(insert) # :nodoc:
544
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
683
545
 
684
- def initialize_type_map(m) # :nodoc:
685
- super
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(",")
553
+ end
686
554
 
687
- register_class_with_limit m, %r(char)i, MysqlString
555
+ sql
556
+ end
688
557
 
689
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
690
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
691
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
692
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
693
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
694
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
695
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
696
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
697
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
698
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
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."
561
+ end
562
+ end
699
563
 
700
- register_integer_type m, %r(^bigint)i, limit: 8
701
- register_integer_type m, %r(^int)i, limit: 4
702
- register_integer_type m, %r(^mediumint)i, limit: 3
703
- register_integer_type m, %r(^smallint)i, limit: 2
704
- register_integer_type m, %r(^tinyint)i, limit: 1
564
+ private
565
+ def initialize_type_map(m = type_map)
566
+ super
705
567
 
706
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
707
- m.alias_type %r(set)i, 'varchar'
708
- m.alias_type %r(year)i, 'integer'
709
- m.alias_type %r(bit)i, 'binary'
568
+ m.register_type(%r(char)i) do |sql_type|
569
+ limit = extract_limit(sql_type)
570
+ Type.lookup(:string, adapter: :mysql2, limit: limit)
571
+ end
710
572
 
711
- m.register_type(%r(datetime)i) do |sql_type|
712
- precision = extract_precision(sql_type)
713
- MysqlDateTime.new(precision: precision)
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
714
606
  end
715
607
 
716
- m.register_type(%r(enum)i) do |sql_type|
717
- limit = sql_type[/^enum\((.+)\)/i, 1]
718
- .split(',').map{|enum| enum.strip.length - 2}.max
719
- MysqlString.new(limit: limit)
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
720
614
  end
721
- end
722
615
 
723
- def register_integer_type(mapping, key, options) # :nodoc:
724
- mapping.register_type(key) do |sql_type|
725
- if /unsigned/i =~ sql_type
726
- Type::UnsignedInteger.new(options)
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)
727
672
  else
728
- Type::Integer.new(options)
673
+ super
729
674
  end
730
675
  end
731
- end
732
676
 
733
- # MySQL is too stupid to create a temporary table for use subquery, so we have
734
- # to give it some prompting in the form of a subsubquery. Ugh!
735
- def subquery_for(key, select)
736
- subsubselect = select.clone
737
- subsubselect.projections = [key]
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
738
680
 
739
- # Materialize subquery by adding distinct
740
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
741
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
742
-
743
- subselect = Arel::SelectManager.new(select.engine)
744
- subselect.project Arel.sql(key.name)
745
- subselect.from subsubselect.as('__active_record_temp')
746
- end
747
-
748
- def add_index_length(option_strings, column_names, options = {})
749
- if options.is_a?(Hash) && length = options[:length]
750
- case length
751
- when Hash
752
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
753
- when Integer
754
- column_names.each {|name| option_strings[name] += "(#{length})"}
681
+ unless options.key?(:default)
682
+ options[:default] = column.default
755
683
  end
756
- end
757
684
 
758
- return option_strings
759
- end
685
+ unless options.key?(:null)
686
+ options[:null] = column.null
687
+ end
760
688
 
761
- def quoted_columns_for_index(column_names, options = {})
762
- option_strings = Hash[column_names.map {|name| [name, '']}]
689
+ unless options.key?(:comment)
690
+ options[:comment] = column.comment
691
+ end
763
692
 
764
- # add index length
765
- option_strings = add_index_length(option_strings, column_names, options)
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
766
697
 
767
- # add index sort order
768
- option_strings = add_index_sort_order(option_strings, column_names, options)
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?
769
700
 
770
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
771
- 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
+ }
772
708
 
773
- def translate_exception(exception, message)
774
- case error_number(exception)
775
- when 1062
776
- RecordNotUnique.new(message, exception)
777
- when 1452
778
- InvalidForeignKey.new(message, exception)
779
- else
780
- super
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))
781
713
  end
782
- end
783
714
 
784
- def add_column_sql(table_name, column_name, type, options = {})
785
- td = create_table_definition table_name, options[:temporary], options[:options]
786
- cd = td.new_column_definition(column_name, type, options)
787
- schema_creation.visit_AddColumn cd
788
- end
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
789
718
 
790
- def change_column_sql(table_name, column_name, type, options = {})
791
- column = column_for(table_name, column_name)
792
-
793
- unless options_include_default?(options)
794
- options[:default] = column.default
719
+ "ADD #{schema_creation.accept(index)}#{algorithm}"
795
720
  end
796
721
 
797
- unless options.has_key?(:null)
798
- options[:null] = column.null
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)}"
799
725
  end
800
726
 
801
- options[:name] = column.name
802
- schema_creation.accept ChangeColumnDefinition.new column, type, options
803
- end
804
-
805
- def rename_column_sql(table_name, column_name, new_column_name)
806
- column = column_for(table_name, column_name)
807
- options = {
808
- name: new_column_name,
809
- default: column.default,
810
- null: column.null,
811
- auto_increment: column.extra == "auto_increment"
812
- }
813
-
814
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
815
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
816
- end
817
-
818
- def remove_column_sql(table_name, column_name, type = nil, options = {})
819
- "DROP #{quote_column_name(column_name)}"
820
- end
821
-
822
- def remove_columns_sql(table_name, *column_names)
823
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
824
- end
825
-
826
- def add_index_sql(table_name, column_name, options = {})
827
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
828
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
829
- 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
830
734
 
831
- def remove_index_sql(table_name, options = {})
832
- index_name = index_name_for_remove(table_name, options)
833
- "DROP INDEX #{index_name}"
834
- 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
835
742
 
836
- def add_timestamps_sql(table_name, options = {})
837
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
838
- end
743
+ def configure_connection
744
+ variables = @config.fetch(:variables, {}).stringify_keys
839
745
 
840
- def remove_timestamps_sql(table_name, options = {})
841
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
842
- end
746
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
747
+ variables["sql_auto_is_null"] = 0
843
748
 
844
- private
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
845
753
 
846
- def version
847
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
848
- end
754
+ defaults = [":default", :default].to_set
849
755
 
850
- def mariadb?
851
- full_version =~ /mariadb/i
852
- end
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')"
770
+ end
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 << ", "
780
+ end
853
781
 
854
- def supports_rename_index?
855
- mariadb? ? false : version >= '5.7.6'
856
- 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(", ")
857
791
 
858
- def configure_connection
859
- variables = @config.fetch(:variables, {}).stringify_keys
792
+ # ...and send them all in one query
793
+ execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
794
+ end
860
795
 
861
- # By default, MySQL 'where id is null' selects the last inserted id.
862
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
863
- variables['sql_auto_is_null'] = 0
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
800
+ end
864
801
 
865
- # Increase timeout so the server doesn't disconnect us.
866
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
867
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
868
- variables["wait_timeout"] = wait_timeout
802
+ def create_table_info(table_name) # :nodoc:
803
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
804
+ end
869
805
 
870
- # Make MySQL reject illegal values rather than truncating or blanking them, see
871
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
872
- # If the user has provided another value for sql_mode, don't replace it.
873
- unless variables.has_key?('sql_mode')
874
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
806
+ def arel_visitor
807
+ Arel::Visitors::MySQL.new(self)
875
808
  end
876
809
 
877
- # NAMES does not have an equals sign, see
878
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
879
- # (trailing comma because variable_assignments will always have content)
880
- if @config[:encoding]
881
- encoding = "NAMES #{@config[:encoding]}"
882
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
883
- encoding << ", "
810
+ def build_statement_pool
811
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
884
812
  end
885
813
 
886
- # Gather up all of the SET variables...
887
- variable_assignments = variables.map do |k, v|
888
- if v == ':default' || v == :default
889
- "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
890
- elsif !v.nil?
891
- "@@SESSION.#{k} = #{quote(v)}"
892
- end
893
- # or else nil; compact to clear nils out
894
- end.compact.join(', ')
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)
895
820
 
896
- # ...and send them all in one query
897
- @connection.query "SET #{encoding} #{variable_assignments}"
898
- end
821
+ options = {
822
+ message: message,
823
+ sql: sql,
824
+ binds: binds,
825
+ }
899
826
 
900
- def extract_foreign_key_action(structure, name, action) # :nodoc:
901
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
902
- case $1
903
- when 'CASCADE'; :cascade
904
- when 'SET NULL'; :nullify
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])
905
833
  end
906
- end
907
- end
908
834
 
909
- class MysqlDateTime < Type::DateTime # :nodoc:
910
- private
911
-
912
- def has_precision?
913
- precision || 0
835
+ MismatchedForeignKey.new(**options)
914
836
  end
915
- end
916
837
 
917
- class MysqlString < Type::String # :nodoc:
918
- def type_cast_for_database(value)
919
- case value
920
- when true then "1"
921
- when false then "0"
922
- else super
923
- end
838
+ def version_string(full_version_string)
839
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
924
840
  end
925
841
 
926
- private
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:
927
845
 
928
- def cast_value(value)
929
- case value
930
- when true then "1"
931
- when false then "0"
932
- else super
933
- end
846
+ ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
847
+ Type::ImmutableString.new(true: "1", false: "0", **args)
934
848
  end
935
- 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)
936
853
  end
937
854
  end
938
855
  end