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
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  sql
15
15
  end
16
16
 
17
- def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
17
+ def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_retry = false) # :nodoc:
18
18
  # Arel::TreeManager -> Arel::Node
19
19
  if arel_or_sql_string.respond_to?(:ast)
20
20
  arel_or_sql_string = arel_or_sql_string.ast
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  end
28
28
 
29
29
  collector = collector()
30
+ collector.retryable = true
30
31
 
31
32
  if prepared_statements
32
33
  collector.preparable = true
@@ -41,10 +42,11 @@ module ActiveRecord
41
42
  else
42
43
  sql = visitor.compile(arel_or_sql_string, collector)
43
44
  end
44
- [sql.freeze, binds, preparable]
45
+ allow_retry = collector.retryable
46
+ [sql.freeze, binds, preparable, allow_retry]
45
47
  else
46
48
  arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
47
- [arel_or_sql_string, binds, preparable]
49
+ [arel_or_sql_string, binds, preparable, allow_retry]
48
50
  end
49
51
  end
50
52
  private :to_sql_and_binds
@@ -53,22 +55,29 @@ module ActiveRecord
53
55
  # can be used to query the database repeatedly.
54
56
  def cacheable_query(klass, arel) # :nodoc:
55
57
  if prepared_statements
58
+ collector = collector()
59
+ collector.retryable = true
56
60
  sql, binds = visitor.compile(arel.ast, collector)
57
- query = klass.query(sql)
61
+ query = klass.query(sql, retryable: collector.retryable)
58
62
  else
59
63
  collector = klass.partial_query_collector
64
+ collector.retryable = true
60
65
  parts, binds = visitor.compile(arel.ast, collector)
61
- query = klass.partial_query(parts)
66
+ query = klass.partial_query(parts, retryable: collector.retryable)
62
67
  end
63
68
  [query, binds]
64
69
  end
65
70
 
66
71
  # Returns an ActiveRecord::Result instance.
67
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
72
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
68
73
  arel = arel_from_relation(arel)
69
- sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
74
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
70
75
 
71
- select(sql, name, binds, prepare: prepared_statements && preparable, async: async && FutureResult::SelectAll)
76
+ select(sql, name, binds,
77
+ prepare: prepared_statements && preparable,
78
+ async: async && FutureResult::SelectAll,
79
+ allow_retry: allow_retry
80
+ )
72
81
  rescue ::RangeError
73
82
  ActiveRecord::Result.empty(async: async)
74
83
  end
@@ -96,16 +105,16 @@ module ActiveRecord
96
105
  select_all(arel, name, binds, async: async).then(&:rows)
97
106
  end
98
107
 
99
- def query_value(sql, name = nil) # :nodoc:
100
- single_value_from_rows(query(sql, name))
108
+ def query_value(...) # :nodoc:
109
+ single_value_from_rows(query(...))
101
110
  end
102
111
 
103
- def query_values(sql, name = nil) # :nodoc:
104
- query(sql, name).map(&:first)
112
+ def query_values(...) # :nodoc:
113
+ query(...).map(&:first)
105
114
  end
106
115
 
107
- def query(sql, name = nil) # :nodoc:
108
- internal_exec_query(sql, name).rows
116
+ def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
117
+ internal_exec_query(sql, name, allow_retry:, materialize_transactions:).rows
109
118
  end
110
119
 
111
120
  # Determines whether the SQL statement is a write query.
@@ -157,14 +166,14 @@ module ActiveRecord
157
166
  # +binds+ as the bind substitutes. +name+ is logged along with
158
167
  # the executed +sql+ statement.
159
168
  def exec_delete(sql, name = nil, binds = [])
160
- internal_exec_query(sql, name, binds)
169
+ affected_rows(internal_execute(sql, name, binds))
161
170
  end
162
171
 
163
172
  # Executes update +sql+ statement in the context of this connection using
164
173
  # +binds+ as the bind substitutes. +name+ is logged along with
165
174
  # the executed +sql+ statement.
166
175
  def exec_update(sql, name = nil, binds = [])
167
- internal_exec_query(sql, name, binds)
176
+ affected_rows(internal_execute(sql, name, binds))
168
177
  end
169
178
 
170
179
  def exec_insert_all(sql, name) # :nodoc:
@@ -214,21 +223,30 @@ module ActiveRecord
214
223
  end
215
224
 
216
225
  def truncate_tables(*table_names) # :nodoc:
217
- table_names -= [schema_migration.table_name, internal_metadata.table_name]
226
+ table_names -= [pool.schema_migration.table_name, pool.internal_metadata.table_name]
218
227
 
219
228
  return if table_names.empty?
220
229
 
221
- with_multi_statements do
222
- disable_referential_integrity do
223
- statements = build_truncate_statements(table_names)
224
- execute_batch(statements, "Truncate Tables")
225
- end
230
+ disable_referential_integrity do
231
+ statements = build_truncate_statements(table_names)
232
+ execute_batch(statements, "Truncate Tables")
226
233
  end
227
234
  end
228
235
 
229
236
  # Runs the given block in a database transaction, and returns the result
230
237
  # of the block.
231
238
  #
239
+ # == Transaction callbacks
240
+ #
241
+ # #transaction yields an ActiveRecord::Transaction object on which it is
242
+ # possible to register callback:
243
+ #
244
+ # ActiveRecord::Base.transaction do |transaction|
245
+ # transaction.before_commit { puts "before commit!" }
246
+ # transaction.after_commit { puts "after commit!" }
247
+ # transaction.after_rollback { puts "after rollback!" }
248
+ # end
249
+ #
232
250
  # == Nested transactions support
233
251
  #
234
252
  # #transaction calls can be nested. By default, this makes all database
@@ -296,9 +314,9 @@ module ActiveRecord
296
314
  # #transaction will raise exceptions when it tries to release the
297
315
  # already-automatically-released savepoints:
298
316
  #
299
- # Model.connection.transaction do # BEGIN
300
- # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
301
- # Model.connection.create_table(...)
317
+ # Model.lease_connection.transaction do # BEGIN
318
+ # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
319
+ # Model.lease_connection.create_table(...)
302
320
  # # active_record_1 now automatically released
303
321
  # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
304
322
  # end
@@ -335,13 +353,29 @@ module ActiveRecord
335
353
  # isolation level.
336
354
  # :args: (requires_new: nil, isolation: nil, &block)
337
355
  def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
356
+ # If we're running inside the single, non-joinable transaction that
357
+ # ActiveRecord::TestFixtures starts around each example (depth == 1),
358
+ # an `isolation:` hint must be validated then ignored so that the
359
+ # adapter isn't asked to change the isolation level mid-transaction.
360
+ if isolation && !requires_new && open_transactions == 1 && !current_transaction.joinable?
361
+ iso = isolation.to_sym
362
+
363
+ unless transaction_isolation_levels.include?(iso)
364
+ raise ActiveRecord::TransactionIsolationError,
365
+ "invalid transaction isolation level: #{iso.inspect}"
366
+ end
367
+
368
+ current_transaction.isolation = iso
369
+ isolation = nil
370
+ end
371
+
338
372
  if !requires_new && current_transaction.joinable?
339
- if isolation
373
+ if isolation && current_transaction.isolation != isolation
340
374
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
341
375
  end
342
- yield
376
+ yield current_transaction.user_transaction
343
377
  else
344
- transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
378
+ within_new_transaction(isolation: isolation, joinable: joinable, &block)
345
379
  end
346
380
  rescue ActiveRecord::Rollback
347
381
  # rollbacks are silently swallowed
@@ -354,10 +388,10 @@ module ActiveRecord
354
388
  :disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
355
389
  to: :transaction_manager
356
390
 
357
- def mark_transaction_written_if_write(sql) # :nodoc:
391
+ def mark_transaction_written # :nodoc:
358
392
  transaction = current_transaction
359
393
  if transaction.open?
360
- transaction.written ||= write_query?(sql)
394
+ transaction.written ||= true
361
395
  end
362
396
  end
363
397
 
@@ -394,13 +428,24 @@ module ActiveRecord
394
428
  # Begins the transaction (and turns off auto-committing).
395
429
  def begin_db_transaction() end
396
430
 
431
+ def begin_deferred_transaction(isolation_level = nil) # :nodoc:
432
+ if isolation_level
433
+ begin_isolated_db_transaction(isolation_level)
434
+ else
435
+ begin_db_transaction
436
+ end
437
+ end
438
+
439
+ TRANSACTION_ISOLATION_LEVELS = {
440
+ read_uncommitted: "READ UNCOMMITTED",
441
+ read_committed: "READ COMMITTED",
442
+ repeatable_read: "REPEATABLE READ",
443
+ serializable: "SERIALIZABLE"
444
+ }.freeze
445
+ private_constant :TRANSACTION_ISOLATION_LEVELS
446
+
397
447
  def transaction_isolation_levels
398
- {
399
- read_uncommitted: "READ UNCOMMITTED",
400
- read_committed: "READ COMMITTED",
401
- repeatable_read: "REPEATABLE READ",
402
- serializable: "SERIALIZABLE"
403
- }
448
+ TRANSACTION_ISOLATION_LEVELS
404
449
  end
405
450
 
406
451
  # Begins the transaction with the isolation level set. Raises an error by
@@ -410,6 +455,15 @@ module ActiveRecord
410
455
  raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
411
456
  end
412
457
 
458
+ # Hook point called after an isolated DB transaction is committed
459
+ # or rolled back.
460
+ # Most adapters don't need to implement anything because the isolation
461
+ # level is set on a per transaction basis.
462
+ # But some databases like SQLite set it on a per connection level
463
+ # and need to explicitly reset it after commit or rollback.
464
+ def reset_isolation_level
465
+ end
466
+
413
467
  # Commits the transaction (and turns on auto-committing).
414
468
  def commit_db_transaction() end
415
469
 
@@ -456,11 +510,9 @@ module ActiveRecord
456
510
  table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
457
511
  statements = table_deletes + fixture_inserts
458
512
 
459
- with_multi_statements do
513
+ transaction(requires_new: true) do
460
514
  disable_referential_integrity do
461
- transaction(requires_new: true) do
462
- execute_batch(statements, "Fixtures Load")
463
- end
515
+ execute_batch(statements, "Fixtures Load")
464
516
  end
465
517
  end
466
518
  end
@@ -469,20 +521,6 @@ module ActiveRecord
469
521
  "DEFAULT VALUES"
470
522
  end
471
523
 
472
- # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
473
- #
474
- # The +limit+ may be anything that can evaluate to a string via #to_s. It
475
- # should look like an integer, or an Arel SQL literal.
476
- #
477
- # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
478
- def sanitize_limit(limit)
479
- if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
480
- limit
481
- else
482
- Integer(limit)
483
- end
484
- end
485
-
486
524
  # Fixture value is quoted by Arel, however scalar values
487
525
  # are not quotable. In this case we want to convert
488
526
  # the column value to YAML.
@@ -495,7 +533,7 @@ module ActiveRecord
495
533
  end
496
534
 
497
535
  # This is a safe default, even if not high precision on all databases
498
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
536
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
499
537
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
500
538
 
501
539
  # Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with
@@ -507,35 +545,78 @@ module ActiveRecord
507
545
  HIGH_PRECISION_CURRENT_TIMESTAMP
508
546
  end
509
547
 
510
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
511
- raise NotImplementedError
548
+ # Same as raw_execute but returns an ActiveRecord::Result object.
549
+ def raw_exec_query(...) # :nodoc:
550
+ cast_result(raw_execute(...))
551
+ end
552
+
553
+ # Execute a query and returns an ActiveRecord::Result
554
+ def internal_exec_query(...) # :nodoc:
555
+ cast_result(internal_execute(...))
556
+ end
557
+
558
+ def default_insert_value(column) # :nodoc:
559
+ DEFAULT_INSERT_VALUE
512
560
  end
513
561
 
514
562
  private
515
- def internal_execute(sql, name = "SCHEMA", allow_retry: false, materialize_transactions: true)
516
- sql = transform_query(sql)
517
- check_if_write_query(sql)
563
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
564
+ private_constant :DEFAULT_INSERT_VALUE
518
565
 
519
- mark_transaction_written_if_write(sql)
566
+ # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
567
+ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
568
+ type_casted_binds = type_casted_binds(binds)
569
+ log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
570
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
571
+ result = perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
572
+ handle_warnings(result, sql)
573
+ result
574
+ end
575
+ end
576
+ end
520
577
 
521
- raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
578
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
579
+ raise NotImplementedError
522
580
  end
523
581
 
524
- def execute_batch(statements, name = nil)
525
- statements.each do |statement|
526
- internal_execute(statement, name)
527
- end
582
+ def handle_warnings(raw_result, sql)
528
583
  end
529
584
 
530
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
585
+ # Receive a native adapter result object and returns an ActiveRecord::Result object.
586
+ def cast_result(raw_result)
531
587
  raise NotImplementedError
532
588
  end
533
589
 
534
- DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
535
- private_constant :DEFAULT_INSERT_VALUE
590
+ def affected_rows(raw_result)
591
+ raise NotImplementedError
592
+ end
593
+
594
+ def preprocess_query(sql)
595
+ if write_query?(sql)
596
+ ensure_writes_are_allowed(sql)
597
+ mark_transaction_written
598
+ end
599
+
600
+ # We call tranformers after the write checks so we don't add extra parsing work.
601
+ # This means we assume no transformer whille change a read for a write
602
+ # but it would be insane to do such a thing.
603
+ ActiveRecord.query_transformers.each do |transformer|
604
+ sql = transformer.call(sql, self)
605
+ end
606
+
607
+ sql
608
+ end
536
609
 
537
- def default_insert_value(column)
538
- DEFAULT_INSERT_VALUE
610
+ # Same as #internal_exec_query, but yields a native adapter result
611
+ def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block)
612
+ sql = preprocess_query(sql)
613
+ raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block)
614
+ end
615
+
616
+ def execute_batch(statements, name = nil, **kwargs)
617
+ statements.each do |statement|
618
+ raw_execute(statement, name, **kwargs)
619
+ end
539
620
  end
540
621
 
541
622
  def build_fixture_sql(fixtures, table_name)
@@ -551,8 +632,8 @@ module ActiveRecord
551
632
 
552
633
  columns.map do |name, column|
553
634
  if fixture.key?(name)
554
- type = lookup_cast_type_from_column(column)
555
- with_yaml_fallback(type.serialize(fixture[name]))
635
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
636
+ with_yaml_fallback(column.fetch_cast_type(self).serialize(fixture[name]))
556
637
  else
557
638
  default_insert_value(column)
558
639
  end
@@ -597,21 +678,19 @@ module ActiveRecord
597
678
  end
598
679
  end
599
680
 
600
- def with_multi_statements
601
- yield
602
- end
603
-
604
681
  def combine_multi_statements(total_sql)
605
682
  total_sql.join(";\n")
606
683
  end
607
684
 
608
685
  # Returns an ActiveRecord::Result instance.
609
- def select(sql, name = nil, binds = [], prepare: false, async: false)
686
+ def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
610
687
  if async && async_enabled?
611
688
  if current_transaction.joinable?
612
689
  raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
613
690
  end
614
691
 
692
+ # We make sure to run query transformers on the original thread
693
+ sql = preprocess_query(sql)
615
694
  future_result = async.new(
616
695
  pool,
617
696
  sql,
@@ -619,19 +698,19 @@ module ActiveRecord
619
698
  binds,
620
699
  prepare: prepare,
621
700
  )
622
- if supports_concurrent_connections? && current_transaction.closed?
701
+ if supports_concurrent_connections? && !current_transaction.joinable?
623
702
  future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
624
703
  else
625
704
  future_result.execute!(self)
626
705
  end
627
- return future_result
628
- end
629
-
630
- result = internal_exec_query(sql, name, binds, prepare: prepare)
631
- if async
632
- FutureResult.wrap(result)
706
+ future_result
633
707
  else
634
- result
708
+ result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
709
+ if async
710
+ FutureResult.wrap(result)
711
+ else
712
+ result
713
+ end
635
714
  end
636
715
  end
637
716