activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  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 +18 -14
  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 +2 -4
  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 +378 -491
  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 +153 -70
  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 +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. 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,9 +170,8 @@ module ActiveRecord
138
170
  true
139
171
  end
140
172
 
141
- def field_ordered_value(column, values) # :nodoc:
142
- field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse])
143
- Arel::Nodes::Descending.new(field)
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
144
175
  end
145
176
 
146
177
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
@@ -187,7 +218,7 @@ module ActiveRecord
187
218
  update("SET FOREIGN_KEY_CHECKS = 0")
188
219
  yield
189
220
  ensure
190
- update("SET FOREIGN_KEY_CHECKS = #{old}")
221
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
191
222
  end
192
223
  end
193
224
 
@@ -195,33 +226,36 @@ module ActiveRecord
195
226
  # DATABASE STATEMENTS ======================================
196
227
  #++
197
228
 
198
- # Executes the SQL statement in the context of this connection.
199
- def execute(sql, name = nil, async: false)
200
- raw_execute(sql, name, async)
201
- end
202
-
203
229
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
204
230
  # to write stuff in an abstract way without concerning ourselves about whether it
205
231
  # needs to be explicitly freed or not.
206
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
207
- 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)
208
238
  end
209
239
 
210
240
  def begin_db_transaction # :nodoc:
211
- execute("BEGIN", "TRANSACTION")
241
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
212
242
  end
213
243
 
214
244
  def begin_isolated_db_transaction(isolation) # :nodoc:
215
- 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)
216
246
  begin_db_transaction
217
247
  end
218
248
 
219
249
  def commit_db_transaction # :nodoc:
220
- execute("COMMIT", "TRANSACTION")
250
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
221
251
  end
222
252
 
223
253
  def exec_rollback_db_transaction # :nodoc:
224
- 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)
225
259
  end
226
260
 
227
261
  def empty_insert_statement_value(primary_key = nil) # :nodoc:
@@ -301,11 +335,12 @@ module ActiveRecord
301
335
  #
302
336
  # Example:
303
337
  # rename_table('octopuses', 'octopi')
304
- 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]
305
340
  schema_cache.clear_data_source_cache!(table_name.to_s)
306
341
  schema_cache.clear_data_source_cache!(new_name.to_s)
307
342
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
308
- rename_table_indexes(table_name, new_name)
343
+ rename_table_indexes(table_name, new_name, **options)
309
344
  end
310
345
 
311
346
  # Drops a table from the database.
@@ -339,11 +374,20 @@ module ActiveRecord
339
374
  end
340
375
 
341
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
+
342
384
  default = extract_new_default_value(default_or_changes)
343
- change_column table_name, column_name, nil, default: default
385
+ ChangeColumnDefaultDefinition.new(column, default)
344
386
  end
345
387
 
346
388
  def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
389
+ validate_change_column_null_argument!(null)
390
+
347
391
  unless null || default.nil?
348
392
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
349
393
  end
@@ -360,18 +404,60 @@ module ActiveRecord
360
404
  execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
361
405
  end
362
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
+
363
443
  def rename_column(table_name, column_name, new_column_name) # :nodoc:
364
444
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
365
445
  rename_column_indexes(table_name, column_name, new_column_name)
366
446
  end
367
447
 
368
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:
369
456
  index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
370
457
 
371
458
  return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
372
459
 
373
- create_index = CreateIndexDefinition.new(index, algorithm)
374
- execute schema_creation.accept(create_index)
460
+ CreateIndexDefinition.new(index, algorithm)
375
461
  end
376
462
 
377
463
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -384,11 +470,13 @@ module ActiveRecord
384
470
 
385
471
  scope = quoted_scope(table_name)
386
472
 
387
- 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")
388
475
  SELECT fk.referenced_table_name AS 'to_table',
389
476
  fk.referenced_column_name AS 'primary_key',
390
477
  fk.column_name AS 'column',
391
478
  fk.constraint_name AS 'name',
479
+ fk.ordinal_position AS 'position',
392
480
  rc.update_rule AS 'on_update',
393
481
  rc.delete_rule AS 'on_delete'
394
482
  FROM information_schema.referential_constraints rc
@@ -401,17 +489,24 @@ module ActiveRecord
401
489
  AND rc.table_name = #{scope[:name]}
402
490
  SQL
403
491
 
404
- 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
405
495
  options = {
406
- column: row["column"],
407
496
  name: row["name"],
408
- 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"])
409
499
  }
410
500
 
411
- options[:on_update] = extract_foreign_key_action(row["on_update"])
412
- 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
413
508
 
414
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
509
+ ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
415
510
  end
416
511
  end
417
512
 
@@ -431,14 +526,22 @@ module ActiveRecord
431
526
  SQL
432
527
  sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
433
528
 
434
- chk_info = exec_query(sql, "SCHEMA")
529
+ chk_info = internal_exec_query(sql, "SCHEMA")
435
530
 
436
531
  chk_info.map do |row|
437
532
  options = {
438
533
  name: row["name"]
439
534
  }
440
535
  expression = row["expression"]
441
- expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
536
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
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
+
442
545
  CheckConstraintDefinition.new(table_name, expression, options)
443
546
  end
444
547
  else
@@ -536,40 +639,75 @@ module ActiveRecord
536
639
  end
537
640
 
538
641
  def build_insert_sql(insert) # :nodoc:
539
- sql = +"INSERT #{insert.into} #{insert.values_list}"
540
-
541
- if insert.skip_duplicates?
542
- no_op_column = quote_column_name(insert.keys.first)
543
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
544
- elsif insert.update_duplicates?
545
- sql << " ON DUPLICATE KEY UPDATE "
546
- if insert.raw_update_sql?
547
- sql << insert.raw_update_sql
548
- else
549
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
550
- 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
551
674
  end
552
675
  end
553
676
 
677
+ sql << " RETURNING #{insert.returning}" if insert.returning
554
678
  sql
555
679
  end
556
680
 
557
681
  def check_version # :nodoc:
558
682
  if database_version < "5.5.8"
559
- 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)
560
695
  end
561
696
  end
562
697
 
563
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
+
564
707
  private
565
708
  def initialize_type_map(m)
566
709
  super
567
710
 
568
- m.register_type(%r(char)i) do |sql_type|
569
- limit = extract_limit(sql_type)
570
- Type.lookup(:string, adapter: :mysql2, limit: limit)
571
- end
572
-
573
711
  m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
574
712
  m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
575
713
  m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -589,9 +727,6 @@ module ActiveRecord
589
727
 
590
728
  m.alias_type %r(year)i, "integer"
591
729
  m.alias_type %r(bit)i, "binary"
592
-
593
- m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
594
- m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
595
730
  end
596
731
 
597
732
  def register_integer_type(mapping, key, **options)
@@ -613,31 +748,55 @@ module ActiveRecord
613
748
  end
614
749
  end
615
750
 
616
- TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
617
- TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
618
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
619
- end
751
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
752
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
620
753
 
621
754
  private
622
- def type_map
623
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
755
+ def strip_whitespace_characters(expression)
756
+ expression = expression.gsub(/\\n|\\\\/, "")
757
+ expression = expression.gsub(/\s{2,}/, " ")
758
+ expression
624
759
  end
625
760
 
626
- def raw_execute(sql, name, async: false)
627
- materialize_transactions
628
- mark_transaction_written_if_write(sql)
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
767
+ end
629
768
 
630
- log(sql, name, async: async) do
631
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
632
- @connection.query(sql)
633
- end
769
+ def handle_warnings(sql)
770
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
771
+
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)
781
+
782
+ ActiveRecord.db_warnings_action.call(warning)
634
783
  end
635
784
  end
636
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
+
637
795
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
638
796
  ER_DB_CREATE_EXISTS = 1007
639
797
  ER_FILSORT_ABORT = 1028
640
798
  ER_DUP_ENTRY = 1062
799
+ ER_SERVER_SHUTDOWN = 1053
641
800
  ER_NOT_NULL_VIOLATION = 1048
642
801
  ER_NO_REFERENCED_ROW = 1216
643
802
  ER_ROW_IS_REFERENCED = 1217
@@ -651,69 +810,59 @@ module ActiveRecord
651
810
  ER_CANNOT_CREATE_TABLE = 1005
652
811
  ER_LOCK_WAIT_TIMEOUT = 1205
653
812
  ER_QUERY_INTERRUPTED = 1317
813
+ ER_CONNECTION_KILLED = 1927
814
+ CR_SERVER_GONE_ERROR = 2006
815
+ CR_SERVER_LOST = 2013
654
816
  ER_QUERY_TIMEOUT = 3024
655
817
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
818
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
656
819
 
657
820
  def translate_exception(exception, message:, sql:, binds:)
658
821
  case error_number(exception)
659
822
  when nil
660
823
  if exception.message.match?(/MySQL client is not connected/i)
661
- ConnectionNotEstablished.new(exception)
824
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
662
825
  else
663
826
  super
664
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)
665
830
  when ER_DB_CREATE_EXISTS
666
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
831
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
667
832
  when ER_DUP_ENTRY
668
- RecordNotUnique.new(message, sql: sql, binds: binds)
833
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
669
834
  when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
670
- InvalidForeignKey.new(message, sql: sql, binds: binds)
835
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
671
836
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
672
- mismatched_foreign_key(message, sql: sql, binds: binds)
837
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
673
838
  when ER_CANNOT_CREATE_TABLE
674
839
  if message.include?("errno: 150")
675
- mismatched_foreign_key(message, sql: sql, binds: binds)
840
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
676
841
  else
677
842
  super
678
843
  end
679
844
  when ER_DATA_TOO_LONG
680
- ValueTooLong.new(message, sql: sql, binds: binds)
845
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
681
846
  when ER_OUT_OF_RANGE
682
- RangeError.new(message, sql: sql, binds: binds)
847
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
683
848
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
684
- NotNullViolation.new(message, sql: sql, binds: binds)
849
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
685
850
  when ER_LOCK_DEADLOCK
686
- Deadlocked.new(message, sql: sql, binds: binds)
851
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
687
852
  when ER_LOCK_WAIT_TIMEOUT
688
- LockWaitTimeout.new(message, sql: sql, binds: binds)
853
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
689
854
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
690
- StatementTimeout.new(message, sql: sql, binds: binds)
855
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
691
856
  when ER_QUERY_INTERRUPTED
692
- QueryCanceled.new(message, sql: sql, binds: binds)
857
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
693
858
  else
694
859
  super
695
860
  end
696
861
  end
697
862
 
698
863
  def change_column_for_alter(table_name, column_name, type, **options)
699
- column = column_for(table_name, column_name)
700
- type ||= column.sql_type
701
-
702
- unless options.key?(:default)
703
- options[:default] = column.default
704
- end
705
-
706
- unless options.key?(:null)
707
- options[:null] = column.null
708
- end
709
-
710
- unless options.key?(:comment)
711
- options[:comment] = column.comment
712
- end
713
-
714
- td = create_table_definition(table_name)
715
- cd = td.new_column_definition(column.name, type, **options)
716
- 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)
717
866
  end
718
867
 
719
868
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -727,7 +876,7 @@ module ActiveRecord
727
876
  comment: column.comment
728
877
  }
729
878
 
730
- 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"]
731
880
  td = create_table_definition(table_name)
732
881
  cd = td.new_column_definition(new_column_name, current_type, **options)
733
882
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -745,6 +894,10 @@ module ActiveRecord
745
894
  "DROP INDEX #{quote_column_name(index_name)}"
746
895
  end
747
896
 
897
+ def supports_insert_raw_alias_syntax?
898
+ !mariadb? && database_version >= "8.0.19"
899
+ end
900
+
748
901
  def supports_rename_index?
749
902
  if mariadb?
750
903
  database_version >= "10.5.2"
@@ -762,11 +915,9 @@ module ActiveRecord
762
915
  end
763
916
 
764
917
  def configure_connection
918
+ super
765
919
  variables = @config.fetch(:variables, {}).stringify_keys
766
920
 
767
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
768
- variables["sql_auto_is_null"] = 0
769
-
770
921
  # Increase timeout so the server doesn't disconnect us.
771
922
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
772
923
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -810,7 +961,7 @@ module ActiveRecord
810
961
  end.join(", ")
811
962
 
812
963
  # ...and send them all in one query
813
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
964
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
814
965
  end
815
966
 
816
967
  def column_definitions(table_name) # :nodoc:
@@ -820,7 +971,7 @@ module ActiveRecord
820
971
  end
821
972
 
822
973
  def create_table_info(table_name) # :nodoc:
823
- 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"]
824
975
  end
825
976
 
826
977
  def arel_visitor
@@ -831,18 +982,17 @@ module ActiveRecord
831
982
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
832
983
  end
833
984
 
834
- 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
+
835
989
  match = %r/
836
990
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
837
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
991
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
838
992
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
839
993
  /xmi.match(sql)
840
994
 
841
- options = {
842
- message: message,
843
- sql: sql,
844
- binds: binds,
845
- }
995
+ options = {}
846
996
 
847
997
  if match
848
998
  options[:table] = match[:table]
@@ -852,20 +1002,33 @@ module ActiveRecord
852
1002
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
853
1003
  end
854
1004
 
855
- MismatchedForeignKey.new(**options)
1005
+ options
856
1006
  end
857
1007
 
858
- def version_string(full_version_string)
859
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
860
- 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
+ }
861
1015
 
862
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
863
- 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)
864
1023
  end
865
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
866
- 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
867
1031
  end
868
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
869
1032
  end
870
1033
  end
871
1034
  end