activerecord 4.2.11.3 → 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 +4 -4
  2. data/CHANGELOG.md +613 -1643
  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 +131 -287
  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 -86
  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 -243
  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 -635
  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 -180
  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 +6 -4
  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 +621 -303
  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 +312 -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 +143 -44
  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 +328 -185
  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 +307 -100
  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 -59
  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 -498
  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 -64
  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,85 +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
239
- end
240
-
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)
113
+ def supports_advisory_locks?
114
+ true
243
115
  end
244
116
 
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
117
+ def supports_insert_on_duplicate_skip?
118
+ true
249
119
  end
250
120
 
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
121
+ def supports_insert_on_duplicate_update?
122
+ true
259
123
  end
260
124
 
261
- def quote_column_name(name) #:nodoc:
262
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
125
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
126
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
263
127
  end
264
128
 
265
- def quote_table_name(name) #:nodoc:
266
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
129
+ def release_advisory_lock(lock_name) # :nodoc:
130
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
267
131
  end
268
132
 
269
- def quoted_true
270
- QUOTED_TRUE
133
+ def native_database_types
134
+ NATIVE_DATABASE_TYPES
271
135
  end
272
136
 
273
- def unquoted_true
274
- 1
137
+ def index_algorithms
138
+ { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
275
139
  end
276
140
 
277
- def quoted_false
278
- QUOTED_FALSE
279
- end
141
+ # HELPER METHODS ===========================================
280
142
 
281
- def unquoted_false
282
- 0
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
283
147
  end
284
148
 
285
- def quoted_date(value)
286
- if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
287
- "#{super}.#{sprintf("%06d", value.usec)}"
288
- else
289
- super
290
- end
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
291
153
  end
292
154
 
293
155
  # REFERENTIAL INTEGRITY ====================================
294
156
 
295
157
  def disable_referential_integrity #:nodoc:
296
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
158
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
297
159
 
298
160
  begin
299
161
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -303,30 +165,42 @@ module ActiveRecord
303
165
  end
304
166
  end
305
167
 
168
+ # CONNECTION MANAGEMENT ====================================
169
+
170
+ def clear_cache! # :nodoc:
171
+ reload_type_map
172
+ super
173
+ end
174
+
306
175
  #--
307
176
  # DATABASE STATEMENTS ======================================
308
177
  #++
309
178
 
310
- def clear_cache!
311
- super
312
- 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)
313
186
  end
314
187
 
315
188
  # Executes the SQL statement in the context of this connection.
316
189
  def execute(sql, name = nil)
317
- log(sql, name) { @connection.query(sql) }
318
- end
190
+ materialize_transactions
319
191
 
320
- # MysqlAdapter has to free a result after using it, so we use this method to write
321
- # stuff in an abstract way without concerning ourselves about whether it needs to be
322
- # explicitly freed or not.
323
- def execute_and_free(sql, name = nil) #:nodoc:
324
- 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
325
197
  end
326
198
 
327
- def update_sql(sql, name = nil) #:nodoc:
328
- super
329
- @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)
330
204
  end
331
205
 
332
206
  def begin_db_transaction
@@ -346,19 +220,7 @@ module ActiveRecord
346
220
  execute "ROLLBACK"
347
221
  end
348
222
 
349
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
350
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
351
- # these, we must use a subquery.
352
- def join_to_update(update, select) #:nodoc:
353
- if select.limit || select.offset || select.orders.any?
354
- super
355
- else
356
- update.table select.source
357
- update.wheres = select.constraints
358
- end
359
- end
360
-
361
- def empty_insert_statement_value
223
+ def empty_insert_statement_value(primary_key = nil)
362
224
  "VALUES ()"
363
225
  end
364
226
 
@@ -374,7 +236,7 @@ module ActiveRecord
374
236
  end
375
237
 
376
238
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
377
- # Charset defaults to utf8.
239
+ # Charset defaults to utf8mb4.
378
240
  #
379
241
  # Example:
380
242
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -382,9 +244,13 @@ module ActiveRecord
382
244
  # create_database 'matt_development', charset: :big5
383
245
  def create_database(name, options = {})
384
246
  if options[:collation]
385
- 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`"
386
252
  else
387
- 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."
388
254
  end
389
255
  end
390
256
 
@@ -393,108 +259,38 @@ module ActiveRecord
393
259
  # Example:
394
260
  # drop_database('sebastian_development')
395
261
  def drop_database(name) #:nodoc:
396
- execute "DROP DATABASE IF EXISTS `#{name}`"
262
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
397
263
  end
398
264
 
399
265
  def current_database
400
- select_value 'SELECT DATABASE() as db'
266
+ query_value("SELECT database()", "SCHEMA")
401
267
  end
402
268
 
403
269
  # Returns the database character set.
404
270
  def charset
405
- show_variable 'character_set_database'
271
+ show_variable "character_set_database"
406
272
  end
407
273
 
408
274
  # Returns the database collation strategy.
409
275
  def collation
410
- show_variable 'collation_database'
411
- end
412
-
413
- def tables(name = nil, database = nil, like = nil) #:nodoc:
414
- sql = "SHOW TABLES "
415
- sql << "IN #{quote_table_name(database)} " if database
416
- sql << "LIKE #{quote(like)}" if like
417
-
418
- execute_and_free(sql, 'SCHEMA') do |result|
419
- result.collect { |field| field.first }
420
- end
421
- end
422
- alias data_sources tables
423
-
424
- def truncate(table_name, name = nil)
425
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
276
+ show_variable "collation_database"
426
277
  end
427
278
 
428
- def table_exists?(name)
429
- return false unless name.present?
430
- return true if tables(nil, nil, name).any?
431
-
432
- name = name.to_s
433
- schema, table = name.split('.', 2)
279
+ def table_comment(table_name) # :nodoc:
280
+ scope = quoted_scope(table_name)
434
281
 
435
- unless table # A table was provided without a schema
436
- table = schema
437
- schema = nil
438
- end
439
-
440
- tables(nil, schema, table).any?
441
- end
442
- alias data_source_exists? table_exists?
443
-
444
- # Returns an array of indexes for the given table.
445
- def indexes(table_name, name = nil) #:nodoc:
446
- indexes = []
447
- current_index = nil
448
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
449
- each_hash(result) do |row|
450
- if current_index != row[:Key_name]
451
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
452
- current_index = row[:Key_name]
453
-
454
- mysql_index_type = row[:Index_type].downcase.to_sym
455
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
456
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
457
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
458
- end
459
-
460
- indexes.last.columns << row[:Column_name]
461
- indexes.last.lengths << row[:Sub_part]
462
- end
463
- end
464
-
465
- indexes
466
- end
467
-
468
- # Returns an array of +Column+ objects for the table specified by +table_name+.
469
- def columns(table_name)#:nodoc:
470
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
471
- execute_and_free(sql, 'SCHEMA') do |result|
472
- each_hash(result).map do |field|
473
- field_name = set_field_encoding(field[:Field])
474
- sql_type = field[:Type]
475
- cast_type = lookup_cast_type(sql_type)
476
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
477
- end
478
- end
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
479
288
  end
480
289
 
481
- def create_table(table_name, options = {}) #:nodoc:
482
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
483
- end
484
-
485
- def bulk_change_table(table_name, operations) #:nodoc:
486
- sqls = operations.flat_map do |command, args|
487
- table, arguments = args.shift, args
488
- method = :"#{command}_sql"
489
-
490
- if respond_to?(method, true)
491
- send(method, table, *arguments)
492
- else
493
- raise "Unknown method called : #{method}(#{arguments.inspect})"
494
- end
495
- end.join(", ")
496
-
497
- 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)}")
498
294
  end
499
295
 
500
296
  # Renames a table.
@@ -506,8 +302,23 @@ module ActiveRecord
506
302
  rename_table_indexes(table_name, new_name)
507
303
  end
508
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.
509
320
  def drop_table(table_name, options = {})
510
- 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}"
511
322
  end
512
323
 
513
324
  def rename_index(table_name, old_name, new_name)
@@ -520,150 +331,153 @@ module ActiveRecord
520
331
  end
521
332
  end
522
333
 
523
- def change_column_default(table_name, column_name, default) #:nodoc:
524
- column = column_for(table_name, column_name)
525
- 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
526
337
  end
527
338
 
528
- def change_column_null(table_name, column_name, null, default = nil)
529
- column = column_for(table_name, column_name)
530
-
339
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
531
340
  unless null || default.nil?
532
341
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
533
342
  end
534
343
 
535
- 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
536
350
  end
537
351
 
538
352
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
539
- 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)}")
540
354
  end
541
355
 
542
356
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
543
- 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)}")
544
358
  rename_column_indexes(table_name, column_name, new_column_name)
545
359
  end
546
360
 
547
361
  def add_index(table_name, column_name, options = {}) #:nodoc:
548
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
549
- 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
550
370
  end
551
371
 
552
372
  def foreign_keys(table_name)
553
- fk_info = select_all <<-SQL.strip_heredoc
554
- SELECT fk.referenced_table_name as 'to_table'
555
- ,fk.referenced_column_name as 'primary_key'
556
- ,fk.column_name as 'column'
557
- ,fk.constraint_name as 'name'
558
- FROM information_schema.key_column_usage fk
559
- WHERE fk.referenced_column_name is not null
560
- AND fk.table_schema = '#{@config[:database]}'
561
- 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]}
562
392
  SQL
563
393
 
564
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
565
-
566
394
  fk_info.map do |row|
567
395
  options = {
568
- column: row['column'],
569
- name: row['name'],
570
- primary_key: row['primary_key']
396
+ column: row["column"],
397
+ name: row["name"],
398
+ primary_key: row["primary_key"]
571
399
  }
572
400
 
573
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
574
- 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"])
575
403
 
576
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
404
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
577
405
  end
578
406
  end
579
407
 
580
- # Maps logical Rails types to MySQL-specific data types.
581
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
582
- case type.to_s
583
- when 'binary'
584
- case limit
585
- when 0..0xfff; "varbinary(#{limit})"
586
- when nil; "blob"
587
- when 0x1000..0xffffffff; "blob(#{limit})"
588
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
589
- end
590
- when 'integer'
591
- case limit
592
- when 1; 'tinyint'
593
- when 2; 'smallint'
594
- when 3; 'mediumint'
595
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
596
- when 5..8; 'bigint'
597
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
598
- end
599
- when 'text'
600
- case limit
601
- when 0..0xff; 'tinytext'
602
- when nil, 0x100..0xffff; 'text'
603
- when 0x10000..0xffffff; 'mediumtext'
604
- when 0x1000000..0xffffffff; 'longtext'
605
- else raise(ActiveRecordError, "No text type has character length #{limit}")
606
- end
607
- when 'datetime'
608
- return super unless precision
408
+ def table_options(table_name) # :nodoc:
409
+ table_options = {}
609
410
 
610
- case precision
611
- when 0..6; "datetime(#{precision})"
612
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
613
- end
614
- else
615
- 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)
616
424
  end
425
+
426
+ table_options
617
427
  end
618
428
 
619
429
  # SHOW VARIABLES LIKE 'name'
620
430
  def show_variable(name)
621
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
622
- variables.first['Value'] unless variables.empty?
431
+ query_value("SELECT @@#{name}", "SCHEMA")
623
432
  rescue ActiveRecord::StatementInvalid
624
433
  nil
625
434
  end
626
435
 
627
- # Returns a table's primary key and belonging sequence.
628
- def pk_and_sequence_for(table)
629
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
630
- create_table = each_hash(result).first[:"Create Table"]
631
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
632
- keys = $1.split(",").map { |key| key.delete('`"') }
633
- keys.length == 1 ? [keys.first, nil] : nil
634
- else
635
- nil
636
- end
637
- end
638
- end
436
+ def primary_keys(table_name) # :nodoc:
437
+ raise ArgumentError unless table_name.present?
639
438
 
640
- # Returns just a table's primary key
641
- def primary_key(table)
642
- pk_and_sequence = pk_and_sequence_for(table)
643
- pk_and_sequence && pk_and_sequence.first
644
- end
439
+ scope = quoted_scope(table_name)
645
440
 
646
- def case_sensitive_modifier(node, table_attribute)
647
- node = Arel::Nodes.build_quoted node, table_attribute
648
- 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
649
449
  end
650
450
 
651
- def case_sensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- 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))
654
461
  else
655
462
  super
656
463
  end
657
464
  end
658
465
 
659
- def case_insensitive_comparison(table, attribute, column, value)
660
- if column.case_sensitive?
661
- 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))
662
471
  else
663
- table[attribute].eq(value)
472
+ super
664
473
  end
665
474
  end
666
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
+
667
481
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
482
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
669
483
  # distinct queries, and requires that the ORDER BY include the distinct column.
@@ -673,274 +487,335 @@ module ActiveRecord
673
487
  # Convert Arel node to string
674
488
  s = s.to_sql unless s.is_a?(String)
675
489
  # Remove any ASC/DESC modifiers
676
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
490
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
677
491
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
678
492
 
679
- [super, *order_columns].join(', ')
493
+ (order_columns << super).join(", ")
680
494
  end
681
495
 
682
496
  def strict_mode?
683
497
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
684
498
  end
685
499
 
686
- def valid_type?(type)
687
- !native_database_types[type].nil?
500
+ def default_index_type?(index) # :nodoc:
501
+ index.using == :btree || super
688
502
  end
689
503
 
690
- protected
504
+ def build_insert_sql(insert) # :nodoc:
505
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
691
506
 
692
- def initialize_type_map(m) # :nodoc:
693
- super
694
-
695
- register_class_with_limit m, %r(char)i, MysqlString
696
-
697
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
698
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
699
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
700
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
701
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
702
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
703
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
704
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
707
-
708
- register_integer_type m, %r(^bigint)i, limit: 8
709
- register_integer_type m, %r(^int)i, limit: 4
710
- register_integer_type m, %r(^mediumint)i, limit: 3
711
- register_integer_type m, %r(^smallint)i, limit: 2
712
- register_integer_type m, %r(^tinyint)i, limit: 1
713
-
714
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
- m.alias_type %r(set)i, 'varchar'
716
- m.alias_type %r(year)i, 'integer'
717
- m.alias_type %r(bit)i, 'binary'
718
-
719
- m.register_type(%r(datetime)i) do |sql_type|
720
- precision = extract_precision(sql_type)
721
- 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(",")
722
513
  end
723
514
 
724
- m.register_type(%r(enum)i) do |sql_type|
725
- limit = sql_type[/^enum\((.+)\)/i, 1]
726
- .split(',').map{|enum| enum.strip.length - 2}.max
727
- MysqlString.new(limit: limit)
728
- end
515
+ sql
729
516
  end
730
517
 
731
- def register_integer_type(mapping, key, options) # :nodoc:
732
- mapping.register_type(key) do |sql_type|
733
- if /unsigned/i =~ sql_type
734
- Type::UnsignedInteger.new(options)
735
- else
736
- Type::Integer.new(options)
737
- 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."
738
521
  end
739
522
  end
740
523
 
741
- # MySQL is too stupid to create a temporary table for use subquery, so we have
742
- # to give it some prompting in the form of a subsubquery. Ugh!
743
- def subquery_for(key, select)
744
- subsubselect = select.clone
745
- subsubselect.projections = [key]
524
+ private
746
525
 
747
- # Materialize subquery by adding distinct
748
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
749
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
526
+ def initialize_type_map(m = type_map)
527
+ super
750
528
 
751
- subselect = Arel::SelectManager.new(select.engine)
752
- subselect.project Arel.sql(key.name)
753
- subselect.from subsubselect.as('__active_record_temp')
754
- 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
755
557
 
756
- def add_index_length(option_strings, column_names, options = {})
757
- if options.is_a?(Hash) && length = options[:length]
758
- case length
759
- when Hash
760
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
761
- when Integer
762
- 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)
763
562
  end
764
563
  end
765
564
 
766
- return option_strings
767
- end
768
-
769
- def quoted_columns_for_index(column_names, options = {})
770
- 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
771
574
 
772
- # add index length
773
- 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
774
582
 
775
- # add index sort order
776
- 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
777
633
 
778
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
779
- 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
780
637
 
781
- def translate_exception(exception, message)
782
- case error_number(exception)
783
- when 1062
784
- RecordNotUnique.new(message, exception)
785
- when 1452
786
- InvalidForeignKey.new(message, exception)
787
- else
788
- super
789
- end
790
- end
638
+ unless options.key?(:default)
639
+ options[:default] = column.default
640
+ end
791
641
 
792
- def add_column_sql(table_name, column_name, type, options = {})
793
- td = create_table_definition table_name, options[:temporary], options[:options]
794
- cd = td.new_column_definition(column_name, type, options)
795
- schema_creation.visit_AddColumn cd
796
- end
642
+ unless options.key?(:null)
643
+ options[:null] = column.null
644
+ end
797
645
 
798
- def change_column_sql(table_name, column_name, type, options = {})
799
- column = column_for(table_name, column_name)
646
+ unless options.key?(:comment)
647
+ options[:comment] = column.comment
648
+ end
800
649
 
801
- unless options_include_default?(options)
802
- 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))
803
653
  end
804
654
 
805
- unless options.has_key?(:null)
806
- 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))
807
667
  end
808
668
 
809
- options[:name] = column.name
810
- schema_creation.accept ChangeColumnDefinition.new column, type, options
811
- 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
812
674
 
813
- def rename_column_sql(table_name, column_name, new_column_name)
814
- column = column_for(table_name, column_name)
815
- options = {
816
- name: new_column_name,
817
- default: column.default,
818
- null: column.null,
819
- auto_increment: column.extra == "auto_increment"
820
- }
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
821
679
 
822
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
823
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
824
- end
680
+ def add_timestamps_for_alter(table_name, options = {})
681
+ options[:null] = false if options[:null].nil?
825
682
 
826
- def remove_column_sql(table_name, column_name, type = nil, options = {})
827
- "DROP #{quote_column_name(column_name)}"
828
- end
683
+ if !options.key?(:precision) && supports_datetime_with_precision?
684
+ options[:precision] = 6
685
+ end
829
686
 
830
- def remove_columns_sql(table_name, *column_names)
831
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
832
- end
687
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
688
+ end
833
689
 
834
- def add_index_sql(table_name, column_name, options = {})
835
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
836
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
837
- 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
838
693
 
839
- def remove_index_sql(table_name, options = {})
840
- index_name = index_name_for_remove(table_name, options)
841
- "DROP INDEX #{index_name}"
842
- end
694
+ def supports_rename_index?
695
+ mariadb? ? false : database_version >= "5.7.6"
696
+ end
843
697
 
844
- def add_timestamps_sql(table_name, options = {})
845
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
846
- end
698
+ def configure_connection
699
+ variables = @config.fetch(:variables, {}).stringify_keys
847
700
 
848
- def remove_timestamps_sql(table_name, options = {})
849
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
850
- end
701
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
702
+ variables["sql_auto_is_null"] = 0
851
703
 
852
- 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
853
708
 
854
- def version
855
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
856
- end
709
+ defaults = [":default", :default].to_set
857
710
 
858
- def mariadb?
859
- full_version =~ /mariadb/i
860
- 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
861
736
 
862
- def supports_rename_index?
863
- mariadb? ? false : version >= '5.7.6'
864
- 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(", ")
865
746
 
866
- def configure_connection
867
- 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
868
750
 
869
- # By default, MySQL 'where id is null' selects the last inserted id.
870
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
871
- 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
872
756
 
873
- # Increase timeout so the server doesn't disconnect us.
874
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
875
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
- variables["wait_timeout"] = 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
877
760
 
878
- # Make MySQL reject illegal values rather than truncating or blanking them, see
879
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
880
- # If the user has provided another value for sql_mode, don't replace it.
881
- unless variables.has_key?('sql_mode')
882
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
761
+ def arel_visitor
762
+ Arel::Visitors::MySQL.new(self)
883
763
  end
884
764
 
885
- # NAMES does not have an equals sign, see
886
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
887
- # (trailing comma because variable_assignments will always have content)
888
- if @config[:encoding]
889
- encoding = "NAMES #{@config[:encoding]}"
890
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
891
- encoding << ", "
765
+ def build_statement_pool
766
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
892
767
  end
893
768
 
894
- # Gather up all of the SET variables...
895
- variable_assignments = variables.map do |k, v|
896
- if v == ':default' || v == :default
897
- "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
898
- elsif !v.nil?
899
- "@@SESSION.#{k} = #{quote(v)}"
900
- end
901
- # or else nil; compact to clear nils out
902
- 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)
903
775
 
904
- # ...and send them all in one query
905
- @connection.query "SET #{encoding} #{variable_assignments}"
906
- end
776
+ options = {
777
+ message: message,
778
+ sql: sql,
779
+ binds: binds,
780
+ }
907
781
 
908
- def extract_foreign_key_action(structure, name, action) # :nodoc:
909
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
910
- case $1
911
- when 'CASCADE'; :cascade
912
- 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])
913
788
  end
914
- end
915
- end
916
789
 
917
- class MysqlDateTime < Type::DateTime # :nodoc:
918
- private
790
+ MismatchedForeignKey.new(options)
791
+ end
919
792
 
920
- def has_precision?
921
- precision || 0
793
+ def version_string(full_version_string)
794
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
922
795
  end
923
- end
924
796
 
925
- class MysqlString < Type::String # :nodoc:
926
- def type_cast_for_database(value)
927
- case value
928
- when true then "1"
929
- when false then "0"
930
- 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
931
804
  end
932
- end
933
805
 
934
- private
806
+ private
935
807
 
936
- def cast_value(value)
937
- case value
938
- when true then "1"
939
- when false then "0"
940
- else super
941
- 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
942
815
  end
943
- end
816
+
817
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
818
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
944
819
  end
945
820
  end
946
821
  end