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
@@ -32,8 +32,11 @@ module ActiveRecord
32
32
 
33
33
  def instrument(name, payload = {}, &block)
34
34
  event = @instrumenter.new_event(name, payload)
35
- @events << event
36
- event.record(&block)
35
+ begin
36
+ event.record(&block)
37
+ ensure
38
+ @events << event
39
+ end
37
40
  end
38
41
 
39
42
  def flush
@@ -57,7 +60,6 @@ module ActiveRecord
57
60
  end
58
61
 
59
62
  delegate :empty?, :to_a, to: :result
60
- delegate_missing_to :result
61
63
 
62
64
  attr_reader :lock_wait
63
65
 
@@ -98,17 +100,21 @@ module ActiveRecord
98
100
  def execute_or_skip
99
101
  return unless pending?
100
102
 
101
- @pool.with_connection do |connection|
102
- return unless @mutex.try_lock
103
- begin
104
- if pending?
105
- @event_buffer = EventBuffer.new(self, @instrumenter)
106
- connection.with_instrumenter(@event_buffer) do
103
+ @session.synchronize do
104
+ return unless pending?
105
+
106
+ @pool.with_connection do |connection|
107
+ return unless @mutex.try_lock
108
+ begin
109
+ if pending?
110
+ @event_buffer = EventBuffer.new(self, @instrumenter)
111
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
112
+
107
113
  execute_query(connection, async: true)
108
114
  end
115
+ ensure
116
+ @mutex.unlock
109
117
  end
110
- ensure
111
- @mutex.unlock
112
118
  end
113
119
  end
114
120
  end
@@ -140,7 +146,9 @@ module ActiveRecord
140
146
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
141
147
  @mutex.synchronize do
142
148
  if pending?
143
- execute_query(@pool.connection)
149
+ @pool.with_connection do |connection|
150
+ execute_query(connection)
151
+ end
144
152
  else
145
153
  @lock_wait = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start)
146
154
  end
@@ -159,7 +167,7 @@ module ActiveRecord
159
167
  end
160
168
 
161
169
  def exec_query(connection, *args, **kwargs)
162
- connection.internal_exec_query(*args, **kwargs)
170
+ connection.raw_exec_query(*args, **kwargs)
163
171
  end
164
172
 
165
173
  class SelectAll < FutureResult # :nodoc:
@@ -7,10 +7,10 @@ module ActiveRecord
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
10
+ MAJOR = 8
11
11
  MINOR = 1
12
- TINY = 5
13
- PRE = "2"
12
+ TINY = 2
13
+ PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
  # Returns the first class in the inheritance hierarchy that descends from either an
98
98
  # abstract class or from <tt>ActiveRecord::Base</tt>.
99
99
  #
100
- # Consider the following behaviour:
100
+ # Consider the following behavior:
101
101
  #
102
102
  # class ApplicationRecord < ActiveRecord::Base
103
103
  # self.abstract_class = true
@@ -165,7 +165,7 @@ module ActiveRecord
165
165
 
166
166
  # Returns whether this class is an abstract class or not.
167
167
  def abstract_class?
168
- defined?(@abstract_class) && @abstract_class == true
168
+ @abstract_class == true
169
169
  end
170
170
 
171
171
  # Sets the application record class for Active Record
@@ -202,7 +202,9 @@ module ActiveRecord
202
202
  "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
203
203
  "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
204
204
  "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
205
- "or overwrite #{name}.inheritance_column to use another column for that information."
205
+ "or overwrite #{name}.inheritance_column to use another column for that information. " \
206
+ "If you wish to disable single-table inheritance for #{name} set " \
207
+ "#{name}.inheritance_column to nil"
206
208
  end
207
209
 
208
210
  # Returns the value to be stored in the polymorphic type column for Polymorphic Associations.
@@ -7,8 +7,17 @@ module ActiveRecord
7
7
  attr_reader :model, :connection, :inserts, :keys
8
8
  attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
9
9
 
10
- def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
11
- @model, @connection, @inserts = model, model.connection, inserts.map(&:stringify_keys)
10
+ class << self
11
+ def execute(relation, ...)
12
+ relation.model.with_connection do |c|
13
+ new(relation, c, ...).execute
14
+ end.tap { relation.reset }
15
+ end
16
+ end
17
+
18
+ def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
19
+ @relation = relation
20
+ @model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
12
21
  @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
13
22
  @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
14
23
 
@@ -23,10 +32,8 @@ module ActiveRecord
23
32
  @keys = @inserts.first.keys
24
33
  end
25
34
 
26
- if model.scope_attributes?
27
- @scope_attributes = model.scope_attributes
28
- @keys |= @scope_attributes.keys
29
- end
35
+ @scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
36
+ @keys |= @scope_attributes.keys
30
37
  @keys = @keys.to_set
31
38
 
32
39
  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
@@ -52,10 +59,9 @@ module ActiveRecord
52
59
  end
53
60
 
54
61
  def primary_keys
55
- Array(connection.schema_cache.primary_keys(model.table_name))
62
+ Array(@model.schema_cache.primary_keys(model.table_name))
56
63
  end
57
64
 
58
-
59
65
  def skip_duplicates?
60
66
  on_duplicate == :skip
61
67
  end
@@ -67,7 +73,7 @@ module ActiveRecord
67
73
  def map_key_with_value
68
74
  inserts.map do |attributes|
69
75
  attributes = attributes.stringify_keys
70
- attributes.merge!(scope_attributes) if scope_attributes
76
+ attributes.merge!(@scope_attributes)
71
77
  attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
72
78
 
73
79
  verify_attributes(attributes)
@@ -92,8 +98,6 @@ module ActiveRecord
92
98
  end
93
99
 
94
100
  private
95
- attr_reader :scope_attributes
96
-
97
101
  def has_attribute_aliases?(attributes)
98
102
  attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
99
103
  end
@@ -163,10 +167,9 @@ module ActiveRecord
163
167
  end
164
168
 
165
169
  def unique_indexes
166
- connection.schema_cache.indexes(model.table_name).select(&:unique)
170
+ @model.schema_cache.indexes(model.table_name).select(&:unique)
167
171
  end
168
172
 
169
-
170
173
  def ensure_valid_options_for_connection!
171
174
  if returning && !connection.supports_insert_returning?
172
175
  raise ArgumentError, "#{connection.class} does not support :returning"
@@ -192,7 +195,7 @@ module ActiveRecord
192
195
 
193
196
 
194
197
  def readonly_columns
195
- primary_keys + model.readonly_attributes.to_a
198
+ primary_keys + model.readonly_attributes
196
199
  end
197
200
 
198
201
  def unique_by_columns
@@ -222,7 +225,7 @@ module ActiveRecord
222
225
  class Builder # :nodoc:
223
226
  attr_reader :model
224
227
 
225
- delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
228
+ delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, :primary_keys, to: :insert_all
226
229
 
227
230
  def initialize(insert_all)
228
231
  @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
@@ -233,11 +236,16 @@ module ActiveRecord
233
236
  end
234
237
 
235
238
  def values_list
236
- types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
239
+ types = extract_types_for(keys_including_timestamps)
237
240
 
238
241
  values_list = insert_all.map_key_with_value do |key, value|
239
- next value if Arel::Nodes::SqlLiteral === value
240
- connection.with_yaml_fallback(types[key].serialize(value))
242
+ if Arel::Nodes::SqlLiteral === value
243
+ value
244
+ elsif primary_keys.include?(key) && value.nil?
245
+ connection.default_insert_value(model.columns_hash[key])
246
+ else
247
+ ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
248
+ end
241
249
  end
242
250
 
243
251
  connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
@@ -300,8 +308,8 @@ module ActiveRecord
300
308
  format_columns(insert_all.keys_including_timestamps)
301
309
  end
302
310
 
303
- def extract_types_from_columns_on(table_name, keys:)
304
- columns = connection.schema_cache.columns_hash(table_name)
311
+ def extract_types_for(keys)
312
+ columns = @model.columns_hash
305
313
 
306
314
  unknown_column = (keys - columns.keys).first
307
315
  raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
@@ -178,7 +178,10 @@ module ActiveRecord
178
178
  def can_use_fast_cache_version?(timestamp)
179
179
  timestamp.is_a?(String) &&
180
180
  cache_timestamp_format == :usec &&
181
- self.class.connection.default_timezone == :utc &&
181
+ # FIXME: checking out a connection for this is wasteful
182
+ # we should store/cache this information in the schema cache
183
+ # or similar.
184
+ self.class.with_connection(&:default_timezone) == :utc &&
182
185
  !updated_at_came_from_user?
183
186
  end
184
187
 
@@ -13,17 +13,13 @@ module ActiveRecord
13
13
  class NullInternalMetadata # :nodoc:
14
14
  end
15
15
 
16
- attr_reader :connection, :arel_table
16
+ attr_reader :arel_table
17
17
 
18
- def initialize(connection)
19
- @connection = connection
18
+ def initialize(pool)
19
+ @pool = pool
20
20
  @arel_table = Arel::Table.new(table_name)
21
21
  end
22
22
 
23
- def enabled?
24
- connection.use_metadata_table?
25
- end
26
-
27
23
  def primary_key
28
24
  "key"
29
25
  end
@@ -36,50 +32,66 @@ module ActiveRecord
36
32
  "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
37
33
  end
38
34
 
35
+ def enabled?
36
+ @pool.db_config.use_metadata_table?
37
+ end
38
+
39
39
  def []=(key, value)
40
40
  return unless enabled?
41
41
 
42
- update_or_create_entry(key, value)
42
+ @pool.with_connection do |connection|
43
+ update_or_create_entry(connection, key, value)
44
+ end
43
45
  end
44
46
 
45
47
  def [](key)
46
48
  return unless enabled?
47
49
 
48
- if entry = select_entry(key)
49
- entry[value_key]
50
+ @pool.with_connection do |connection|
51
+ if entry = select_entry(connection, key)
52
+ entry[value_key]
53
+ end
50
54
  end
51
55
  end
52
56
 
53
57
  def delete_all_entries
54
58
  dm = Arel::DeleteManager.new(arel_table)
55
59
 
56
- connection.delete(dm, "#{self.class} Destroy")
60
+ @pool.with_connection do |connection|
61
+ connection.delete(dm, "#{self.class} Destroy")
62
+ end
57
63
  end
58
64
 
59
65
  def count
60
66
  sm = Arel::SelectManager.new(arel_table)
61
67
  sm.project(*Arel::Nodes::Count.new([Arel.star]))
62
68
 
63
- connection.select_values(sm, "#{self.class} Count").first
69
+ @pool.with_connection do |connection|
70
+ connection.select_values(sm, "#{self.class} Count").first
71
+ end
64
72
  end
65
73
 
66
74
  def create_table_and_set_flags(environment, schema_sha1 = nil)
67
75
  return unless enabled?
68
76
 
69
- create_table
70
- update_or_create_entry(:environment, environment)
71
- update_or_create_entry(:schema_sha1, schema_sha1) if schema_sha1
77
+ @pool.with_connection do |connection|
78
+ create_table
79
+ update_or_create_entry(connection, :environment, environment)
80
+ update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
81
+ end
72
82
  end
73
83
 
74
84
  # Creates an internal metadata table with columns +key+ and +value+
75
85
  def create_table
76
86
  return unless enabled?
77
87
 
78
- unless connection.table_exists?(table_name)
79
- connection.create_table(table_name, id: false) do |t|
80
- t.string :key, **connection.internal_string_options_for_primary_key
81
- t.string :value
82
- t.timestamps
88
+ @pool.with_connection do |connection|
89
+ unless connection.table_exists?(table_name)
90
+ connection.create_table(table_name, id: false) do |t|
91
+ t.string :key, **connection.internal_string_options_for_primary_key
92
+ t.string :value
93
+ t.timestamps
94
+ end
83
95
  end
84
96
  end
85
97
  end
@@ -87,49 +99,51 @@ module ActiveRecord
87
99
  def drop_table
88
100
  return unless enabled?
89
101
 
90
- connection.drop_table table_name, if_exists: true
102
+ @pool.with_connection do |connection|
103
+ connection.drop_table table_name, if_exists: true
104
+ end
91
105
  end
92
106
 
93
107
  def table_exists?
94
- connection.schema_cache.data_source_exists?(table_name)
108
+ @pool.schema_cache.data_source_exists?(table_name)
95
109
  end
96
110
 
97
111
  private
98
- def update_or_create_entry(key, value)
99
- entry = select_entry(key)
112
+ def update_or_create_entry(connection, key, value)
113
+ entry = select_entry(connection, key)
100
114
 
101
115
  if entry
102
116
  if entry[value_key] != value
103
- update_entry(key, value)
117
+ update_entry(connection, key, value)
104
118
  else
105
119
  entry[value_key]
106
120
  end
107
121
  else
108
- create_entry(key, value)
122
+ create_entry(connection, key, value)
109
123
  end
110
124
  end
111
125
 
112
- def current_time
126
+ def current_time(connection)
113
127
  connection.default_timezone == :utc ? Time.now.utc : Time.now
114
128
  end
115
129
 
116
- def create_entry(key, value)
130
+ def create_entry(connection, key, value)
117
131
  im = Arel::InsertManager.new(arel_table)
118
132
  im.insert [
119
133
  [arel_table[primary_key], key],
120
134
  [arel_table[value_key], value],
121
- [arel_table[:created_at], current_time],
122
- [arel_table[:updated_at], current_time]
135
+ [arel_table[:created_at], current_time(connection)],
136
+ [arel_table[:updated_at], current_time(connection)]
123
137
  ]
124
138
 
125
139
  connection.insert(im, "#{self.class} Create", primary_key, key)
126
140
  end
127
141
 
128
- def update_entry(key, new_value)
142
+ def update_entry(connection, key, new_value)
129
143
  um = Arel::UpdateManager.new(arel_table)
130
144
  um.set [
131
145
  [arel_table[value_key], new_value],
132
- [arel_table[:updated_at], current_time]
146
+ [arel_table[:updated_at], current_time(connection)]
133
147
  ]
134
148
 
135
149
  um.where(arel_table[primary_key].eq(key))
@@ -137,9 +151,9 @@ module ActiveRecord
137
151
  connection.update(um, "#{self.class} Update")
138
152
  end
139
153
 
140
- def select_entry(key)
154
+ def select_entry(connection, key)
141
155
  sm = Arel::SelectManager.new(arel_table)
142
- sm.project(Arel::Nodes::SqlLiteral.new("*"))
156
+ sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
143
157
  sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
144
158
  sm.order(arel_table[primary_key].asc)
145
159
  sm.limit = 1
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
10
10
  # and the update is ignored.
11
11
  #
12
- # Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
12
+ # Check out ActiveRecord::Locking::Pessimistic for an alternative.
13
13
  #
14
14
  # == Usage
15
15
  #
@@ -101,6 +101,13 @@ module ActiveRecord
101
101
  attribute_names = attribute_names.dup if attribute_names.frozen?
102
102
  attribute_names << locking_column
103
103
 
104
+ if self[locking_column].nil?
105
+ raise(<<-MSG.squish)
106
+ For optimistic locking, locking_column ('#{locking_column}') can't be nil.
107
+ Are you missing a default value or validation on '#{locking_column}'?
108
+ MSG
109
+ end
110
+
104
111
  self[locking_column] += 1
105
112
 
106
113
  affected_rows = self.class._update_record(
@@ -182,14 +189,15 @@ module ActiveRecord
182
189
  super
183
190
  end
184
191
 
185
- def define_attribute(name, cast_type, **) # :nodoc:
186
- if lock_optimistically && name == locking_column
187
- cast_type = LockingType.new(cast_type)
192
+ private
193
+ def hook_attribute_type(name, cast_type)
194
+ if lock_optimistically && name == locking_column
195
+ cast_type = LockingType.new(cast_type)
196
+ end
197
+
198
+ super
188
199
  end
189
- super
190
- end
191
200
 
192
- private
193
201
  def inherited(base)
194
202
  super
195
203
  base.class_eval do
@@ -67,6 +67,10 @@ module ActiveRecord
67
67
  # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
68
68
  # the locked record.
69
69
  def lock!(lock = true)
70
+ if self.class.current_preventing_writes
71
+ raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
72
+ end
73
+
70
74
  if persisted?
71
75
  if has_changes_to_save?
72
76
  raise(<<-MSG.squish)
@@ -79,6 +83,7 @@ module ActiveRecord
79
83
 
80
84
  reload(lock: lock)
81
85
  end
86
+
82
87
  self
83
88
  end
84
89
 
@@ -1,32 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- class LogSubscriber < ActiveSupport::LogSubscriber
4
+ class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
5
5
  IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
6
 
7
7
  class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
8
8
 
9
- def self.runtime=(value)
10
- ActiveRecord.deprecator.warn(<<-MSG.squish)
11
- ActiveRecord::LogSubscriber.runtime= is deprecated and will be removed in Rails 7.2.
12
- MSG
13
- ActiveRecord::RuntimeRegistry.sql_runtime = value
14
- end
15
-
16
- def self.runtime
17
- ActiveRecord.deprecator.warn(<<-MSG.squish)
18
- ActiveRecord::LogSubscriber.runtime is deprecated and will be removed in Rails 7.2.
19
- MSG
20
- ActiveRecord::RuntimeRegistry.sql_runtime
21
- end
22
-
23
- def self.reset_runtime
24
- ActiveRecord.deprecator.warn(<<-MSG.squish)
25
- ActiveRecord::LogSubscriber.reset_runtime is deprecated and will be removed in Rails 7.2.
26
- MSG
27
- ActiveRecord::RuntimeRegistry.reset
28
- end
29
-
30
9
  def strict_loading_violation(event)
31
10
  debug do
32
11
  owner = event.payload[:owner]
@@ -147,18 +126,8 @@ module ActiveRecord
147
126
  end
148
127
  end
149
128
 
150
- if Thread.respond_to?(:each_caller_location)
151
- def query_source_location
152
- Thread.each_caller_location do |location|
153
- frame = backtrace_cleaner.clean_frame(location)
154
- return frame if frame
155
- end
156
- nil
157
- end
158
- else
159
- def query_source_location
160
- backtrace_cleaner.clean(caller(1).lazy).first
161
- end
129
+ def query_source_location
130
+ backtrace_cleaner.first_clean_frame
162
131
  end
163
132
 
164
133
  def filter(name, value)
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  end
80
80
 
81
81
  def add_cached_associations(record, entry)
82
- record.class.reflections.each_value do |reflection|
82
+ record.class.normalized_reflections.each_value do |reflection|
83
83
  if record.association_cached?(reflection.name) && record.association(reflection.name).loaded?
84
84
  entry << reflection.name << encode(record.association(reflection.name).target)
85
85
  end
@@ -9,25 +9,40 @@ module ActiveRecord
9
9
  # shard to switch to and allows for applications to write custom strategies
10
10
  # for swapping if needed.
11
11
  #
12
- # The ShardSelector takes a set of options (currently only +lock+ is supported)
13
- # that can be used by the middleware to alter behavior. +lock+ is
14
- # true by default and will prohibit the request from switching shards once
15
- # inside the block. If +lock+ is false, then shard swapping will be allowed.
16
- # For tenant based sharding, +lock+ should always be true to prevent application
17
- # code from mistakenly switching between tenants.
12
+ # == Setup
18
13
  #
19
- # Options can be set in the config:
14
+ # Applications must provide a resolver that will provide application-specific logic for
15
+ # selecting the appropriate shard. Setting +config.active_record.shard_resolver+ will cause
16
+ # Rails to add ShardSelector to the default middleware stack.
20
17
  #
21
- # config.active_record.shard_selector = { lock: true }
18
+ # The resolver, along with any configuration options, can be set in the application
19
+ # configuration using an initializer like so:
22
20
  #
23
- # Applications must also provide the code for the resolver as it depends on application
24
- # specific models. An example resolver would look like this:
21
+ # Rails.application.configure do
22
+ # config.active_record.shard_selector = { lock: false, class_name: "AnimalsRecord" }
23
+ # config.active_record.shard_resolver = ->(request) {
24
+ # subdomain = request.subdomain
25
+ # tenant = Tenant.find_by_subdomain!(subdomain)
26
+ # tenant.shard
27
+ # }
28
+ # end
29
+ #
30
+ # == Configuration
31
+ #
32
+ # The behavior of ShardSelector can be altered through some configuration options.
33
+ #
34
+ # [+lock:+]
35
+ # +lock+ is true by default and will prohibit the request from switching shards once inside
36
+ # the block. If +lock+ is false, then shard switching will be allowed. For tenant based
37
+ # sharding, +lock+ should always be true to prevent application code from mistakenly switching
38
+ # between tenants.
39
+ #
40
+ # [+class_name:+]
41
+ # +class_name+ is the name of the abstract connection class to switch. By
42
+ # default, the ShardSelector will use ActiveRecord::Base, but if the
43
+ # application has multiple databases, then this option should be set to
44
+ # the name of the sharded database's abstract connection class.
25
45
  #
26
- # config.active_record.shard_resolver = ->(request) {
27
- # subdomain = request.subdomain
28
- # tenant = Tenant.find_by_subdomain!(subdomain)
29
- # tenant.shard
30
- # }
31
46
  class ShardSelector
32
47
  def initialize(app, resolver, options = {})
33
48
  @app = app
@@ -53,8 +68,10 @@ module ActiveRecord
53
68
  end
54
69
 
55
70
  def set_shard(shard, &block)
56
- ActiveRecord::Base.connected_to(shard: shard.to_sym) do
57
- ActiveRecord::Base.prohibit_shard_swapping(options.fetch(:lock, true), &block)
71
+ klass = options[:class_name]&.constantize || ActiveRecord::Base
72
+
73
+ klass.connected_to(shard: shard.to_sym) do
74
+ klass.prohibit_shard_swapping(options.fetch(:lock, true), &block)
58
75
  end
59
76
  end
60
77
  end