activerecord 3.2.6 → 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 (371) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +611 -6417
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +44 -47
  5. data/examples/performance.rb +79 -71
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +268 -238
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +173 -81
  11. data/lib/active_record/associations/association_scope.rb +124 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -38
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +11 -9
  14. data/lib/active_record/associations/builder/association.rb +113 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +105 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +53 -56
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +98 -41
  18. data/lib/active_record/associations/builder/has_many.rb +11 -63
  19. data/lib/active_record/associations/builder/has_one.rb +47 -45
  20. data/lib/active_record/associations/builder/singular_association.rb +30 -18
  21. data/lib/active_record/associations/collection_association.rb +217 -295
  22. data/lib/active_record/associations/collection_proxy.rb +1074 -77
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +78 -50
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -61
  26. data/lib/active_record/associations/has_one_association.rb +75 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +45 -119
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +208 -164
  32. data/lib/active_record/associations/preloader/association.rb +93 -87
  33. data/lib/active_record/associations/preloader/through_association.rb +87 -38
  34. data/lib/active_record/associations/preloader.rb +134 -110
  35. data/lib/active_record/associations/singular_association.rb +19 -24
  36. data/lib/active_record/associations/through_association.rb +61 -27
  37. data/lib/active_record/associations.rb +1766 -1505
  38. data/lib/active_record/attribute_assignment.rb +57 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +58 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +187 -67
  42. data/lib/active_record/attribute_methods/primary_key.rb +100 -78
  43. data/lib/active_record/attribute_methods/query.rb +10 -8
  44. data/lib/active_record/attribute_methods/read.rb +29 -118
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -72
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -42
  47. data/lib/active_record/attribute_methods/write.rb +36 -44
  48. data/lib/active_record/attribute_methods.rb +306 -161
  49. data/lib/active_record/attributes.rb +279 -0
  50. data/lib/active_record/autosave_association.rb +324 -238
  51. data/lib/active_record/base.rb +114 -507
  52. data/lib/active_record/callbacks.rb +147 -83
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +962 -279
  56. data/lib/active_record/connection_adapters/abstract/database_limits.rb +32 -5
  57. data/lib/active_record/connection_adapters/abstract/database_statements.rb +331 -209
  58. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -23
  59. data/lib/active_record/connection_adapters/abstract/quoting.rb +201 -65
  60. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +510 -289
  63. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1182 -313
  65. data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
  66. data/lib/active_record/connection_adapters/abstract_adapter.rb +585 -120
  67. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +610 -463
  68. data/lib/active_record/connection_adapters/column.rb +58 -233
  69. data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
  70. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  73. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  74. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  79. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +75 -207
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +695 -1052
  116. data/lib/active_record/connection_adapters/schema_cache.rb +115 -24
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +528 -26
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +267 -0
  128. data/lib/active_record/core.rb +599 -0
  129. data/lib/active_record/counter_cache.rb +177 -103
  130. data/lib/active_record/database_configurations/database_config.rb +37 -0
  131. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  132. data/lib/active_record/database_configurations/url_config.rb +79 -0
  133. data/lib/active_record/database_configurations.rb +233 -0
  134. data/lib/active_record/define_callbacks.rb +22 -0
  135. data/lib/active_record/dynamic_matchers.rb +107 -64
  136. data/lib/active_record/enum.rb +274 -0
  137. data/lib/active_record/errors.rb +254 -61
  138. data/lib/active_record/explain.rb +35 -70
  139. data/lib/active_record/explain_registry.rb +32 -0
  140. data/lib/active_record/explain_subscriber.rb +18 -8
  141. data/lib/active_record/fixture_set/file.rb +82 -0
  142. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  143. data/lib/active_record/fixture_set/render_context.rb +17 -0
  144. data/lib/active_record/fixture_set/table_row.rb +153 -0
  145. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  146. data/lib/active_record/fixtures.rb +291 -475
  147. data/lib/active_record/gem_version.rb +17 -0
  148. data/lib/active_record/inheritance.rb +219 -100
  149. data/lib/active_record/insert_all.rb +179 -0
  150. data/lib/active_record/integration.rb +175 -17
  151. data/lib/active_record/internal_metadata.rb +53 -0
  152. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  153. data/lib/active_record/locale/en.yml +9 -1
  154. data/lib/active_record/locking/optimistic.rb +106 -92
  155. data/lib/active_record/locking/pessimistic.rb +23 -11
  156. data/lib/active_record/log_subscriber.rb +80 -30
  157. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  158. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  159. data/lib/active_record/middleware/database_selector.rb +75 -0
  160. data/lib/active_record/migration/command_recorder.rb +235 -56
  161. data/lib/active_record/migration/compatibility.rb +244 -0
  162. data/lib/active_record/migration/join_table.rb +17 -0
  163. data/lib/active_record/migration.rb +917 -301
  164. data/lib/active_record/model_schema.rb +351 -175
  165. data/lib/active_record/nested_attributes.rb +366 -235
  166. data/lib/active_record/no_touching.rb +65 -0
  167. data/lib/active_record/null_relation.rb +68 -0
  168. data/lib/active_record/persistence.rb +761 -166
  169. data/lib/active_record/query_cache.rb +22 -44
  170. data/lib/active_record/querying.rb +55 -31
  171. data/lib/active_record/railtie.rb +185 -47
  172. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  173. data/lib/active_record/railties/console_sandbox.rb +5 -4
  174. data/lib/active_record/railties/controller_runtime.rb +35 -33
  175. data/lib/active_record/railties/databases.rake +366 -463
  176. data/lib/active_record/readonly_attributes.rb +4 -6
  177. data/lib/active_record/reflection.rb +736 -228
  178. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  179. data/lib/active_record/relation/batches.rb +252 -52
  180. data/lib/active_record/relation/calculations.rb +340 -270
  181. data/lib/active_record/relation/delegation.rb +117 -36
  182. data/lib/active_record/relation/finder_methods.rb +439 -286
  183. data/lib/active_record/relation/from_clause.rb +26 -0
  184. data/lib/active_record/relation/merger.rb +184 -0
  185. data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
  186. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  187. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  188. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  189. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  190. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  191. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder.rb +131 -39
  193. data/lib/active_record/relation/query_attribute.rb +50 -0
  194. data/lib/active_record/relation/query_methods.rb +1163 -221
  195. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  196. data/lib/active_record/relation/spawn_methods.rb +49 -120
  197. data/lib/active_record/relation/where_clause.rb +190 -0
  198. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  199. data/lib/active_record/relation.rb +671 -349
  200. data/lib/active_record/result.rb +149 -15
  201. data/lib/active_record/runtime_registry.rb +24 -0
  202. data/lib/active_record/sanitization.rb +153 -133
  203. data/lib/active_record/schema.rb +22 -19
  204. data/lib/active_record/schema_dumper.rb +178 -112
  205. data/lib/active_record/schema_migration.rb +60 -0
  206. data/lib/active_record/scoping/default.rb +107 -98
  207. data/lib/active_record/scoping/named.rb +130 -115
  208. data/lib/active_record/scoping.rb +77 -123
  209. data/lib/active_record/secure_token.rb +40 -0
  210. data/lib/active_record/serialization.rb +10 -6
  211. data/lib/active_record/statement_cache.rb +148 -0
  212. data/lib/active_record/store.rb +256 -16
  213. data/lib/active_record/suppressor.rb +61 -0
  214. data/lib/active_record/table_metadata.rb +75 -0
  215. data/lib/active_record/tasks/database_tasks.rb +506 -0
  216. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  217. data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
  218. data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
  219. data/lib/active_record/test_databases.rb +23 -0
  220. data/lib/active_record/test_fixtures.rb +224 -0
  221. data/lib/active_record/timestamp.rb +93 -39
  222. data/lib/active_record/touch_later.rb +66 -0
  223. data/lib/active_record/transactions.rb +260 -129
  224. data/lib/active_record/translation.rb +3 -1
  225. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  226. data/lib/active_record/type/date.rb +9 -0
  227. data/lib/active_record/type/date_time.rb +9 -0
  228. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  229. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  230. data/lib/active_record/type/internal/timezone.rb +17 -0
  231. data/lib/active_record/type/json.rb +30 -0
  232. data/lib/active_record/type/serialized.rb +71 -0
  233. data/lib/active_record/type/text.rb +11 -0
  234. data/lib/active_record/type/time.rb +21 -0
  235. data/lib/active_record/type/type_map.rb +62 -0
  236. data/lib/active_record/type/unsigned_integer.rb +17 -0
  237. data/lib/active_record/type.rb +78 -0
  238. data/lib/active_record/type_caster/connection.rb +34 -0
  239. data/lib/active_record/type_caster/map.rb +20 -0
  240. data/lib/active_record/type_caster.rb +9 -0
  241. data/lib/active_record/validations/absence.rb +25 -0
  242. data/lib/active_record/validations/associated.rb +35 -18
  243. data/lib/active_record/validations/length.rb +26 -0
  244. data/lib/active_record/validations/presence.rb +68 -0
  245. data/lib/active_record/validations/uniqueness.rb +123 -77
  246. data/lib/active_record/validations.rb +54 -43
  247. data/lib/active_record/version.rb +7 -7
  248. data/lib/active_record.rb +97 -49
  249. data/lib/arel/alias_predication.rb +9 -0
  250. data/lib/arel/attributes/attribute.rb +37 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/collectors/bind.rb +24 -0
  253. data/lib/arel/collectors/composite.rb +31 -0
  254. data/lib/arel/collectors/plain_string.rb +20 -0
  255. data/lib/arel/collectors/sql_string.rb +20 -0
  256. data/lib/arel/collectors/substitute_binds.rb +28 -0
  257. data/lib/arel/crud.rb +42 -0
  258. data/lib/arel/delete_manager.rb +18 -0
  259. data/lib/arel/errors.rb +9 -0
  260. data/lib/arel/expressions.rb +29 -0
  261. data/lib/arel/factory_methods.rb +49 -0
  262. data/lib/arel/insert_manager.rb +49 -0
  263. data/lib/arel/math.rb +45 -0
  264. data/lib/arel/nodes/and.rb +32 -0
  265. data/lib/arel/nodes/ascending.rb +23 -0
  266. data/lib/arel/nodes/binary.rb +52 -0
  267. data/lib/arel/nodes/bind_param.rb +36 -0
  268. data/lib/arel/nodes/case.rb +55 -0
  269. data/lib/arel/nodes/casted.rb +50 -0
  270. data/lib/arel/nodes/comment.rb +29 -0
  271. data/lib/arel/nodes/count.rb +12 -0
  272. data/lib/arel/nodes/delete_statement.rb +45 -0
  273. data/lib/arel/nodes/descending.rb +23 -0
  274. data/lib/arel/nodes/equality.rb +18 -0
  275. data/lib/arel/nodes/extract.rb +24 -0
  276. data/lib/arel/nodes/false.rb +16 -0
  277. data/lib/arel/nodes/full_outer_join.rb +8 -0
  278. data/lib/arel/nodes/function.rb +44 -0
  279. data/lib/arel/nodes/grouping.rb +8 -0
  280. data/lib/arel/nodes/in.rb +8 -0
  281. data/lib/arel/nodes/infix_operation.rb +80 -0
  282. data/lib/arel/nodes/inner_join.rb +8 -0
  283. data/lib/arel/nodes/insert_statement.rb +37 -0
  284. data/lib/arel/nodes/join_source.rb +20 -0
  285. data/lib/arel/nodes/matches.rb +18 -0
  286. data/lib/arel/nodes/named_function.rb +23 -0
  287. data/lib/arel/nodes/node.rb +50 -0
  288. data/lib/arel/nodes/node_expression.rb +13 -0
  289. data/lib/arel/nodes/outer_join.rb +8 -0
  290. data/lib/arel/nodes/over.rb +15 -0
  291. data/lib/arel/nodes/regexp.rb +16 -0
  292. data/lib/arel/nodes/right_outer_join.rb +8 -0
  293. data/lib/arel/nodes/select_core.rb +67 -0
  294. data/lib/arel/nodes/select_statement.rb +41 -0
  295. data/lib/arel/nodes/sql_literal.rb +16 -0
  296. data/lib/arel/nodes/string_join.rb +11 -0
  297. data/lib/arel/nodes/table_alias.rb +27 -0
  298. data/lib/arel/nodes/terminal.rb +16 -0
  299. data/lib/arel/nodes/true.rb +16 -0
  300. data/lib/arel/nodes/unary.rb +45 -0
  301. data/lib/arel/nodes/unary_operation.rb +20 -0
  302. data/lib/arel/nodes/unqualified_column.rb +22 -0
  303. data/lib/arel/nodes/update_statement.rb +41 -0
  304. data/lib/arel/nodes/values_list.rb +9 -0
  305. data/lib/arel/nodes/window.rb +126 -0
  306. data/lib/arel/nodes/with.rb +11 -0
  307. data/lib/arel/nodes.rb +68 -0
  308. data/lib/arel/order_predications.rb +13 -0
  309. data/lib/arel/predications.rb +257 -0
  310. data/lib/arel/select_manager.rb +271 -0
  311. data/lib/arel/table.rb +110 -0
  312. data/lib/arel/tree_manager.rb +72 -0
  313. data/lib/arel/update_manager.rb +34 -0
  314. data/lib/arel/visitors/depth_first.rb +204 -0
  315. data/lib/arel/visitors/dot.rb +297 -0
  316. data/lib/arel/visitors/ibm_db.rb +34 -0
  317. data/lib/arel/visitors/informix.rb +62 -0
  318. data/lib/arel/visitors/mssql.rb +157 -0
  319. data/lib/arel/visitors/mysql.rb +83 -0
  320. data/lib/arel/visitors/oracle.rb +159 -0
  321. data/lib/arel/visitors/oracle12.rb +66 -0
  322. data/lib/arel/visitors/postgresql.rb +110 -0
  323. data/lib/arel/visitors/sqlite.rb +39 -0
  324. data/lib/arel/visitors/to_sql.rb +889 -0
  325. data/lib/arel/visitors/visitor.rb +46 -0
  326. data/lib/arel/visitors/where_sql.rb +23 -0
  327. data/lib/arel/visitors.rb +20 -0
  328. data/lib/arel/window_predications.rb +9 -0
  329. data/lib/arel.rb +51 -0
  330. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  331. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +59 -9
  333. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  334. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  335. data/lib/rails/generators/active_record/migration.rb +41 -8
  336. data/lib/rails/generators/active_record/model/model_generator.rb +24 -22
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  339. data/lib/rails/generators/active_record.rb +10 -16
  340. metadata +285 -149
  341. data/examples/associations.png +0 -0
  342. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  343. data/lib/active_record/associations/join_helper.rb +0 -55
  344. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  345. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  346. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  347. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  348. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  349. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  350. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  351. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  352. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  353. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -188
  354. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -426
  355. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -579
  356. data/lib/active_record/dynamic_finder_match.rb +0 -68
  357. data/lib/active_record/dynamic_scope_match.rb +0 -23
  358. data/lib/active_record/fixtures/file.rb +0 -65
  359. data/lib/active_record/identity_map.rb +0 -162
  360. data/lib/active_record/observer.rb +0 -121
  361. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  362. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  363. data/lib/active_record/session_store.rb +0 -358
  364. data/lib/active_record/test_case.rb +0 -73
  365. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  366. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  367. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  368. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  369. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  370. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  371. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,177 +1,143 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'arel/visitors/bind_visitor'
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
- class Column < ConnectionAdapters::Column # :nodoc:
8
- attr_reader :collation
17
+ include MySQL::Quoting
18
+ include MySQL::SchemaStatements
9
19
 
10
- def initialize(name, default, sql_type = nil, null = true, collation = nil)
11
- super(name, default, sql_type, null)
12
- @collation = collation
13
- end
20
+ ##
21
+ # :singleton-method:
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
24
+ # to your application.rb file:
25
+ #
26
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
27
+ class_attribute :emulate_booleans, default: true
14
28
 
15
- def extract_default(default)
16
- if sql_type =~ /blob/i || type == :text
17
- if default.blank?
18
- return null ? nil : ''
19
- else
20
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
21
- end
22
- elsif missing_default_forged_as_empty_string?(default)
23
- nil
24
- else
25
- super
26
- end
27
- end
29
+ NATIVE_DATABASE_TYPES = {
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" },
44
+ }
28
45
 
29
- def has_default?
30
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
31
- super
32
- end
46
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
47
+ private
33
48
 
34
- # Must return the relevant concrete adapter
35
- def adapter
36
- raise NotImplementedError
37
- end
49
+ def dealloc(stmt)
50
+ stmt.close
51
+ end
52
+ end
38
53
 
39
- def case_sensitive?
40
- collation && !collation.match(/_ci$/)
41
- end
54
+ def initialize(connection, logger, connection_options, config)
55
+ super(connection, logger, config)
56
+ end
42
57
 
43
- private
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
44
63
 
45
- def simplified_type(field_type)
46
- return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
64
+ def mariadb? # :nodoc:
65
+ /mariadb/i.match?(full_version)
66
+ end
47
67
 
48
- case field_type
49
- when /enum/i, /set/i then :string
50
- when /year/i then :integer
51
- when /bit/i then :binary
52
- else
53
- super
54
- end
55
- end
68
+ def supports_bulk_alter?
69
+ true
70
+ end
56
71
 
57
- def extract_limit(sql_type)
58
- case sql_type
59
- when /blob|text/i
60
- case sql_type
61
- when /tiny/i
62
- 255
63
- when /medium/i
64
- 16777215
65
- when /long/i
66
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
67
- else
68
- super # we could return 65535 here, but we leave it undecorated by default
69
- end
70
- when /^bigint/i; 8
71
- when /^int/i; 4
72
- when /^mediumint/i; 3
73
- when /^smallint/i; 2
74
- when /^tinyint/i; 1
75
- else
76
- super
77
- end
78
- end
72
+ def supports_index_sort_order?
73
+ !mariadb? && database_version >= "8.0.1"
74
+ end
79
75
 
80
- # MySQL misreports NOT NULL column default when none is given.
81
- # We can't detect this for columns which may have a legitimate ''
82
- # default (string) but we can for others (integer, datetime, boolean,
83
- # and the rest).
84
- #
85
- # Test whether the column has default '', is not null, and is not
86
- # a type allowing default ''.
87
- def missing_default_forged_as_empty_string?(default)
88
- type != :string && !null && default == ''
89
- end
76
+ def supports_expression_index?
77
+ !mariadb? && database_version >= "8.0.13"
90
78
  end
91
79
 
92
- ##
93
- # :singleton-method:
94
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
95
- # as boolean. If you wish to disable this emulation (which was the default
96
- # behavior in versions 0.13.1 and earlier) you can add the following line
97
- # to your application.rb file:
98
- #
99
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
100
- class_attribute :emulate_booleans
101
- self.emulate_booleans = true
80
+ def supports_transaction_isolation?
81
+ true
82
+ end
102
83
 
103
- LOST_CONNECTION_ERROR_MESSAGES = [
104
- "Server shutdown in progress",
105
- "Broken pipe",
106
- "Lost connection to MySQL server during query",
107
- "MySQL server has gone away" ]
84
+ def supports_explain?
85
+ true
86
+ end
108
87
 
109
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
88
+ def supports_indexes_in_create?
89
+ true
90
+ end
110
91
 
111
- NATIVE_DATABASE_TYPES = {
112
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
113
- :string => { :name => "varchar", :limit => 255 },
114
- :text => { :name => "text" },
115
- :integer => { :name => "int", :limit => 4 },
116
- :float => { :name => "float" },
117
- :decimal => { :name => "decimal" },
118
- :datetime => { :name => "datetime" },
119
- :timestamp => { :name => "datetime" },
120
- :time => { :name => "time" },
121
- :date => { :name => "date" },
122
- :binary => { :name => "blob" },
123
- :boolean => { :name => "tinyint", :limit => 1 }
124
- }
92
+ def supports_foreign_keys?
93
+ true
94
+ end
125
95
 
126
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
127
- include Arel::Visitors::BindVisitor
96
+ def supports_views?
97
+ true
128
98
  end
129
99
 
130
- # FIXME: Make the first parameter more similar for the two adapters
131
- def initialize(connection, logger, connection_options, config)
132
- super(connection, logger)
133
- @connection_options, @config = connection_options, config
134
- @quoted_column_names, @quoted_table_names = {}, {}
100
+ def supports_datetime_with_precision?
101
+ mariadb? || database_version >= "5.6.4"
102
+ end
135
103
 
136
- if config.fetch(:prepared_statements) { true }
137
- @visitor = Arel::Visitors::MySQL.new self
138
- else
139
- @visitor = BindSubstitution.new self
140
- end
104
+ def supports_virtual_columns?
105
+ mariadb? || database_version >= "5.7.5"
141
106
  end
142
107
 
143
- def adapter_name #:nodoc:
144
- self.class::ADAPTER_NAME
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"
145
111
  end
146
112
 
147
- # Returns true, since this connection adapter supports migrations.
148
- def supports_migrations?
113
+ def supports_advisory_locks?
149
114
  true
150
115
  end
151
116
 
152
- def supports_primary_key?
117
+ def supports_insert_on_duplicate_skip?
153
118
  true
154
119
  end
155
120
 
156
- # Returns true, since this connection adapter supports savepoints.
157
- def supports_savepoints?
121
+ def supports_insert_on_duplicate_update?
158
122
  true
159
123
  end
160
124
 
161
- def supports_bulk_alter? #:nodoc:
162
- true
125
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
126
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
163
127
  end
164
128
 
165
- # Technically MySQL allows to create indexes with the sort order syntax
166
- # but at the moment (5.5) it doesn't yet implement them
167
- def supports_index_sort_order?
168
- true
129
+ def release_advisory_lock(lock_name) # :nodoc:
130
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
169
131
  end
170
132
 
171
133
  def native_database_types
172
134
  NATIVE_DATABASE_TYPES
173
135
  end
174
136
 
137
+ def index_algorithms
138
+ { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
139
+ end
140
+
175
141
  # HELPER METHODS ===========================================
176
142
 
177
143
  # The two drivers have slightly different ways of yielding hashes of results, so
@@ -180,50 +146,16 @@ module ActiveRecord
180
146
  raise NotImplementedError
181
147
  end
182
148
 
183
- # Overridden by the adapters to instantiate their specific Column type.
184
- def new_column(field, default, type, null, collation) # :nodoc:
185
- Column.new(field, default, type, null, collation)
186
- end
187
-
188
- # Must return the Mysql error number from the exception, if the exception has an
149
+ # Must return the MySQL error number from the exception, if the exception has an
189
150
  # error number.
190
151
  def error_number(exception) # :nodoc:
191
152
  raise NotImplementedError
192
153
  end
193
154
 
194
- # QUOTING ==================================================
195
-
196
- def quote(value, column = nil)
197
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
198
- s = column.class.string_to_binary(value).unpack("H*")[0]
199
- "x'#{s}'"
200
- elsif value.kind_of?(BigDecimal)
201
- value.to_s("F")
202
- else
203
- super
204
- end
205
- end
206
-
207
- def quote_column_name(name) #:nodoc:
208
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
209
- end
210
-
211
- def quote_table_name(name) #:nodoc:
212
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
213
- end
214
-
215
- def quoted_true
216
- QUOTED_TRUE
217
- end
218
-
219
- def quoted_false
220
- QUOTED_FALSE
221
- end
222
-
223
155
  # REFERENTIAL INTEGRITY ====================================
224
156
 
225
- def disable_referential_integrity(&block) #:nodoc:
226
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
157
+ def disable_referential_integrity #:nodoc:
158
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
227
159
 
228
160
  begin
229
161
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -233,121 +165,92 @@ module ActiveRecord
233
165
  end
234
166
  end
235
167
 
168
+ # CONNECTION MANAGEMENT ====================================
169
+
170
+ def clear_cache! # :nodoc:
171
+ reload_type_map
172
+ super
173
+ end
174
+
175
+ #--
236
176
  # DATABASE STATEMENTS ======================================
177
+ #++
178
+
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)
186
+ end
237
187
 
238
188
  # Executes the SQL statement in the context of this connection.
239
189
  def execute(sql, name = nil)
240
- if name == :skip_logging
241
- @connection.query(sql)
242
- else
243
- log(sql, name) { @connection.query(sql) }
244
- end
245
- rescue ActiveRecord::StatementInvalid => exception
246
- if exception.message.split(":").first =~ /Packets out of order/
247
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
248
- else
249
- raise
190
+ materialize_transactions
191
+
192
+ log(sql, name) do
193
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
194
+ @connection.query(sql)
195
+ end
250
196
  end
251
197
  end
252
198
 
253
- # MysqlAdapter has to free a result after using it, so we use this method to write
254
- # stuff in a abstract way without concerning ourselves about whether it needs to be
255
- # explicitly freed or not.
256
- def execute_and_free(sql, name = nil) #:nodoc:
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:
257
203
  yield execute(sql, name)
258
204
  end
259
205
 
260
- def update_sql(sql, name = nil) #:nodoc:
261
- super
262
- @connection.affected_rows
263
- end
264
-
265
206
  def begin_db_transaction
266
207
  execute "BEGIN"
267
- rescue Exception
268
- # Transactions aren't supported
208
+ end
209
+
210
+ def begin_isolated_db_transaction(isolation)
211
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
212
+ begin_db_transaction
269
213
  end
270
214
 
271
215
  def commit_db_transaction #:nodoc:
272
216
  execute "COMMIT"
273
- rescue Exception
274
- # Transactions aren't supported
275
217
  end
276
218
 
277
- def rollback_db_transaction #:nodoc:
219
+ def exec_rollback_db_transaction #:nodoc:
278
220
  execute "ROLLBACK"
279
- rescue Exception
280
- # Transactions aren't supported
281
- end
282
-
283
- def create_savepoint
284
- execute("SAVEPOINT #{current_savepoint_name}")
285
- end
286
-
287
- def rollback_to_savepoint
288
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
289
- end
290
-
291
- def release_savepoint
292
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
293
221
  end
294
222
 
295
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
296
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
297
- # these, we must use a subquery. However, MySQL is too stupid to create a
298
- # temporary table for this automatically, so we have to give it some prompting
299
- # in the form of a subsubquery. Ugh!
300
- def join_to_update(update, select) #:nodoc:
301
- if select.limit || select.offset || select.orders.any?
302
- subsubselect = select.clone
303
- subsubselect.projections = [update.key]
304
-
305
- subselect = Arel::SelectManager.new(select.engine)
306
- subselect.project Arel.sql(update.key.name)
307
- subselect.from subsubselect.as('__active_record_temp')
308
-
309
- update.where update.key.in(subselect)
310
- else
311
- update.table select.source
312
- update.wheres = select.constraints
313
- end
223
+ def empty_insert_statement_value(primary_key = nil)
224
+ "VALUES ()"
314
225
  end
315
226
 
316
227
  # SCHEMA STATEMENTS ========================================
317
228
 
318
- def structure_dump #:nodoc:
319
- if supports_views?
320
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
321
- else
322
- sql = "SHOW TABLES"
323
- end
324
-
325
- select_all(sql).map { |table|
326
- table.delete('Table_type')
327
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
328
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
329
- }.join
330
- end
331
-
332
229
  # Drops the database specified on the +name+ attribute
333
230
  # and creates it again using the provided +options+.
334
231
  def recreate_database(name, options = {})
335
232
  drop_database(name)
336
- create_database(name, options)
233
+ sql = create_database(name, options)
234
+ reconnect!
235
+ sql
337
236
  end
338
237
 
339
238
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
340
- # Charset defaults to utf8.
239
+ # Charset defaults to utf8mb4.
341
240
  #
342
241
  # Example:
343
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
242
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
344
243
  # create_database 'matt_development'
345
- # create_database 'matt_development', :charset => :big5
244
+ # create_database 'matt_development', charset: :big5
346
245
  def create_database(name, options = {})
347
246
  if options[:collation]
348
- 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`"
349
252
  else
350
- 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."
351
254
  end
352
255
  end
353
256
 
@@ -356,319 +259,563 @@ module ActiveRecord
356
259
  # Example:
357
260
  # drop_database('sebastian_development')
358
261
  def drop_database(name) #:nodoc:
359
- execute "DROP DATABASE IF EXISTS `#{name}`"
262
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
360
263
  end
361
264
 
362
265
  def current_database
363
- select_value 'SELECT DATABASE() as db'
266
+ query_value("SELECT database()", "SCHEMA")
364
267
  end
365
268
 
366
269
  # Returns the database character set.
367
270
  def charset
368
- show_variable 'character_set_database'
271
+ show_variable "character_set_database"
369
272
  end
370
273
 
371
274
  # Returns the database collation strategy.
372
275
  def collation
373
- show_variable 'collation_database'
276
+ show_variable "collation_database"
374
277
  end
375
278
 
376
- def tables(name = nil, database = nil, like = nil) #:nodoc:
377
- sql = "SHOW TABLES "
378
- sql << "IN #{quote_table_name(database)} " if database
379
- sql << "LIKE #{quote(like)}" if like
279
+ def table_comment(table_name) # :nodoc:
280
+ scope = quoted_scope(table_name)
380
281
 
381
- execute_and_free(sql, 'SCHEMA') do |result|
382
- result.collect { |field| field.first }
383
- 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
384
288
  end
385
289
 
386
- def table_exists?(name)
387
- return false unless name
388
- return true if tables(nil, nil, name).any?
389
-
390
- name = name.to_s
391
- schema, table = name.split('.', 2)
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)}")
294
+ end
392
295
 
393
- unless table # A table was provided without a schema
394
- table = schema
395
- schema = nil
396
- end
296
+ # Renames a table.
297
+ #
298
+ # Example:
299
+ # rename_table('octopuses', 'octopi')
300
+ def rename_table(table_name, new_name)
301
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
302
+ rename_table_indexes(table_name, new_name)
303
+ end
397
304
 
398
- tables(nil, schema, table).any?
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.
320
+ def drop_table(table_name, options = {})
321
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
399
322
  end
400
323
 
401
- # Returns an array of indexes for the given table.
402
- def indexes(table_name, name = nil) #:nodoc:
403
- indexes = []
404
- current_index = nil
405
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
406
- each_hash(result) do |row|
407
- if current_index != row[:Key_name]
408
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
409
- current_index = row[:Key_name]
410
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
411
- end
324
+ def rename_index(table_name, old_name, new_name)
325
+ if supports_rename_index?
326
+ validate_index_length!(table_name, new_name)
412
327
 
413
- indexes.last.columns << row[:Column_name]
414
- indexes.last.lengths << row[:Sub_part]
415
- end
328
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
329
+ else
330
+ super
416
331
  end
332
+ end
417
333
 
418
- indexes
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
419
337
  end
420
338
 
421
- # Returns an array of +Column+ objects for the table specified by +table_name+.
422
- def columns(table_name, name = nil)#:nodoc:
423
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
424
- execute_and_free(sql, 'SCHEMA') do |result|
425
- each_hash(result).map do |field|
426
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
427
- end
339
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
340
+ unless null || default.nil?
341
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
428
342
  end
429
- end
430
343
 
431
- def create_table(table_name, options = {}) #:nodoc:
432
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
344
+ change_column table_name, column_name, nil, null: null
433
345
  end
434
346
 
435
- def bulk_change_table(table_name, operations) #:nodoc:
436
- sqls = operations.map do |command, args|
437
- table, arguments = args.shift, args
438
- method = :"#{command}_sql"
439
-
440
- if respond_to?(method, true)
441
- send(method, table, *arguments)
442
- else
443
- raise "Unknown method called : #{method}(#{arguments.inspect})"
444
- end
445
- end.flatten.join(", ")
446
-
447
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
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
448
350
  end
449
351
 
450
- # Renames a table.
451
- #
452
- # Example:
453
- # rename_table('octopuses', 'octopi')
454
- def rename_table(table_name, new_name)
455
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
352
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
353
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
456
354
  end
457
355
 
458
- def add_column(table_name, column_name, type, options = {})
459
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
356
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
357
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
358
+ rename_column_indexes(table_name, column_name, new_column_name)
359
+ end
360
+
361
+ def add_index(table_name, column_name, options = {}) #:nodoc:
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
370
+ end
371
+
372
+ def foreign_keys(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]}
392
+ SQL
393
+
394
+ fk_info.map do |row|
395
+ options = {
396
+ column: row["column"],
397
+ name: row["name"],
398
+ primary_key: row["primary_key"]
399
+ }
400
+
401
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
402
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
403
+
404
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
405
+ end
460
406
  end
461
407
 
462
- def change_column_default(table_name, column_name, default)
463
- column = column_for(table_name, column_name)
464
- change_column table_name, column_name, column.sql_type, :default => default
465
- end
408
+ def table_options(table_name) # :nodoc:
409
+ table_options = {}
466
410
 
467
- def change_column_null(table_name, column_name, null, default = nil)
468
- column = column_for(table_name, column_name)
411
+ create_table_info = create_table_info(table_name)
469
412
 
470
- unless null || default.nil?
471
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
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)
472
424
  end
473
425
 
474
- change_column table_name, column_name, column.sql_type, :null => null
426
+ table_options
475
427
  end
476
428
 
477
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
478
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
429
+ # SHOW VARIABLES LIKE 'name'
430
+ def show_variable(name)
431
+ query_value("SELECT @@#{name}", "SCHEMA")
432
+ rescue ActiveRecord::StatementInvalid
433
+ nil
479
434
  end
480
435
 
481
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
482
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
483
- end
484
-
485
- # Maps logical Rails types to MySQL-specific data types.
486
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
487
- case type.to_s
488
- when 'integer'
489
- case limit
490
- when 1; 'tinyint'
491
- when 2; 'smallint'
492
- when 3; 'mediumint'
493
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
494
- when 5..8; 'bigint'
495
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
496
- end
497
- when 'text'
498
- case limit
499
- when 0..0xff; 'tinytext'
500
- when nil, 0x100..0xffff; 'text'
501
- when 0x10000..0xffffff; 'mediumtext'
502
- when 0x1000000..0xffffffff; 'longtext'
503
- else raise(ActiveRecordError, "No text type has character length #{limit}")
504
- end
436
+ def primary_keys(table_name) # :nodoc:
437
+ raise ArgumentError unless table_name.present?
438
+
439
+ scope = quoted_scope(table_name)
440
+
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
449
+ end
450
+
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))
505
461
  else
506
462
  super
507
463
  end
508
464
  end
509
465
 
510
- def add_column_position!(sql, options)
511
- if options[:first]
512
- sql << " FIRST"
513
- elsif options[:after]
514
- sql << " AFTER #{quote_column_name(options[:after])}"
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))
471
+ else
472
+ super
515
473
  end
516
474
  end
517
475
 
518
- # SHOW VARIABLES LIKE 'name'
519
- def show_variable(name)
520
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
521
- variables.first['Value'] unless variables.empty?
476
+ def can_perform_case_insensitive_comparison_for?(column)
477
+ column.case_sensitive?
522
478
  end
479
+ private :can_perform_case_insensitive_comparison_for?
523
480
 
524
- # Returns a table's primary key and belonging sequence.
525
- def pk_and_sequence_for(table)
526
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
527
- create_table = each_hash(result).first[:"Create Table"]
528
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
529
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
530
- keys.length == 1 ? [keys.first, nil] : nil
531
- else
532
- nil
533
- end
534
- end
481
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
482
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
483
+ # distinct queries, and requires that the ORDER BY include the distinct column.
484
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
485
+ def columns_for_distinct(columns, orders) # :nodoc:
486
+ order_columns = orders.reject(&:blank?).map { |s|
487
+ # Convert Arel node to string
488
+ s = s.to_sql unless s.is_a?(String)
489
+ # Remove any ASC/DESC modifiers
490
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
491
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
492
+
493
+ (order_columns << super).join(", ")
535
494
  end
536
495
 
537
- # Returns just a table's primary key
538
- def primary_key(table)
539
- pk_and_sequence = pk_and_sequence_for(table)
540
- pk_and_sequence && pk_and_sequence.first
496
+ def strict_mode?
497
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
541
498
  end
542
499
 
543
- def case_sensitive_modifier(node)
544
- Arel::Nodes::Bin.new(node)
500
+ def default_index_type?(index) # :nodoc:
501
+ index.using == :btree || super
545
502
  end
546
503
 
547
- def case_insensitive_comparison(table, attribute, column, value)
548
- if column.case_sensitive?
549
- super
550
- else
551
- table[attribute].eq(value)
504
+ def build_insert_sql(insert) # :nodoc:
505
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
506
+
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(",")
552
513
  end
514
+
515
+ sql
553
516
  end
554
517
 
555
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
556
- where_sql
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."
521
+ end
557
522
  end
558
523
 
559
- protected
524
+ private
525
+
526
+ def initialize_type_map(m = type_map)
527
+ super
560
528
 
561
- def add_index_length(option_strings, column_names, options = {})
562
- if options.is_a?(Hash) && length = options[:length]
563
- case length
564
- when Hash
565
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
566
- when Fixnum
567
- column_names.each {|name| option_strings[name] += "(#{length})"}
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
557
+
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)
568
562
  end
569
563
  end
570
564
 
571
- return option_strings
572
- end
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
573
574
 
574
- def quoted_columns_for_index(column_names, options = {})
575
- option_strings = Hash[column_names.map {|name| [name, '']}]
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
582
+
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
576
633
 
577
- # add index length
578
- option_strings = add_index_length(option_strings, column_names, options)
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
579
637
 
580
- # add index sort order
581
- option_strings = add_index_sort_order(option_strings, column_names, options)
638
+ unless options.key?(:default)
639
+ options[:default] = column.default
640
+ end
582
641
 
583
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
584
- end
642
+ unless options.key?(:null)
643
+ options[:null] = column.null
644
+ end
585
645
 
586
- def translate_exception(exception, message)
587
- case error_number(exception)
588
- when 1062
589
- RecordNotUnique.new(message, exception)
590
- when 1452
591
- InvalidForeignKey.new(message, exception)
592
- else
593
- super
646
+ unless options.key?(:comment)
647
+ options[:comment] = column.comment
648
+ end
649
+
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))
594
653
  end
595
- end
596
654
 
597
- def add_column_sql(table_name, column_name, type, options = {})
598
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
599
- add_column_options!(add_column_sql, options)
600
- add_column_position!(add_column_sql, options)
601
- add_column_sql
602
- end
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))
667
+ end
668
+
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
674
+
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
679
+
680
+ def add_timestamps_for_alter(table_name, options = {})
681
+ options[:null] = false if options[:null].nil?
603
682
 
604
- def change_column_sql(table_name, column_name, type, options = {})
605
- column = column_for(table_name, column_name)
683
+ if !options.key?(:precision) && supports_datetime_with_precision?
684
+ options[:precision] = 6
685
+ end
606
686
 
607
- unless options_include_default?(options)
608
- options[:default] = column.default
687
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
609
688
  end
610
689
 
611
- unless options.has_key?(:null)
612
- options[:null] = column.null
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)]
613
692
  end
614
693
 
615
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
616
- add_column_options!(change_column_sql, options)
617
- add_column_position!(change_column_sql, options)
618
- change_column_sql
619
- end
694
+ def supports_rename_index?
695
+ mariadb? ? false : database_version >= "5.7.6"
696
+ end
620
697
 
621
- def rename_column_sql(table_name, column_name, new_column_name)
622
- options = {}
698
+ def configure_connection
699
+ variables = @config.fetch(:variables, {}).stringify_keys
623
700
 
624
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
625
- options[:default] = column.default
626
- options[:null] = column.null
627
- else
628
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
701
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
702
+ variables["sql_auto_is_null"] = 0
703
+
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
708
+
709
+ defaults = [":default", :default].to_set
710
+
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
736
+
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(", ")
746
+
747
+ # ...and send them all in one query
748
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
629
749
  end
630
750
 
631
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
632
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
633
- add_column_options!(rename_column_sql, options)
634
- rename_column_sql
635
- end
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
636
756
 
637
- def remove_column_sql(table_name, *column_names)
638
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
639
- end
640
- alias :remove_columns_sql :remove_column
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
641
760
 
642
- def add_index_sql(table_name, column_name, options = {})
643
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
644
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
645
- end
761
+ def arel_visitor
762
+ Arel::Visitors::MySQL.new(self)
763
+ end
646
764
 
647
- def remove_index_sql(table_name, options = {})
648
- index_name = index_name_for_remove(table_name, options)
649
- "DROP INDEX #{index_name}"
650
- end
765
+ def build_statement_pool
766
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
767
+ end
651
768
 
652
- def add_timestamps_sql(table_name)
653
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
654
- end
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)
775
+
776
+ options = {
777
+ message: message,
778
+ sql: sql,
779
+ binds: binds,
780
+ }
781
+
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])
788
+ end
655
789
 
656
- def remove_timestamps_sql(table_name)
657
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
658
- end
790
+ MismatchedForeignKey.new(options)
791
+ end
659
792
 
660
- private
793
+ def version_string(full_version_string)
794
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
795
+ end
661
796
 
662
- def supports_views?
663
- version[0] >= 5
664
- end
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
804
+ end
805
+
806
+ private
665
807
 
666
- def column_for(table_name, column_name)
667
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
668
- raise "No such column: #{table_name}.#{column_name}"
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
669
815
  end
670
- column
671
- end
816
+
817
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
818
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
672
819
  end
673
820
  end
674
821
  end