activerecord 7.2.0 → 8.0.0.beta1

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -745
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +25 -5
  5. data/lib/active_record/associations/builder/association.rb +7 -6
  6. data/lib/active_record/associations/collection_association.rb +10 -8
  7. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  8. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +5 -5
  11. data/lib/active_record/associations/preloader/association.rb +2 -2
  12. data/lib/active_record/associations/singular_association.rb +8 -3
  13. data/lib/active_record/associations.rb +34 -4
  14. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  15. data/lib/active_record/attribute_assignment.rb +9 -1
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  17. data/lib/active_record/attributes.rb +6 -5
  18. data/lib/active_record/autosave_association.rb +69 -27
  19. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +23 -44
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
  25. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
  31. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  32. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  33. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +44 -46
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  37. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  38. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  40. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
  42. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  43. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  44. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  45. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  46. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
  47. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
  48. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  49. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  50. data/lib/active_record/connection_handling.rb +22 -0
  51. data/lib/active_record/core.rb +16 -9
  52. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  53. data/lib/active_record/encryption/config.rb +3 -1
  54. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  55. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  56. data/lib/active_record/encryption/encryptor.rb +16 -9
  57. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  58. data/lib/active_record/encryption/key_provider.rb +1 -1
  59. data/lib/active_record/encryption/scheme.rb +8 -1
  60. data/lib/active_record/encryption.rb +2 -0
  61. data/lib/active_record/enum.rb +8 -0
  62. data/lib/active_record/errors.rb +13 -5
  63. data/lib/active_record/fixtures.rb +0 -1
  64. data/lib/active_record/future_result.rb +14 -10
  65. data/lib/active_record/gem_version.rb +3 -3
  66. data/lib/active_record/insert_all.rb +1 -1
  67. data/lib/active_record/migration/command_recorder.rb +22 -5
  68. data/lib/active_record/migration/compatibility.rb +5 -2
  69. data/lib/active_record/migration.rb +35 -33
  70. data/lib/active_record/model_schema.rb +6 -3
  71. data/lib/active_record/nested_attributes.rb +11 -2
  72. data/lib/active_record/persistence.rb +128 -130
  73. data/lib/active_record/query_logs.rb +97 -39
  74. data/lib/active_record/query_logs_formatter.rb +17 -28
  75. data/lib/active_record/querying.rb +6 -6
  76. data/lib/active_record/railtie.rb +8 -14
  77. data/lib/active_record/reflection.rb +19 -10
  78. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  79. data/lib/active_record/relation/batches.rb +135 -75
  80. data/lib/active_record/relation/calculations.rb +24 -19
  81. data/lib/active_record/relation/delegation.rb +25 -14
  82. data/lib/active_record/relation/finder_methods.rb +18 -18
  83. data/lib/active_record/relation/merger.rb +8 -8
  84. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  85. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  86. data/lib/active_record/relation/predicate_builder.rb +6 -1
  87. data/lib/active_record/relation/query_methods.rb +58 -37
  88. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  89. data/lib/active_record/relation/spawn_methods.rb +1 -1
  90. data/lib/active_record/relation.rb +72 -61
  91. data/lib/active_record/result.rb +68 -7
  92. data/lib/active_record/sanitization.rb +7 -6
  93. data/lib/active_record/schema_dumper.rb +5 -0
  94. data/lib/active_record/schema_migration.rb +2 -1
  95. data/lib/active_record/scoping/named.rb +6 -2
  96. data/lib/active_record/statement_cache.rb +12 -12
  97. data/lib/active_record/store.rb +7 -3
  98. data/lib/active_record/tasks/database_tasks.rb +36 -16
  99. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  100. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  101. data/lib/active_record/test_fixtures.rb +12 -0
  102. data/lib/active_record/token_for.rb +1 -1
  103. data/lib/active_record/validations/uniqueness.rb +9 -8
  104. data/lib/active_record.rb +15 -0
  105. data/lib/arel/collectors/bind.rb +1 -1
  106. metadata +14 -14
@@ -72,14 +72,70 @@ module ActiveRecord
72
72
  #
73
73
  # config.active_record.cache_query_log_tags = true
74
74
  module QueryLogs
75
- mattr_accessor :taggings, instance_accessor: false, default: {}
76
- mattr_accessor :tags, instance_accessor: false, default: [ :application ]
77
- mattr_accessor :prepend_comment, instance_accessor: false, default: false
78
- mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
79
- mattr_accessor :tags_formatter, instance_accessor: false
75
+ class GetKeyHandler # :nodoc:
76
+ def initialize(name)
77
+ @name = name
78
+ end
79
+
80
+ def call(context)
81
+ context[@name]
82
+ end
83
+ end
84
+
85
+ class IdentityHandler # :nodoc:
86
+ def initialize(value)
87
+ @value = value
88
+ end
89
+
90
+ def call(_context)
91
+ @value
92
+ end
93
+ end
94
+
95
+ class ZeroArityHandler # :nodoc:
96
+ def initialize(proc)
97
+ @proc = proc
98
+ end
99
+
100
+ def call(_context)
101
+ @proc.call
102
+ end
103
+ end
104
+
105
+ @taggings = {}.freeze
106
+ @tags = [ :application ].freeze
107
+ @prepend_comment = false
108
+ @cache_query_log_tags = false
109
+ @tags_formatter = false
110
+
80
111
  thread_mattr_accessor :cached_comment, instance_accessor: false
81
112
 
82
113
  class << self
114
+ attr_reader :tags, :taggings, :tags_formatter # :nodoc:
115
+ attr_accessor :prepend_comment, :cache_query_log_tags # :nodoc:
116
+
117
+ def taggings=(taggings) # :nodoc:
118
+ @taggings = taggings.freeze
119
+ @handlers = rebuild_handlers
120
+ end
121
+
122
+ def tags=(tags) # :nodoc:
123
+ @tags = tags.freeze
124
+ @handlers = rebuild_handlers
125
+ end
126
+
127
+ def tags_formatter=(format) # :nodoc:
128
+ @formatter = case format
129
+ when :legacy
130
+ LegacyFormatter
131
+ when :sqlcommenter
132
+ SQLCommenter
133
+ else
134
+ raise ArgumentError, "Formatter is unsupported: #{format}"
135
+ end
136
+ @tags_formatter = format
137
+ end
138
+
83
139
  def call(sql, connection) # :nodoc:
84
140
  comment = self.comment(connection)
85
141
 
@@ -96,19 +152,6 @@ module ActiveRecord
96
152
  self.cached_comment = nil
97
153
  end
98
154
 
99
- # Updates the formatter to be what the passed in format is.
100
- def update_formatter(format)
101
- self.tags_formatter =
102
- case format
103
- when :legacy
104
- LegacyFormatter.new
105
- when :sqlcommenter
106
- SQLCommenter.new
107
- else
108
- raise ArgumentError, "Formatter is unsupported: #{formatter}"
109
- end
110
- end
111
-
112
155
  if Thread.respond_to?(:each_caller_location)
113
156
  def query_source_location # :nodoc:
114
157
  Thread.each_caller_location do |location|
@@ -126,6 +169,35 @@ module ActiveRecord
126
169
  ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
127
170
 
128
171
  private
172
+ def rebuild_handlers
173
+ handlers = []
174
+ @tags.each do |i|
175
+ if i.is_a?(Hash)
176
+ i.each do |k, v|
177
+ handlers << [k, build_handler(k, v)]
178
+ end
179
+ else
180
+ handlers << [i, build_handler(i)]
181
+ end
182
+ end
183
+ handlers.sort_by! { |(key, _)| key.to_s }
184
+ end
185
+
186
+ def build_handler(name, handler = nil)
187
+ handler ||= @taggings[name]
188
+ if handler.nil?
189
+ GetKeyHandler.new(name)
190
+ elsif handler.respond_to?(:call)
191
+ if handler.arity == 0
192
+ ZeroArityHandler.new(handler)
193
+ else
194
+ handler
195
+ end
196
+ else
197
+ IdentityHandler.new(handler)
198
+ end
199
+ end
200
+
129
201
  # Returns an SQL comment +String+ containing the query log tags.
130
202
  # Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
131
203
  def comment(connection)
@@ -136,10 +208,6 @@ module ActiveRecord
136
208
  end
137
209
  end
138
210
 
139
- def formatter
140
- self.tags_formatter || self.update_formatter(:legacy)
141
- end
142
-
143
211
  def uncached_comment(connection)
144
212
  content = tag_content(connection)
145
213
 
@@ -165,25 +233,15 @@ module ActiveRecord
165
233
  context = ActiveSupport::ExecutionContext.to_h
166
234
  context[:connection] ||= connection
167
235
 
168
- pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
169
- key, handler = tag
170
- handler ||= taggings[key]
171
-
172
- val = if handler.nil?
173
- context[key]
174
- elsif handler.respond_to?(:call)
175
- if handler.arity == 0
176
- handler.call
177
- else
178
- handler.call(context)
179
- end
180
- else
181
- handler
182
- end
183
- [key, val] unless val.nil?
236
+ pairs = @handlers.filter_map do |(key, handler)|
237
+ val = handler.call(context)
238
+ @formatter.format(key, val) unless val.nil?
184
239
  end
185
- self.formatter.format(pairs)
240
+ @formatter.join(pairs)
186
241
  end
187
242
  end
243
+
244
+ @handlers = rebuild_handlers
245
+ self.tags_formatter = :legacy
188
246
  end
189
247
  end
@@ -2,40 +2,29 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module QueryLogs
5
- class LegacyFormatter # :nodoc:
6
- def initialize
7
- @key_value_separator = ":"
8
- end
9
-
10
- # Formats the key value pairs into a string.
11
- def format(pairs)
12
- pairs.map! do |key, value|
13
- "#{key}#{key_value_separator}#{format_value(value)}"
14
- end.join(",")
15
- end
16
-
17
- private
18
- attr_reader :key_value_separator
19
-
20
- def format_value(value)
21
- value
5
+ module LegacyFormatter # :nodoc:
6
+ class << self
7
+ # Formats the key value pairs into a string.
8
+ def format(key, value)
9
+ "#{key}:#{value}"
22
10
  end
23
- end
24
11
 
25
- class SQLCommenter < LegacyFormatter # :nodoc:
26
- def initialize
27
- @key_value_separator = "="
12
+ def join(pairs)
13
+ pairs.join(",")
14
+ end
28
15
  end
16
+ end
29
17
 
30
- def format(pairs)
31
- pairs.sort_by! { |pair| pair.first.to_s }
32
- super
33
- end
18
+ class SQLCommenter # :nodoc:
19
+ class << self
20
+ def format(key, value)
21
+ "#{key}='#{ERB::Util.url_encode(value)}'"
22
+ end
34
23
 
35
- private
36
- def format_value(value)
37
- "'#{ERB::Util.url_encode(value)}'"
24
+ def join(pairs)
25
+ pairs.join(",")
38
26
  end
27
+ end
39
28
  end
40
29
  end
41
30
  end
@@ -56,12 +56,10 @@ module ActiveRecord
56
56
  end
57
57
 
58
58
  # Same as <tt>#find_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
59
- def async_find_by_sql(sql, binds = [], preparable: nil, &block)
60
- result = with_connection do |c|
61
- _query_by_sql(c, sql, binds, preparable: preparable, async: true)
62
- end
63
-
64
- result.then do |result|
59
+ def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
60
+ with_connection do |c|
61
+ _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry, async: true)
62
+ end.then do |result|
65
63
  _load_from_sql(result, &block)
66
64
  end
67
65
  end
@@ -71,6 +69,8 @@ module ActiveRecord
71
69
  end
72
70
 
73
71
  def _load_from_sql(result_set, &block) # :nodoc:
72
+ return [] if result_set.empty?
73
+
74
74
  column_types = result_set.column_types
75
75
 
76
76
  unless column_types.empty?
@@ -69,6 +69,7 @@ module ActiveRecord
69
69
  Rails.logger.broadcast_to(console)
70
70
  end
71
71
  ActiveRecord.verbose_query_logs = false
72
+ ActiveRecord::Base.attributes_for_inspect = :all if Rails.env.production?
72
73
  end
73
74
 
74
75
  runner do
@@ -356,16 +357,19 @@ To keep using the current cache store, you can turn off cache versioning entirel
356
357
  end
357
358
 
358
359
  initializer "active_record_encryption.configuration" do |app|
359
- ActiveSupport.on_load(:active_record) do
360
- ActiveRecord::Encryption.configure \
360
+ ActiveSupport.on_load(:active_record_encryption) do
361
+ ActiveRecord::Encryption.configure(
361
362
  primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
362
363
  deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
363
364
  key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
364
365
  **app.config.active_record.encryption
366
+ )
365
367
 
366
368
  auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app)
367
369
  auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters
370
+ end
368
371
 
372
+ ActiveSupport.on_load(:active_record) do
369
373
  # Support extended queries for deterministic attributes and validations
370
374
  if ActiveRecord::Encryption.config.extend_queries
371
375
  ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
@@ -385,7 +389,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
385
389
  config.after_initialize do
386
390
  if app.config.active_record.query_log_tags_enabled
387
391
  ActiveRecord.query_transformers << ActiveRecord::QueryLogs
388
- ActiveRecord::QueryLogs.taggings.merge!(
392
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
389
393
  application: Rails.application.class.name.split("::").first,
390
394
  pid: -> { Process.pid.to_s },
391
395
  socket: ->(context) { context[:connection].pool.db_config.socket },
@@ -400,7 +404,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
400
404
  end
401
405
 
402
406
  if app.config.active_record.query_log_tags_format
403
- ActiveRecord::QueryLogs.update_formatter(app.config.active_record.query_log_tags_format)
407
+ ActiveRecord::QueryLogs.tags_formatter = app.config.active_record.query_log_tags_format
404
408
  end
405
409
 
406
410
  if app.config.active_record.cache_query_log_tags
@@ -432,15 +436,5 @@ To keep using the current cache store, you can turn off cache versioning entirel
432
436
  end
433
437
  end
434
438
  end
435
-
436
- initializer "active_record.attributes_for_inspect" do |app|
437
- ActiveSupport.on_load(:active_record) do
438
- if app.config.consider_all_requests_local
439
- if app.config.active_record.attributes_for_inspect.nil?
440
- ActiveRecord::Base.attributes_for_inspect = :all
441
- end
442
- end
443
- end
444
- end
445
439
  end
446
440
  end
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  normalized_reflections.stringify_keys
80
80
  end
81
81
 
82
- def normalized_reflections # :nodoc
82
+ def normalized_reflections # :nodoc:
83
83
  @__reflections ||= begin
84
84
  ref = {}
85
85
 
@@ -428,15 +428,19 @@ module ActiveRecord
428
428
  # a new association object. Use +build_association+ or +create_association+
429
429
  # instead. This allows plugins to hook into association object creation.
430
430
  def klass
431
- @klass ||= compute_class(compute_name(class_name))
431
+ @klass ||= _klass(class_name)
432
432
  end
433
433
 
434
- def compute_class(name)
435
- name.constantize
434
+ def _klass(class_name) # :nodoc:
435
+ if active_record.name.demodulize == class_name
436
+ return compute_class("::#{class_name}") rescue NameError
437
+ end
438
+
439
+ compute_class(class_name)
436
440
  end
437
441
 
438
- def compute_name(name) # :nodoc:
439
- active_record.name.demodulize == name ? "::#{name}" : name
442
+ def compute_class(name)
443
+ name.constantize
440
444
  end
441
445
 
442
446
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -558,12 +562,12 @@ module ActiveRecord
558
562
  def foreign_key(infer_from_inverse_of: true)
559
563
  @foreign_key ||= if options[:foreign_key]
560
564
  if options[:foreign_key].is_a?(Array)
561
- options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
565
+ options[:foreign_key].map { |fk| -fk.to_s.freeze }.freeze
562
566
  else
563
567
  options[:foreign_key].to_s.freeze
564
568
  end
565
569
  elsif options[:query_constraints]
566
- options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
570
+ options[:query_constraints].map { |fk| -fk.to_s.freeze }.freeze
567
571
  else
568
572
  derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
569
573
 
@@ -571,7 +575,12 @@ module ActiveRecord
571
575
  derived_fk = derive_fk_query_constraints(derived_fk)
572
576
  end
573
577
 
574
- derived_fk
578
+ if derived_fk.is_a?(Array)
579
+ derived_fk.map! { |fk| -fk.freeze }
580
+ derived_fk.freeze
581
+ else
582
+ -derived_fk.freeze
583
+ end
575
584
  end
576
585
  end
577
586
 
@@ -986,7 +995,7 @@ module ActiveRecord
986
995
  end
987
996
 
988
997
  def klass
989
- @klass ||= delegate_reflection.compute_class(compute_name(class_name))
998
+ @klass ||= delegate_reflection._klass(class_name)
990
999
  end
991
1000
 
992
1001
  # Returns the source of the through reflection. It checks both a singularized
@@ -5,11 +5,12 @@ module ActiveRecord
5
5
  class BatchEnumerator
6
6
  include Enumerable
7
7
 
8
- def initialize(of: 1000, start: nil, finish: nil, relation:, order: :asc, use_ranges: nil) # :nodoc:
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:, cursor:, order: :asc, use_ranges: nil) # :nodoc:
9
9
  @of = of
10
10
  @relation = relation
11
11
  @start = start
12
12
  @finish = finish
13
+ @cursor = cursor
13
14
  @order = order
14
15
  @use_ranges = use_ranges
15
16
  end
@@ -52,7 +53,7 @@ module ActiveRecord
52
53
  def each_record(&block)
53
54
  return to_enum(:each_record) unless block_given?
54
55
 
55
- @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, order: @order).each do |relation|
56
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, cursor: @cursor, order: @order).each do |relation|
56
57
  relation.records.each(&block)
57
58
  end
58
59
  end
@@ -105,7 +106,7 @@ module ActiveRecord
105
106
  # relation.update_all(awesome: true)
106
107
  # end
107
108
  def each(&block)
108
- enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, order: @order, use_ranges: @use_ranges)
109
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, cursor: @cursor, order: @order, use_ranges: @use_ranges)
109
110
  return enum.each(&block) if block_given?
110
111
  enum
111
112
  end