activerecord 7.2.4 → 8.1.4

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -28,6 +28,21 @@ module ActiveRecord
28
28
  # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
29
29
  class_attribute :emulate_booleans, default: true
30
30
 
31
+ class_attribute :cli_arg_map, instance_accessor: false, default: {
32
+ host: "--host",
33
+ port: "--port",
34
+ socket: "--socket",
35
+ username: "--user",
36
+ password: "--password",
37
+ encoding: "--default-character-set",
38
+ sslca: "--ssl-ca",
39
+ sslcert: "--ssl-cert",
40
+ sslcapath: "--ssl-capath",
41
+ sslcipher: "--ssl-cipher",
42
+ sslkey: "--ssl-key",
43
+ ssl_mode: "--ssl-mode"
44
+ }.freeze
45
+
31
46
  NATIVE_DATABASE_TYPES = {
32
47
  primary_key: "bigint auto_increment PRIMARY KEY",
33
48
  string: { name: "varchar", limit: 255 },
@@ -42,7 +57,7 @@ module ActiveRecord
42
57
  date: { name: "date" },
43
58
  binary: { name: "blob" },
44
59
  blob: { name: "blob" },
45
- boolean: { name: "tinyint", limit: 1 },
60
+ boolean: { name: "boolean" },
46
61
  json: { name: "json" },
47
62
  }
48
63
 
@@ -54,22 +69,14 @@ module ActiveRecord
54
69
  end
55
70
 
56
71
  class << self
72
+ def cli_args(config) # :nodoc:
73
+ cli_arg_map.filter_map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }
74
+ end
75
+
57
76
  def dbconsole(config, options = {})
58
77
  mysql_config = config.configuration_hash
59
78
 
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] }
79
+ args = cli_args(mysql_config.except(:password))
73
80
 
74
81
  if mysql_config[:password] && options[:include_password]
75
82
  args << "--password=#{mysql_config[:password]}"
@@ -79,7 +86,11 @@ module ActiveRecord
79
86
 
80
87
  args << config.database
81
88
 
82
- find_cmd_and_exec(["mysql", "mysql5"], *args)
89
+ find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
90
+ end
91
+
92
+ def native_database_types # :nodoc:
93
+ NATIVE_DATABASE_TYPES
83
94
  end
84
95
  end
85
96
 
@@ -98,7 +109,7 @@ module ActiveRecord
98
109
  end
99
110
 
100
111
  def supports_index_sort_order?
101
- !mariadb? && database_version >= "8.0.1"
112
+ mariadb? ? database_version >= "10.8.1" : database_version >= "8.0.1"
102
113
  end
103
114
 
104
115
  def supports_expression_index?
@@ -138,7 +149,7 @@ module ActiveRecord
138
149
  end
139
150
 
140
151
  def supports_datetime_with_precision?
141
- mariadb? || database_version >= "5.6.4"
152
+ true
142
153
  end
143
154
 
144
155
  def supports_virtual_columns?
@@ -178,6 +189,26 @@ module ActiveRecord
178
189
  supports_insert_returning? ? column.auto_populated? : column.auto_increment?
179
190
  end
180
191
 
192
+ # See https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html for more details on MySQL feature.
193
+ # See https://mariadb.com/kb/en/ignored-indexes/ for more details on the MariaDB feature.
194
+ def supports_disabling_indexes?
195
+ if mariadb?
196
+ database_version >= "10.6.0"
197
+ else
198
+ database_version >= "8.0.0"
199
+ end
200
+ end
201
+
202
+ def create_or_find_by_lock # :nodoc:
203
+ # A shared lock provides a current read under REPEATABLE READ without
204
+ # upgrading the shared record lock acquired by a duplicate INSERT.
205
+ if mariadb? || database_version < "8.0.1"
206
+ "LOCK IN SHARE MODE"
207
+ else
208
+ "FOR SHARE"
209
+ end
210
+ end
211
+
181
212
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
182
213
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
183
214
  end
@@ -186,10 +217,6 @@ module ActiveRecord
186
217
  query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
187
218
  end
188
219
 
189
- def native_database_types
190
- NATIVE_DATABASE_TYPES
191
- end
192
-
193
220
  def index_algorithms
194
221
  {
195
222
  default: "ALGORITHM = DEFAULT",
@@ -199,14 +226,6 @@ module ActiveRecord
199
226
  }
200
227
  end
201
228
 
202
- # HELPER METHODS ===========================================
203
-
204
- # The two drivers have slightly different ways of yielding hashes of results, so
205
- # this method must be implemented to provide a uniform interface.
206
- def each_hash(result) # :nodoc:
207
- raise NotImplementedError
208
- end
209
-
210
229
  # Must return the MySQL error number from the exception, if the exception has an
211
230
  # error number.
212
231
  def error_number(exception) # :nodoc:
@@ -230,24 +249,31 @@ module ActiveRecord
230
249
  # DATABASE STATEMENTS ======================================
231
250
  #++
232
251
 
233
- # Mysql2Adapter doesn't have to free a result after using it, but we use this method
234
- # to write stuff in an abstract way without concerning ourselves about whether it
235
- # needs to be explicitly freed or not.
236
- def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
237
- sql = transform_query(sql)
238
- check_if_write_query(sql)
239
-
240
- mark_transaction_written_if_write(sql)
241
- yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
242
- end
243
-
244
252
  def begin_db_transaction # :nodoc:
245
253
  internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
246
254
  end
247
255
 
248
256
  def begin_isolated_db_transaction(isolation) # :nodoc:
249
- internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
250
- begin_db_transaction
257
+ # From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
258
+ # So we don't need to implement #reset_isolation_level
259
+ if multi_statements_enabled?
260
+ execute_batch(
261
+ ["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
262
+ "TRANSACTION",
263
+ allow_retry: true,
264
+ materialize_transactions: false,
265
+ )
266
+ else
267
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do
268
+ internal_execute(
269
+ "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}",
270
+ "TRANSACTION",
271
+ allow_retry: false,
272
+ materialize_transactions: false,
273
+ )
274
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: false, materialize_transactions: false)
275
+ end
276
+ end
251
277
  end
252
278
 
253
279
  def commit_db_transaction # :nodoc:
@@ -347,7 +373,7 @@ module ActiveRecord
347
373
  rename_table_indexes(table_name, new_name, **options)
348
374
  end
349
375
 
350
- # Drops a table from the database.
376
+ # Drops a table or tables from the database.
351
377
  #
352
378
  # [<tt>:force</tt>]
353
379
  # Set to +:cascade+ to drop dependent objects as well.
@@ -361,10 +387,10 @@ module ActiveRecord
361
387
  #
362
388
  # Although this command ignores most +options+ and the block if one is given,
363
389
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
364
- # In that case, +options+ and the block will be used by create_table.
365
- def drop_table(table_name, **options)
366
- schema_cache.clear_data_source_cache!(table_name.to_s)
367
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
390
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
391
+ def drop_table(*table_names, **options)
392
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
393
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
368
394
  end
369
395
 
370
396
  def rename_index(table_name, old_name, new_name)
@@ -468,6 +494,24 @@ module ActiveRecord
468
494
  CreateIndexDefinition.new(index, algorithm)
469
495
  end
470
496
 
497
+ def enable_index(table_name, index_name) # :nodoc:
498
+ raise NotImplementedError unless supports_disabling_indexes?
499
+
500
+ query = <<~SQL
501
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{quote_column_name(index_name)} #{mariadb? ? "NOT IGNORED" : "VISIBLE"}
502
+ SQL
503
+ execute(query)
504
+ end
505
+
506
+ def disable_index(table_name, index_name) # :nodoc:
507
+ raise NotImplementedError unless supports_disabling_indexes?
508
+
509
+ query = <<~SQL
510
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{quote_column_name(index_name)} #{mariadb? ? "IGNORED" : "INVISIBLE"}
511
+ SQL
512
+ execute(query)
513
+ end
514
+
471
515
  def add_sql_comment!(sql, comment) # :nodoc:
472
516
  sql << " COMMENT #{quote(comment)}" if comment.present?
473
517
  sql
@@ -588,7 +632,7 @@ module ActiveRecord
588
632
 
589
633
  # SHOW VARIABLES LIKE 'name'
590
634
  def show_variable(name)
591
- query_value("SELECT @@#{name}", "SCHEMA")
635
+ query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
592
636
  rescue ActiveRecord::StatementInvalid
593
637
  nil
594
638
  end
@@ -693,8 +737,8 @@ module ActiveRecord
693
737
  end
694
738
 
695
739
  def check_version # :nodoc:
696
- if database_version < "5.5.8"
697
- raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
740
+ if database_version < "5.6.4"
741
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
698
742
  end
699
743
  end
700
744
 
@@ -743,12 +787,12 @@ module ActiveRecord
743
787
  m.alias_type %r(bit)i, "binary"
744
788
  end
745
789
 
746
- def register_integer_type(mapping, key, **options)
790
+ def register_integer_type(mapping, key, limit:)
747
791
  mapping.register_type(key) do |sql_type|
748
792
  if /\bunsigned\b/.match?(sql_type)
749
- Type::UnsignedInteger.new(**options)
793
+ Type::UnsignedInteger.new(limit: limit)
750
794
  else
751
- Type::Integer.new(**options)
795
+ Type::Integer.new(limit: limit)
752
796
  end
753
797
  end
754
798
  end
@@ -778,14 +822,13 @@ module ActiveRecord
778
822
  end
779
823
  end
780
824
 
781
- def handle_warnings(sql)
825
+ def handle_warnings(_initial_result, sql)
782
826
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
783
827
 
784
- @affected_rows_before_warnings = @raw_connection.affected_rows
785
828
  warning_count = @raw_connection.warning_count
786
829
  result = @raw_connection.query("SHOW WARNINGS")
787
830
  result = [
788
- ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
831
+ ["Warning", nil, "Query had warning_count=#{warning_count} but `SHOW WARNINGS` did not return the warnings. Check MySQL logs or database configuration."],
789
832
  ] if result.count == 0
790
833
  result.each do |level, code, message|
791
834
  warning = SQLWarning.new(message, code, level, sql, @pool)
@@ -799,14 +842,10 @@ module ActiveRecord
799
842
  warning.level == "Note" || super
800
843
  end
801
844
 
802
- # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
803
- # made since we established the connection
804
- def sync_timezone_changes(raw_connection)
805
- end
806
-
807
845
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
808
846
  ER_DB_CREATE_EXISTS = 1007
809
847
  ER_FILSORT_ABORT = 1028
848
+ ER_NO_DB_ERROR = 1046
810
849
  ER_DUP_ENTRY = 1062
811
850
  ER_SERVER_SHUTDOWN = 1053
812
851
  ER_NOT_NULL_VIOLATION = 1048
@@ -827,6 +866,8 @@ module ActiveRecord
827
866
  CR_SERVER_LOST = 2013
828
867
  ER_QUERY_TIMEOUT = 3024
829
868
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
869
+ ER_CHECK_CONSTRAINT_VIOLATED = 3819
870
+ ER_CONSTRAINT_FAILED = 4025
830
871
  ER_CLIENT_INTERACTION_TIMEOUT = 4031
831
872
 
832
873
  def translate_exception(exception, message:, sql:, binds:)
@@ -837,7 +878,7 @@ module ActiveRecord
837
878
  else
838
879
  super
839
880
  end
840
- when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
881
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT, ER_NO_DB_ERROR
841
882
  ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
842
883
  when ER_DB_CREATE_EXISTS
843
884
  DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
@@ -859,6 +900,8 @@ module ActiveRecord
859
900
  RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
860
901
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
861
902
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
903
+ when ER_CHECK_CONSTRAINT_VIOLATED, ER_CONSTRAINT_FAILED
904
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
862
905
  when ER_LOCK_DEADLOCK
863
906
  Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
864
907
  when ER_LOCK_WAIT_TIMEOUT
@@ -973,12 +1016,36 @@ module ActiveRecord
973
1016
  end.join(", ")
974
1017
 
975
1018
  # ...and send them all in one query
976
- internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
1019
+ raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
977
1020
  end
978
1021
 
979
1022
  def column_definitions(table_name) # :nodoc:
980
- execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
981
- each_hash(result)
1023
+ fields = internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA", allow_retry: true)
1024
+
1025
+ update_fields_for_mariadb(table_name, fields) if mariadb?
1026
+
1027
+ fields
1028
+ end
1029
+
1030
+ def update_fields_for_mariadb(table_name, fields)
1031
+ has_function_default_candidate = fields.any? do |field|
1032
+ default = field["Default"]
1033
+ default&.match?(/[a-zA-Z_]\w*\(/) && !/\ACURRENT_TIMESTAMP/i.match?(default)
1034
+ end
1035
+
1036
+ if has_function_default_candidate
1037
+ table_info = create_table_info(table_name)
1038
+ fields.each do |field|
1039
+ default = field["Default"]
1040
+ next unless default&.match?(/[a-zA-Z_]\w*\(/)
1041
+ next if /\ACURRENT_TIMESTAMP/i.match?(default)
1042
+
1043
+ field_name = field["Field"]
1044
+ match = table_info&.match(/`#{field_name}` .+ DEFAULT ('|\d+|[A-z]+)/)
1045
+ if match && match[1].match?(/\A[A-z]/)
1046
+ field["Extra"] = "DEFAULT_GENERATED"
1047
+ end
1048
+ end
982
1049
  end
983
1050
  end
984
1051
 
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  class Column
8
8
  include Deduplicable
9
9
 
10
- attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
10
+ attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment, :cast_type
11
11
 
12
12
  delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
13
13
 
@@ -17,8 +17,9 @@ module ActiveRecord
17
17
  # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
18
18
  # +sql_type_metadata+ is various information about the type of the column
19
19
  # +null+ determines if this column allows +NULL+ values.
20
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
20
+ def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
21
21
  @name = name.freeze
22
+ @cast_type = cast_type
22
23
  @sql_type_metadata = sql_type_metadata
23
24
  @null = null
24
25
  @default = default
@@ -27,6 +28,11 @@ module ActiveRecord
27
28
  @comment = comment
28
29
  end
29
30
 
31
+ def fetch_cast_type(connection) # :nodoc:
32
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
33
+ @cast_type || connection.lookup_cast_type(sql_type)
34
+ end
35
+
30
36
  def has_default?
31
37
  !default.nil? || default_function
32
38
  end
@@ -45,6 +51,7 @@ module ActiveRecord
45
51
 
46
52
  def init_with(coder)
47
53
  @name = coder["name"]
54
+ @cast_type = coder["cast_type"]
48
55
  @sql_type_metadata = coder["sql_type_metadata"]
49
56
  @null = coder["null"]
50
57
  @default = coder["default"]
@@ -55,6 +62,7 @@ module ActiveRecord
55
62
 
56
63
  def encode_with(coder)
57
64
  coder["name"] = @name
65
+ coder["cast_type"] = @cast_type
58
66
  coder["sql_type_metadata"] = @sql_type_metadata
59
67
  coder["null"] = @null
60
68
  coder["default"] = @default
@@ -75,6 +83,7 @@ module ActiveRecord
75
83
  def ==(other)
76
84
  other.is_a?(Column) &&
77
85
  name == other.name &&
86
+ cast_type == other.cast_type &&
78
87
  default == other.default &&
79
88
  sql_type_metadata == other.sql_type_metadata &&
80
89
  null == other.null &&
@@ -88,6 +97,7 @@ module ActiveRecord
88
97
  Column.hash ^
89
98
  name.hash ^
90
99
  name.encoding.hash ^
100
+ cast_type.hash ^
91
101
  default.hash ^
92
102
  sql_type_metadata.hash ^
93
103
  null.hash ^
@@ -104,7 +114,7 @@ module ActiveRecord
104
114
  def deduplicated
105
115
  @name = -name
106
116
  @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
107
- @default = -default if default
117
+ @default = -default if String === default
108
118
  @default_function = -default_function if default_function
109
119
  @collation = -collation if collation
110
120
  @comment = -comment if comment
@@ -114,7 +124,7 @@ module ActiveRecord
114
124
 
115
125
  class NullColumn < Column
116
126
  def initialize(name, **)
117
- super(name, nil)
127
+ super(name, nil, nil)
118
128
  end
119
129
  end
120
130
  end
@@ -45,16 +45,16 @@ module ActiveRecord
45
45
  end
46
46
  end
47
47
 
48
+ def default_insert_value(column) # :nodoc:
49
+ super unless column.auto_increment?
50
+ end
51
+
48
52
  private
49
53
  # https://mariadb.com/kb/en/analyze-statement/
50
54
  def analyze_without_explain?
51
55
  mariadb? && database_version >= "10.1.0"
52
56
  end
53
57
 
54
- def default_insert_value(column)
55
- super unless column.auto_increment?
56
- end
57
-
58
58
  def returning_column_values(result)
59
59
  if supports_insert_returning?
60
60
  result.rows.first
@@ -77,14 +77,6 @@ module ActiveRecord
77
77
  0
78
78
  end
79
79
 
80
- def quoted_date(value)
81
- if supports_datetime_with_precision?
82
- super
83
- else
84
- super.sub(/\.\d{6}\z/, "")
85
- end
86
- end
87
-
88
80
  def quoted_binary(value)
89
81
  "x'#{value.hex}'"
90
82
  end
@@ -49,6 +49,8 @@ module ActiveRecord
49
49
  sql << "USING #{o.using}" if o.using
50
50
  sql << "ON #{quote_table_name(o.table)}" if create
51
51
  sql << "(#{quoted_columns(o)})"
52
+ sql << "INVISIBLE" if o.disabled? && !mariadb?
53
+ sql << "IGNORED" if o.disabled? && mariadb?
52
54
 
53
55
  add_sql_comment!(sql.join(" "), o.comment)
54
56
  end
@@ -5,6 +5,7 @@ module ActiveRecord
5
5
  module MySQL
6
6
  module ColumnMethods
7
7
  extend ActiveSupport::Concern
8
+ extend ConnectionAdapters::ColumnMethods::ClassMethods
8
9
 
9
10
  ##
10
11
  # :method: blob
@@ -42,18 +43,26 @@ module ActiveRecord
42
43
  # :method: unsigned_bigint
43
44
  # :call-seq: unsigned_bigint(*names, **options)
44
45
 
45
- ##
46
- # :method: unsigned_float
47
- # :call-seq: unsigned_float(*names, **options)
46
+ define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
47
+ :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint
48
+ end
48
49
 
49
- ##
50
- # :method: unsigned_decimal
51
- # :call-seq: unsigned_decimal(*names, **options)
50
+ # = Active Record MySQL Adapter \Index Definition
51
+ class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition # :nodoc:
52
+ attr_accessor :enabled
53
+
54
+ def initialize(*args, **kwargs)
55
+ @enabled = kwargs.key?(:enabled) ? kwargs.delete(:enabled) : true
56
+ super
57
+ end
52
58
 
53
- included do
54
- define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
55
- :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
56
- :unsigned_float, :unsigned_decimal
59
+ def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, enabled: nil, **options)
60
+ super(columns, name:, unique:, valid:, include:, nulls_not_distinct:, **options) &&
61
+ (enabled.nil? || self.enabled == enabled)
62
+ end
63
+
64
+ def disabled?
65
+ !@enabled
57
66
  end
58
67
  end
59
68
 
@@ -106,6 +115,28 @@ module ActiveRecord
106
115
  # = Active Record MySQL Adapter \Table
107
116
  class Table < ActiveRecord::ConnectionAdapters::Table
108
117
  include ColumnMethods
118
+
119
+ # Enables an index to be used by query optimizers.
120
+ #
121
+ # t.enable_index(:email)
122
+ #
123
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
124
+ #
125
+ # See {connection.enable_index}[rdoc-ref:SchemaStatements#enable_index]
126
+ def enable_index(index_name)
127
+ @base.enable_index(name, index_name)
128
+ end
129
+
130
+ # Disables an index not to be used by query optimizers.
131
+ #
132
+ # t.disable_index(:email)
133
+ #
134
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
135
+ #
136
+ # See {connection.disable_index}[rdoc-ref:SchemaStatements#disable_index]
137
+ def disable_index(index_name)
138
+ @base.disable_index(name, index_name)
139
+ end
109
140
  end
110
141
  end
111
142
  end