activerecord 6.0.0 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -3,6 +3,7 @@
3
3
  require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
5
  require "active_record/connection_adapters/mysql/column"
6
+ require "active_record/connection_adapters/mysql/database_statements"
6
7
  require "active_record/connection_adapters/mysql/explain_pretty_printer"
7
8
  require "active_record/connection_adapters/mysql/quoting"
8
9
  require "active_record/connection_adapters/mysql/schema_creation"
@@ -14,6 +15,7 @@ require "active_record/connection_adapters/mysql/type_metadata"
14
15
  module ActiveRecord
15
16
  module ConnectionAdapters
16
17
  class AbstractMysqlAdapter < AbstractAdapter
18
+ include MySQL::DatabaseStatements
17
19
  include MySQL::Quoting
18
20
  include MySQL::SchemaStatements
19
21
 
@@ -31,6 +33,7 @@ module ActiveRecord
31
33
  string: { name: "varchar", limit: 255 },
32
34
  text: { name: "text" },
33
35
  integer: { name: "int", limit: 4 },
36
+ bigint: { name: "bigint" },
34
37
  float: { name: "float", limit: 24 },
35
38
  decimal: { name: "decimal" },
36
39
  datetime: { name: "datetime" },
@@ -45,17 +48,42 @@ module ActiveRecord
45
48
 
46
49
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
47
50
  private
48
-
49
51
  def dealloc(stmt)
50
52
  stmt.close
51
53
  end
52
54
  end
53
55
 
54
- def initialize(connection, logger, connection_options, config)
55
- super(connection, logger, config)
56
+ class << self
57
+ def dbconsole(config, options = {})
58
+ mysql_config = config.configuration_hash
59
+
60
+ args = {
61
+ host: "--host",
62
+ port: "--port",
63
+ socket: "--socket",
64
+ username: "--user",
65
+ encoding: "--default-character-set",
66
+ sslca: "--ssl-ca",
67
+ sslcert: "--ssl-cert",
68
+ sslcapath: "--ssl-capath",
69
+ sslcipher: "--ssl-cipher",
70
+ sslkey: "--ssl-key",
71
+ ssl_mode: "--ssl-mode"
72
+ }.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
73
+
74
+ if mysql_config[:password] && options[:include_password]
75
+ args << "--password=#{mysql_config[:password]}"
76
+ elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
77
+ args << "-p"
78
+ end
79
+
80
+ args << config.database
81
+
82
+ find_cmd_and_exec(["mysql", "mysql5"], *args)
83
+ end
56
84
  end
57
85
 
58
- def get_database_version #:nodoc:
86
+ def get_database_version # :nodoc:
59
87
  full_version_string = get_full_version
60
88
  version_string = version_string(full_version_string)
61
89
  Version.new(version_string, full_version_string)
@@ -81,6 +109,10 @@ module ActiveRecord
81
109
  true
82
110
  end
83
111
 
112
+ def supports_restart_db_transaction?
113
+ true
114
+ end
115
+
84
116
  def supports_explain?
85
117
  true
86
118
  end
@@ -93,6 +125,14 @@ module ActiveRecord
93
125
  true
94
126
  end
95
127
 
128
+ def supports_check_constraints?
129
+ if mariadb?
130
+ database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
131
+ else
132
+ database_version >= "8.0.16"
133
+ end
134
+ end
135
+
96
136
  def supports_views?
97
137
  true
98
138
  end
@@ -105,11 +145,19 @@ module ActiveRecord
105
145
  mariadb? || database_version >= "5.7.5"
106
146
  end
107
147
 
108
- # See https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html for more details.
148
+ # See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
109
149
  def supports_optimizer_hints?
110
150
  !mariadb? && database_version >= "5.7.7"
111
151
  end
112
152
 
153
+ def supports_common_table_expressions?
154
+ if mariadb?
155
+ database_version >= "10.2.1"
156
+ else
157
+ database_version >= "8.0.1"
158
+ end
159
+ end
160
+
113
161
  def supports_advisory_locks?
114
162
  true
115
163
  end
@@ -122,6 +170,14 @@ module ActiveRecord
122
170
  true
123
171
  end
124
172
 
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
177
+ def return_value_after_insert?(column) # :nodoc:
178
+ supports_insert_returning? ? column.auto_populated? : column.auto_increment?
179
+ end
180
+
125
181
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
126
182
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
127
183
  end
@@ -135,7 +191,12 @@ module ActiveRecord
135
191
  end
136
192
 
137
193
  def index_algorithms
138
- { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
194
+ {
195
+ default: "ALGORITHM = DEFAULT",
196
+ copy: "ALGORITHM = COPY",
197
+ inplace: "ALGORITHM = INPLACE",
198
+ instant: "ALGORITHM = INSTANT",
199
+ }
139
200
  end
140
201
 
141
202
  # HELPER METHODS ===========================================
@@ -154,73 +215,54 @@ module ActiveRecord
154
215
 
155
216
  # REFERENTIAL INTEGRITY ====================================
156
217
 
157
- def disable_referential_integrity #:nodoc:
218
+ def disable_referential_integrity # :nodoc:
158
219
  old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
159
220
 
160
221
  begin
161
222
  update("SET FOREIGN_KEY_CHECKS = 0")
162
223
  yield
163
224
  ensure
164
- update("SET FOREIGN_KEY_CHECKS = #{old}")
225
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
165
226
  end
166
227
  end
167
228
 
168
- # CONNECTION MANAGEMENT ====================================
169
-
170
- def clear_cache! # :nodoc:
171
- reload_type_map
172
- super
173
- end
174
-
175
229
  #--
176
230
  # DATABASE STATEMENTS ======================================
177
231
  #++
178
232
 
179
- def explain(arel, binds = [])
180
- sql = "EXPLAIN #{to_sql(arel, binds)}"
181
- start = Concurrent.monotonic_time
182
- result = exec_query(sql, "EXPLAIN", binds)
183
- elapsed = Concurrent.monotonic_time - start
184
-
185
- MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
186
- end
187
-
188
- # Executes the SQL statement in the context of this connection.
189
- def execute(sql, name = nil)
190
- materialize_transactions
191
-
192
- log(sql, name) do
193
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
194
- @connection.query(sql)
195
- end
196
- end
197
- end
198
-
199
233
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
200
234
  # to write stuff in an abstract way without concerning ourselves about whether it
201
235
  # needs to be explicitly freed or not.
202
- def execute_and_free(sql, name = nil) # :nodoc:
203
- yield execute(sql, name)
236
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
237
+ sql = transform_query(sql)
238
+ check_if_write_query(sql)
239
+
240
+ mark_transaction_written_if_write(sql)
241
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
204
242
  end
205
243
 
206
- def begin_db_transaction
207
- execute "BEGIN"
244
+ def begin_db_transaction # :nodoc:
245
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
208
246
  end
209
247
 
210
- def begin_isolated_db_transaction(isolation)
211
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
248
+ def begin_isolated_db_transaction(isolation) # :nodoc:
249
+ internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
212
250
  begin_db_transaction
213
251
  end
214
252
 
215
- def commit_db_transaction #:nodoc:
216
- execute "COMMIT"
253
+ def commit_db_transaction # :nodoc:
254
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
217
255
  end
218
256
 
219
- def exec_rollback_db_transaction #:nodoc:
220
- execute "ROLLBACK"
257
+ def exec_rollback_db_transaction # :nodoc:
258
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
221
259
  end
222
260
 
223
- def empty_insert_statement_value(primary_key = nil)
261
+ def exec_restart_db_transaction # :nodoc:
262
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
263
+ end
264
+
265
+ def empty_insert_statement_value(primary_key = nil) # :nodoc:
224
266
  "VALUES ()"
225
267
  end
226
268
 
@@ -258,7 +300,7 @@ module ActiveRecord
258
300
  #
259
301
  # Example:
260
302
  # drop_database('sebastian_development')
261
- def drop_database(name) #:nodoc:
303
+ def drop_database(name) # :nodoc:
262
304
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
263
305
  end
264
306
 
@@ -297,9 +339,12 @@ module ActiveRecord
297
339
  #
298
340
  # Example:
299
341
  # rename_table('octopuses', 'octopi')
300
- def rename_table(table_name, new_name)
342
+ def rename_table(table_name, new_name, **options)
343
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
344
+ schema_cache.clear_data_source_cache!(table_name.to_s)
345
+ schema_cache.clear_data_source_cache!(new_name.to_s)
301
346
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
302
- rename_table_indexes(table_name, new_name)
347
+ rename_table_indexes(table_name, new_name, **options)
303
348
  end
304
349
 
305
350
  # Drops a table from the database.
@@ -317,7 +362,8 @@ module ActiveRecord
317
362
  # Although this command ignores most +options+ and the block if one is given,
318
363
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
319
364
  # In that case, +options+ and the block will be used by create_table.
320
- def drop_table(table_name, options = {})
365
+ def drop_table(table_name, **options)
366
+ schema_cache.clear_data_source_cache!(table_name.to_s)
321
367
  execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
322
368
  end
323
369
 
@@ -331,12 +377,21 @@ module ActiveRecord
331
377
  end
332
378
  end
333
379
 
334
- def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
380
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
381
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
382
+ end
383
+
384
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
385
+ column = column_for(table_name, column_name)
386
+ return unless column
387
+
335
388
  default = extract_new_default_value(default_or_changes)
336
- change_column table_name, column_name, nil, default: default
389
+ ChangeColumnDefaultDefinition.new(column, default)
337
390
  end
338
391
 
339
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
392
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
393
+ validate_change_column_null_argument!(null)
394
+
340
395
  unless null || default.nil?
341
396
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
342
397
  end
@@ -349,19 +404,68 @@ module ActiveRecord
349
404
  change_column table_name, column_name, nil, comment: comment
350
405
  end
351
406
 
352
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
353
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
407
+ def change_column(table_name, column_name, type, **options) # :nodoc:
408
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
409
+ end
410
+
411
+ # Builds a ChangeColumnDefinition object.
412
+ #
413
+ # This definition object contains information about the column change that would occur
414
+ # if the same arguments were passed to #change_column. See #change_column for information about
415
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
416
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
417
+ column = column_for(table_name, column_name)
418
+ type ||= column.sql_type
419
+
420
+ unless options.key?(:default)
421
+ options[:default] = if column.default_function
422
+ -> { column.default_function }
423
+ else
424
+ column.default
425
+ end
426
+ end
427
+
428
+ unless options.key?(:null)
429
+ options[:null] = column.null
430
+ end
431
+
432
+ unless options.key?(:comment)
433
+ options[:comment] = column.comment
434
+ end
435
+
436
+ if options[:collation] == :no_collation
437
+ options.delete(:collation)
438
+ else
439
+ options[:collation] ||= column.collation if text_type?(type)
440
+ end
441
+
442
+ unless options.key?(:auto_increment)
443
+ options[:auto_increment] = column.auto_increment?
444
+ end
445
+
446
+ td = create_table_definition(table_name)
447
+ cd = td.new_column_definition(column.name, type, **options)
448
+ ChangeColumnDefinition.new(cd, column.name)
354
449
  end
355
450
 
356
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
451
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
357
452
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
358
453
  rename_column_indexes(table_name, column_name, new_column_name)
359
454
  end
360
455
 
361
- def add_index(table_name, column_name, options = {}) #:nodoc:
362
- index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
363
- sql = +"CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
364
- execute add_sql_comment!(sql, comment)
456
+ def add_index(table_name, column_name, **options) # :nodoc:
457
+ create_index = build_create_index_definition(table_name, column_name, **options)
458
+ return unless create_index
459
+
460
+ execute schema_creation.accept(create_index)
461
+ end
462
+
463
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
464
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
465
+
466
+ return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
467
+
468
+ CreateIndexDefinition.new(index, algorithm)
365
469
  end
366
470
 
367
471
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -374,11 +478,13 @@ module ActiveRecord
374
478
 
375
479
  scope = quoted_scope(table_name)
376
480
 
377
- fk_info = exec_query(<<~SQL, "SCHEMA")
481
+ # MySQL returns 1 row for each column of composite foreign keys.
482
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA")
378
483
  SELECT fk.referenced_table_name AS 'to_table',
379
484
  fk.referenced_column_name AS 'primary_key',
380
485
  fk.column_name AS 'column',
381
486
  fk.constraint_name AS 'name',
487
+ fk.ordinal_position AS 'position',
382
488
  rc.update_rule AS 'on_update',
383
489
  rc.delete_rule AS 'on_delete'
384
490
  FROM information_schema.referential_constraints rc
@@ -391,38 +497,92 @@ module ActiveRecord
391
497
  AND rc.table_name = #{scope[:name]}
392
498
  SQL
393
499
 
394
- fk_info.map do |row|
500
+ grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
501
+ grouped_fk.map do |group|
502
+ row = group.first
395
503
  options = {
396
- column: row["column"],
397
504
  name: row["name"],
398
- primary_key: row["primary_key"]
505
+ on_update: extract_foreign_key_action(row["on_update"]),
506
+ on_delete: extract_foreign_key_action(row["on_delete"])
399
507
  }
400
508
 
401
- options[:on_update] = extract_foreign_key_action(row["on_update"])
402
- options[:on_delete] = extract_foreign_key_action(row["on_delete"])
509
+ if group.one?
510
+ options[:column] = unquote_identifier(row["column"])
511
+ options[:primary_key] = row["primary_key"]
512
+ else
513
+ options[:column] = group.map { |row| unquote_identifier(row["column"]) }
514
+ options[:primary_key] = group.map { |row| row["primary_key"] }
515
+ end
403
516
 
404
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
517
+ ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
405
518
  end
406
519
  end
407
520
 
408
- def table_options(table_name) # :nodoc:
409
- table_options = {}
521
+ def check_constraints(table_name)
522
+ if supports_check_constraints?
523
+ scope = quoted_scope(table_name)
524
+
525
+ sql = <<~SQL
526
+ SELECT cc.constraint_name AS 'name',
527
+ cc.check_clause AS 'expression'
528
+ FROM information_schema.check_constraints cc
529
+ JOIN information_schema.table_constraints tc
530
+ USING (constraint_schema, constraint_name)
531
+ WHERE tc.table_schema = #{scope[:schema]}
532
+ AND tc.table_name = #{scope[:name]}
533
+ AND cc.constraint_schema = #{scope[:schema]}
534
+ SQL
535
+ sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
536
+
537
+ chk_info = internal_exec_query(sql, "SCHEMA")
538
+
539
+ chk_info.map do |row|
540
+ options = {
541
+ name: row["name"]
542
+ }
543
+ expression = row["expression"]
544
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
545
+ expression = strip_whitespace_characters(expression)
546
+
547
+ unless mariadb?
548
+ # MySQL returns check constraints expression in an already escaped form.
549
+ # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
550
+ expression = expression.gsub("\\'", "'")
551
+ end
410
552
 
553
+ CheckConstraintDefinition.new(table_name, expression, options)
554
+ end
555
+ else
556
+ raise NotImplementedError
557
+ end
558
+ end
559
+
560
+ def table_options(table_name) # :nodoc:
411
561
  create_table_info = create_table_info(table_name)
412
562
 
413
563
  # strip create_definitions and partition_options
414
- raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
564
+ # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
565
+ raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
566
+
567
+ return if raw_table_options.empty?
568
+
569
+ table_options = {}
570
+
571
+ if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
572
+ raw_table_options = $` + $' # before part + after part
573
+ table_options[:charset] = charset
574
+ table_options[:collation] = collation if collation
575
+ end
415
576
 
416
577
  # strip AUTO_INCREMENT
417
578
  raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
418
579
 
419
- table_options[:options] = raw_table_options
420
-
421
580
  # strip COMMENT
422
581
  if raw_table_options.sub!(/ COMMENT='.+'/, "")
423
582
  table_options[:comment] = table_comment(table_name)
424
583
  end
425
584
 
585
+ table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
426
586
  table_options
427
587
  end
428
588
 
@@ -440,29 +600,14 @@ module ActiveRecord
440
600
 
441
601
  query_values(<<~SQL, "SCHEMA")
442
602
  SELECT column_name
443
- FROM information_schema.key_column_usage
444
- WHERE constraint_name = 'PRIMARY'
603
+ FROM information_schema.statistics
604
+ WHERE index_name = 'PRIMARY'
445
605
  AND table_schema = #{scope[:schema]}
446
606
  AND table_name = #{scope[:name]}
447
- ORDER BY ordinal_position
607
+ ORDER BY seq_in_index
448
608
  SQL
449
609
  end
450
610
 
451
- def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
452
- column = column_for_attribute(attribute)
453
-
454
- if column.collation && !column.case_sensitive? && !value.nil?
455
- ActiveSupport::Deprecation.warn(<<~MSG.squish)
456
- Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
457
- To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model,
458
- pass `case_sensitive: true` option explicitly to the uniqueness validator.
459
- MSG
460
- attribute.eq(Arel::Nodes::Bin.new(value))
461
- else
462
- super
463
- end
464
- end
465
-
466
611
  def case_sensitive_comparison(attribute, value) # :nodoc:
467
612
  column = column_for_attribute(attribute)
468
613
 
@@ -481,14 +626,14 @@ module ActiveRecord
481
626
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
482
627
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
483
628
  # distinct queries, and requires that the ORDER BY include the distinct column.
484
- # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
629
+ # See https://dev.mysql.com/doc/refman/en/group-by-handling.html
485
630
  def columns_for_distinct(columns, orders) # :nodoc:
486
- order_columns = orders.reject(&:blank?).map { |s|
631
+ order_columns = orders.compact_blank.map { |s|
487
632
  # Convert Arel node to string
488
- s = s.to_sql unless s.is_a?(String)
633
+ s = visitor.compile(s) unless s.is_a?(String)
489
634
  # Remove any ASC/DESC modifiers
490
635
  s.gsub(/\s+(?:ASC|DESC)\b/i, "")
491
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
636
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
492
637
 
493
638
  (order_columns << super).join(", ")
494
639
  end
@@ -502,86 +647,168 @@ module ActiveRecord
502
647
  end
503
648
 
504
649
  def build_insert_sql(insert) # :nodoc:
505
- sql = +"INSERT #{insert.into} #{insert.values_list}"
650
+ # Can use any column as it will be assigned to itself.
651
+ no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
652
+
653
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
654
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
655
+ if supports_insert_raw_alias_syntax?
656
+ quoted_table_name = insert.model.quoted_table_name
657
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
658
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
659
+
660
+ if insert.skip_duplicates?
661
+ if no_op_column
662
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
663
+ end
664
+ elsif insert.update_duplicates?
665
+ if insert.raw_update_sql?
666
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
667
+ else
668
+ sql << " ON DUPLICATE KEY UPDATE "
669
+ sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
670
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
671
+ end
672
+ end
673
+ else
674
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
506
675
 
507
- if insert.skip_duplicates?
508
- no_op_column = quote_column_name(insert.keys.first)
509
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
510
- elsif insert.update_duplicates?
511
- sql << " ON DUPLICATE KEY UPDATE "
512
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
676
+ if insert.skip_duplicates?
677
+ if no_op_column
678
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
679
+ end
680
+ elsif insert.update_duplicates?
681
+ sql << " ON DUPLICATE KEY UPDATE "
682
+ if insert.raw_update_sql?
683
+ sql << insert.raw_update_sql
684
+ else
685
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
686
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
687
+ end
688
+ end
513
689
  end
514
690
 
691
+ sql << " RETURNING #{insert.returning}" if insert.returning
515
692
  sql
516
693
  end
517
694
 
518
695
  def check_version # :nodoc:
519
696
  if database_version < "5.5.8"
520
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
697
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
521
698
  end
522
699
  end
523
700
 
524
- private
701
+ #--
702
+ # QUOTING ==================================================
703
+ #++
525
704
 
526
- def initialize_type_map(m = type_map)
527
- super
705
+ # Quotes strings for use in SQL input.
706
+ def quote_string(string)
707
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
708
+ connection.escape(string)
709
+ end
710
+ end
528
711
 
529
- register_class_with_limit m, %r(char)i, MysqlString
530
-
531
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
532
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
533
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
534
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
535
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
536
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
537
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
538
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
539
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
540
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
541
-
542
- register_integer_type m, %r(^bigint)i, limit: 8
543
- register_integer_type m, %r(^int)i, limit: 4
544
- register_integer_type m, %r(^mediumint)i, limit: 3
545
- register_integer_type m, %r(^smallint)i, limit: 2
546
- register_integer_type m, %r(^tinyint)i, limit: 1
547
-
548
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
549
- m.alias_type %r(year)i, "integer"
550
- m.alias_type %r(bit)i, "binary"
551
-
552
- m.register_type(%r(enum)i) do |sql_type|
553
- limit = sql_type[/^enum\s*\((.+)\)/i, 1]
554
- .split(",").map { |enum| enum.strip.length - 2 }.max
555
- MysqlString.new(limit: limit)
712
+ class << self
713
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
714
+ super(default_timezone: default_timezone).tap do |m|
715
+ if emulate_booleans
716
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
717
+ end
556
718
  end
719
+ end
557
720
 
558
- m.register_type(%r(^set)i) do |sql_type|
559
- limit = sql_type[/^set\s*\((.+)\)/i, 1]
560
- .split(",").map { |set| set.strip.length - 1 }.sum - 1
561
- MysqlString.new(limit: limit)
721
+ private
722
+ def initialize_type_map(m)
723
+ super
724
+
725
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
726
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
727
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
728
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
729
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
730
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
731
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
732
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
733
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
734
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
735
+
736
+ register_integer_type m, %r(^bigint)i, limit: 8
737
+ register_integer_type m, %r(^int)i, limit: 4
738
+ register_integer_type m, %r(^mediumint)i, limit: 3
739
+ register_integer_type m, %r(^smallint)i, limit: 2
740
+ register_integer_type m, %r(^tinyint)i, limit: 1
741
+
742
+ m.alias_type %r(year)i, "integer"
743
+ m.alias_type %r(bit)i, "binary"
562
744
  end
563
- end
564
745
 
565
- def register_integer_type(mapping, key, options)
566
- mapping.register_type(key) do |sql_type|
567
- if /\bunsigned\b/.match?(sql_type)
568
- Type::UnsignedInteger.new(options)
746
+ def register_integer_type(mapping, key, **options)
747
+ mapping.register_type(key) do |sql_type|
748
+ if /\bunsigned\b/.match?(sql_type)
749
+ Type::UnsignedInteger.new(**options)
750
+ else
751
+ Type::Integer.new(**options)
752
+ end
753
+ end
754
+ end
755
+
756
+ def extract_precision(sql_type)
757
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
758
+ super || 0
569
759
  else
570
- Type::Integer.new(options)
760
+ super
571
761
  end
572
762
  end
763
+ end
764
+
765
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
766
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
767
+
768
+ private
769
+ def strip_whitespace_characters(expression)
770
+ expression.gsub('\\\n', "").gsub("x0A", "").squish
573
771
  end
574
772
 
575
- def extract_precision(sql_type)
576
- if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
577
- super || 0
578
- else
579
- super
773
+ def extended_type_map_key
774
+ if @default_timezone
775
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
776
+ elsif emulate_booleans
777
+ EMULATE_BOOLEANS_TRUE
580
778
  end
581
779
  end
582
780
 
583
- # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
781
+ def handle_warnings(sql)
782
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
783
+
784
+ @affected_rows_before_warnings = @raw_connection.affected_rows
785
+ warning_count = @raw_connection.warning_count
786
+ result = @raw_connection.query("SHOW WARNINGS")
787
+ result = [
788
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
789
+ ] if result.count == 0
790
+ result.each do |level, code, message|
791
+ warning = SQLWarning.new(message, code, level, sql, @pool)
792
+ next if warning_ignored?(warning)
793
+
794
+ ActiveRecord.db_warnings_action.call(warning)
795
+ end
796
+ end
797
+
798
+ def warning_ignored?(warning)
799
+ warning.level == "Note" || super
800
+ end
801
+
802
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
803
+ # made since we established the connection
804
+ def sync_timezone_changes(raw_connection)
805
+ end
806
+
807
+ # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
808
+ ER_DB_CREATE_EXISTS = 1007
809
+ ER_FILSORT_ABORT = 1028
584
810
  ER_DUP_ENTRY = 1062
811
+ ER_SERVER_SHUTDOWN = 1053
585
812
  ER_NOT_NULL_VIOLATION = 1048
586
813
  ER_NO_REFERENCED_ROW = 1216
587
814
  ER_ROW_IS_REFERENCED = 1217
@@ -595,112 +822,114 @@ module ActiveRecord
595
822
  ER_CANNOT_CREATE_TABLE = 1005
596
823
  ER_LOCK_WAIT_TIMEOUT = 1205
597
824
  ER_QUERY_INTERRUPTED = 1317
825
+ ER_CONNECTION_KILLED = 1927
826
+ CR_SERVER_GONE_ERROR = 2006
827
+ CR_SERVER_LOST = 2013
598
828
  ER_QUERY_TIMEOUT = 3024
599
829
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
830
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
600
831
 
601
832
  def translate_exception(exception, message:, sql:, binds:)
602
833
  case error_number(exception)
834
+ when nil
835
+ if exception.message.match?(/MySQL client is not connected/i)
836
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
837
+ else
838
+ super
839
+ end
840
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
841
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
842
+ when ER_DB_CREATE_EXISTS
843
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
603
844
  when ER_DUP_ENTRY
604
- RecordNotUnique.new(message, sql: sql, binds: binds)
845
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
605
846
  when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
606
- InvalidForeignKey.new(message, sql: sql, binds: binds)
847
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
607
848
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
608
- mismatched_foreign_key(message, sql: sql, binds: binds)
849
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
609
850
  when ER_CANNOT_CREATE_TABLE
610
851
  if message.include?("errno: 150")
611
- mismatched_foreign_key(message, sql: sql, binds: binds)
852
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
612
853
  else
613
854
  super
614
855
  end
615
856
  when ER_DATA_TOO_LONG
616
- ValueTooLong.new(message, sql: sql, binds: binds)
857
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
617
858
  when ER_OUT_OF_RANGE
618
- RangeError.new(message, sql: sql, binds: binds)
859
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
619
860
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
620
- NotNullViolation.new(message, sql: sql, binds: binds)
861
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
621
862
  when ER_LOCK_DEADLOCK
622
- Deadlocked.new(message, sql: sql, binds: binds)
863
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
623
864
  when ER_LOCK_WAIT_TIMEOUT
624
- LockWaitTimeout.new(message, sql: sql, binds: binds)
625
- when ER_QUERY_TIMEOUT
626
- StatementTimeout.new(message, sql: sql, binds: binds)
865
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
866
+ when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
867
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
627
868
  when ER_QUERY_INTERRUPTED
628
- QueryCanceled.new(message, sql: sql, binds: binds)
869
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
629
870
  else
630
871
  super
631
872
  end
632
873
  end
633
874
 
634
- def change_column_for_alter(table_name, column_name, type, options = {})
635
- column = column_for(table_name, column_name)
636
- type ||= column.sql_type
637
-
638
- unless options.key?(:default)
639
- options[:default] = column.default
640
- end
641
-
642
- unless options.key?(:null)
643
- options[:null] = column.null
644
- end
645
-
646
- unless options.key?(:comment)
647
- options[:comment] = column.comment
648
- end
649
-
650
- td = create_table_definition(table_name)
651
- cd = td.new_column_definition(column.name, type, options)
652
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
875
+ def change_column_for_alter(table_name, column_name, type, **options)
876
+ cd = build_change_column_definition(table_name, column_name, type, **options)
877
+ schema_creation.accept(cd)
653
878
  end
654
879
 
655
880
  def rename_column_for_alter(table_name, column_name, new_column_name)
881
+ return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
882
+
656
883
  column = column_for(table_name, column_name)
657
884
  options = {
658
885
  default: column.default,
659
886
  null: column.null,
660
- auto_increment: column.auto_increment?
887
+ auto_increment: column.auto_increment?,
888
+ comment: column.comment
661
889
  }
662
890
 
663
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
891
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
664
892
  td = create_table_definition(table_name)
665
- cd = td.new_column_definition(new_column_name, current_type, options)
893
+ cd = td.new_column_definition(new_column_name, current_type, **options)
666
894
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
667
895
  end
668
896
 
669
- def add_index_for_alter(table_name, column_name, options = {})
670
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
671
- index_algorithm[0, 0] = ", " if index_algorithm.present?
672
- "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
897
+ def add_index_for_alter(table_name, column_name, **options)
898
+ index, algorithm, _ = add_index_options(table_name, column_name, **options)
899
+ algorithm = ", #{algorithm}" if algorithm
900
+
901
+ "ADD #{schema_creation.accept(index)}#{algorithm}"
673
902
  end
674
903
 
675
- def remove_index_for_alter(table_name, options = {})
676
- index_name = index_name_for_remove(table_name, options)
904
+ def remove_index_for_alter(table_name, column_name = nil, **options)
905
+ index_name = index_name_for_remove(table_name, column_name, options)
677
906
  "DROP INDEX #{quote_column_name(index_name)}"
678
907
  end
679
908
 
680
- def add_timestamps_for_alter(table_name, options = {})
681
- options[:null] = false if options[:null].nil?
682
-
683
- if !options.key?(:precision) && supports_datetime_with_precision?
684
- options[:precision] = 6
685
- end
686
-
687
- [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
909
+ def supports_insert_raw_alias_syntax?
910
+ !mariadb? && database_version >= "8.0.19"
688
911
  end
689
912
 
690
- def remove_timestamps_for_alter(table_name, options = {})
691
- [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
913
+ def supports_rename_index?
914
+ if mariadb?
915
+ database_version >= "10.5.2"
916
+ else
917
+ database_version >= "5.7.6"
918
+ end
692
919
  end
693
920
 
694
- def supports_rename_index?
695
- mariadb? ? false : database_version >= "5.7.6"
921
+ def supports_rename_column?
922
+ if mariadb?
923
+ database_version >= "10.5.2"
924
+ else
925
+ database_version >= "8.0.3"
926
+ end
696
927
  end
697
928
 
698
929
  def configure_connection
930
+ super
699
931
  variables = @config.fetch(:variables, {}).stringify_keys
700
932
 
701
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
702
- variables["sql_auto_is_null"] = 0
703
-
704
933
  # Increase timeout so the server doesn't disconnect us.
705
934
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
706
935
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -709,7 +938,7 @@ module ActiveRecord
709
938
  defaults = [":default", :default].to_set
710
939
 
711
940
  # Make MySQL reject illegal values rather than truncating or blanking them, see
712
- # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
941
+ # https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
713
942
  # If the user has provided another value for sql_mode, don't replace it.
714
943
  if sql_mode = variables.delete("sql_mode")
715
944
  sql_mode = quote(sql_mode)
@@ -726,7 +955,7 @@ module ActiveRecord
726
955
  sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
727
956
 
728
957
  # NAMES does not have an equals sign, see
729
- # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
958
+ # https://dev.mysql.com/doc/refman/en/set-names.html
730
959
  # (trailing comma because variable_assignments will always have content)
731
960
  if @config[:encoding]
732
961
  encoding = +"NAMES #{@config[:encoding]}"
@@ -735,17 +964,16 @@ module ActiveRecord
735
964
  end
736
965
 
737
966
  # Gather up all of the SET variables...
738
- variable_assignments = variables.map do |k, v|
967
+ variable_assignments = variables.filter_map do |k, v|
739
968
  if defaults.include?(v)
740
969
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
741
970
  elsif !v.nil?
742
971
  "@@SESSION.#{k} = #{quote(v)}"
743
972
  end
744
- # or else nil; compact to clear nils out
745
- end.compact.join(", ")
973
+ end.join(", ")
746
974
 
747
975
  # ...and send them all in one query
748
- execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
976
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
749
977
  end
750
978
 
751
979
  def column_definitions(table_name) # :nodoc:
@@ -755,7 +983,7 @@ module ActiveRecord
755
983
  end
756
984
 
757
985
  def create_table_info(table_name) # :nodoc:
758
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
986
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
759
987
  end
760
988
 
761
989
  def arel_visitor
@@ -766,18 +994,17 @@ module ActiveRecord
766
994
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
767
995
  end
768
996
 
769
- def mismatched_foreign_key(message, sql:, binds:)
997
+ def mismatched_foreign_key_details(message:, sql:)
998
+ foreign_key_pat =
999
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
1000
+
770
1001
  match = %r/
771
1002
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
772
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
1003
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
773
1004
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
774
1005
  /xmi.match(sql)
775
1006
 
776
- options = {
777
- message: message,
778
- sql: sql,
779
- binds: binds,
780
- }
1007
+ options = {}
781
1008
 
782
1009
  if match
783
1010
  options[:table] = match[:table]
@@ -787,35 +1014,33 @@ module ActiveRecord
787
1014
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
788
1015
  end
789
1016
 
790
- MismatchedForeignKey.new(options)
1017
+ options
791
1018
  end
792
1019
 
793
- def version_string(full_version_string)
794
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
795
- end
1020
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
1021
+ options = {
1022
+ message: message,
1023
+ sql: sql,
1024
+ binds: binds,
1025
+ connection_pool: connection_pool
1026
+ }
796
1027
 
797
- class MysqlString < Type::String # :nodoc:
798
- def serialize(value)
799
- case value
800
- when true then "1"
801
- when false then "0"
802
- else super
803
- end
1028
+ if sql
1029
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
1030
+ else
1031
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
804
1032
  end
805
1033
 
806
- private
807
-
808
- def cast_value(value)
809
- case value
810
- when true then "1"
811
- when false then "0"
812
- else super
813
- end
814
- end
1034
+ MismatchedForeignKey.new(**options)
815
1035
  end
816
1036
 
817
- ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
818
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
1037
+ def version_string(full_version_string)
1038
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1039
+ matches[1]
1040
+ else
1041
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1042
+ end
1043
+ end
819
1044
  end
820
1045
  end
821
1046
  end