activerecord 7.0.8.1 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  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 +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  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 +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  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 +11 -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 +64 -50
  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 +11 -25
  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 +323 -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 +217 -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 +307 -129
  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 +278 -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 +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  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 +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  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 +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  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/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +3 -3
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. 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,79 @@ 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) if 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
+ if no_op_column
652
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
653
+ end
654
+ elsif insert.update_duplicates?
655
+ if insert.raw_update_sql?
656
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
657
+ else
658
+ sql << " ON DUPLICATE KEY UPDATE "
659
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
660
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
661
+ end
662
+ end
663
+ else
664
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
665
+
666
+ if insert.skip_duplicates?
667
+ if no_op_column
668
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
669
+ end
670
+ elsif insert.update_duplicates?
671
+ sql << " ON DUPLICATE KEY UPDATE "
672
+ if insert.raw_update_sql?
673
+ sql << insert.raw_update_sql
674
+ else
675
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
676
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
677
+ end
547
678
  end
548
679
  end
549
680
 
681
+ sql << " RETURNING #{insert.returning}" if insert.returning
550
682
  sql
551
683
  end
552
684
 
553
685
  def check_version # :nodoc:
554
686
  if database_version < "5.5.8"
555
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
687
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
688
+ end
689
+ end
690
+
691
+ #--
692
+ # QUOTING ==================================================
693
+ #++
694
+
695
+ # Quotes strings for use in SQL input.
696
+ def quote_string(string)
697
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
698
+ connection.escape(string)
556
699
  end
557
700
  end
558
701
 
559
702
  class << self
703
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
704
+ super(default_timezone: default_timezone).tap do |m|
705
+ if emulate_booleans
706
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
707
+ end
708
+ end
709
+ end
710
+
560
711
  private
561
712
  def initialize_type_map(m)
562
713
  super
563
714
 
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
715
  m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
570
716
  m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
571
717
  m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -585,9 +731,6 @@ module ActiveRecord
585
731
 
586
732
  m.alias_type %r(year)i, "integer"
587
733
  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
734
  end
592
735
 
593
736
  def register_integer_type(mapping, key, **options)
@@ -609,10 +752,8 @@ module ActiveRecord
609
752
  end
610
753
  end
611
754
 
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
755
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
756
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
616
757
 
617
758
  private
618
759
  def strip_whitespace_characters(expression)
@@ -621,29 +762,45 @@ module ActiveRecord
621
762
  expression
622
763
  end
623
764
 
624
- def text_type?(type)
625
- TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
765
+ def extended_type_map_key
766
+ if @default_timezone
767
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
768
+ elsif emulate_booleans
769
+ EMULATE_BOOLEANS_TRUE
770
+ end
626
771
  end
627
772
 
628
- def type_map
629
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
630
- end
773
+ def handle_warnings(sql)
774
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
631
775
 
632
- def raw_execute(sql, name, async: false)
633
- materialize_transactions
634
- mark_transaction_written_if_write(sql)
776
+ @affected_rows_before_warnings = @raw_connection.affected_rows
777
+ warning_count = @raw_connection.warning_count
778
+ result = @raw_connection.query("SHOW WARNINGS")
779
+ result = [
780
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
781
+ ] if result.count == 0
782
+ result.each do |level, code, message|
783
+ warning = SQLWarning.new(message, code, level, sql, @pool)
784
+ next if warning_ignored?(warning)
635
785
 
636
- log(sql, name, async: async) do
637
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
638
- @connection.query(sql)
639
- end
786
+ ActiveRecord.db_warnings_action.call(warning)
640
787
  end
641
788
  end
642
789
 
790
+ def warning_ignored?(warning)
791
+ warning.level == "Note" || super
792
+ end
793
+
794
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
795
+ # made since we established the connection
796
+ def sync_timezone_changes(raw_connection)
797
+ end
798
+
643
799
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
644
800
  ER_DB_CREATE_EXISTS = 1007
645
801
  ER_FILSORT_ABORT = 1028
646
802
  ER_DUP_ENTRY = 1062
803
+ ER_SERVER_SHUTDOWN = 1053
647
804
  ER_NOT_NULL_VIOLATION = 1048
648
805
  ER_NO_REFERENCED_ROW = 1216
649
806
  ER_ROW_IS_REFERENCED = 1217
@@ -657,77 +814,59 @@ module ActiveRecord
657
814
  ER_CANNOT_CREATE_TABLE = 1005
658
815
  ER_LOCK_WAIT_TIMEOUT = 1205
659
816
  ER_QUERY_INTERRUPTED = 1317
817
+ ER_CONNECTION_KILLED = 1927
818
+ CR_SERVER_GONE_ERROR = 2006
819
+ CR_SERVER_LOST = 2013
660
820
  ER_QUERY_TIMEOUT = 3024
661
821
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
822
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
662
823
 
663
824
  def translate_exception(exception, message:, sql:, binds:)
664
825
  case error_number(exception)
665
826
  when nil
666
827
  if exception.message.match?(/MySQL client is not connected/i)
667
- ConnectionNotEstablished.new(exception)
828
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
668
829
  else
669
830
  super
670
831
  end
832
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
833
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
671
834
  when ER_DB_CREATE_EXISTS
672
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
835
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
673
836
  when ER_DUP_ENTRY
674
- RecordNotUnique.new(message, sql: sql, binds: binds)
837
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
675
838
  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)
839
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
677
840
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
678
- mismatched_foreign_key(message, sql: sql, binds: binds)
841
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
679
842
  when ER_CANNOT_CREATE_TABLE
680
843
  if message.include?("errno: 150")
681
- mismatched_foreign_key(message, sql: sql, binds: binds)
844
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
682
845
  else
683
846
  super
684
847
  end
685
848
  when ER_DATA_TOO_LONG
686
- ValueTooLong.new(message, sql: sql, binds: binds)
849
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
687
850
  when ER_OUT_OF_RANGE
688
- RangeError.new(message, sql: sql, binds: binds)
851
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
689
852
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
690
- NotNullViolation.new(message, sql: sql, binds: binds)
853
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
691
854
  when ER_LOCK_DEADLOCK
692
- Deadlocked.new(message, sql: sql, binds: binds)
855
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
693
856
  when ER_LOCK_WAIT_TIMEOUT
694
- LockWaitTimeout.new(message, sql: sql, binds: binds)
857
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
695
858
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
696
- StatementTimeout.new(message, sql: sql, binds: binds)
859
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
697
860
  when ER_QUERY_INTERRUPTED
698
- QueryCanceled.new(message, sql: sql, binds: binds)
861
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
699
862
  else
700
863
  super
701
864
  end
702
865
  end
703
866
 
704
867
  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))
868
+ cd = build_change_column_definition(table_name, column_name, type, **options)
869
+ schema_creation.accept(cd)
731
870
  end
732
871
 
733
872
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -741,7 +880,7 @@ module ActiveRecord
741
880
  comment: column.comment
742
881
  }
743
882
 
744
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
883
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
745
884
  td = create_table_definition(table_name)
746
885
  cd = td.new_column_definition(new_column_name, current_type, **options)
747
886
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -759,6 +898,10 @@ module ActiveRecord
759
898
  "DROP INDEX #{quote_column_name(index_name)}"
760
899
  end
761
900
 
901
+ def supports_insert_raw_alias_syntax?
902
+ !mariadb? && database_version >= "8.0.19"
903
+ end
904
+
762
905
  def supports_rename_index?
763
906
  if mariadb?
764
907
  database_version >= "10.5.2"
@@ -776,11 +919,9 @@ module ActiveRecord
776
919
  end
777
920
 
778
921
  def configure_connection
922
+ super
779
923
  variables = @config.fetch(:variables, {}).stringify_keys
780
924
 
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
925
  # Increase timeout so the server doesn't disconnect us.
785
926
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
786
927
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -824,7 +965,7 @@ module ActiveRecord
824
965
  end.join(", ")
825
966
 
826
967
  # ...and send them all in one query
827
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
968
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
828
969
  end
829
970
 
830
971
  def column_definitions(table_name) # :nodoc:
@@ -834,7 +975,7 @@ module ActiveRecord
834
975
  end
835
976
 
836
977
  def create_table_info(table_name) # :nodoc:
837
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
978
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
838
979
  end
839
980
 
840
981
  def arel_visitor
@@ -845,18 +986,17 @@ module ActiveRecord
845
986
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
846
987
  end
847
988
 
848
- def mismatched_foreign_key(message, sql:, binds:)
989
+ def mismatched_foreign_key_details(message:, sql:)
990
+ foreign_key_pat =
991
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
992
+
849
993
  match = %r/
850
994
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
851
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
995
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
852
996
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
853
997
  /xmi.match(sql)
854
998
 
855
- options = {
856
- message: message,
857
- sql: sql,
858
- binds: binds,
859
- }
999
+ options = {}
860
1000
 
861
1001
  if match
862
1002
  options[:table] = match[:table]
@@ -866,20 +1006,33 @@ module ActiveRecord
866
1006
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
867
1007
  end
868
1008
 
869
- MismatchedForeignKey.new(**options)
1009
+ options
870
1010
  end
871
1011
 
872
- def version_string(full_version_string)
873
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
874
- end
1012
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
1013
+ options = {
1014
+ message: message,
1015
+ sql: sql,
1016
+ binds: binds,
1017
+ connection_pool: connection_pool
1018
+ }
1019
+
1020
+ if sql
1021
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
1022
+ else
1023
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
1024
+ end
875
1025
 
876
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
877
- Type::ImmutableString.new(true: "1", false: "0", **args)
1026
+ MismatchedForeignKey.new(**options)
878
1027
  end
879
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
880
- Type::String.new(true: "1", false: "0", **args)
1028
+
1029
+ def version_string(full_version_string)
1030
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1031
+ matches[1]
1032
+ else
1033
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1034
+ end
881
1035
  end
882
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
883
1036
  end
884
1037
  end
885
1038
  end