activerecord 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1607 -1040
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  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 +18 -3
  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 +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  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.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  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 +345 -219
  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 +40 -26
  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 +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  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 +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -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 +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  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 +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -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 +3 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  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 +358 -57
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +73 -96
  97. data/lib/active_record/core.rb +136 -148
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  100. data/lib/active_record/database_configurations/database_config.rb +9 -3
  101. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  102. data/lib/active_record/database_configurations/url_config.rb +17 -11
  103. data/lib/active_record/database_configurations.rb +87 -34
  104. data/lib/active_record/delegated_type.rb +9 -4
  105. data/lib/active_record/deprecator.rb +7 -0
  106. data/lib/active_record/destroy_association_async_job.rb +2 -0
  107. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  108. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  109. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  110. data/lib/active_record/encryption/config.rb +25 -1
  111. data/lib/active_record/encryption/configurable.rb +13 -14
  112. data/lib/active_record/encryption/context.rb +10 -3
  113. data/lib/active_record/encryption/contexts.rb +8 -4
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  116. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  118. data/lib/active_record/encryption/encryptor.rb +7 -7
  119. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  120. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
  121. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  122. data/lib/active_record/encryption/key_generator.rb +12 -1
  123. data/lib/active_record/encryption/message.rb +1 -1
  124. data/lib/active_record/encryption/message_serializer.rb +2 -0
  125. data/lib/active_record/encryption/properties.rb +4 -4
  126. data/lib/active_record/encryption/scheme.rb +20 -23
  127. data/lib/active_record/encryption.rb +1 -0
  128. data/lib/active_record/enum.rb +114 -27
  129. data/lib/active_record/errors.rb +108 -15
  130. data/lib/active_record/explain.rb +23 -3
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  133. data/lib/active_record/fixture_set/render_context.rb +2 -0
  134. data/lib/active_record/fixture_set/table_row.rb +29 -8
  135. data/lib/active_record/fixtures.rb +121 -73
  136. data/lib/active_record/future_result.rb +30 -5
  137. data/lib/active_record/gem_version.rb +2 -2
  138. data/lib/active_record/inheritance.rb +30 -16
  139. data/lib/active_record/insert_all.rb +55 -8
  140. data/lib/active_record/integration.rb +10 -10
  141. data/lib/active_record/internal_metadata.rb +118 -30
  142. data/lib/active_record/locking/optimistic.rb +32 -18
  143. data/lib/active_record/locking/pessimistic.rb +8 -5
  144. data/lib/active_record/log_subscriber.rb +39 -17
  145. data/lib/active_record/marshalling.rb +56 -0
  146. data/lib/active_record/message_pack.rb +124 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  148. data/lib/active_record/middleware/database_selector.rb +18 -13
  149. data/lib/active_record/middleware/shard_selector.rb +7 -5
  150. data/lib/active_record/migration/command_recorder.rb +104 -9
  151. data/lib/active_record/migration/compatibility.rb +158 -64
  152. data/lib/active_record/migration/default_strategy.rb +23 -0
  153. data/lib/active_record/migration/execution_strategy.rb +19 -0
  154. data/lib/active_record/migration.rb +271 -117
  155. data/lib/active_record/model_schema.rb +82 -50
  156. data/lib/active_record/nested_attributes.rb +23 -3
  157. data/lib/active_record/normalization.rb +159 -0
  158. data/lib/active_record/persistence.rb +200 -47
  159. data/lib/active_record/promise.rb +84 -0
  160. data/lib/active_record/query_cache.rb +3 -21
  161. data/lib/active_record/query_logs.rb +87 -51
  162. data/lib/active_record/query_logs_formatter.rb +41 -0
  163. data/lib/active_record/querying.rb +16 -3
  164. data/lib/active_record/railtie.rb +127 -61
  165. data/lib/active_record/railties/controller_runtime.rb +12 -8
  166. data/lib/active_record/railties/databases.rake +142 -143
  167. data/lib/active_record/railties/job_runtime.rb +23 -0
  168. data/lib/active_record/readonly_attributes.rb +32 -5
  169. data/lib/active_record/reflection.rb +177 -45
  170. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  171. data/lib/active_record/relation/batches.rb +190 -61
  172. data/lib/active_record/relation/calculations.rb +200 -83
  173. data/lib/active_record/relation/delegation.rb +23 -9
  174. data/lib/active_record/relation/finder_methods.rb +77 -16
  175. data/lib/active_record/relation/merger.rb +2 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  177. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  179. data/lib/active_record/relation/predicate_builder.rb +26 -14
  180. data/lib/active_record/relation/query_attribute.rb +25 -1
  181. data/lib/active_record/relation/query_methods.rb +429 -76
  182. data/lib/active_record/relation/spawn_methods.rb +18 -1
  183. data/lib/active_record/relation.rb +98 -41
  184. data/lib/active_record/result.rb +25 -9
  185. data/lib/active_record/runtime_registry.rb +10 -1
  186. data/lib/active_record/sanitization.rb +57 -16
  187. data/lib/active_record/schema.rb +36 -22
  188. data/lib/active_record/schema_dumper.rb +65 -23
  189. data/lib/active_record/schema_migration.rb +68 -33
  190. data/lib/active_record/scoping/default.rb +20 -12
  191. data/lib/active_record/scoping/named.rb +2 -2
  192. data/lib/active_record/scoping.rb +2 -1
  193. data/lib/active_record/secure_password.rb +60 -0
  194. data/lib/active_record/secure_token.rb +21 -3
  195. data/lib/active_record/serialization.rb +5 -0
  196. data/lib/active_record/signed_id.rb +9 -7
  197. data/lib/active_record/store.rb +16 -11
  198. data/lib/active_record/suppressor.rb +3 -1
  199. data/lib/active_record/table_metadata.rb +16 -3
  200. data/lib/active_record/tasks/database_tasks.rb +138 -107
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  204. data/lib/active_record/test_fixtures.rb +123 -99
  205. data/lib/active_record/timestamp.rb +26 -14
  206. data/lib/active_record/token_for.rb +113 -0
  207. data/lib/active_record/touch_later.rb +11 -6
  208. data/lib/active_record/transactions.rb +39 -13
  209. data/lib/active_record/translation.rb +1 -1
  210. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  211. data/lib/active_record/type/internal/timezone.rb +7 -2
  212. data/lib/active_record/type/serialized.rb +8 -4
  213. data/lib/active_record/type/time.rb +4 -0
  214. data/lib/active_record/validations/absence.rb +1 -1
  215. data/lib/active_record/validations/associated.rb +3 -3
  216. data/lib/active_record/validations/numericality.rb +5 -4
  217. data/lib/active_record/validations/presence.rb +5 -28
  218. data/lib/active_record/validations/uniqueness.rb +50 -5
  219. data/lib/active_record/validations.rb +8 -4
  220. data/lib/active_record/version.rb +1 -1
  221. data/lib/active_record.rb +143 -16
  222. data/lib/arel/errors.rb +10 -0
  223. data/lib/arel/factory_methods.rb +4 -0
  224. data/lib/arel/filter_predications.rb +1 -1
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/binary.rb +6 -1
  227. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  228. data/lib/arel/nodes/cte.rb +36 -0
  229. data/lib/arel/nodes/filter.rb +1 -1
  230. data/lib/arel/nodes/fragments.rb +35 -0
  231. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  232. data/lib/arel/nodes/leading_join.rb +8 -0
  233. data/lib/arel/nodes/node.rb +111 -2
  234. data/lib/arel/nodes/sql_literal.rb +6 -0
  235. data/lib/arel/nodes/table_alias.rb +4 -0
  236. data/lib/arel/nodes.rb +4 -0
  237. data/lib/arel/predications.rb +2 -0
  238. data/lib/arel/table.rb +9 -5
  239. data/lib/arel/visitors/mysql.rb +8 -1
  240. data/lib/arel/visitors/to_sql.rb +81 -17
  241. data/lib/arel/visitors/visitor.rb +2 -2
  242. data/lib/arel.rb +16 -2
  243. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  244. data/lib/rails/generators/active_record/migration.rb +3 -1
  245. data/lib/rails/generators/active_record/model/USAGE +113 -0
  246. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  247. metadata +50 -15
  248. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  249. 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,11 +170,6 @@ 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)
144
- end
145
-
146
173
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
147
174
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
148
175
  end
@@ -195,33 +222,36 @@ module ActiveRecord
195
222
  # DATABASE STATEMENTS ======================================
196
223
  #++
197
224
 
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
225
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
204
226
  # to write stuff in an abstract way without concerning ourselves about whether it
205
227
  # needs to be explicitly freed or not.
206
228
  def execute_and_free(sql, name = nil, async: false) # :nodoc:
207
- 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)
208
234
  end
209
235
 
210
236
  def begin_db_transaction # :nodoc:
211
- execute("BEGIN", "TRANSACTION")
237
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
212
238
  end
213
239
 
214
240
  def begin_isolated_db_transaction(isolation) # :nodoc:
215
- 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)
216
242
  begin_db_transaction
217
243
  end
218
244
 
219
245
  def commit_db_transaction # :nodoc:
220
- execute("COMMIT", "TRANSACTION")
246
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
221
247
  end
222
248
 
223
249
  def exec_rollback_db_transaction # :nodoc:
224
- 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)
225
255
  end
226
256
 
227
257
  def empty_insert_statement_value(primary_key = nil) # :nodoc:
@@ -301,7 +331,8 @@ module ActiveRecord
301
331
  #
302
332
  # Example:
303
333
  # rename_table('octopuses', 'octopi')
304
- 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]
305
336
  schema_cache.clear_data_source_cache!(table_name.to_s)
306
337
  schema_cache.clear_data_source_cache!(new_name.to_s)
307
338
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
@@ -339,11 +370,20 @@ module ActiveRecord
339
370
  end
340
371
 
341
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
+
342
380
  default = extract_new_default_value(default_or_changes)
343
- change_column table_name, column_name, nil, default: default
381
+ ChangeColumnDefaultDefinition.new(column, default)
344
382
  end
345
383
 
346
384
  def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
385
+ validate_change_column_null_argument!(null)
386
+
347
387
  unless null || default.nil?
348
388
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
349
389
  end
@@ -360,18 +400,60 @@ module ActiveRecord
360
400
  execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
361
401
  end
362
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
+
363
439
  def rename_column(table_name, column_name, new_column_name) # :nodoc:
364
440
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
365
441
  rename_column_indexes(table_name, column_name, new_column_name)
366
442
  end
367
443
 
368
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:
369
452
  index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
370
453
 
371
454
  return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
372
455
 
373
- create_index = CreateIndexDefinition.new(index, algorithm)
374
- execute schema_creation.accept(create_index)
456
+ CreateIndexDefinition.new(index, algorithm)
375
457
  end
376
458
 
377
459
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -384,11 +466,13 @@ module ActiveRecord
384
466
 
385
467
  scope = quoted_scope(table_name)
386
468
 
387
- 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")
388
471
  SELECT fk.referenced_table_name AS 'to_table',
389
472
  fk.referenced_column_name AS 'primary_key',
390
473
  fk.column_name AS 'column',
391
474
  fk.constraint_name AS 'name',
475
+ fk.ordinal_position AS 'position',
392
476
  rc.update_rule AS 'on_update',
393
477
  rc.delete_rule AS 'on_delete'
394
478
  FROM information_schema.referential_constraints rc
@@ -401,17 +485,24 @@ module ActiveRecord
401
485
  AND rc.table_name = #{scope[:name]}
402
486
  SQL
403
487
 
404
- 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
405
491
  options = {
406
- column: row["column"],
407
492
  name: row["name"],
408
- 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"])
409
495
  }
410
496
 
411
- options[:on_update] = extract_foreign_key_action(row["on_update"])
412
- 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
413
504
 
414
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
505
+ ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
415
506
  end
416
507
  end
417
508
 
@@ -431,14 +522,22 @@ module ActiveRecord
431
522
  SQL
432
523
  sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
433
524
 
434
- chk_info = exec_query(sql, "SCHEMA")
525
+ chk_info = internal_exec_query(sql, "SCHEMA")
435
526
 
436
527
  chk_info.map do |row|
437
528
  options = {
438
529
  name: row["name"]
439
530
  }
440
531
  expression = row["expression"]
441
- expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
532
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
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
+
442
541
  CheckConstraintDefinition.new(table_name, expression, options)
443
542
  end
444
543
  else
@@ -561,15 +660,18 @@ module ActiveRecord
561
660
  end
562
661
 
563
662
  class << self
663
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
664
+ super(default_timezone: default_timezone).tap do |m|
665
+ if emulate_booleans
666
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
667
+ end
668
+ end
669
+ end
670
+
564
671
  private
565
672
  def initialize_type_map(m)
566
673
  super
567
674
 
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
675
  m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
574
676
  m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
575
677
  m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -589,9 +691,6 @@ module ActiveRecord
589
691
 
590
692
  m.alias_type %r(year)i, "integer"
591
693
  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
694
  end
596
695
 
597
696
  def register_integer_type(mapping, key, **options)
@@ -613,27 +712,46 @@ module ActiveRecord
613
712
  end
614
713
  end
615
714
 
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
715
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
716
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
620
717
 
621
718
  private
622
- def type_map
623
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
719
+ def strip_whitespace_characters(expression)
720
+ expression = expression.gsub(/\\n|\\\\/, "")
721
+ expression = expression.gsub(/\s{2,}/, " ")
722
+ expression
624
723
  end
625
724
 
626
- def raw_execute(sql, name, async: false)
627
- materialize_transactions
628
- mark_transaction_written_if_write(sql)
725
+ def extended_type_map_key
726
+ if @default_timezone
727
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
728
+ elsif emulate_booleans
729
+ EMULATE_BOOLEANS_TRUE
730
+ end
731
+ end
629
732
 
630
- log(sql, name, async: async) do
631
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
632
- @connection.query(sql)
633
- end
733
+ def handle_warnings(sql)
734
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
735
+
736
+ @affected_rows_before_warnings = @raw_connection.affected_rows
737
+ result = @raw_connection.query("SHOW WARNINGS")
738
+ result.each do |level, code, message|
739
+ warning = SQLWarning.new(message, code, level, sql, @pool)
740
+ next if warning_ignored?(warning)
741
+
742
+ ActiveRecord.db_warnings_action.call(warning)
634
743
  end
635
744
  end
636
745
 
746
+ def warning_ignored?(warning)
747
+ warning.level == "Note" || super
748
+ end
749
+
750
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
751
+ # made since we established the connection
752
+ def sync_timezone_changes(raw_connection)
753
+ end
754
+
637
755
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
638
756
  ER_DB_CREATE_EXISTS = 1007
639
757
  ER_FILSORT_ABORT = 1028
@@ -651,69 +769,59 @@ module ActiveRecord
651
769
  ER_CANNOT_CREATE_TABLE = 1005
652
770
  ER_LOCK_WAIT_TIMEOUT = 1205
653
771
  ER_QUERY_INTERRUPTED = 1317
772
+ ER_CONNECTION_KILLED = 1927
773
+ CR_SERVER_GONE_ERROR = 2006
774
+ CR_SERVER_LOST = 2013
654
775
  ER_QUERY_TIMEOUT = 3024
655
776
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
777
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
656
778
 
657
779
  def translate_exception(exception, message:, sql:, binds:)
658
780
  case error_number(exception)
659
781
  when nil
660
782
  if exception.message.match?(/MySQL client is not connected/i)
661
- ConnectionNotEstablished.new(exception)
783
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
662
784
  else
663
785
  super
664
786
  end
787
+ when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
788
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
665
789
  when ER_DB_CREATE_EXISTS
666
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
790
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
667
791
  when ER_DUP_ENTRY
668
- RecordNotUnique.new(message, sql: sql, binds: binds)
792
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
669
793
  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)
794
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
671
795
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
672
- mismatched_foreign_key(message, sql: sql, binds: binds)
796
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
673
797
  when ER_CANNOT_CREATE_TABLE
674
798
  if message.include?("errno: 150")
675
- mismatched_foreign_key(message, sql: sql, binds: binds)
799
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
676
800
  else
677
801
  super
678
802
  end
679
803
  when ER_DATA_TOO_LONG
680
- ValueTooLong.new(message, sql: sql, binds: binds)
804
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
681
805
  when ER_OUT_OF_RANGE
682
- RangeError.new(message, sql: sql, binds: binds)
806
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
683
807
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
684
- NotNullViolation.new(message, sql: sql, binds: binds)
808
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
685
809
  when ER_LOCK_DEADLOCK
686
- Deadlocked.new(message, sql: sql, binds: binds)
810
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
687
811
  when ER_LOCK_WAIT_TIMEOUT
688
- LockWaitTimeout.new(message, sql: sql, binds: binds)
812
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
689
813
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
690
- StatementTimeout.new(message, sql: sql, binds: binds)
814
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
691
815
  when ER_QUERY_INTERRUPTED
692
- QueryCanceled.new(message, sql: sql, binds: binds)
816
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
693
817
  else
694
818
  super
695
819
  end
696
820
  end
697
821
 
698
822
  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))
823
+ cd = build_change_column_definition(table_name, column_name, type, **options)
824
+ schema_creation.accept(cd)
717
825
  end
718
826
 
719
827
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -727,7 +835,7 @@ module ActiveRecord
727
835
  comment: column.comment
728
836
  }
729
837
 
730
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
838
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
731
839
  td = create_table_definition(table_name)
732
840
  cd = td.new_column_definition(new_column_name, current_type, **options)
733
841
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -764,9 +872,6 @@ module ActiveRecord
764
872
  def configure_connection
765
873
  variables = @config.fetch(:variables, {}).stringify_keys
766
874
 
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
875
  # Increase timeout so the server doesn't disconnect us.
771
876
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
772
877
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -810,7 +915,7 @@ module ActiveRecord
810
915
  end.join(", ")
811
916
 
812
917
  # ...and send them all in one query
813
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
918
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
814
919
  end
815
920
 
816
921
  def column_definitions(table_name) # :nodoc:
@@ -820,7 +925,7 @@ module ActiveRecord
820
925
  end
821
926
 
822
927
  def create_table_info(table_name) # :nodoc:
823
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
928
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
824
929
  end
825
930
 
826
931
  def arel_visitor
@@ -831,18 +936,17 @@ module ActiveRecord
831
936
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
832
937
  end
833
938
 
834
- def mismatched_foreign_key(message, sql:, binds:)
939
+ def mismatched_foreign_key_details(message:, sql:)
940
+ foreign_key_pat =
941
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
942
+
835
943
  match = %r/
836
944
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
837
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
945
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
838
946
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
839
947
  /xmi.match(sql)
840
948
 
841
- options = {
842
- message: message,
843
- sql: sql,
844
- binds: binds,
845
- }
949
+ options = {}
846
950
 
847
951
  if match
848
952
  options[:table] = match[:table]
@@ -852,20 +956,29 @@ module ActiveRecord
852
956
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
853
957
  end
854
958
 
959
+ options
960
+ end
961
+
962
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
963
+ options = {
964
+ message: message,
965
+ sql: sql,
966
+ binds: binds,
967
+ connection_pool: connection_pool
968
+ }
969
+
970
+ if sql
971
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
972
+ else
973
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
974
+ end
975
+
855
976
  MismatchedForeignKey.new(**options)
856
977
  end
857
978
 
858
979
  def version_string(full_version_string)
859
980
  full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
860
981
  end
861
-
862
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
863
- Type::ImmutableString.new(true: "1", false: "0", **args)
864
- end
865
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
866
- Type::String.new(true: "1", false: "0", **args)
867
- end
868
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
869
982
  end
870
983
  end
871
984
  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)