activerecord 7.2.4 → 8.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  # This is a thread locals registry for EXPLAIN. For example
@@ -9,8 +8,53 @@ module ActiveRecord
9
8
  #
10
9
  # returns the collected queries local to the current thread.
11
10
  class ExplainRegistry # :nodoc:
11
+ class Subscriber
12
+ MUTEX = Mutex.new
13
+ @subscribed = false
14
+
15
+ class << self
16
+ def ensure_subscribed
17
+ return if @subscribed
18
+ MUTEX.synchronize do
19
+ return if @subscribed
20
+
21
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
22
+ @subscribed = true
23
+ end
24
+ end
25
+ end
26
+
27
+ def start(name, id, payload)
28
+ # unused
29
+ end
30
+
31
+ def finish(name, id, payload)
32
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
33
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
34
+ end
35
+ end
36
+
37
+ def silenced?(_name)
38
+ !ExplainRegistry.collect?
39
+ end
40
+
41
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
42
+ # our own EXPLAINs no matter how loopingly beautiful that would be.
43
+ #
44
+ # On the other hand, we want to monitor the performance of our real database
45
+ # queries, not the performance of the access to the query cache.
46
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
47
+ EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
48
+ def ignore_payload?(payload)
49
+ payload[:exception] ||
50
+ payload[:cached] ||
51
+ IGNORED_PAYLOADS.include?(payload[:name]) ||
52
+ !payload[:sql].match?(EXPLAINED_SQLS)
53
+ end
54
+ end
55
+
12
56
  class << self
13
- delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
57
+ delegate :start, :reset, :collect, :collect=, :collect?, :queries, to: :instance
14
58
 
15
59
  private
16
60
  def instance
@@ -25,6 +69,11 @@ module ActiveRecord
25
69
  reset
26
70
  end
27
71
 
72
+ def start
73
+ Subscriber.ensure_subscribed
74
+ @collect = true
75
+ end
76
+
28
77
  def collect?
29
78
  @collect
30
79
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class FilterAttributeHandler # :nodoc:
5
+ class << self
6
+ def on_sensitive_attribute_declared(&block)
7
+ @sensitive_attribute_declaration_listeners ||= Concurrent::Array.new
8
+ @sensitive_attribute_declaration_listeners << block
9
+ end
10
+
11
+ def sensitive_attribute_was_declared(klass, list)
12
+ @sensitive_attribute_declaration_listeners&.each do |block|
13
+ block.call(klass, list)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ @attributes_by_class = Concurrent::Map.new
21
+ @collecting = true
22
+ end
23
+
24
+ def enable
25
+ install_collecting_hook
26
+
27
+ apply_collected_attributes
28
+ @collecting = false
29
+ end
30
+
31
+ private
32
+ attr_reader :app
33
+
34
+ def install_collecting_hook
35
+ self.class.on_sensitive_attribute_declared do |klass, list|
36
+ attribute_was_declared(klass, list)
37
+ end
38
+ end
39
+
40
+ def attribute_was_declared(klass, list)
41
+ if collecting?
42
+ collect_for_later(klass, list)
43
+ else
44
+ apply_filter(klass, list)
45
+ end
46
+ end
47
+
48
+ def apply_collected_attributes
49
+ @attributes_by_class.each do |klass, list|
50
+ apply_filter(klass, list)
51
+ end
52
+ end
53
+
54
+ def collecting?
55
+ @collecting
56
+ end
57
+
58
+ def collect_for_later(klass, list)
59
+ @attributes_by_class[klass] ||= Concurrent::Array.new
60
+ @attributes_by_class[klass] += list
61
+ end
62
+
63
+ def apply_filter(klass, list)
64
+ list.each do |attribute|
65
+ next if klass.abstract_class? || klass == Base
66
+
67
+ case attribute
68
+ when Symbol, String
69
+ klass_name = klass.name ? klass.model_name.element : nil
70
+ filter = [klass_name, attribute.to_s].compact.join(".")
71
+ app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "erb"
4
4
  require "yaml"
5
- require "zlib"
6
- require "set"
7
5
  require "active_support/dependencies"
8
6
  require "active_support/core_ext/digest/uuid"
9
7
  require "active_record/test_fixtures"
@@ -244,10 +242,10 @@ module ActiveRecord
244
242
  # and one for the humans. Why don't we generate the primary key instead?
245
243
  # Hashing each fixture's label yields a consistent ID:
246
244
  #
247
- # george: # generated id: 503576764
245
+ # george: # generated id: 380982691
248
246
  # name: George the Monkey
249
247
  #
250
- # reginald: # generated id: 324201669
248
+ # reginald: # generated id: 41001176
251
249
  # name: Reginald the Pirate
252
250
  #
253
251
  # Active Record looks at the fixture's model class, discovers the correct
@@ -100,17 +100,23 @@ module ActiveRecord
100
100
  def execute_or_skip
101
101
  return unless pending?
102
102
 
103
- @pool.with_connection do |connection|
104
- return unless @mutex.try_lock
105
- begin
106
- if pending?
107
- @event_buffer = EventBuffer.new(self, @instrumenter)
108
- 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
+ previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
109
+ begin
110
+ if pending?
111
+ @event_buffer = EventBuffer.new(self, @instrumenter)
112
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
113
+
109
114
  execute_query(connection, async: true)
110
115
  end
116
+ ensure
117
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
118
+ @mutex.unlock
111
119
  end
112
- ensure
113
- @mutex.unlock
114
120
  end
115
121
  end
116
122
  end
@@ -163,7 +169,7 @@ module ActiveRecord
163
169
  end
164
170
 
165
171
  def exec_query(connection, *args, **kwargs)
166
- connection.internal_exec_query(*args, **kwargs)
172
+ connection.raw_exec_query(*args, **kwargs)
167
173
  end
168
174
 
169
175
  class SelectAll < FutureResult # :nodoc:
@@ -7,8 +7,8 @@ module ActiveRecord
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
10
+ MAJOR = 8
11
+ MINOR = 1
12
12
  TINY = 4
13
13
  PRE = nil
14
14
 
@@ -32,9 +32,9 @@ module ActiveRecord
32
32
  # be triggered. In that case, it'll work just like normal subclasses with no special magic
33
33
  # for differentiating between them or reloading the right type with find.
34
34
  #
35
- # Note, all the attributes for all the cases are kept in the same table.
36
- # Read more:
37
- # * https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
35
+ # All subclasses share the same database table. See the
36
+ # {Single Table Inheritance pattern}[https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html]
37
+ # for more background.
38
38
  #
39
39
  module Inheritance
40
40
  extend ActiveSupport::Concern
@@ -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
@@ -237,6 +237,11 @@ module ActiveRecord
237
237
  end
238
238
 
239
239
  protected
240
+ def reload_schema_from_cache(*) # :nodoc:
241
+ @finder_needs_type_condition = nil
242
+ super
243
+ end
244
+
240
245
  # Returns the class type of the record using the current module as a prefix. So descendants of
241
246
  # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
242
247
  def compute_type(type_name)
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  def execute(relation, ...)
12
12
  relation.model.with_connection do |c|
13
13
  new(relation, c, ...).execute
14
- end
14
+ end.tap { relation.reset }
15
15
  end
16
16
  end
17
17
 
@@ -39,7 +39,7 @@ module ActiveRecord
39
39
  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
40
40
  @returning = false if @returning == []
41
41
 
42
- @unique_by = find_unique_index_for(@unique_by)
42
+ @unique_by = find_unique_index_for(@unique_by) if @on_duplicate != :raise
43
43
 
44
44
  configure_on_duplicate_update_logic
45
45
  ensure_valid_options_for_connection!
@@ -48,7 +48,7 @@ module ActiveRecord
48
48
  def execute
49
49
  return ActiveRecord::Result.empty if inserts.empty?
50
50
 
51
- message = +"#{model} "
51
+ message = +"#{model.name} "
52
52
  message << "Bulk " if inserts.many?
53
53
  message << (on_duplicate == :update ? "Upsert" : "Insert")
54
54
  connection.exec_insert_all to_sql, message
@@ -225,7 +225,7 @@ module ActiveRecord
225
225
  class Builder # :nodoc:
226
226
  attr_reader :model
227
227
 
228
- 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
229
229
 
230
230
  def initialize(insert_all)
231
231
  @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
@@ -236,11 +236,16 @@ module ActiveRecord
236
236
  end
237
237
 
238
238
  def values_list
239
- types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
239
+ types = extract_types_for(keys_including_timestamps)
240
240
 
241
241
  values_list = insert_all.map_key_with_value do |key, value|
242
- next value if Arel::Nodes::SqlLiteral === value
243
- 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
244
249
  end
245
250
 
246
251
  connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
@@ -303,8 +308,8 @@ module ActiveRecord
303
308
  format_columns(insert_all.keys_including_timestamps)
304
309
  end
305
310
 
306
- def extract_types_from_columns_on(table_name, keys:)
307
- columns = @model.schema_cache.columns_hash(table_name)
311
+ def extract_types_for(keys)
312
+ columns = @model.columns_hash
308
313
 
309
314
  unknown_column = (keys - columns.keys).first
310
315
  raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
@@ -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(
@@ -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,7 +1,7 @@
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
@@ -86,23 +86,35 @@ module ActiveRecord
86
86
  end
87
87
 
88
88
  def sql_color(sql)
89
- case sql
90
- when /\A\s*rollback/mi
91
- RED
92
- when /select .*for update/mi, /\A\s*lock/mi
93
- WHITE
94
- when /\A\s*select/i
95
- BLUE
96
- when /\A\s*insert/i
97
- GREEN
98
- when /\A\s*update/i
99
- YELLOW
100
- when /\A\s*delete/i
101
- RED
102
- when /transaction\s*\Z/i
103
- CYAN
89
+ color = if (match = sql.match(/\A\s*(\w+)(?:\s|\Z)/))
90
+ case match[1].downcase
91
+ when "rollback"
92
+ RED
93
+ when "lock"
94
+ WHITE
95
+ when "select"
96
+ if sql.match?(/for update/mi)
97
+ WHITE
98
+ else
99
+ BLUE
100
+ end
101
+ when "insert"
102
+ GREEN
103
+ when "update"
104
+ YELLOW
105
+ when "delete"
106
+ RED
107
+ end
108
+ end
109
+
110
+ if color
111
+ color
104
112
  else
105
- MAGENTA
113
+ if sql.match?(/transaction\s*\Z/i)
114
+ CYAN
115
+ else
116
+ MAGENTA
117
+ end
106
118
  end
107
119
  end
108
120
 
@@ -126,18 +138,8 @@ module ActiveRecord
126
138
  end
127
139
  end
128
140
 
129
- if Thread.respond_to?(:each_caller_location)
130
- def query_source_location
131
- Thread.each_caller_location do |location|
132
- frame = backtrace_cleaner.clean_frame(location)
133
- return frame if frame
134
- end
135
- nil
136
- end
137
- else
138
- def query_source_location
139
- backtrace_cleaner.clean(caller(1).lazy).first
140
- end
141
+ def query_source_location
142
+ backtrace_cleaner.first_clean_frame
141
143
  end
142
144
 
143
145
  def filter(name, value)
@@ -23,6 +23,11 @@ module ActiveRecord
23
23
  extend self
24
24
 
25
25
  def install(registry)
26
+ registry.register_type 118, ActiveRecord::Type::Time::Value,
27
+ packer: method(:write_time_value),
28
+ unpacker: method(:read_time_value),
29
+ recursive: true
30
+
26
31
  registry.register_type 119, ActiveModel::Type::Binary::Data,
27
32
  packer: :to_s,
28
33
  unpacker: :new
@@ -33,6 +38,14 @@ module ActiveRecord
33
38
  recursive: true
34
39
  end
35
40
 
41
+ def write_time_value(value, packer)
42
+ packer.write(value.__getobj__)
43
+ end
44
+
45
+ def read_time_value(unpacker)
46
+ ActiveRecord::Type::Time::Value.new(unpacker.read)
47
+ end
48
+
36
49
  def write_record(record, packer)
37
50
  packer.write(ActiveRecord::MessagePack.dump(record))
38
51
  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