activerecord 7.0.6 → 7.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1356 -1425
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -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 +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 +18 -10
  15. data/lib/active_record/associations/collection_proxy.rb +21 -11
  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 -7
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +6 -8
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +60 -18
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +496 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -113
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +14 -8
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +163 -81
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +142 -58
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +265 -112
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +186 -34
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +12 -8
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +27 -16
  165. data/lib/active_record/relation/query_attribute.rb +25 -1
  166. data/lib/active_record/relation/query_methods.rb +377 -69
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +11 -2
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +39 -13
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/serialized.rb +4 -0
  196. data/lib/active_record/type/time.rb +4 -0
  197. data/lib/active_record/validations/absence.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +5 -4
  199. data/lib/active_record/validations/presence.rb +5 -28
  200. data/lib/active_record/validations/uniqueness.rb +47 -2
  201. data/lib/active_record/validations.rb +8 -4
  202. data/lib/active_record/version.rb +1 -1
  203. data/lib/active_record.rb +121 -16
  204. data/lib/arel/errors.rb +10 -0
  205. data/lib/arel/factory_methods.rb +4 -0
  206. data/lib/arel/nodes/and.rb +4 -0
  207. data/lib/arel/nodes/binary.rb +6 -1
  208. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  209. data/lib/arel/nodes/cte.rb +36 -0
  210. data/lib/arel/nodes/fragments.rb +35 -0
  211. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  212. data/lib/arel/nodes/leading_join.rb +8 -0
  213. data/lib/arel/nodes/node.rb +111 -2
  214. data/lib/arel/nodes/sql_literal.rb +6 -0
  215. data/lib/arel/nodes/table_alias.rb +4 -0
  216. data/lib/arel/nodes.rb +4 -0
  217. data/lib/arel/predications.rb +2 -0
  218. data/lib/arel/table.rb +9 -5
  219. data/lib/arel/visitors/mysql.rb +8 -1
  220. data/lib/arel/visitors/to_sql.rb +81 -17
  221. data/lib/arel/visitors/visitor.rb +2 -2
  222. data/lib/arel.rb +16 -2
  223. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  224. data/lib/rails/generators/active_record/migration.rb +3 -1
  225. data/lib/rails/generators/active_record/model/USAGE +113 -0
  226. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  227. metadata +52 -17
  228. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  229. 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.map { |value| Arel::Nodes.build_quoted(value) }])
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: 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,15 +485,22 @@ 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: unquote_identifier(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
505
  ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
415
506
  end
@@ -431,14 +522,15 @@ 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)
442
534
  CheckConstraintDefinition.new(table_name, expression, options)
443
535
  end
444
536
  else
@@ -561,15 +653,18 @@ module ActiveRecord
561
653
  end
562
654
 
563
655
  class << self
656
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
657
+ super(default_timezone: default_timezone).tap do |m|
658
+ if emulate_booleans
659
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
660
+ end
661
+ end
662
+ end
663
+
564
664
  private
565
665
  def initialize_type_map(m)
566
666
  super
567
667
 
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
668
  m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
574
669
  m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
575
670
  m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -589,9 +684,6 @@ module ActiveRecord
589
684
 
590
685
  m.alias_type %r(year)i, "integer"
591
686
  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
687
  end
596
688
 
597
689
  def register_integer_type(mapping, key, **options)
@@ -613,31 +705,46 @@ module ActiveRecord
613
705
  end
614
706
  end
615
707
 
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
708
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
709
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
620
710
 
621
711
  private
622
- def text_type?(type)
623
- TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
712
+ def strip_whitespace_characters(expression)
713
+ expression = expression.gsub(/\\n|\\\\/, "")
714
+ expression = expression.gsub(/\s{2,}/, " ")
715
+ expression
624
716
  end
625
717
 
626
- def type_map
627
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
718
+ def extended_type_map_key
719
+ if @default_timezone
720
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
721
+ elsif emulate_booleans
722
+ EMULATE_BOOLEANS_TRUE
723
+ end
628
724
  end
629
725
 
630
- def raw_execute(sql, name, async: false)
631
- materialize_transactions
632
- mark_transaction_written_if_write(sql)
726
+ def handle_warnings(sql)
727
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
633
728
 
634
- log(sql, name, async: async) do
635
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
636
- @connection.query(sql)
637
- end
729
+ @affected_rows_before_warnings = @raw_connection.affected_rows
730
+ result = @raw_connection.query("SHOW WARNINGS")
731
+ result.each do |level, code, message|
732
+ warning = SQLWarning.new(message, code, level, sql, @pool)
733
+ next if warning_ignored?(warning)
734
+
735
+ ActiveRecord.db_warnings_action.call(warning)
638
736
  end
639
737
  end
640
738
 
739
+ def warning_ignored?(warning)
740
+ warning.level == "Note" || super
741
+ end
742
+
743
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
744
+ # made since we established the connection
745
+ def sync_timezone_changes(raw_connection)
746
+ end
747
+
641
748
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
642
749
  ER_DB_CREATE_EXISTS = 1007
643
750
  ER_FILSORT_ABORT = 1028
@@ -655,77 +762,59 @@ module ActiveRecord
655
762
  ER_CANNOT_CREATE_TABLE = 1005
656
763
  ER_LOCK_WAIT_TIMEOUT = 1205
657
764
  ER_QUERY_INTERRUPTED = 1317
765
+ ER_CONNECTION_KILLED = 1927
766
+ CR_SERVER_GONE_ERROR = 2006
767
+ CR_SERVER_LOST = 2013
658
768
  ER_QUERY_TIMEOUT = 3024
659
769
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
770
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
660
771
 
661
772
  def translate_exception(exception, message:, sql:, binds:)
662
773
  case error_number(exception)
663
774
  when nil
664
775
  if exception.message.match?(/MySQL client is not connected/i)
665
- ConnectionNotEstablished.new(exception)
776
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
666
777
  else
667
778
  super
668
779
  end
780
+ when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
781
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
669
782
  when ER_DB_CREATE_EXISTS
670
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
783
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
671
784
  when ER_DUP_ENTRY
672
- RecordNotUnique.new(message, sql: sql, binds: binds)
785
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
673
786
  when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
674
- InvalidForeignKey.new(message, sql: sql, binds: binds)
787
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
675
788
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
676
- mismatched_foreign_key(message, sql: sql, binds: binds)
789
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
677
790
  when ER_CANNOT_CREATE_TABLE
678
791
  if message.include?("errno: 150")
679
- mismatched_foreign_key(message, sql: sql, binds: binds)
792
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
680
793
  else
681
794
  super
682
795
  end
683
796
  when ER_DATA_TOO_LONG
684
- ValueTooLong.new(message, sql: sql, binds: binds)
797
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
685
798
  when ER_OUT_OF_RANGE
686
- RangeError.new(message, sql: sql, binds: binds)
799
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
687
800
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
688
- NotNullViolation.new(message, sql: sql, binds: binds)
801
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
689
802
  when ER_LOCK_DEADLOCK
690
- Deadlocked.new(message, sql: sql, binds: binds)
803
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
691
804
  when ER_LOCK_WAIT_TIMEOUT
692
- LockWaitTimeout.new(message, sql: sql, binds: binds)
805
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
693
806
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
694
- StatementTimeout.new(message, sql: sql, binds: binds)
807
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
695
808
  when ER_QUERY_INTERRUPTED
696
- QueryCanceled.new(message, sql: sql, binds: binds)
809
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
697
810
  else
698
811
  super
699
812
  end
700
813
  end
701
814
 
702
815
  def change_column_for_alter(table_name, column_name, type, **options)
703
- column = column_for(table_name, column_name)
704
- type ||= column.sql_type
705
-
706
- unless options.key?(:default)
707
- options[:default] = column.default
708
- end
709
-
710
- unless options.key?(:null)
711
- options[:null] = column.null
712
- end
713
-
714
- unless options.key?(:comment)
715
- options[:comment] = column.comment
716
- end
717
-
718
- unless options.key?(:collation)
719
- options[:collation] = column.collation if text_type?(type)
720
- end
721
-
722
- unless options.key?(:auto_increment)
723
- options[:auto_increment] = column.auto_increment?
724
- end
725
-
726
- td = create_table_definition(table_name)
727
- cd = td.new_column_definition(column.name, type, **options)
728
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
816
+ cd = build_change_column_definition(table_name, column_name, type, **options)
817
+ schema_creation.accept(cd)
729
818
  end
730
819
 
731
820
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -739,7 +828,7 @@ module ActiveRecord
739
828
  comment: column.comment
740
829
  }
741
830
 
742
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
831
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
743
832
  td = create_table_definition(table_name)
744
833
  cd = td.new_column_definition(new_column_name, current_type, **options)
745
834
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -776,9 +865,6 @@ module ActiveRecord
776
865
  def configure_connection
777
866
  variables = @config.fetch(:variables, {}).stringify_keys
778
867
 
779
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
780
- variables["sql_auto_is_null"] = 0
781
-
782
868
  # Increase timeout so the server doesn't disconnect us.
783
869
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
784
870
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -822,7 +908,7 @@ module ActiveRecord
822
908
  end.join(", ")
823
909
 
824
910
  # ...and send them all in one query
825
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
911
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
826
912
  end
827
913
 
828
914
  def column_definitions(table_name) # :nodoc:
@@ -832,7 +918,7 @@ module ActiveRecord
832
918
  end
833
919
 
834
920
  def create_table_info(table_name) # :nodoc:
835
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
921
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
836
922
  end
837
923
 
838
924
  def arel_visitor
@@ -843,18 +929,17 @@ module ActiveRecord
843
929
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
844
930
  end
845
931
 
846
- def mismatched_foreign_key(message, sql:, binds:)
932
+ def mismatched_foreign_key_details(message:, sql:)
933
+ foreign_key_pat =
934
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
935
+
847
936
  match = %r/
848
937
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
849
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
938
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
850
939
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
851
940
  /xmi.match(sql)
852
941
 
853
- options = {
854
- message: message,
855
- sql: sql,
856
- binds: binds,
857
- }
942
+ options = {}
858
943
 
859
944
  if match
860
945
  options[:table] = match[:table]
@@ -864,20 +949,29 @@ module ActiveRecord
864
949
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
865
950
  end
866
951
 
952
+ options
953
+ end
954
+
955
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
956
+ options = {
957
+ message: message,
958
+ sql: sql,
959
+ binds: binds,
960
+ connection_pool: connection_pool
961
+ }
962
+
963
+ if sql
964
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
965
+ else
966
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
967
+ end
968
+
867
969
  MismatchedForeignKey.new(**options)
868
970
  end
869
971
 
870
972
  def version_string(full_version_string)
871
973
  full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
872
974
  end
873
-
874
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
875
- Type::ImmutableString.new(true: "1", false: "0", **args)
876
- end
877
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
878
- Type::String.new(true: "1", false: "0", **args)
879
- end
880
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
881
975
  end
882
976
  end
883
977
  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)