activerecord 4.2.0 → 6.1.7.1

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