activerecord 7.1.5.2 → 8.1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  date: { name: "date" },
43
43
  binary: { name: "blob" },
44
44
  blob: { name: "blob" },
45
- boolean: { name: "tinyint", limit: 1 },
45
+ boolean: { name: "boolean" },
46
46
  json: { name: "json" },
47
47
  }
48
48
 
@@ -79,7 +79,11 @@ module ActiveRecord
79
79
 
80
80
  args << config.database
81
81
 
82
- find_cmd_and_exec(["mysql", "mysql5"], *args)
82
+ find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
83
+ end
84
+
85
+ def native_database_types # :nodoc:
86
+ NATIVE_DATABASE_TYPES
83
87
  end
84
88
  end
85
89
 
@@ -98,7 +102,7 @@ module ActiveRecord
98
102
  end
99
103
 
100
104
  def supports_index_sort_order?
101
- !mariadb? && database_version >= "8.0.1"
105
+ mariadb? ? database_version >= "10.8.1" : database_version >= "8.0.1"
102
106
  end
103
107
 
104
108
  def supports_expression_index?
@@ -138,7 +142,7 @@ module ActiveRecord
138
142
  end
139
143
 
140
144
  def supports_datetime_with_precision?
141
- mariadb? || database_version >= "5.6.4"
145
+ true
142
146
  end
143
147
 
144
148
  def supports_virtual_columns?
@@ -170,6 +174,24 @@ module ActiveRecord
170
174
  true
171
175
  end
172
176
 
177
+ def supports_insert_returning?
178
+ mariadb? && database_version >= "10.5.0"
179
+ end
180
+
181
+ def return_value_after_insert?(column) # :nodoc:
182
+ supports_insert_returning? ? column.auto_populated? : column.auto_increment?
183
+ end
184
+
185
+ # See https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html for more details on MySQL feature.
186
+ # See https://mariadb.com/kb/en/ignored-indexes/ for more details on the MariaDB feature.
187
+ def supports_disabling_indexes?
188
+ if mariadb?
189
+ database_version >= "10.6.0"
190
+ else
191
+ database_version >= "8.0.0"
192
+ end
193
+ end
194
+
173
195
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
174
196
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
175
197
  end
@@ -178,10 +200,6 @@ module ActiveRecord
178
200
  query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
179
201
  end
180
202
 
181
- def native_database_types
182
- NATIVE_DATABASE_TYPES
183
- end
184
-
185
203
  def index_algorithms
186
204
  {
187
205
  default: "ALGORITHM = DEFAULT",
@@ -191,14 +209,6 @@ module ActiveRecord
191
209
  }
192
210
  end
193
211
 
194
- # HELPER METHODS ===========================================
195
-
196
- # The two drivers have slightly different ways of yielding hashes of results, so
197
- # this method must be implemented to provide a uniform interface.
198
- def each_hash(result) # :nodoc:
199
- raise NotImplementedError
200
- end
201
-
202
212
  # Must return the MySQL error number from the exception, if the exception has an
203
213
  # error number.
204
214
  def error_number(exception) # :nodoc:
@@ -214,7 +224,7 @@ module ActiveRecord
214
224
  update("SET FOREIGN_KEY_CHECKS = 0")
215
225
  yield
216
226
  ensure
217
- update("SET FOREIGN_KEY_CHECKS = #{old}")
227
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
218
228
  end
219
229
  end
220
230
 
@@ -222,24 +232,19 @@ module ActiveRecord
222
232
  # DATABASE STATEMENTS ======================================
223
233
  #++
224
234
 
225
- # Mysql2Adapter doesn't have to free a result after using it, but we use this method
226
- # to write stuff in an abstract way without concerning ourselves about whether it
227
- # needs to be explicitly freed or not.
228
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
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)
234
- end
235
-
236
235
  def begin_db_transaction # :nodoc:
237
236
  internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
238
237
  end
239
238
 
240
239
  def begin_isolated_db_transaction(isolation) # :nodoc:
241
- internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
242
- begin_db_transaction
240
+ # From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
241
+ # So we don't need to implement #reset_isolation_level
242
+ execute_batch(
243
+ ["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
244
+ "TRANSACTION",
245
+ allow_retry: true,
246
+ materialize_transactions: false,
247
+ )
243
248
  end
244
249
 
245
250
  def commit_db_transaction # :nodoc:
@@ -339,7 +344,7 @@ module ActiveRecord
339
344
  rename_table_indexes(table_name, new_name, **options)
340
345
  end
341
346
 
342
- # Drops a table from the database.
347
+ # Drops a table or tables from the database.
343
348
  #
344
349
  # [<tt>:force</tt>]
345
350
  # Set to +:cascade+ to drop dependent objects as well.
@@ -353,10 +358,10 @@ module ActiveRecord
353
358
  #
354
359
  # Although this command ignores most +options+ and the block if one is given,
355
360
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
356
- # In that case, +options+ and the block will be used by create_table.
357
- def drop_table(table_name, **options)
358
- schema_cache.clear_data_source_cache!(table_name.to_s)
359
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
361
+ # 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.
362
+ def drop_table(*table_names, **options)
363
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
364
+ 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}"
360
365
  end
361
366
 
362
367
  def rename_index(table_name, old_name, new_name)
@@ -410,7 +415,11 @@ module ActiveRecord
410
415
  type ||= column.sql_type
411
416
 
412
417
  unless options.key?(:default)
413
- options[:default] = column.default
418
+ options[:default] = if column.default_function
419
+ -> { column.default_function }
420
+ else
421
+ column.default
422
+ end
414
423
  end
415
424
 
416
425
  unless options.key?(:null)
@@ -456,6 +465,24 @@ module ActiveRecord
456
465
  CreateIndexDefinition.new(index, algorithm)
457
466
  end
458
467
 
468
+ def enable_index(table_name, index_name) # :nodoc:
469
+ raise NotImplementedError unless supports_disabling_indexes?
470
+
471
+ query = <<~SQL
472
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "NOT IGNORED" : "VISIBLE"}
473
+ SQL
474
+ execute(query)
475
+ end
476
+
477
+ def disable_index(table_name, index_name) # :nodoc:
478
+ raise NotImplementedError unless supports_disabling_indexes?
479
+
480
+ query = <<~SQL
481
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "IGNORED" : "INVISIBLE"}
482
+ SQL
483
+ execute(query)
484
+ end
485
+
459
486
  def add_sql_comment!(sql, comment) # :nodoc:
460
487
  sql << " COMMENT #{quote(comment)}" if comment.present?
461
488
  sql
@@ -576,7 +603,7 @@ module ActiveRecord
576
603
 
577
604
  # SHOW VARIABLES LIKE 'name'
578
605
  def show_variable(name)
579
- query_value("SELECT @@#{name}", "SCHEMA")
606
+ query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
580
607
  rescue ActiveRecord::StatementInvalid
581
608
  nil
582
609
  end
@@ -635,22 +662,26 @@ module ActiveRecord
635
662
  end
636
663
 
637
664
  def build_insert_sql(insert) # :nodoc:
638
- no_op_column = quote_column_name(insert.keys.first)
665
+ # Can use any column as it will be assigned to itself.
666
+ no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
639
667
 
640
668
  # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
641
669
  # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
642
670
  if supports_insert_raw_alias_syntax?
643
- values_alias = quote_table_name("#{insert.model.table_name}_values")
671
+ quoted_table_name = insert.model.quoted_table_name
672
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
644
673
  sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
645
674
 
646
675
  if insert.skip_duplicates?
647
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
676
+ if no_op_column
677
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
678
+ end
648
679
  elsif insert.update_duplicates?
649
680
  if insert.raw_update_sql?
650
681
  sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
651
682
  else
652
683
  sql << " ON DUPLICATE KEY UPDATE "
653
- sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
684
+ sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
654
685
  sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
655
686
  end
656
687
  end
@@ -658,7 +689,9 @@ module ActiveRecord
658
689
  sql = +"INSERT #{insert.into} #{insert.values_list}"
659
690
 
660
691
  if insert.skip_duplicates?
661
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
692
+ if no_op_column
693
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
694
+ end
662
695
  elsif insert.update_duplicates?
663
696
  sql << " ON DUPLICATE KEY UPDATE "
664
697
  if insert.raw_update_sql?
@@ -670,12 +703,24 @@ module ActiveRecord
670
703
  end
671
704
  end
672
705
 
706
+ sql << " RETURNING #{insert.returning}" if insert.returning
673
707
  sql
674
708
  end
675
709
 
676
710
  def check_version # :nodoc:
677
- if database_version < "5.5.8"
678
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
711
+ if database_version < "5.6.4"
712
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
713
+ end
714
+ end
715
+
716
+ #--
717
+ # QUOTING ==================================================
718
+ #++
719
+
720
+ # Quotes strings for use in SQL input.
721
+ def quote_string(string)
722
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
723
+ connection.escape(string)
679
724
  end
680
725
  end
681
726
 
@@ -713,12 +758,12 @@ module ActiveRecord
713
758
  m.alias_type %r(bit)i, "binary"
714
759
  end
715
760
 
716
- def register_integer_type(mapping, key, **options)
761
+ def register_integer_type(mapping, key, limit:)
717
762
  mapping.register_type(key) do |sql_type|
718
763
  if /\bunsigned\b/.match?(sql_type)
719
- Type::UnsignedInteger.new(**options)
764
+ Type::UnsignedInteger.new(limit: limit)
720
765
  else
721
- Type::Integer.new(**options)
766
+ Type::Integer.new(limit: limit)
722
767
  end
723
768
  end
724
769
  end
@@ -737,9 +782,7 @@ module ActiveRecord
737
782
 
738
783
  private
739
784
  def strip_whitespace_characters(expression)
740
- expression = expression.gsub(/\\n|\\\\/, "")
741
- expression = expression.gsub(/\s{2,}/, " ")
742
- expression
785
+ expression.gsub('\\\n', "").gsub("x0A", "").squish
743
786
  end
744
787
 
745
788
  def extended_type_map_key
@@ -750,11 +793,14 @@ module ActiveRecord
750
793
  end
751
794
  end
752
795
 
753
- def handle_warnings(sql)
796
+ def handle_warnings(_initial_result, sql)
754
797
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
755
798
 
756
- @affected_rows_before_warnings = @raw_connection.affected_rows
799
+ warning_count = @raw_connection.warning_count
757
800
  result = @raw_connection.query("SHOW WARNINGS")
801
+ result = [
802
+ ["Warning", nil, "Query had warning_count=#{warning_count} but `SHOW WARNINGS` did not return the warnings. Check MySQL logs or database configuration."],
803
+ ] if result.count == 0
758
804
  result.each do |level, code, message|
759
805
  warning = SQLWarning.new(message, code, level, sql, @pool)
760
806
  next if warning_ignored?(warning)
@@ -767,15 +813,11 @@ module ActiveRecord
767
813
  warning.level == "Note" || super
768
814
  end
769
815
 
770
- # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
771
- # made since we established the connection
772
- def sync_timezone_changes(raw_connection)
773
- end
774
-
775
816
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
776
817
  ER_DB_CREATE_EXISTS = 1007
777
818
  ER_FILSORT_ABORT = 1028
778
819
  ER_DUP_ENTRY = 1062
820
+ ER_SERVER_SHUTDOWN = 1053
779
821
  ER_NOT_NULL_VIOLATION = 1048
780
822
  ER_NO_REFERENCED_ROW = 1216
781
823
  ER_ROW_IS_REFERENCED = 1217
@@ -794,6 +836,8 @@ module ActiveRecord
794
836
  CR_SERVER_LOST = 2013
795
837
  ER_QUERY_TIMEOUT = 3024
796
838
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
839
+ ER_CHECK_CONSTRAINT_VIOLATED = 3819
840
+ ER_CONSTRAINT_FAILED = 4025
797
841
  ER_CLIENT_INTERACTION_TIMEOUT = 4031
798
842
 
799
843
  def translate_exception(exception, message:, sql:, binds:)
@@ -804,7 +848,7 @@ module ActiveRecord
804
848
  else
805
849
  super
806
850
  end
807
- when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
851
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
808
852
  ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
809
853
  when ER_DB_CREATE_EXISTS
810
854
  DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
@@ -826,6 +870,8 @@ module ActiveRecord
826
870
  RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
827
871
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
828
872
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
873
+ when ER_CHECK_CONSTRAINT_VIOLATED, ER_CONSTRAINT_FAILED
874
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
829
875
  when ER_LOCK_DEADLOCK
830
876
  Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
831
877
  when ER_LOCK_WAIT_TIMEOUT
@@ -894,6 +940,7 @@ module ActiveRecord
894
940
  end
895
941
 
896
942
  def configure_connection
943
+ super
897
944
  variables = @config.fetch(:variables, {}).stringify_keys
898
945
 
899
946
  # Increase timeout so the server doesn't disconnect us.
@@ -939,13 +986,11 @@ module ActiveRecord
939
986
  end.join(", ")
940
987
 
941
988
  # ...and send them all in one query
942
- internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
989
+ raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
943
990
  end
944
991
 
945
992
  def column_definitions(table_name) # :nodoc:
946
- execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
947
- each_hash(result)
948
- end
993
+ internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA", allow_retry: true)
949
994
  end
950
995
 
951
996
  def create_table_info(table_name) # :nodoc:
@@ -1001,7 +1046,11 @@ module ActiveRecord
1001
1046
  end
1002
1047
 
1003
1048
  def version_string(full_version_string)
1004
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
1049
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1050
+ matches[1]
1051
+ else
1052
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1053
+ end
1005
1054
  end
1006
1055
  end
1007
1056
  end
@@ -17,16 +17,22 @@ 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
- @default = default
25
+ @default = default.nil? || cast_type.mutable? ? default : cast_type.deserialize(default)
25
26
  @default_function = default_function
26
27
  @collation = collation
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 ^
@@ -100,11 +110,14 @@ module ActiveRecord
100
110
  false
101
111
  end
102
112
 
113
+ protected
114
+ attr_reader :cast_type
115
+
103
116
  private
104
117
  def deduplicated
105
118
  @name = -name
106
119
  @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
107
- @default = -default if default
120
+ @default = -default if String === default
108
121
  @default_function = -default_function if default_function
109
122
  @collation = -collation if collation
110
123
  @comment = -comment if comment
@@ -114,7 +127,7 @@ module ActiveRecord
114
127
 
115
128
  class NullColumn < Column
116
129
  def initialize(name, **)
117
- super(name, nil)
130
+ super(name, nil, nil)
118
131
  end
119
132
  end
120
133
  end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
13
13
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
14
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
14
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)", retryable: true).freeze # :nodoc:
15
15
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
16
16
 
17
17
  def write_query?(sql) # :nodoc:
@@ -45,14 +45,22 @@ 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?
58
+ def returning_column_values(result)
59
+ if supports_insert_returning?
60
+ result.rows.first
61
+ else
62
+ super
63
+ end
56
64
  end
57
65
 
58
66
  def combine_multi_statements(total_sql)
@@ -6,9 +6,52 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ extend ActiveSupport::Concern
10
+
9
11
  QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
12
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
13
 
14
+ module ClassMethods # :nodoc:
15
+ def column_name_matcher
16
+ /
17
+ \A
18
+ (
19
+ (?:
20
+ # `table_name`.`column_name` | function(one or no argument)
21
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
22
+ )
23
+ (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
24
+ )
25
+ (?:\s*,\s*\g<1>)*
26
+ \z
27
+ /ix
28
+ end
29
+
30
+ def column_name_with_order_matcher
31
+ /
32
+ \A
33
+ (
34
+ (?:
35
+ # `table_name`.`column_name` | function(one or no argument)
36
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
37
+ )
38
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
39
+ (?:\s+ASC|\s+DESC)?
40
+ )
41
+ (?:\s*,\s*\g<1>)*
42
+ \z
43
+ /ix
44
+ end
45
+
46
+ def quote_column_name(name)
47
+ QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub('`', '``')}`".freeze
48
+ end
49
+
50
+ def quote_table_name(name)
51
+ QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub('`', '``').gsub(".", "`.`")}`".freeze
52
+ end
53
+ end
54
+
12
55
  def cast_bound_value(value)
13
56
  case value
14
57
  when Rational
@@ -21,22 +64,11 @@ module ActiveRecord
21
64
  "1"
22
65
  when false
23
66
  "0"
24
- when ActiveSupport::Duration
25
- warn_quote_duration_deprecated
26
- value.to_s
27
67
  else
28
68
  value
29
69
  end
30
70
  end
31
71
 
32
- def quote_column_name(name)
33
- QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
34
- end
35
-
36
- def quote_table_name(name)
37
- QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
38
- end
39
-
40
72
  def unquoted_true
41
73
  1
42
74
  end
@@ -45,14 +77,6 @@ module ActiveRecord
45
77
  0
46
78
  end
47
79
 
48
- def quoted_date(value)
49
- if supports_datetime_with_precision?
50
- super
51
- else
52
- super.sub(/\.\d{6}\z/, "")
53
- end
54
- end
55
-
56
80
  def quoted_binary(value)
57
81
  "x'#{value.hex}'"
58
82
  end
@@ -78,49 +102,18 @@ module ActiveRecord
78
102
  else
79
103
  value.getlocal
80
104
  end
81
- when Date, Time
105
+ when Time
106
+ if default_timezone == :utc
107
+ value.utc? ? value : value.getutc
108
+ else
109
+ value.utc? ? value.getlocal : value
110
+ end
111
+ when Date
82
112
  value
83
113
  else
84
114
  super
85
115
  end
86
116
  end
87
-
88
- def column_name_matcher
89
- COLUMN_NAME
90
- end
91
-
92
- def column_name_with_order_matcher
93
- COLUMN_NAME_WITH_ORDER
94
- end
95
-
96
- COLUMN_NAME = /
97
- \A
98
- (
99
- (?:
100
- # `table_name`.`column_name` | function(one or no argument)
101
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
102
- )
103
- (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
104
- )
105
- (?:\s*,\s*\g<1>)*
106
- \z
107
- /ix
108
-
109
- COLUMN_NAME_WITH_ORDER = /
110
- \A
111
- (
112
- (?:
113
- # `table_name`.`column_name` | function(one or no argument)
114
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
115
- )
116
- (?:\s+COLLATE\s+(?:\w+|"\w+"))?
117
- (?:\s+ASC|\s+DESC)?
118
- )
119
- (?:\s*,\s*\g<1>)*
120
- \z
121
- /ix
122
-
123
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
124
117
  end
125
118
  end
126
119
  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