activerecord 4.2.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

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