activerecord 4.2.8 → 6.0.0

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 (372) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +612 -1583
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +13 -12
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record.rb +41 -22
  8. data/lib/active_record/aggregations.rb +267 -251
  9. data/lib/active_record/association_relation.rb +11 -6
  10. data/lib/active_record/associations.rb +1737 -1597
  11. data/lib/active_record/associations/alias_tracker.rb +29 -35
  12. data/lib/active_record/associations/association.rb +125 -58
  13. data/lib/active_record/associations/association_scope.rb +103 -132
  14. data/lib/active_record/associations/belongs_to_association.rb +65 -60
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -40
  17. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  18. data/lib/active_record/associations/builder/collection_association.rb +10 -33
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +52 -66
  20. data/lib/active_record/associations/builder/has_many.rb +8 -4
  21. data/lib/active_record/associations/builder/has_one.rb +46 -5
  22. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  23. data/lib/active_record/associations/collection_association.rb +134 -286
  24. data/lib/active_record/associations/collection_proxy.rb +241 -146
  25. data/lib/active_record/associations/foreign_association.rb +10 -1
  26. data/lib/active_record/associations/has_many_association.rb +34 -97
  27. data/lib/active_record/associations/has_many_through_association.rb +60 -87
  28. data/lib/active_record/associations/has_one_association.rb +61 -49
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +137 -167
  31. data/lib/active_record/associations/join_dependency/join_association.rb +38 -88
  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 +90 -92
  35. data/lib/active_record/associations/preloader/association.rb +90 -123
  36. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  37. data/lib/active_record/associations/singular_association.rb +18 -39
  38. data/lib/active_record/associations/through_association.rb +38 -18
  39. data/lib/active_record/attribute_assignment.rb +56 -183
  40. data/lib/active_record/attribute_decorators.rb +39 -15
  41. data/lib/active_record/attribute_methods.rb +120 -135
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -8
  43. data/lib/active_record/attribute_methods/dirty.rb +174 -144
  44. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  45. data/lib/active_record/attribute_methods/query.rb +6 -5
  46. data/lib/active_record/attribute_methods/read.rb +20 -76
  47. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  49. data/lib/active_record/attribute_methods/write.rb +32 -54
  50. data/lib/active_record/attributes.rb +214 -82
  51. data/lib/active_record/autosave_association.rb +91 -37
  52. data/lib/active_record/base.rb +57 -45
  53. data/lib/active_record/callbacks.rb +100 -74
  54. data/lib/active_record/coders/json.rb +3 -1
  55. data/lib/active_record/coders/yaml_column.rb +24 -12
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -296
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -115
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +356 -227
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +664 -244
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -83
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +460 -204
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +510 -627
  69. data/lib/active_record/connection_adapters/column.rb +56 -43
  70. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -188
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -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 +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  100. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +470 -290
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +551 -356
  117. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -345
  127. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  128. data/lib/active_record/connection_handling.rb +176 -41
  129. data/lib/active_record/core.rb +251 -231
  130. data/lib/active_record/counter_cache.rb +67 -49
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +87 -105
  137. data/lib/active_record/enum.rb +163 -86
  138. data/lib/active_record/errors.rb +188 -53
  139. data/lib/active_record/explain.rb +23 -11
  140. data/lib/active_record/explain_registry.rb +4 -2
  141. data/lib/active_record/explain_subscriber.rb +10 -5
  142. data/lib/active_record/fixture_set/file.rb +35 -9
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +228 -499
  148. data/lib/active_record/gem_version.rb +5 -3
  149. data/lib/active_record/inheritance.rb +158 -112
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +123 -29
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  154. data/lib/active_record/locale/en.yml +3 -2
  155. data/lib/active_record/locking/optimistic.rb +87 -96
  156. data/lib/active_record/locking/pessimistic.rb +18 -6
  157. data/lib/active_record/log_subscriber.rb +76 -33
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +626 -283
  162. data/lib/active_record/migration/command_recorder.rb +177 -90
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +8 -6
  165. data/lib/active_record/model_schema.rb +314 -112
  166. data/lib/active_record/nested_attributes.rb +264 -222
  167. data/lib/active_record/no_touching.rb +14 -1
  168. data/lib/active_record/null_relation.rb +24 -37
  169. data/lib/active_record/persistence.rb +557 -125
  170. data/lib/active_record/query_cache.rb +19 -23
  171. data/lib/active_record/querying.rb +43 -29
  172. data/lib/active_record/railtie.rb +147 -46
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +2 -0
  175. data/lib/active_record/railties/controller_runtime.rb +34 -33
  176. data/lib/active_record/railties/databases.rake +330 -197
  177. data/lib/active_record/readonly_attributes.rb +5 -4
  178. data/lib/active_record/reflection.rb +428 -279
  179. data/lib/active_record/relation.rb +518 -341
  180. data/lib/active_record/relation/batches.rb +207 -55
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +267 -253
  183. data/lib/active_record/relation/delegation.rb +70 -80
  184. data/lib/active_record/relation/finder_methods.rb +277 -241
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +78 -87
  187. data/lib/active_record/relation/predicate_builder.rb +114 -119
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +575 -394
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +11 -13
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +79 -42
  202. data/lib/active_record/runtime_registry.rb +6 -4
  203. data/lib/active_record/sanitization.rb +144 -121
  204. data/lib/active_record/schema.rb +21 -24
  205. data/lib/active_record/schema_dumper.rb +112 -93
  206. data/lib/active_record/schema_migration.rb +24 -17
  207. data/lib/active_record/scoping.rb +45 -26
  208. data/lib/active_record/scoping/default.rb +101 -85
  209. data/lib/active_record/scoping/named.rb +86 -33
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +5 -5
  212. data/lib/active_record/statement_cache.rb +73 -36
  213. data/lib/active_record/store.rb +127 -42
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +308 -99
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +55 -99
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -41
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +224 -0
  222. data/lib/active_record/timestamp.rb +86 -40
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +216 -150
  225. data/lib/active_record/translation.rb +3 -1
  226. data/lib/active_record/type.rb +78 -23
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +4 -45
  229. data/lib/active_record/type/date_time.rb +4 -49
  230. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  231. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +24 -15
  235. data/lib/active_record/type/text.rb +2 -2
  236. data/lib/active_record/type/time.rb +11 -16
  237. data/lib/active_record/type/type_map.rb +15 -17
  238. data/lib/active_record/type/unsigned_integer.rb +9 -7
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +39 -35
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +13 -4
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +14 -13
  247. data/lib/active_record/validations/uniqueness.rb +42 -55
  248. data/lib/active_record/version.rb +3 -1
  249. data/lib/arel.rb +51 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +7 -5
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +31 -1
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
  337. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  340. metadata +164 -60
  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_set.rb +0 -81
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  352. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  353. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  354. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  355. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  356. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  359. data/lib/active_record/type/big_integer.rb +0 -13
  360. data/lib/active_record/type/binary.rb +0 -50
  361. data/lib/active_record/type/boolean.rb +0 -31
  362. data/lib/active_record/type/decimal.rb +0 -58
  363. data/lib/active_record/type/decorator.rb +0 -14
  364. data/lib/active_record/type/float.rb +0 -19
  365. data/lib/active_record/type/integer.rb +0 -59
  366. data/lib/active_record/type/mutable.rb +0 -16
  367. data/lib/active_record/type/numeric.rb +0 -36
  368. data/lib/active_record/type/string.rb +0 -40
  369. data/lib/active_record/type/time_value.rb +0 -38
  370. data/lib/active_record/type/value.rb +0 -110
  371. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  372. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,209 +1,88 @@
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
+
49
+ def dealloc(stmt)
50
+ stmt.close
51
+ end
52
+ end
166
53
 
167
- # FIXME: Make the first parameter more similar for the two adapters
168
54
  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 = {}, {}
55
+ super(connection, logger, config)
56
+ end
172
57
 
173
- @visitor = Arel::Visitors::MySQL.new self
58
+ def get_database_version #:nodoc:
59
+ full_version_string = get_full_version
60
+ version_string = version_string(full_version_string)
61
+ Version.new(version_string, full_version_string)
62
+ end
174
63
 
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
64
+ def mariadb? # :nodoc:
65
+ /mariadb/i.match?(full_version)
180
66
  end
181
67
 
182
- # Returns true, since this connection adapter supports migrations.
183
- def supports_migrations?
68
+ def supports_bulk_alter?
184
69
  true
185
70
  end
186
71
 
187
- def supports_primary_key?
188
- true
72
+ def supports_index_sort_order?
73
+ !mariadb? && database_version >= "8.0.1"
189
74
  end
190
75
 
191
- def supports_bulk_alter? #:nodoc:
192
- true
76
+ def supports_expression_index?
77
+ !mariadb? && database_version >= "8.0.13"
193
78
  end
194
79
 
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?
80
+ def supports_transaction_isolation?
198
81
  true
199
82
  end
200
83
 
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'
84
+ def supports_explain?
85
+ true
207
86
  end
208
87
 
209
88
  def supports_indexes_in_create?
@@ -215,77 +94,68 @@ module ActiveRecord
215
94
  end
216
95
 
217
96
  def supports_views?
218
- version >= '5.0.0'
97
+ true
219
98
  end
220
99
 
221
100
  def supports_datetime_with_precision?
222
- version >= '5.6.4'
101
+ mariadb? || database_version >= "5.6.4"
223
102
  end
224
103
 
225
- def native_database_types
226
- NATIVE_DATABASE_TYPES
104
+ def supports_virtual_columns?
105
+ mariadb? || database_version >= "5.7.5"
227
106
  end
228
107
 
229
- def index_algorithms
230
- { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
108
+ # See https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html for more details.
109
+ def supports_optimizer_hints?
110
+ !mariadb? && database_version >= "5.7.7"
231
111
  end
232
112
 
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
113
+ def supports_advisory_locks?
114
+ true
239
115
  end
240
116
 
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)
117
+ def supports_insert_on_duplicate_skip?
118
+ true
243
119
  end
244
120
 
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
121
+ def supports_insert_on_duplicate_update?
122
+ true
249
123
  end
250
124
 
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
125
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
126
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
259
127
  end
260
128
 
261
- def quote_column_name(name) #:nodoc:
262
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
129
+ def release_advisory_lock(lock_name) # :nodoc:
130
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
263
131
  end
264
132
 
265
- def quote_table_name(name) #:nodoc:
266
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
133
+ def native_database_types
134
+ NATIVE_DATABASE_TYPES
267
135
  end
268
136
 
269
- def quoted_true
270
- QUOTED_TRUE
137
+ def index_algorithms
138
+ { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
271
139
  end
272
140
 
273
- def unquoted_true
274
- 1
275
- end
141
+ # HELPER METHODS ===========================================
276
142
 
277
- def quoted_false
278
- QUOTED_FALSE
143
+ # The two drivers have slightly different ways of yielding hashes of results, so
144
+ # this method must be implemented to provide a uniform interface.
145
+ def each_hash(result) # :nodoc:
146
+ raise NotImplementedError
279
147
  end
280
148
 
281
- def unquoted_false
282
- 0
149
+ # Must return the MySQL error number from the exception, if the exception has an
150
+ # error number.
151
+ def error_number(exception) # :nodoc:
152
+ raise NotImplementedError
283
153
  end
284
154
 
285
155
  # REFERENTIAL INTEGRITY ====================================
286
156
 
287
157
  def disable_referential_integrity #:nodoc:
288
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
158
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
289
159
 
290
160
  begin
291
161
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -295,30 +165,42 @@ module ActiveRecord
295
165
  end
296
166
  end
297
167
 
168
+ # CONNECTION MANAGEMENT ====================================
169
+
170
+ def clear_cache! # :nodoc:
171
+ reload_type_map
172
+ super
173
+ end
174
+
298
175
  #--
299
176
  # DATABASE STATEMENTS ======================================
300
177
  #++
301
178
 
302
- def clear_cache!
303
- super
304
- reload_type_map
179
+ def explain(arel, binds = [])
180
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
181
+ start = Concurrent.monotonic_time
182
+ result = exec_query(sql, "EXPLAIN", binds)
183
+ elapsed = Concurrent.monotonic_time - start
184
+
185
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
305
186
  end
306
187
 
307
188
  # Executes the SQL statement in the context of this connection.
308
189
  def execute(sql, name = nil)
309
- log(sql, name) { @connection.query(sql) }
310
- end
190
+ materialize_transactions
311
191
 
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)
192
+ log(sql, name) do
193
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
194
+ @connection.query(sql)
195
+ end
196
+ end
317
197
  end
318
198
 
319
- def update_sql(sql, name = nil) #:nodoc:
320
- super
321
- @connection.affected_rows
199
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
200
+ # to write stuff in an abstract way without concerning ourselves about whether it
201
+ # needs to be explicitly freed or not.
202
+ def execute_and_free(sql, name = nil) # :nodoc:
203
+ yield execute(sql, name)
322
204
  end
323
205
 
324
206
  def begin_db_transaction
@@ -338,19 +220,7 @@ module ActiveRecord
338
220
  execute "ROLLBACK"
339
221
  end
340
222
 
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
351
- end
352
-
353
- def empty_insert_statement_value
223
+ def empty_insert_statement_value(primary_key = nil)
354
224
  "VALUES ()"
355
225
  end
356
226
 
@@ -366,7 +236,7 @@ module ActiveRecord
366
236
  end
367
237
 
368
238
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
369
- # Charset defaults to utf8.
239
+ # Charset defaults to utf8mb4.
370
240
  #
371
241
  # Example:
372
242
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -374,9 +244,13 @@ module ActiveRecord
374
244
  # create_database 'matt_development', charset: :big5
375
245
  def create_database(name, options = {})
376
246
  if options[:collation]
377
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
247
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
248
+ elsif options[:charset]
249
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
250
+ elsif row_format_dynamic_by_default?
251
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
378
252
  else
379
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
253
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
380
254
  end
381
255
  end
382
256
 
@@ -385,108 +259,38 @@ module ActiveRecord
385
259
  # Example:
386
260
  # drop_database('sebastian_development')
387
261
  def drop_database(name) #:nodoc:
388
- execute "DROP DATABASE IF EXISTS `#{name}`"
262
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
389
263
  end
390
264
 
391
265
  def current_database
392
- select_value 'SELECT DATABASE() as db'
266
+ query_value("SELECT database()", "SCHEMA")
393
267
  end
394
268
 
395
269
  # Returns the database character set.
396
270
  def charset
397
- show_variable 'character_set_database'
271
+ show_variable "character_set_database"
398
272
  end
399
273
 
400
274
  # Returns the database collation strategy.
401
275
  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
418
- end
419
-
420
- def table_exists?(name)
421
- return false unless name.present?
422
- return true if tables(nil, nil, name).any?
423
-
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
276
+ show_variable "collation_database"
458
277
  end
459
278
 
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
279
+ def table_comment(table_name) # :nodoc:
280
+ scope = quoted_scope(table_name)
472
281
 
473
- def create_table(table_name, options = {}) #:nodoc:
474
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
282
+ query_value(<<~SQL, "SCHEMA").presence
283
+ SELECT table_comment
284
+ FROM information_schema.tables
285
+ WHERE table_schema = #{scope[:schema]}
286
+ AND table_name = #{scope[:name]}
287
+ SQL
475
288
  end
476
289
 
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}")
290
+ def change_table_comment(table_name, comment_or_changes) # :nodoc:
291
+ comment = extract_new_comment_value(comment_or_changes)
292
+ comment = "" if comment.nil?
293
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
490
294
  end
491
295
 
492
296
  # Renames a table.
@@ -498,8 +302,23 @@ module ActiveRecord
498
302
  rename_table_indexes(table_name, new_name)
499
303
  end
500
304
 
305
+ # Drops a table from the database.
306
+ #
307
+ # [<tt>:force</tt>]
308
+ # Set to +:cascade+ to drop dependent objects as well.
309
+ # Defaults to false.
310
+ # [<tt>:if_exists</tt>]
311
+ # Set to +true+ to only drop the table if it exists.
312
+ # Defaults to false.
313
+ # [<tt>:temporary</tt>]
314
+ # Set to +true+ to drop temporary table.
315
+ # Defaults to false.
316
+ #
317
+ # Although this command ignores most +options+ and the block if one is given,
318
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
319
+ # In that case, +options+ and the block will be used by create_table.
501
320
  def drop_table(table_name, options = {})
502
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
321
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
503
322
  end
504
323
 
505
324
  def rename_index(table_name, old_name, new_name)
@@ -512,150 +331,153 @@ module ActiveRecord
512
331
  end
513
332
  end
514
333
 
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
334
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
335
+ default = extract_new_default_value(default_or_changes)
336
+ change_column table_name, column_name, nil, default: default
518
337
  end
519
338
 
520
- def change_column_null(table_name, column_name, null, default = nil)
521
- column = column_for(table_name, column_name)
522
-
339
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
523
340
  unless null || default.nil?
524
341
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
525
342
  end
526
343
 
527
- change_column table_name, column_name, column.sql_type, :null => null
344
+ change_column table_name, column_name, nil, null: null
345
+ end
346
+
347
+ def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
348
+ comment = extract_new_comment_value(comment_or_changes)
349
+ change_column table_name, column_name, nil, comment: comment
528
350
  end
529
351
 
530
352
  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)}")
353
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
532
354
  end
533
355
 
534
356
  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)}")
357
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
536
358
  rename_column_indexes(table_name, column_name, new_column_name)
537
359
  end
538
360
 
539
361
  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}"
362
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
363
+ sql = +"CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
364
+ execute add_sql_comment!(sql, comment)
365
+ end
366
+
367
+ def add_sql_comment!(sql, comment) # :nodoc:
368
+ sql << " COMMENT #{quote(comment)}" if comment.present?
369
+ sql
542
370
  end
543
371
 
544
372
  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}'
373
+ raise ArgumentError unless table_name.present?
374
+
375
+ scope = quoted_scope(table_name)
376
+
377
+ fk_info = exec_query(<<~SQL, "SCHEMA")
378
+ SELECT fk.referenced_table_name AS 'to_table',
379
+ fk.referenced_column_name AS 'primary_key',
380
+ fk.column_name AS 'column',
381
+ fk.constraint_name AS 'name',
382
+ rc.update_rule AS 'on_update',
383
+ rc.delete_rule AS 'on_delete'
384
+ FROM information_schema.referential_constraints rc
385
+ JOIN information_schema.key_column_usage fk
386
+ USING (constraint_schema, constraint_name)
387
+ WHERE fk.referenced_column_name IS NOT NULL
388
+ AND fk.table_schema = #{scope[:schema]}
389
+ AND fk.table_name = #{scope[:name]}
390
+ AND rc.constraint_schema = #{scope[:schema]}
391
+ AND rc.table_name = #{scope[:name]}
554
392
  SQL
555
393
 
556
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
557
-
558
394
  fk_info.map do |row|
559
395
  options = {
560
- column: row['column'],
561
- name: row['name'],
562
- primary_key: row['primary_key']
396
+ column: row["column"],
397
+ name: row["name"],
398
+ primary_key: row["primary_key"]
563
399
  }
564
400
 
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")
401
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
402
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
567
403
 
568
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
404
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
569
405
  end
570
406
  end
571
407
 
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
408
+ def table_options(table_name) # :nodoc:
409
+ table_options = {}
601
410
 
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.")
605
- end
606
- else
607
- super
411
+ create_table_info = create_table_info(table_name)
412
+
413
+ # strip create_definitions and partition_options
414
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
415
+
416
+ # strip AUTO_INCREMENT
417
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
418
+
419
+ table_options[:options] = raw_table_options
420
+
421
+ # strip COMMENT
422
+ if raw_table_options.sub!(/ COMMENT='.+'/, "")
423
+ table_options[:comment] = table_comment(table_name)
608
424
  end
425
+
426
+ table_options
609
427
  end
610
428
 
611
429
  # SHOW VARIABLES LIKE 'name'
612
430
  def show_variable(name)
613
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
614
- variables.first['Value'] unless variables.empty?
431
+ query_value("SELECT @@#{name}", "SCHEMA")
615
432
  rescue ActiveRecord::StatementInvalid
616
433
  nil
617
434
  end
618
435
 
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
436
+ def primary_keys(table_name) # :nodoc:
437
+ raise ArgumentError unless table_name.present?
631
438
 
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
439
+ scope = quoted_scope(table_name)
637
440
 
638
- def case_sensitive_modifier(node, table_attribute)
639
- node = Arel::Nodes.build_quoted node, table_attribute
640
- Arel::Nodes::Bin.new(node)
441
+ query_values(<<~SQL, "SCHEMA")
442
+ SELECT column_name
443
+ FROM information_schema.key_column_usage
444
+ WHERE constraint_name = 'PRIMARY'
445
+ AND table_schema = #{scope[:schema]}
446
+ AND table_name = #{scope[:name]}
447
+ ORDER BY ordinal_position
448
+ SQL
641
449
  end
642
450
 
643
- def case_sensitive_comparison(table, attribute, column, value)
644
- if column.case_sensitive?
645
- table[attribute].eq(value)
451
+ def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
452
+ column = column_for_attribute(attribute)
453
+
454
+ if column.collation && !column.case_sensitive? && !value.nil?
455
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
456
+ Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
457
+ To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model,
458
+ pass `case_sensitive: true` option explicitly to the uniqueness validator.
459
+ MSG
460
+ attribute.eq(Arel::Nodes::Bin.new(value))
646
461
  else
647
462
  super
648
463
  end
649
464
  end
650
465
 
651
- def case_insensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- super
466
+ def case_sensitive_comparison(attribute, value) # :nodoc:
467
+ column = column_for_attribute(attribute)
468
+
469
+ if column.collation && !column.case_sensitive?
470
+ attribute.eq(Arel::Nodes::Bin.new(value))
654
471
  else
655
- table[attribute].eq(value)
472
+ super
656
473
  end
657
474
  end
658
475
 
476
+ def can_perform_case_insensitive_comparison_for?(column)
477
+ column.case_sensitive?
478
+ end
479
+ private :can_perform_case_insensitive_comparison_for?
480
+
659
481
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
660
482
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
661
483
  # distinct queries, and requires that the ORDER BY include the distinct column.
@@ -665,274 +487,335 @@ module ActiveRecord
665
487
  # Convert Arel node to string
666
488
  s = s.to_sql unless s.is_a?(String)
667
489
  # Remove any ASC/DESC modifiers
668
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
490
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
669
491
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
670
492
 
671
- [super, *order_columns].join(', ')
493
+ (order_columns << super).join(", ")
672
494
  end
673
495
 
674
496
  def strict_mode?
675
497
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
676
498
  end
677
499
 
678
- def valid_type?(type)
679
- !native_database_types[type].nil?
500
+ def default_index_type?(index) # :nodoc:
501
+ index.using == :btree || super
680
502
  end
681
503
 
682
- protected
683
-
684
- def initialize_type_map(m) # :nodoc:
685
- super
504
+ def build_insert_sql(insert) # :nodoc:
505
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
686
506
 
687
- register_class_with_limit m, %r(char)i, MysqlString
688
-
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)
699
-
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
705
-
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'
710
-
711
- m.register_type(%r(datetime)i) do |sql_type|
712
- precision = extract_precision(sql_type)
713
- MysqlDateTime.new(precision: precision)
507
+ if insert.skip_duplicates?
508
+ no_op_column = quote_column_name(insert.keys.first)
509
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
510
+ elsif insert.update_duplicates?
511
+ sql << " ON DUPLICATE KEY UPDATE "
512
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
714
513
  end
715
514
 
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)
720
- end
515
+ sql
721
516
  end
722
517
 
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)
727
- else
728
- Type::Integer.new(options)
729
- end
518
+ def check_version # :nodoc:
519
+ if database_version < "5.5.8"
520
+ raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
730
521
  end
731
522
  end
732
523
 
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]
524
+ private
738
525
 
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?
526
+ def initialize_type_map(m = type_map)
527
+ super
742
528
 
743
- subselect = Arel::SelectManager.new(select.engine)
744
- subselect.project Arel.sql(key.name)
745
- subselect.from subsubselect.as('__active_record_temp')
746
- end
529
+ register_class_with_limit m, %r(char)i, MysqlString
530
+
531
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
532
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
533
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
534
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
535
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
536
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
537
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
538
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
539
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
540
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
541
+
542
+ register_integer_type m, %r(^bigint)i, limit: 8
543
+ register_integer_type m, %r(^int)i, limit: 4
544
+ register_integer_type m, %r(^mediumint)i, limit: 3
545
+ register_integer_type m, %r(^smallint)i, limit: 2
546
+ register_integer_type m, %r(^tinyint)i, limit: 1
547
+
548
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
549
+ m.alias_type %r(year)i, "integer"
550
+ m.alias_type %r(bit)i, "binary"
551
+
552
+ m.register_type(%r(enum)i) do |sql_type|
553
+ limit = sql_type[/^enum\s*\((.+)\)/i, 1]
554
+ .split(",").map { |enum| enum.strip.length - 2 }.max
555
+ MysqlString.new(limit: limit)
556
+ end
747
557
 
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})"}
558
+ m.register_type(%r(^set)i) do |sql_type|
559
+ limit = sql_type[/^set\s*\((.+)\)/i, 1]
560
+ .split(",").map { |set| set.strip.length - 1 }.sum - 1
561
+ MysqlString.new(limit: limit)
755
562
  end
756
563
  end
757
564
 
758
- return option_strings
759
- end
760
-
761
- def quoted_columns_for_index(column_names, options = {})
762
- option_strings = Hash[column_names.map {|name| [name, '']}]
565
+ def register_integer_type(mapping, key, options)
566
+ mapping.register_type(key) do |sql_type|
567
+ if /\bunsigned\b/.match?(sql_type)
568
+ Type::UnsignedInteger.new(options)
569
+ else
570
+ Type::Integer.new(options)
571
+ end
572
+ end
573
+ end
763
574
 
764
- # add index length
765
- option_strings = add_index_length(option_strings, column_names, options)
575
+ def extract_precision(sql_type)
576
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
577
+ super || 0
578
+ else
579
+ super
580
+ end
581
+ end
766
582
 
767
- # add index sort order
768
- option_strings = add_index_sort_order(option_strings, column_names, options)
583
+ # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
584
+ ER_DUP_ENTRY = 1062
585
+ ER_NOT_NULL_VIOLATION = 1048
586
+ ER_NO_REFERENCED_ROW = 1216
587
+ ER_ROW_IS_REFERENCED = 1217
588
+ ER_DO_NOT_HAVE_DEFAULT = 1364
589
+ ER_ROW_IS_REFERENCED_2 = 1451
590
+ ER_NO_REFERENCED_ROW_2 = 1452
591
+ ER_DATA_TOO_LONG = 1406
592
+ ER_OUT_OF_RANGE = 1264
593
+ ER_LOCK_DEADLOCK = 1213
594
+ ER_CANNOT_ADD_FOREIGN = 1215
595
+ ER_CANNOT_CREATE_TABLE = 1005
596
+ ER_LOCK_WAIT_TIMEOUT = 1205
597
+ ER_QUERY_INTERRUPTED = 1317
598
+ ER_QUERY_TIMEOUT = 3024
599
+ ER_FK_INCOMPATIBLE_COLUMNS = 3780
600
+
601
+ def translate_exception(exception, message:, sql:, binds:)
602
+ case error_number(exception)
603
+ when ER_DUP_ENTRY
604
+ RecordNotUnique.new(message, sql: sql, binds: binds)
605
+ when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
606
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
607
+ when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
608
+ mismatched_foreign_key(message, sql: sql, binds: binds)
609
+ when ER_CANNOT_CREATE_TABLE
610
+ if message.include?("errno: 150")
611
+ mismatched_foreign_key(message, sql: sql, binds: binds)
612
+ else
613
+ super
614
+ end
615
+ when ER_DATA_TOO_LONG
616
+ ValueTooLong.new(message, sql: sql, binds: binds)
617
+ when ER_OUT_OF_RANGE
618
+ RangeError.new(message, sql: sql, binds: binds)
619
+ when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
620
+ NotNullViolation.new(message, sql: sql, binds: binds)
621
+ when ER_LOCK_DEADLOCK
622
+ Deadlocked.new(message, sql: sql, binds: binds)
623
+ when ER_LOCK_WAIT_TIMEOUT
624
+ LockWaitTimeout.new(message, sql: sql, binds: binds)
625
+ when ER_QUERY_TIMEOUT
626
+ StatementTimeout.new(message, sql: sql, binds: binds)
627
+ when ER_QUERY_INTERRUPTED
628
+ QueryCanceled.new(message, sql: sql, binds: binds)
629
+ else
630
+ super
631
+ end
632
+ end
769
633
 
770
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
771
- end
634
+ def change_column_for_alter(table_name, column_name, type, options = {})
635
+ column = column_for(table_name, column_name)
636
+ type ||= column.sql_type
772
637
 
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
781
- end
782
- end
638
+ unless options.key?(:default)
639
+ options[:default] = column.default
640
+ end
783
641
 
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
642
+ unless options.key?(:null)
643
+ options[:null] = column.null
644
+ end
789
645
 
790
- def change_column_sql(table_name, column_name, type, options = {})
791
- column = column_for(table_name, column_name)
646
+ unless options.key?(:comment)
647
+ options[:comment] = column.comment
648
+ end
792
649
 
793
- unless options_include_default?(options)
794
- options[:default] = column.default
650
+ td = create_table_definition(table_name)
651
+ cd = td.new_column_definition(column.name, type, options)
652
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
795
653
  end
796
654
 
797
- unless options.has_key?(:null)
798
- options[:null] = column.null
655
+ def rename_column_for_alter(table_name, column_name, new_column_name)
656
+ column = column_for(table_name, column_name)
657
+ options = {
658
+ default: column.default,
659
+ null: column.null,
660
+ auto_increment: column.auto_increment?
661
+ }
662
+
663
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
664
+ td = create_table_definition(table_name)
665
+ cd = td.new_column_definition(new_column_name, current_type, options)
666
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
799
667
  end
800
668
 
801
- options[:name] = column.name
802
- schema_creation.accept ChangeColumnDefinition.new column, type, options
803
- end
669
+ def add_index_for_alter(table_name, column_name, options = {})
670
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
671
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
672
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
673
+ end
804
674
 
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
- }
675
+ def remove_index_for_alter(table_name, options = {})
676
+ index_name = index_name_for_remove(table_name, options)
677
+ "DROP INDEX #{quote_column_name(index_name)}"
678
+ end
813
679
 
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
680
+ def add_timestamps_for_alter(table_name, options = {})
681
+ options[:null] = false if options[:null].nil?
817
682
 
818
- def remove_column_sql(table_name, column_name, type = nil, options = {})
819
- "DROP #{quote_column_name(column_name)}"
820
- end
683
+ if !options.key?(:precision) && supports_datetime_with_precision?
684
+ options[:precision] = 6
685
+ end
821
686
 
822
- def remove_columns_sql(table_name, *column_names)
823
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
824
- end
687
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
688
+ end
825
689
 
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
690
+ def remove_timestamps_for_alter(table_name, options = {})
691
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
692
+ end
830
693
 
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
694
+ def supports_rename_index?
695
+ mariadb? ? false : database_version >= "5.7.6"
696
+ end
835
697
 
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
698
+ def configure_connection
699
+ variables = @config.fetch(:variables, {}).stringify_keys
839
700
 
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
701
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
702
+ variables["sql_auto_is_null"] = 0
843
703
 
844
- private
704
+ # Increase timeout so the server doesn't disconnect us.
705
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
706
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
707
+ variables["wait_timeout"] = wait_timeout
845
708
 
846
- def version
847
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
848
- end
709
+ defaults = [":default", :default].to_set
849
710
 
850
- def mariadb?
851
- full_version =~ /mariadb/i
852
- end
711
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
712
+ # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
713
+ # If the user has provided another value for sql_mode, don't replace it.
714
+ if sql_mode = variables.delete("sql_mode")
715
+ sql_mode = quote(sql_mode)
716
+ elsif !defaults.include?(strict_mode?)
717
+ if strict_mode?
718
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
719
+ else
720
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
721
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
722
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
723
+ end
724
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
725
+ end
726
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
727
+
728
+ # NAMES does not have an equals sign, see
729
+ # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
730
+ # (trailing comma because variable_assignments will always have content)
731
+ if @config[:encoding]
732
+ encoding = +"NAMES #{@config[:encoding]}"
733
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
734
+ encoding << ", "
735
+ end
853
736
 
854
- def supports_rename_index?
855
- mariadb? ? false : version >= '5.7.6'
856
- end
737
+ # Gather up all of the SET variables...
738
+ variable_assignments = variables.map do |k, v|
739
+ if defaults.include?(v)
740
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
741
+ elsif !v.nil?
742
+ "@@SESSION.#{k} = #{quote(v)}"
743
+ end
744
+ # or else nil; compact to clear nils out
745
+ end.compact.join(", ")
857
746
 
858
- def configure_connection
859
- variables = @config.fetch(:variables, {}).stringify_keys
747
+ # ...and send them all in one query
748
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
749
+ end
860
750
 
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
751
+ def column_definitions(table_name) # :nodoc:
752
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
753
+ each_hash(result)
754
+ end
755
+ end
864
756
 
865
- # Increase timeout so the server doesn't disconnect us.
866
- wait_timeout = @config[:wait_timeout]
867
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
868
- variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
757
+ def create_table_info(table_name) # :nodoc:
758
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
759
+ end
869
760
 
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' : ''
761
+ def arel_visitor
762
+ Arel::Visitors::MySQL.new(self)
875
763
  end
876
764
 
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 << ", "
765
+ def build_statement_pool
766
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
884
767
  end
885
768
 
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(', ')
769
+ def mismatched_foreign_key(message, sql:, binds:)
770
+ match = %r/
771
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
772
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
773
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
774
+ /xmi.match(sql)
895
775
 
896
- # ...and send them all in one query
897
- @connection.query "SET #{encoding} #{variable_assignments}"
898
- end
776
+ options = {
777
+ message: message,
778
+ sql: sql,
779
+ binds: binds,
780
+ }
899
781
 
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
782
+ if match
783
+ options[:table] = match[:table]
784
+ options[:foreign_key] = match[:foreign_key]
785
+ options[:target_table] = match[:target_table]
786
+ options[:primary_key] = match[:primary_key]
787
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
905
788
  end
906
- end
907
- end
908
789
 
909
- class MysqlDateTime < Type::DateTime # :nodoc:
910
- private
790
+ MismatchedForeignKey.new(options)
791
+ end
911
792
 
912
- def has_precision?
913
- precision || 0
793
+ def version_string(full_version_string)
794
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
914
795
  end
915
- end
916
796
 
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
797
+ class MysqlString < Type::String # :nodoc:
798
+ def serialize(value)
799
+ case value
800
+ when true then "1"
801
+ when false then "0"
802
+ else super
803
+ end
923
804
  end
924
- end
925
805
 
926
- private
806
+ private
927
807
 
928
- def cast_value(value)
929
- case value
930
- when true then "1"
931
- when false then "0"
932
- else super
933
- end
808
+ def cast_value(value)
809
+ case value
810
+ when true then "1"
811
+ when false then "0"
812
+ else super
813
+ end
814
+ end
934
815
  end
935
- end
816
+
817
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
818
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
936
819
  end
937
820
  end
938
821
  end