activerecord 4.2.11.1 → 6.0.3.5

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 +4 -4
  2. data/CHANGELOG.md +721 -1522
  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 +266 -251
  9. data/lib/active_record/association_relation.rb +20 -13
  10. data/lib/active_record/associations/alias_tracker.rb +29 -36
  11. data/lib/active_record/associations/association.rb +128 -57
  12. data/lib/active_record/associations/association_scope.rb +103 -132
  13. data/lib/active_record/associations/belongs_to_association.rb +65 -60
  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 -33
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -66
  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 +136 -288
  23. data/lib/active_record/associations/collection_proxy.rb +241 -147
  24. data/lib/active_record/associations/foreign_association.rb +10 -1
  25. data/lib/active_record/associations/has_many_association.rb +34 -98
  26. data/lib/active_record/associations/has_many_through_association.rb +60 -87
  27. data/lib/active_record/associations/has_one_association.rb +61 -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 +38 -86
  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 +149 -166
  33. data/lib/active_record/associations/preloader/association.rb +90 -123
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  35. data/lib/active_record/associations/preloader.rb +90 -93
  36. data/lib/active_record/associations/singular_association.rb +18 -39
  37. data/lib/active_record/associations/through_association.rb +38 -18
  38. data/lib/active_record/associations.rb +1737 -1597
  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 +13 -9
  42. data/lib/active_record/attribute_methods/dirty.rb +174 -144
  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 +57 -37
  48. data/lib/active_record/attribute_methods/write.rb +32 -55
  49. data/lib/active_record/attribute_methods.rb +120 -135
  50. data/lib/active_record/attributes.rb +213 -82
  51. data/lib/active_record/autosave_association.rb +97 -41
  52. data/lib/active_record/base.rb +57 -45
  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 +23 -12
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +804 -297
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +240 -115
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +83 -24
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -47
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +371 -242
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +694 -256
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +190 -83
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +473 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +507 -639
  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 +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -181
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -114
  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 -58
  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 +4 -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 +9 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  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 +51 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  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 +462 -296
  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 +558 -356
  117. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +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 -349
  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 +252 -230
  130. data/lib/active_record/counter_cache.rb +70 -49
  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 +163 -86
  138. data/lib/active_record/errors.rb +188 -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 +10 -5
  142. data/lib/active_record/fixture_set/file.rb +35 -9
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +152 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  147. data/lib/active_record/fixtures.rb +227 -501
  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 +21 -3
  154. data/lib/active_record/locale/en.yml +3 -2
  155. data/lib/active_record/locking/optimistic.rb +86 -96
  156. data/lib/active_record/locking/pessimistic.rb +18 -6
  157. data/lib/active_record/log_subscriber.rb +76 -33
  158. data/lib/active_record/middleware/database_selector/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 +623 -305
  165. data/lib/active_record/model_schema.rb +313 -112
  166. data/lib/active_record/nested_attributes.rb +263 -223
  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 +557 -126
  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 +143 -44
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +2 -0
  175. data/lib/active_record/railties/controller_runtime.rb +34 -33
  176. data/lib/active_record/railties/databases.rake +331 -185
  177. data/lib/active_record/readonly_attributes.rb +5 -4
  178. data/lib/active_record/reflection.rb +430 -281
  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 +268 -254
  182. data/lib/active_record/relation/delegation.rb +75 -84
  183. data/lib/active_record/relation/finder_methods.rb +285 -241
  184. data/lib/active_record/relation/from_clause.rb +30 -0
  185. data/lib/active_record/relation/merger.rb +78 -88
  186. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
  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 +110 -119
  194. data/lib/active_record/relation/query_attribute.rb +50 -0
  195. data/lib/active_record/relation/query_methods.rb +603 -397
  196. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  197. data/lib/active_record/relation/spawn_methods.rb +11 -14
  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 +530 -341
  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 -17
  207. data/lib/active_record/scoping/default.rb +98 -83
  208. data/lib/active_record/scoping/named.rb +86 -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 +307 -100
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +55 -100
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +80 -41
  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 +225 -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 +223 -157
  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 -45
  228. data/lib/active_record/type/date_time.rb +4 -49
  229. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  230. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  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 +23 -15
  234. data/lib/active_record/type/text.rb +2 -2
  235. data/lib/active_record/type/time.rb +11 -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 +42 -55
  247. data/lib/active_record/validations.rb +38 -35
  248. data/lib/active_record/version.rb +3 -1
  249. data/lib/active_record.rb +42 -22
  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 -2
  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/model/templates/{module.rb → module.rb.tt} +0 -0
  340. data/lib/rails/generators/active_record.rb +7 -5
  341. metadata +168 -59
  342. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  343. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  344. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  345. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  346. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  347. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  348. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  349. data/lib/active_record/attribute.rb +0 -163
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/attribute_set.rb +0 -81
  352. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  353. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  354. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  355. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  356. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  357. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  358. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  359. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  360. data/lib/active_record/type/big_integer.rb +0 -13
  361. data/lib/active_record/type/binary.rb +0 -50
  362. data/lib/active_record/type/boolean.rb +0 -31
  363. data/lib/active_record/type/decimal.rb +0 -64
  364. data/lib/active_record/type/decorator.rb +0 -14
  365. data/lib/active_record/type/float.rb +0 -19
  366. data/lib/active_record/type/integer.rb +0 -59
  367. data/lib/active_record/type/mutable.rb +0 -16
  368. data/lib/active_record/type/numeric.rb +0 -36
  369. data/lib/active_record/type/string.rb +0 -40
  370. data/lib/active_record/type/time_value.rb +0 -38
  371. data/lib/active_record/type/value.rb +0 -110
  372. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  373. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,209 +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
- def prepare_column_options(column, types) # :nodoc:
62
- spec = super
63
- spec.delete(:limit) if :boolean === column.type
64
- spec
65
- end
66
-
67
- class Column < ConnectionAdapters::Column # :nodoc:
68
- attr_reader :collation, :strict, :extra
69
-
70
- def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
71
- @strict = strict
72
- @collation = collation
73
- @extra = extra
74
- super(name, default, cast_type, sql_type, null)
75
- assert_valid_default(default)
76
- extract_default
77
- end
78
-
79
- def extract_default
80
- if blob_or_text_column?
81
- @default = null || strict ? nil : ''
82
- elsif missing_default_forged_as_empty_string?(@default)
83
- @default = nil
84
- end
85
- end
86
-
87
- def has_default?
88
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
89
- super
90
- end
91
-
92
- def blob_or_text_column?
93
- sql_type =~ /blob/i || type == :text
94
- end
95
-
96
- def case_sensitive?
97
- collation && !collation.match(/_ci$/)
98
- end
99
-
100
- def ==(other)
101
- super &&
102
- collation == other.collation &&
103
- strict == other.strict &&
104
- extra == other.extra
105
- end
106
-
107
- private
108
-
109
- # MySQL misreports NOT NULL column default when none is given.
110
- # We can't detect this for columns which may have a legitimate ''
111
- # default (string) but we can for others (integer, datetime, boolean,
112
- # and the rest).
113
- #
114
- # Test whether the column has default '', is not null, and is not
115
- # a type allowing default ''.
116
- def missing_default_forged_as_empty_string?(default)
117
- type != :string && !null && default == ''
118
- end
119
-
120
- def assert_valid_default(default)
121
- if blob_or_text_column? && default.present?
122
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
123
- end
124
- end
125
-
126
- def attributes_for_hash
127
- super + [collation, strict, extra]
128
- end
129
- end
17
+ include MySQL::Quoting
18
+ include MySQL::SchemaStatements
130
19
 
131
20
  ##
132
21
  # :singleton-method:
133
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
134
- # as boolean. If you wish to disable this emulation (which was the default
135
- # behavior in versions 0.13.1 and earlier) you can add the following line
22
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
23
+ # as boolean. If you wish to disable this emulation you can add the following line
136
24
  # to your application.rb file:
137
25
  #
138
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
139
- class_attribute :emulate_booleans
140
- self.emulate_booleans = true
141
-
142
- LOST_CONNECTION_ERROR_MESSAGES = [
143
- "Server shutdown in progress",
144
- "Broken pipe",
145
- "Lost connection to MySQL server during query",
146
- "MySQL server has gone away" ]
147
-
148
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
26
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
27
+ class_attribute :emulate_booleans, default: true
149
28
 
150
29
  NATIVE_DATABASE_TYPES = {
151
- :primary_key => "int(11) auto_increment PRIMARY KEY",
152
- :string => { :name => "varchar", :limit => 255 },
153
- :text => { :name => "text" },
154
- :integer => { :name => "int", :limit => 4 },
155
- :float => { :name => "float" },
156
- :decimal => { :name => "decimal" },
157
- :datetime => { :name => "datetime" },
158
- :time => { :name => "time" },
159
- :date => { :name => "date" },
160
- :binary => { :name => "blob" },
161
- :boolean => { :name => "tinyint", :limit => 1 }
30
+ primary_key: "bigint auto_increment PRIMARY KEY",
31
+ string: { name: "varchar", limit: 255 },
32
+ text: { name: "text" },
33
+ integer: { name: "int", limit: 4 },
34
+ float: { name: "float", limit: 24 },
35
+ decimal: { name: "decimal" },
36
+ datetime: { name: "datetime" },
37
+ timestamp: { name: "timestamp" },
38
+ time: { name: "time" },
39
+ date: { name: "date" },
40
+ binary: { name: "blob" },
41
+ blob: { name: "blob" },
42
+ boolean: { name: "tinyint", limit: 1 },
43
+ json: { name: "json" },
162
44
  }
163
45
 
164
- INDEX_TYPES = [:fulltext, :spatial]
165
- INDEX_USINGS = [:btree, :hash]
46
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
47
+ private
48
+ def dealloc(stmt)
49
+ stmt.close
50
+ end
51
+ end
166
52
 
167
- # FIXME: Make the first parameter more similar for the two adapters
168
53
  def initialize(connection, logger, connection_options, config)
169
- super(connection, logger)
170
- @connection_options, @config = connection_options, config
171
- @quoted_column_names, @quoted_table_names = {}, {}
54
+ super(connection, logger, config)
55
+ end
172
56
 
173
- @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
174
62
 
175
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
176
- @prepared_statements = true
177
- else
178
- @prepared_statements = false
179
- end
63
+ def mariadb? # :nodoc:
64
+ /mariadb/i.match?(full_version)
180
65
  end
181
66
 
182
- # Returns true, since this connection adapter supports migrations.
183
- def supports_migrations?
67
+ def supports_bulk_alter?
184
68
  true
185
69
  end
186
70
 
187
- def supports_primary_key?
188
- true
71
+ def supports_index_sort_order?
72
+ !mariadb? && database_version >= "8.0.1"
189
73
  end
190
74
 
191
- def supports_bulk_alter? #:nodoc:
192
- true
75
+ def supports_expression_index?
76
+ !mariadb? && database_version >= "8.0.13"
193
77
  end
194
78
 
195
- # Technically MySQL allows to create indexes with the sort order syntax
196
- # but at the moment (5.5) it doesn't yet implement them
197
- def supports_index_sort_order?
79
+ def supports_transaction_isolation?
198
80
  true
199
81
  end
200
82
 
201
- # MySQL 4 technically support transaction isolation, but it is affected by a bug
202
- # where the transaction level gets persisted for the whole session:
203
- #
204
- # http://bugs.mysql.com/bug.php?id=39170
205
- def supports_transaction_isolation?
206
- version >= '5.0.0'
83
+ def supports_explain?
84
+ true
207
85
  end
208
86
 
209
87
  def supports_indexes_in_create?
@@ -215,85 +93,76 @@ module ActiveRecord
215
93
  end
216
94
 
217
95
  def supports_views?
218
- version >= '5.0.0'
96
+ true
219
97
  end
220
98
 
221
99
  def supports_datetime_with_precision?
222
- version >= '5.6.4'
100
+ mariadb? || database_version >= "5.6.4"
223
101
  end
224
102
 
225
- def native_database_types
226
- NATIVE_DATABASE_TYPES
103
+ def supports_virtual_columns?
104
+ mariadb? || database_version >= "5.7.5"
227
105
  end
228
106
 
229
- def index_algorithms
230
- { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
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"
231
110
  end
232
111
 
233
- # HELPER METHODS ===========================================
234
-
235
- # The two drivers have slightly different ways of yielding hashes of results, so
236
- # this method must be implemented to provide a uniform interface.
237
- def each_hash(result) # :nodoc:
238
- raise NotImplementedError
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
239
118
  end
240
119
 
241
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
242
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
120
+ def supports_advisory_locks?
121
+ true
243
122
  end
244
123
 
245
- # Must return the MySQL error number from the exception, if the exception has an
246
- # error number.
247
- def error_number(exception) # :nodoc:
248
- raise NotImplementedError
124
+ def supports_insert_on_duplicate_skip?
125
+ true
249
126
  end
250
127
 
251
- # QUOTING ==================================================
252
-
253
- def _quote(value) # :nodoc:
254
- if value.is_a?(Type::Binary::Data)
255
- "x'#{value.hex}'"
256
- else
257
- super
258
- end
128
+ def supports_insert_on_duplicate_update?
129
+ true
259
130
  end
260
131
 
261
- def quote_column_name(name) #:nodoc:
262
- @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
263
134
  end
264
135
 
265
- def quote_table_name(name) #:nodoc:
266
- @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
267
138
  end
268
139
 
269
- def quoted_true
270
- QUOTED_TRUE
140
+ def native_database_types
141
+ NATIVE_DATABASE_TYPES
271
142
  end
272
143
 
273
- def unquoted_true
274
- 1
144
+ def index_algorithms
145
+ { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
275
146
  end
276
147
 
277
- def quoted_false
278
- QUOTED_FALSE
279
- end
148
+ # HELPER METHODS ===========================================
280
149
 
281
- def unquoted_false
282
- 0
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
283
154
  end
284
155
 
285
- def quoted_date(value)
286
- if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
287
- "#{super}.#{sprintf("%06d", value.usec)}"
288
- else
289
- super
290
- end
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
291
160
  end
292
161
 
293
162
  # REFERENTIAL INTEGRITY ====================================
294
163
 
295
164
  def disable_referential_integrity #:nodoc:
296
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
165
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
297
166
 
298
167
  begin
299
168
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -303,30 +172,42 @@ module ActiveRecord
303
172
  end
304
173
  end
305
174
 
175
+ # CONNECTION MANAGEMENT ====================================
176
+
177
+ def clear_cache! # :nodoc:
178
+ reload_type_map
179
+ super
180
+ end
181
+
306
182
  #--
307
183
  # DATABASE STATEMENTS ======================================
308
184
  #++
309
185
 
310
- def clear_cache!
311
- super
312
- 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)
313
193
  end
314
194
 
315
195
  # Executes the SQL statement in the context of this connection.
316
196
  def execute(sql, name = nil)
317
- log(sql, name) { @connection.query(sql) }
318
- end
197
+ materialize_transactions
319
198
 
320
- # MysqlAdapter has to free a result after using it, so we use this method to write
321
- # stuff in an abstract way without concerning ourselves about whether it needs to be
322
- # explicitly freed or not.
323
- def execute_and_free(sql, name = nil) #:nodoc:
324
- yield execute(sql, name)
199
+ log(sql, name) do
200
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
201
+ @connection.query(sql)
202
+ end
203
+ end
325
204
  end
326
205
 
327
- def update_sql(sql, name = nil) #:nodoc:
328
- super
329
- @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)
330
211
  end
331
212
 
332
213
  def begin_db_transaction
@@ -346,19 +227,7 @@ module ActiveRecord
346
227
  execute "ROLLBACK"
347
228
  end
348
229
 
349
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
350
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
351
- # these, we must use a subquery.
352
- def join_to_update(update, select) #:nodoc:
353
- if select.limit || select.offset || select.orders.any?
354
- super
355
- else
356
- update.table select.source
357
- update.wheres = select.constraints
358
- end
359
- end
360
-
361
- def empty_insert_statement_value
230
+ def empty_insert_statement_value(primary_key = nil)
362
231
  "VALUES ()"
363
232
  end
364
233
 
@@ -374,7 +243,7 @@ module ActiveRecord
374
243
  end
375
244
 
376
245
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
377
- # Charset defaults to utf8.
246
+ # Charset defaults to utf8mb4.
378
247
  #
379
248
  # Example:
380
249
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -382,9 +251,13 @@ module ActiveRecord
382
251
  # create_database 'matt_development', charset: :big5
383
252
  def create_database(name, options = {})
384
253
  if options[:collation]
385
- 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`"
386
259
  else
387
- 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."
388
261
  end
389
262
  end
390
263
 
@@ -393,108 +266,38 @@ module ActiveRecord
393
266
  # Example:
394
267
  # drop_database('sebastian_development')
395
268
  def drop_database(name) #:nodoc:
396
- execute "DROP DATABASE IF EXISTS `#{name}`"
269
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
397
270
  end
398
271
 
399
272
  def current_database
400
- select_value 'SELECT DATABASE() as db'
273
+ query_value("SELECT database()", "SCHEMA")
401
274
  end
402
275
 
403
276
  # Returns the database character set.
404
277
  def charset
405
- show_variable 'character_set_database'
278
+ show_variable "character_set_database"
406
279
  end
407
280
 
408
281
  # Returns the database collation strategy.
409
282
  def collation
410
- show_variable 'collation_database'
283
+ show_variable "collation_database"
411
284
  end
412
285
 
413
- def tables(name = nil, database = nil, like = nil) #:nodoc:
414
- sql = "SHOW TABLES "
415
- sql << "IN #{quote_table_name(database)} " if database
416
- sql << "LIKE #{quote(like)}" if like
286
+ def table_comment(table_name) # :nodoc:
287
+ scope = quoted_scope(table_name)
417
288
 
418
- execute_and_free(sql, 'SCHEMA') do |result|
419
- result.collect { |field| field.first }
420
- end
421
- end
422
- alias data_sources tables
423
-
424
- def truncate(table_name, name = nil)
425
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
426
- end
427
-
428
- def table_exists?(name)
429
- return false unless name.present?
430
- return true if tables(nil, nil, name).any?
431
-
432
- name = name.to_s
433
- schema, table = name.split('.', 2)
434
-
435
- unless table # A table was provided without a schema
436
- table = schema
437
- schema = nil
438
- end
439
-
440
- tables(nil, schema, table).any?
441
- end
442
- alias data_source_exists? table_exists?
443
-
444
- # Returns an array of indexes for the given table.
445
- def indexes(table_name, name = nil) #:nodoc:
446
- indexes = []
447
- current_index = nil
448
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
449
- each_hash(result) do |row|
450
- if current_index != row[:Key_name]
451
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
452
- current_index = row[:Key_name]
453
-
454
- mysql_index_type = row[:Index_type].downcase.to_sym
455
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
456
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
457
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
458
- end
459
-
460
- indexes.last.columns << row[:Column_name]
461
- indexes.last.lengths << row[:Sub_part]
462
- end
463
- end
464
-
465
- indexes
466
- end
467
-
468
- # Returns an array of +Column+ objects for the table specified by +table_name+.
469
- def columns(table_name)#:nodoc:
470
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
471
- execute_and_free(sql, 'SCHEMA') do |result|
472
- each_hash(result).map do |field|
473
- field_name = set_field_encoding(field[:Field])
474
- sql_type = field[:Type]
475
- cast_type = lookup_cast_type(sql_type)
476
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
477
- end
478
- end
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
479
295
  end
480
296
 
481
- def create_table(table_name, options = {}) #:nodoc:
482
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
483
- end
484
-
485
- def bulk_change_table(table_name, operations) #:nodoc:
486
- sqls = operations.flat_map do |command, args|
487
- table, arguments = args.shift, args
488
- method = :"#{command}_sql"
489
-
490
- if respond_to?(method, true)
491
- send(method, table, *arguments)
492
- else
493
- raise "Unknown method called : #{method}(#{arguments.inspect})"
494
- end
495
- end.join(", ")
496
-
497
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
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)}")
498
301
  end
499
302
 
500
303
  # Renames a table.
@@ -506,8 +309,23 @@ module ActiveRecord
506
309
  rename_table_indexes(table_name, new_name)
507
310
  end
508
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.
509
327
  def drop_table(table_name, options = {})
510
- 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}"
511
329
  end
512
330
 
513
331
  def rename_index(table_name, old_name, new_name)
@@ -520,150 +338,154 @@ module ActiveRecord
520
338
  end
521
339
  end
522
340
 
523
- def change_column_default(table_name, column_name, default) #:nodoc:
524
- column = column_for(table_name, column_name)
525
- change_column table_name, column_name, column.sql_type, :default => default
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
526
344
  end
527
345
 
528
- def change_column_null(table_name, column_name, null, default = nil)
529
- column = column_for(table_name, column_name)
530
-
346
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
531
347
  unless null || default.nil?
532
348
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
533
349
  end
534
350
 
535
- 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
536
357
  end
537
358
 
538
359
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
539
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
360
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
540
361
  end
541
362
 
542
363
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
543
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
364
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
544
365
  rename_column_indexes(table_name, column_name, new_column_name)
545
366
  end
546
367
 
547
368
  def add_index(table_name, column_name, options = {}) #:nodoc:
548
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
549
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
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
550
377
  end
551
378
 
552
379
  def foreign_keys(table_name)
553
- fk_info = select_all <<-SQL.strip_heredoc
554
- SELECT fk.referenced_table_name as 'to_table'
555
- ,fk.referenced_column_name as 'primary_key'
556
- ,fk.column_name as 'column'
557
- ,fk.constraint_name as 'name'
558
- FROM information_schema.key_column_usage fk
559
- WHERE fk.referenced_column_name is not null
560
- AND fk.table_schema = '#{@config[:database]}'
561
- AND fk.table_name = '#{table_name}'
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]}
562
399
  SQL
563
400
 
564
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
565
-
566
401
  fk_info.map do |row|
567
402
  options = {
568
- column: row['column'],
569
- name: row['name'],
570
- primary_key: row['primary_key']
403
+ column: row["column"],
404
+ name: row["name"],
405
+ primary_key: row["primary_key"]
571
406
  }
572
407
 
573
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
574
- options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
408
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
409
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
575
410
 
576
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
411
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
577
412
  end
578
413
  end
579
414
 
580
- # Maps logical Rails types to MySQL-specific data types.
581
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
582
- case type.to_s
583
- when 'binary'
584
- case limit
585
- when 0..0xfff; "varbinary(#{limit})"
586
- when nil; "blob"
587
- when 0x1000..0xffffffff; "blob(#{limit})"
588
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
589
- end
590
- when 'integer'
591
- case limit
592
- when 1; 'tinyint'
593
- when 2; 'smallint'
594
- when 3; 'mediumint'
595
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
596
- when 5..8; 'bigint'
597
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
598
- end
599
- when 'text'
600
- case limit
601
- when 0..0xff; 'tinytext'
602
- when nil, 0x100..0xffff; 'text'
603
- when 0x10000..0xffffff; 'mediumtext'
604
- when 0x1000000..0xffffffff; 'longtext'
605
- else raise(ActiveRecordError, "No text type has character length #{limit}")
606
- end
607
- when 'datetime'
608
- return super unless precision
415
+ def table_options(table_name) # :nodoc:
416
+ table_options = {}
609
417
 
610
- case precision
611
- when 0..6; "datetime(#{precision})"
612
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
613
- end
614
- else
615
- super
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)
616
432
  end
433
+
434
+ table_options
617
435
  end
618
436
 
619
437
  # SHOW VARIABLES LIKE 'name'
620
438
  def show_variable(name)
621
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
622
- variables.first['Value'] unless variables.empty?
439
+ query_value("SELECT @@#{name}", "SCHEMA")
623
440
  rescue ActiveRecord::StatementInvalid
624
441
  nil
625
442
  end
626
443
 
627
- # Returns a table's primary key and belonging sequence.
628
- def pk_and_sequence_for(table)
629
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
630
- create_table = each_hash(result).first[:"Create Table"]
631
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
632
- keys = $1.split(",").map { |key| key.delete('`"') }
633
- keys.length == 1 ? [keys.first, nil] : nil
634
- else
635
- nil
636
- end
637
- end
638
- end
444
+ def primary_keys(table_name) # :nodoc:
445
+ raise ArgumentError unless table_name.present?
639
446
 
640
- # Returns just a table's primary key
641
- def primary_key(table)
642
- pk_and_sequence = pk_and_sequence_for(table)
643
- pk_and_sequence && pk_and_sequence.first
644
- end
447
+ scope = quoted_scope(table_name)
645
448
 
646
- def case_sensitive_modifier(node, table_attribute)
647
- node = Arel::Nodes.build_quoted node, table_attribute
648
- 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
649
457
  end
650
458
 
651
- def case_sensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- 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))
654
469
  else
655
470
  super
656
471
  end
657
472
  end
658
473
 
659
- def case_insensitive_comparison(table, attribute, column, value)
660
- if column.case_sensitive?
661
- 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))
662
479
  else
663
- table[attribute].eq(value)
480
+ super
664
481
  end
665
482
  end
666
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
+
667
489
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
490
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
669
491
  # distinct queries, and requires that the ORDER BY include the distinct column.
@@ -671,276 +493,322 @@ module ActiveRecord
671
493
  def columns_for_distinct(columns, orders) # :nodoc:
672
494
  order_columns = orders.reject(&:blank?).map { |s|
673
495
  # Convert Arel node to string
674
- s = s.to_sql unless s.is_a?(String)
496
+ s = visitor.compile(s) unless s.is_a?(String)
675
497
  # Remove any ASC/DESC modifiers
676
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
498
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
677
499
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
678
500
 
679
- [super, *order_columns].join(', ')
501
+ (order_columns << super).join(", ")
680
502
  end
681
503
 
682
504
  def strict_mode?
683
505
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
684
506
  end
685
507
 
686
- def valid_type?(type)
687
- !native_database_types[type].nil?
508
+ def default_index_type?(index) # :nodoc:
509
+ index.using == :btree || super
688
510
  end
689
511
 
690
- protected
691
-
692
- def initialize_type_map(m) # :nodoc:
693
- super
512
+ def build_insert_sql(insert) # :nodoc:
513
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
694
514
 
695
- register_class_with_limit m, %r(char)i, MysqlString
696
-
697
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
698
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
699
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
700
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
701
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
702
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
703
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
704
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
707
-
708
- register_integer_type m, %r(^bigint)i, limit: 8
709
- register_integer_type m, %r(^int)i, limit: 4
710
- register_integer_type m, %r(^mediumint)i, limit: 3
711
- register_integer_type m, %r(^smallint)i, limit: 2
712
- register_integer_type m, %r(^tinyint)i, limit: 1
713
-
714
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
- m.alias_type %r(set)i, 'varchar'
716
- m.alias_type %r(year)i, 'integer'
717
- m.alias_type %r(bit)i, 'binary'
718
-
719
- m.register_type(%r(datetime)i) do |sql_type|
720
- precision = extract_precision(sql_type)
721
- MysqlDateTime.new(precision: precision)
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(",")
722
521
  end
723
522
 
724
- m.register_type(%r(enum)i) do |sql_type|
725
- limit = sql_type[/^enum\((.+)\)/i, 1]
726
- .split(',').map{|enum| enum.strip.length - 2}.max
727
- MysqlString.new(limit: limit)
728
- end
523
+ sql
729
524
  end
730
525
 
731
- def register_integer_type(mapping, key, options) # :nodoc:
732
- mapping.register_type(key) do |sql_type|
733
- if /unsigned/i =~ sql_type
734
- Type::UnsignedInteger.new(options)
735
- else
736
- Type::Integer.new(options)
737
- end
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."
738
529
  end
739
530
  end
740
531
 
741
- # MySQL is too stupid to create a temporary table for use subquery, so we have
742
- # to give it some prompting in the form of a subsubquery. Ugh!
743
- def subquery_for(key, select)
744
- subsubselect = select.clone
745
- subsubselect.projections = [key]
746
-
747
- # Materialize subquery by adding distinct
748
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
749
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
532
+ private
533
+ def initialize_type_map(m = type_map)
534
+ super
750
535
 
751
- subselect = Arel::SelectManager.new(select.engine)
752
- subselect.project Arel.sql(key.name)
753
- subselect.from subsubselect.as('__active_record_temp')
754
- 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
755
564
 
756
- def add_index_length(option_strings, column_names, options = {})
757
- if options.is_a?(Hash) && length = options[:length]
758
- case length
759
- when Hash
760
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
761
- when Integer
762
- column_names.each {|name| option_strings[name] += "(#{length})"}
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)
763
569
  end
764
570
  end
765
571
 
766
- return option_strings
767
- end
768
-
769
- def quoted_columns_for_index(column_names, options = {})
770
- option_strings = Hash[column_names.map {|name| [name, '']}]
771
-
772
- # add index length
773
- option_strings = add_index_length(option_strings, column_names, options)
774
-
775
- # add index sort order
776
- option_strings = add_index_sort_order(option_strings, column_names, options)
777
-
778
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
779
- end
780
-
781
- def translate_exception(exception, message)
782
- case error_number(exception)
783
- when 1062
784
- RecordNotUnique.new(message, exception)
785
- when 1452
786
- InvalidForeignKey.new(message, exception)
787
- else
788
- super
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
789
580
  end
790
- end
791
-
792
- def add_column_sql(table_name, column_name, type, options = {})
793
- td = create_table_definition table_name, options[:temporary], options[:options]
794
- cd = td.new_column_definition(column_name, type, options)
795
- schema_creation.visit_AddColumn cd
796
- end
797
581
 
798
- def change_column_sql(table_name, column_name, type, options = {})
799
- column = column_for(table_name, column_name)
800
-
801
- unless options_include_default?(options)
802
- 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
803
588
  end
804
589
 
805
- unless options.has_key?(:null)
806
- 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
807
640
  end
808
641
 
809
- options[:name] = column.name
810
- schema_creation.accept ChangeColumnDefinition.new column, type, options
811
- 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
812
645
 
813
- def rename_column_sql(table_name, column_name, new_column_name)
814
- column = column_for(table_name, column_name)
815
- options = {
816
- name: new_column_name,
817
- default: column.default,
818
- null: column.null,
819
- auto_increment: column.extra == "auto_increment"
820
- }
646
+ unless options.key?(:default)
647
+ options[:default] = column.default
648
+ end
821
649
 
822
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
823
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
824
- end
650
+ unless options.key?(:null)
651
+ options[:null] = column.null
652
+ end
825
653
 
826
- def remove_column_sql(table_name, column_name, type = nil, options = {})
827
- "DROP #{quote_column_name(column_name)}"
828
- end
654
+ unless options.key?(:comment)
655
+ options[:comment] = column.comment
656
+ end
829
657
 
830
- def remove_columns_sql(table_name, *column_names)
831
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
832
- 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
833
662
 
834
- def add_index_sql(table_name, column_name, options = {})
835
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
836
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
837
- end
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
+ }
838
670
 
839
- def remove_index_sql(table_name, options = {})
840
- index_name = index_name_for_remove(table_name, options)
841
- "DROP INDEX #{index_name}"
842
- end
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
843
676
 
844
- def add_timestamps_sql(table_name, options = {})
845
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
846
- end
677
+ def add_index_for_alter(table_name, column_name, options = {})
678
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, **options)
679
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
680
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
681
+ end
847
682
 
848
- def remove_timestamps_sql(table_name, options = {})
849
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
850
- end
683
+ def remove_index_for_alter(table_name, options = {})
684
+ index_name = index_name_for_remove(table_name, options)
685
+ "DROP INDEX #{quote_column_name(index_name)}"
686
+ end
851
687
 
852
- private
688
+ def supports_rename_index?
689
+ mariadb? ? false : database_version >= "5.7.6"
690
+ end
853
691
 
854
- def version
855
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
856
- end
692
+ def configure_connection
693
+ variables = @config.fetch(:variables, {}).stringify_keys
857
694
 
858
- def mariadb?
859
- full_version =~ /mariadb/i
860
- end
695
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
696
+ variables["sql_auto_is_null"] = 0
861
697
 
862
- def supports_rename_index?
863
- mariadb? ? false : version >= '5.7.6'
864
- end
698
+ # Increase timeout so the server doesn't disconnect us.
699
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
700
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
701
+ variables["wait_timeout"] = wait_timeout
865
702
 
866
- def configure_connection
867
- variables = @config.fetch(:variables, {}).stringify_keys
703
+ defaults = [":default", :default].to_set
868
704
 
869
- # By default, MySQL 'where id is null' selects the last inserted id.
870
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
871
- variables['sql_auto_is_null'] = 0
705
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
706
+ # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
707
+ # If the user has provided another value for sql_mode, don't replace it.
708
+ if sql_mode = variables.delete("sql_mode")
709
+ sql_mode = quote(sql_mode)
710
+ elsif !defaults.include?(strict_mode?)
711
+ if strict_mode?
712
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
713
+ else
714
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
715
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
716
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
717
+ end
718
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
719
+ end
720
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
721
+
722
+ # NAMES does not have an equals sign, see
723
+ # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
724
+ # (trailing comma because variable_assignments will always have content)
725
+ if @config[:encoding]
726
+ encoding = +"NAMES #{@config[:encoding]}"
727
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
728
+ encoding << ", "
729
+ end
872
730
 
873
- # Increase timeout so the server doesn't disconnect us.
874
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
875
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
- variables["wait_timeout"] = wait_timeout
731
+ # Gather up all of the SET variables...
732
+ variable_assignments = variables.map do |k, v|
733
+ if defaults.include?(v)
734
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
735
+ elsif !v.nil?
736
+ "@@SESSION.#{k} = #{quote(v)}"
737
+ end
738
+ # or else nil; compact to clear nils out
739
+ end.compact.join(", ")
877
740
 
878
- # Make MySQL reject illegal values rather than truncating or blanking them, see
879
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
880
- # If the user has provided another value for sql_mode, don't replace it.
881
- unless variables.has_key?('sql_mode')
882
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
741
+ # ...and send them all in one query
742
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
883
743
  end
884
744
 
885
- # NAMES does not have an equals sign, see
886
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
887
- # (trailing comma because variable_assignments will always have content)
888
- if @config[:encoding]
889
- encoding = "NAMES #{@config[:encoding]}"
890
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
891
- encoding << ", "
745
+ def column_definitions(table_name) # :nodoc:
746
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
747
+ each_hash(result)
748
+ end
892
749
  end
893
750
 
894
- # Gather up all of the SET variables...
895
- variable_assignments = variables.map do |k, v|
896
- if v == ':default' || v == :default
897
- "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
898
- elsif !v.nil?
899
- "@@SESSION.#{k} = #{quote(v)}"
900
- end
901
- # or else nil; compact to clear nils out
902
- end.compact.join(', ')
751
+ def create_table_info(table_name) # :nodoc:
752
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
753
+ end
903
754
 
904
- # ...and send them all in one query
905
- @connection.query "SET #{encoding} #{variable_assignments}"
906
- end
755
+ def arel_visitor
756
+ Arel::Visitors::MySQL.new(self)
757
+ end
907
758
 
908
- def extract_foreign_key_action(structure, name, action) # :nodoc:
909
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
910
- case $1
911
- when 'CASCADE'; :cascade
912
- when 'SET NULL'; :nullify
913
- end
759
+ def build_statement_pool
760
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
914
761
  end
915
- end
916
762
 
917
- class MysqlDateTime < Type::DateTime # :nodoc:
918
- private
763
+ def mismatched_foreign_key(message, sql:, binds:)
764
+ match = %r/
765
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
766
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
767
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
768
+ /xmi.match(sql)
919
769
 
920
- def has_precision?
921
- precision || 0
922
- end
923
- end
770
+ options = {
771
+ message: message,
772
+ sql: sql,
773
+ binds: binds,
774
+ }
924
775
 
925
- class MysqlString < Type::String # :nodoc:
926
- def type_cast_for_database(value)
927
- case value
928
- when true then "1"
929
- when false then "0"
930
- else super
776
+ if match
777
+ options[:table] = match[:table]
778
+ options[:foreign_key] = match[:foreign_key]
779
+ options[:target_table] = match[:target_table]
780
+ options[:primary_key] = match[:primary_key]
781
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
931
782
  end
783
+
784
+ MismatchedForeignKey.new(**options)
932
785
  end
933
786
 
934
- private
787
+ def version_string(full_version_string)
788
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
789
+ end
935
790
 
936
- def cast_value(value)
937
- case value
938
- when true then "1"
939
- when false then "0"
940
- else super
791
+ class MysqlString < Type::String # :nodoc:
792
+ def serialize(value)
793
+ case value
794
+ when true then "1"
795
+ when false then "0"
796
+ else super
797
+ end
941
798
  end
799
+
800
+ private
801
+ def cast_value(value)
802
+ case value
803
+ when true then "1"
804
+ when false then "0"
805
+ else super
806
+ end
807
+ end
942
808
  end
943
- end
809
+
810
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
811
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
944
812
  end
945
813
  end
946
814
  end