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