activerecord 7.0.8 → 7.2.0

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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +5 -5
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +328 -471
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +58 -45
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +10 -24
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +317 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -128
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +274 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. data/lib/active_record/null_relation.rb +0 -63
@@ -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
 
@@ -51,8 +53,34 @@ module ActiveRecord
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
86
  def get_database_version # :nodoc:
@@ -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
@@ -95,7 +127,7 @@ module ActiveRecord
95
127
 
96
128
  def supports_check_constraints?
97
129
  if mariadb?
98
- database_version >= "10.2.1"
130
+ database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
99
131
  else
100
132
  database_version >= "8.0.16"
101
133
  end
@@ -138,6 +170,10 @@ module ActiveRecord
138
170
  true
139
171
  end
140
172
 
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
141
177
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
142
178
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
143
179
  end
@@ -182,7 +218,7 @@ module ActiveRecord
182
218
  update("SET FOREIGN_KEY_CHECKS = 0")
183
219
  yield
184
220
  ensure
185
- update("SET FOREIGN_KEY_CHECKS = #{old}")
221
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
186
222
  end
187
223
  end
188
224
 
@@ -190,33 +226,36 @@ module ActiveRecord
190
226
  # DATABASE STATEMENTS ======================================
191
227
  #++
192
228
 
193
- # Executes the SQL statement in the context of this connection.
194
- def execute(sql, name = nil, async: false)
195
- raw_execute(sql, name, async: async)
196
- end
197
-
198
229
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
199
230
  # to write stuff in an abstract way without concerning ourselves about whether it
200
231
  # needs to be explicitly freed or not.
201
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
202
- yield execute(sql, name, async: async)
232
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
233
+ sql = transform_query(sql)
234
+ check_if_write_query(sql)
235
+
236
+ mark_transaction_written_if_write(sql)
237
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
203
238
  end
204
239
 
205
240
  def begin_db_transaction # :nodoc:
206
- execute("BEGIN", "TRANSACTION")
241
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
207
242
  end
208
243
 
209
244
  def begin_isolated_db_transaction(isolation) # :nodoc:
210
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
245
+ internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
211
246
  begin_db_transaction
212
247
  end
213
248
 
214
249
  def commit_db_transaction # :nodoc:
215
- execute("COMMIT", "TRANSACTION")
250
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
216
251
  end
217
252
 
218
253
  def exec_rollback_db_transaction # :nodoc:
219
- execute("ROLLBACK", "TRANSACTION")
254
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
255
+ end
256
+
257
+ def exec_restart_db_transaction # :nodoc:
258
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
220
259
  end
221
260
 
222
261
  def empty_insert_statement_value(primary_key = nil) # :nodoc:
@@ -296,11 +335,12 @@ module ActiveRecord
296
335
  #
297
336
  # Example:
298
337
  # rename_table('octopuses', 'octopi')
299
- def rename_table(table_name, new_name)
338
+ def rename_table(table_name, new_name, **options)
339
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
300
340
  schema_cache.clear_data_source_cache!(table_name.to_s)
301
341
  schema_cache.clear_data_source_cache!(new_name.to_s)
302
342
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
303
- rename_table_indexes(table_name, new_name)
343
+ rename_table_indexes(table_name, new_name, **options)
304
344
  end
305
345
 
306
346
  # Drops a table from the database.
@@ -334,11 +374,20 @@ module ActiveRecord
334
374
  end
335
375
 
336
376
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
377
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
378
+ end
379
+
380
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
381
+ column = column_for(table_name, column_name)
382
+ return unless column
383
+
337
384
  default = extract_new_default_value(default_or_changes)
338
- change_column table_name, column_name, nil, default: default
385
+ ChangeColumnDefaultDefinition.new(column, default)
339
386
  end
340
387
 
341
388
  def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
389
+ validate_change_column_null_argument!(null)
390
+
342
391
  unless null || default.nil?
343
392
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
344
393
  end
@@ -355,18 +404,60 @@ module ActiveRecord
355
404
  execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
356
405
  end
357
406
 
407
+ # Builds a ChangeColumnDefinition object.
408
+ #
409
+ # This definition object contains information about the column change that would occur
410
+ # if the same arguments were passed to #change_column. See #change_column for information about
411
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
412
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
413
+ column = column_for(table_name, column_name)
414
+ type ||= column.sql_type
415
+
416
+ unless options.key?(:default)
417
+ options[:default] = column.default
418
+ end
419
+
420
+ unless options.key?(:null)
421
+ options[:null] = column.null
422
+ end
423
+
424
+ unless options.key?(:comment)
425
+ options[:comment] = column.comment
426
+ end
427
+
428
+ if options[:collation] == :no_collation
429
+ options.delete(:collation)
430
+ else
431
+ options[:collation] ||= column.collation if text_type?(type)
432
+ end
433
+
434
+ unless options.key?(:auto_increment)
435
+ options[:auto_increment] = column.auto_increment?
436
+ end
437
+
438
+ td = create_table_definition(table_name)
439
+ cd = td.new_column_definition(column.name, type, **options)
440
+ ChangeColumnDefinition.new(cd, column.name)
441
+ end
442
+
358
443
  def rename_column(table_name, column_name, new_column_name) # :nodoc:
359
444
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
360
445
  rename_column_indexes(table_name, column_name, new_column_name)
361
446
  end
362
447
 
363
448
  def add_index(table_name, column_name, **options) # :nodoc:
449
+ create_index = build_create_index_definition(table_name, column_name, **options)
450
+ return unless create_index
451
+
452
+ execute schema_creation.accept(create_index)
453
+ end
454
+
455
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
364
456
  index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
365
457
 
366
458
  return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
367
459
 
368
- create_index = CreateIndexDefinition.new(index, algorithm)
369
- execute schema_creation.accept(create_index)
460
+ CreateIndexDefinition.new(index, algorithm)
370
461
  end
371
462
 
372
463
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -379,11 +470,13 @@ module ActiveRecord
379
470
 
380
471
  scope = quoted_scope(table_name)
381
472
 
382
- fk_info = exec_query(<<~SQL, "SCHEMA")
473
+ # MySQL returns 1 row for each column of composite foreign keys.
474
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA")
383
475
  SELECT fk.referenced_table_name AS 'to_table',
384
476
  fk.referenced_column_name AS 'primary_key',
385
477
  fk.column_name AS 'column',
386
478
  fk.constraint_name AS 'name',
479
+ fk.ordinal_position AS 'position',
387
480
  rc.update_rule AS 'on_update',
388
481
  rc.delete_rule AS 'on_delete'
389
482
  FROM information_schema.referential_constraints rc
@@ -396,15 +489,22 @@ module ActiveRecord
396
489
  AND rc.table_name = #{scope[:name]}
397
490
  SQL
398
491
 
399
- fk_info.map do |row|
492
+ grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
493
+ grouped_fk.map do |group|
494
+ row = group.first
400
495
  options = {
401
- column: unquote_identifier(row["column"]),
402
496
  name: row["name"],
403
- primary_key: row["primary_key"]
497
+ on_update: extract_foreign_key_action(row["on_update"]),
498
+ on_delete: extract_foreign_key_action(row["on_delete"])
404
499
  }
405
500
 
406
- options[:on_update] = extract_foreign_key_action(row["on_update"])
407
- options[:on_delete] = extract_foreign_key_action(row["on_delete"])
501
+ if group.one?
502
+ options[:column] = unquote_identifier(row["column"])
503
+ options[:primary_key] = row["primary_key"]
504
+ else
505
+ options[:column] = group.map { |row| unquote_identifier(row["column"]) }
506
+ options[:primary_key] = group.map { |row| row["primary_key"] }
507
+ end
408
508
 
409
509
  ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
410
510
  end
@@ -426,7 +526,7 @@ module ActiveRecord
426
526
  SQL
427
527
  sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
428
528
 
429
- chk_info = exec_query(sql, "SCHEMA")
529
+ chk_info = internal_exec_query(sql, "SCHEMA")
430
530
 
431
531
  chk_info.map do |row|
432
532
  options = {
@@ -435,6 +535,13 @@ module ActiveRecord
435
535
  expression = row["expression"]
436
536
  expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
437
537
  expression = strip_whitespace_characters(expression)
538
+
539
+ unless mariadb?
540
+ # MySQL returns check constraints expression in an already escaped form.
541
+ # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
542
+ expression = expression.gsub("\\'", "'")
543
+ end
544
+
438
545
  CheckConstraintDefinition.new(table_name, expression, options)
439
546
  end
440
547
  else
@@ -532,40 +639,75 @@ module ActiveRecord
532
639
  end
533
640
 
534
641
  def build_insert_sql(insert) # :nodoc:
535
- sql = +"INSERT #{insert.into} #{insert.values_list}"
536
-
537
- if insert.skip_duplicates?
538
- no_op_column = quote_column_name(insert.keys.first)
539
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
540
- elsif insert.update_duplicates?
541
- sql << " ON DUPLICATE KEY UPDATE "
542
- if insert.raw_update_sql?
543
- sql << insert.raw_update_sql
544
- else
545
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
546
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
642
+ no_op_column = quote_column_name(insert.keys.first)
643
+
644
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
645
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
646
+ if supports_insert_raw_alias_syntax?
647
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
648
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
649
+
650
+ if insert.skip_duplicates?
651
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
652
+ elsif insert.update_duplicates?
653
+ if insert.raw_update_sql?
654
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
655
+ else
656
+ sql << " ON DUPLICATE KEY UPDATE "
657
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
658
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
659
+ end
660
+ end
661
+ else
662
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
663
+
664
+ if insert.skip_duplicates?
665
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
666
+ elsif insert.update_duplicates?
667
+ sql << " ON DUPLICATE KEY UPDATE "
668
+ if insert.raw_update_sql?
669
+ sql << insert.raw_update_sql
670
+ else
671
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
672
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
673
+ end
547
674
  end
548
675
  end
549
676
 
677
+ sql << " RETURNING #{insert.returning}" if insert.returning
550
678
  sql
551
679
  end
552
680
 
553
681
  def check_version # :nodoc:
554
682
  if database_version < "5.5.8"
555
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
683
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
684
+ end
685
+ end
686
+
687
+ #--
688
+ # QUOTING ==================================================
689
+ #++
690
+
691
+ # Quotes strings for use in SQL input.
692
+ def quote_string(string)
693
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
694
+ connection.escape(string)
556
695
  end
557
696
  end
558
697
 
559
698
  class << self
699
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
700
+ super(default_timezone: default_timezone).tap do |m|
701
+ if emulate_booleans
702
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
703
+ end
704
+ end
705
+ end
706
+
560
707
  private
561
708
  def initialize_type_map(m)
562
709
  super
563
710
 
564
- m.register_type(%r(char)i) do |sql_type|
565
- limit = extract_limit(sql_type)
566
- Type.lookup(:string, adapter: :mysql2, limit: limit)
567
- end
568
-
569
711
  m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
570
712
  m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
571
713
  m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -585,9 +727,6 @@ module ActiveRecord
585
727
 
586
728
  m.alias_type %r(year)i, "integer"
587
729
  m.alias_type %r(bit)i, "binary"
588
-
589
- m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
590
- m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
591
730
  end
592
731
 
593
732
  def register_integer_type(mapping, key, **options)
@@ -609,10 +748,8 @@ module ActiveRecord
609
748
  end
610
749
  end
611
750
 
612
- TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
613
- TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
614
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
615
- end
751
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
752
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
616
753
 
617
754
  private
618
755
  def strip_whitespace_characters(expression)
@@ -621,29 +758,45 @@ module ActiveRecord
621
758
  expression
622
759
  end
623
760
 
624
- def text_type?(type)
625
- TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
761
+ def extended_type_map_key
762
+ if @default_timezone
763
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
764
+ elsif emulate_booleans
765
+ EMULATE_BOOLEANS_TRUE
766
+ end
626
767
  end
627
768
 
628
- def type_map
629
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
630
- end
769
+ def handle_warnings(sql)
770
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
631
771
 
632
- def raw_execute(sql, name, async: false)
633
- materialize_transactions
634
- mark_transaction_written_if_write(sql)
772
+ @affected_rows_before_warnings = @raw_connection.affected_rows
773
+ warning_count = @raw_connection.warning_count
774
+ result = @raw_connection.query("SHOW WARNINGS")
775
+ result = [
776
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
777
+ ] if result.count == 0
778
+ result.each do |level, code, message|
779
+ warning = SQLWarning.new(message, code, level, sql, @pool)
780
+ next if warning_ignored?(warning)
635
781
 
636
- log(sql, name, async: async) do
637
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
638
- @connection.query(sql)
639
- end
782
+ ActiveRecord.db_warnings_action.call(warning)
640
783
  end
641
784
  end
642
785
 
786
+ def warning_ignored?(warning)
787
+ warning.level == "Note" || super
788
+ end
789
+
790
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
791
+ # made since we established the connection
792
+ def sync_timezone_changes(raw_connection)
793
+ end
794
+
643
795
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
644
796
  ER_DB_CREATE_EXISTS = 1007
645
797
  ER_FILSORT_ABORT = 1028
646
798
  ER_DUP_ENTRY = 1062
799
+ ER_SERVER_SHUTDOWN = 1053
647
800
  ER_NOT_NULL_VIOLATION = 1048
648
801
  ER_NO_REFERENCED_ROW = 1216
649
802
  ER_ROW_IS_REFERENCED = 1217
@@ -657,77 +810,59 @@ module ActiveRecord
657
810
  ER_CANNOT_CREATE_TABLE = 1005
658
811
  ER_LOCK_WAIT_TIMEOUT = 1205
659
812
  ER_QUERY_INTERRUPTED = 1317
813
+ ER_CONNECTION_KILLED = 1927
814
+ CR_SERVER_GONE_ERROR = 2006
815
+ CR_SERVER_LOST = 2013
660
816
  ER_QUERY_TIMEOUT = 3024
661
817
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
818
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
662
819
 
663
820
  def translate_exception(exception, message:, sql:, binds:)
664
821
  case error_number(exception)
665
822
  when nil
666
823
  if exception.message.match?(/MySQL client is not connected/i)
667
- ConnectionNotEstablished.new(exception)
824
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
668
825
  else
669
826
  super
670
827
  end
828
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
829
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
671
830
  when ER_DB_CREATE_EXISTS
672
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
831
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
673
832
  when ER_DUP_ENTRY
674
- RecordNotUnique.new(message, sql: sql, binds: binds)
833
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
675
834
  when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
676
- InvalidForeignKey.new(message, sql: sql, binds: binds)
835
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
677
836
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
678
- mismatched_foreign_key(message, sql: sql, binds: binds)
837
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
679
838
  when ER_CANNOT_CREATE_TABLE
680
839
  if message.include?("errno: 150")
681
- mismatched_foreign_key(message, sql: sql, binds: binds)
840
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
682
841
  else
683
842
  super
684
843
  end
685
844
  when ER_DATA_TOO_LONG
686
- ValueTooLong.new(message, sql: sql, binds: binds)
845
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
687
846
  when ER_OUT_OF_RANGE
688
- RangeError.new(message, sql: sql, binds: binds)
847
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
689
848
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
690
- NotNullViolation.new(message, sql: sql, binds: binds)
849
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
691
850
  when ER_LOCK_DEADLOCK
692
- Deadlocked.new(message, sql: sql, binds: binds)
851
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
693
852
  when ER_LOCK_WAIT_TIMEOUT
694
- LockWaitTimeout.new(message, sql: sql, binds: binds)
853
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
695
854
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
696
- StatementTimeout.new(message, sql: sql, binds: binds)
855
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
697
856
  when ER_QUERY_INTERRUPTED
698
- QueryCanceled.new(message, sql: sql, binds: binds)
857
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
699
858
  else
700
859
  super
701
860
  end
702
861
  end
703
862
 
704
863
  def change_column_for_alter(table_name, column_name, type, **options)
705
- column = column_for(table_name, column_name)
706
- type ||= column.sql_type
707
-
708
- unless options.key?(:default)
709
- options[:default] = column.default
710
- end
711
-
712
- unless options.key?(:null)
713
- options[:null] = column.null
714
- end
715
-
716
- unless options.key?(:comment)
717
- options[:comment] = column.comment
718
- end
719
-
720
- unless options.key?(:collation)
721
- options[:collation] = column.collation if text_type?(type)
722
- end
723
-
724
- unless options.key?(:auto_increment)
725
- options[:auto_increment] = column.auto_increment?
726
- end
727
-
728
- td = create_table_definition(table_name)
729
- cd = td.new_column_definition(column.name, type, **options)
730
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
864
+ cd = build_change_column_definition(table_name, column_name, type, **options)
865
+ schema_creation.accept(cd)
731
866
  end
732
867
 
733
868
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -741,7 +876,7 @@ module ActiveRecord
741
876
  comment: column.comment
742
877
  }
743
878
 
744
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
879
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
745
880
  td = create_table_definition(table_name)
746
881
  cd = td.new_column_definition(new_column_name, current_type, **options)
747
882
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -759,6 +894,10 @@ module ActiveRecord
759
894
  "DROP INDEX #{quote_column_name(index_name)}"
760
895
  end
761
896
 
897
+ def supports_insert_raw_alias_syntax?
898
+ !mariadb? && database_version >= "8.0.19"
899
+ end
900
+
762
901
  def supports_rename_index?
763
902
  if mariadb?
764
903
  database_version >= "10.5.2"
@@ -776,11 +915,9 @@ module ActiveRecord
776
915
  end
777
916
 
778
917
  def configure_connection
918
+ super
779
919
  variables = @config.fetch(:variables, {}).stringify_keys
780
920
 
781
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
782
- variables["sql_auto_is_null"] = 0
783
-
784
921
  # Increase timeout so the server doesn't disconnect us.
785
922
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
786
923
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -824,7 +961,7 @@ module ActiveRecord
824
961
  end.join(", ")
825
962
 
826
963
  # ...and send them all in one query
827
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
964
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
828
965
  end
829
966
 
830
967
  def column_definitions(table_name) # :nodoc:
@@ -834,7 +971,7 @@ module ActiveRecord
834
971
  end
835
972
 
836
973
  def create_table_info(table_name) # :nodoc:
837
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
974
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
838
975
  end
839
976
 
840
977
  def arel_visitor
@@ -845,18 +982,17 @@ module ActiveRecord
845
982
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
846
983
  end
847
984
 
848
- def mismatched_foreign_key(message, sql:, binds:)
985
+ def mismatched_foreign_key_details(message:, sql:)
986
+ foreign_key_pat =
987
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
988
+
849
989
  match = %r/
850
990
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
851
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
991
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
852
992
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
853
993
  /xmi.match(sql)
854
994
 
855
- options = {
856
- message: message,
857
- sql: sql,
858
- binds: binds,
859
- }
995
+ options = {}
860
996
 
861
997
  if match
862
998
  options[:table] = match[:table]
@@ -866,20 +1002,33 @@ module ActiveRecord
866
1002
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
867
1003
  end
868
1004
 
869
- MismatchedForeignKey.new(**options)
1005
+ options
870
1006
  end
871
1007
 
872
- def version_string(full_version_string)
873
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
874
- end
1008
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
1009
+ options = {
1010
+ message: message,
1011
+ sql: sql,
1012
+ binds: binds,
1013
+ connection_pool: connection_pool
1014
+ }
875
1015
 
876
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
877
- Type::ImmutableString.new(true: "1", false: "0", **args)
1016
+ if sql
1017
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
1018
+ else
1019
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
1020
+ end
1021
+
1022
+ MismatchedForeignKey.new(**options)
878
1023
  end
879
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
880
- Type::String.new(true: "1", false: "0", **args)
1024
+
1025
+ def version_string(full_version_string)
1026
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1027
+ matches[1]
1028
+ else
1029
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1030
+ end
881
1031
  end
882
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
883
1032
  end
884
1033
  end
885
1034
  end