activerecord 4.2.0 → 6.0.5.1

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