activerecord 8.1.0.beta1 → 8.1.0.rc1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -4
  3. data/lib/active_record/associations/belongs_to_association.rb +2 -0
  4. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  5. data/lib/active_record/attribute_methods.rb +1 -1
  6. data/lib/active_record/autosave_association.rb +2 -2
  7. data/lib/active_record/base.rb +2 -3
  8. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +1 -3
  9. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -29
  10. data/lib/active_record/connection_adapters/abstract/database_statements.rb +32 -13
  11. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
  12. data/lib/active_record/connection_adapters/abstract/transaction.rb +9 -0
  13. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -11
  14. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +0 -2
  15. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +1 -1
  16. data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -2
  17. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  18. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -1
  19. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -5
  20. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  21. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
  22. data/lib/active_record/connection_adapters/postgresql_adapter.rb +16 -5
  23. data/lib/active_record/connection_handling.rb +2 -1
  24. data/lib/active_record/database_configurations/hash_config.rb +5 -2
  25. data/lib/active_record/encryption/encryptor.rb +12 -0
  26. data/lib/active_record/errors.rb +3 -3
  27. data/lib/active_record/explain.rb +1 -1
  28. data/lib/active_record/explain_registry.rb +51 -1
  29. data/lib/active_record/gem_version.rb +1 -1
  30. data/lib/active_record/log_subscriber.rb +1 -1
  31. data/lib/active_record/migration/compatibility.rb +1 -1
  32. data/lib/active_record/model_schema.rb +26 -3
  33. data/lib/active_record/railties/controller_runtime.rb +11 -6
  34. data/lib/active_record/railties/databases.rake +1 -1
  35. data/lib/active_record/railties/job_runtime.rb +2 -2
  36. data/lib/active_record/relation/batches.rb +3 -3
  37. data/lib/active_record/relation/merger.rb +2 -2
  38. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  39. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  40. data/lib/active_record/relation/predicate_builder.rb +7 -5
  41. data/lib/active_record/relation/query_methods.rb +1 -1
  42. data/lib/active_record/relation/where_clause.rb +2 -0
  43. data/lib/active_record/relation.rb +1 -1
  44. data/lib/active_record/runtime_registry.rb +41 -58
  45. data/lib/active_record/structured_event_subscriber.rb +85 -0
  46. data/lib/active_record/table_metadata.rb +5 -20
  47. data/lib/active_record/tasks/database_tasks.rb +24 -14
  48. data/lib/active_record/test_databases.rb +4 -2
  49. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  50. metadata +9 -9
  51. data/lib/active_record/explain_subscriber.rb +0 -34
@@ -92,8 +92,6 @@ module ActiveRecord
92
92
  true
93
93
  end
94
94
 
95
- # HELPER METHODS ===========================================
96
-
97
95
  def error_number(exception)
98
96
  exception.error_number if exception.respond_to?(:error_number)
99
97
  end
@@ -30,6 +30,10 @@ module ActiveRecord
30
30
  @generated.present?
31
31
  end
32
32
 
33
+ def virtual_stored?
34
+ @generated == "s"
35
+ end
36
+
33
37
  def has_default?
34
38
  super && !virtual?
35
39
  end
@@ -127,7 +127,14 @@ module ActiveRecord
127
127
  def cancel_any_running_query
128
128
  return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
129
129
 
130
- @raw_connection.cancel
130
+ # Skip @raw_connection.cancel (PG::Connection#cancel) when using libpq >= 18 with pg < 1.6.0,
131
+ # because the pg gem cannot obtain the backend_key in that case.
132
+ # This method is only called from exec_rollback_db_transaction and exec_restart_db_transaction.
133
+ # Even without cancel, rollback will still run. However, since any running
134
+ # query must finish first, the rollback may take longer.
135
+ if !(PG.library_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0"))
136
+ @raw_connection.cancel
137
+ end
131
138
  @raw_connection.block
132
139
  rescue PG::Error
133
140
  end
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
8
  delegate :quoted_include_columns_for_index, to: :@conn
9
+ delegate :database_version, to: :@conn
9
10
 
10
11
  def visit_AlterTable(o)
11
12
  sql = super
@@ -126,16 +127,17 @@ module ActiveRecord
126
127
  end
127
128
 
128
129
  if as = options[:as]
129
- sql << " GENERATED ALWAYS AS (#{as})"
130
+ stored = options[:stored]
130
131
 
131
- if options[:stored]
132
- sql << " STORED"
133
- else
132
+ if stored != true && database_version < 18_00_00
134
133
  raise ArgumentError, <<~MSG
135
- PostgreSQL currently does not support VIRTUAL (not persisted) generated columns.
134
+ PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
136
135
  Specify 'stored: true' option for '#{options[:column].name}'
137
136
  MSG
138
137
  end
138
+
139
+ sql << " GENERATED ALWAYS AS (#{as})"
140
+ sql << (stored ? " STORED" : " VIRTUAL")
139
141
  end
140
142
  super
141
143
  end
@@ -103,7 +103,7 @@ module ActiveRecord
103
103
 
104
104
  if @connection.supports_virtual_columns? && column.virtual?
105
105
  spec[:as] = extract_expression_for_virtual_column(column)
106
- spec[:stored] = true
106
+ spec[:stored] = "true" if column.virtual_stored?
107
107
  spec = { type: schema_type(column).inspect }.merge!(spec)
108
108
  end
109
109
 
@@ -435,16 +435,13 @@ module ActiveRecord
435
435
  def primary_keys(table_name) # :nodoc:
436
436
  query_values(<<~SQL, "SCHEMA")
437
437
  SELECT a.attname
438
- FROM (
439
- SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
440
- FROM pg_index
441
- WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
442
- AND indisprimary
443
- ) i
444
- JOIN pg_attribute a
445
- ON a.attrelid = i.indrelid
446
- AND a.attnum = i.indkey[i.idx]
447
- ORDER BY i.idx
438
+ FROM pg_index i
439
+ JOIN pg_attribute a
440
+ ON a.attrelid = i.indrelid
441
+ AND a.attnum = ANY(i.indkey)
442
+ WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
443
+ AND i.indisprimary
444
+ ORDER BY array_position(i.indkey, a.attnum)
448
445
  SQL
449
446
  end
450
447
 
@@ -288,6 +288,16 @@ module ActiveRecord
288
288
  database_version >= 10_00_00 # >= 10.0
289
289
  end
290
290
 
291
+ if PG::Connection.method_defined?(:close_prepared) # pg 1.6.0 & libpq 17
292
+ def supports_close_prepared? # :nodoc:
293
+ database_version >= 17_00_00
294
+ end
295
+ else
296
+ def supports_close_prepared? # :nodoc:
297
+ false
298
+ end
299
+ end
300
+
291
301
  def index_algorithms
292
302
  { concurrently: "CONCURRENTLY" }
293
303
  end
@@ -309,8 +319,12 @@ module ActiveRecord
309
319
  # accessed while holding the connection's lock. (And we
310
320
  # don't need the complication of with_raw_connection because
311
321
  # a reconnect would invalidate the entire statement pool.)
312
- if conn = @connection.instance_variable_get(:@raw_connection)
313
- conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
322
+ if (conn = @connection.instance_variable_get(:@raw_connection)) && conn.status == PG::CONNECTION_OK
323
+ if @connection.supports_close_prepared?
324
+ conn.close_prepared key
325
+ else
326
+ conn.query "DEALLOCATE #{key}"
327
+ end
314
328
  end
315
329
  rescue PG::Error
316
330
  end
@@ -666,9 +680,6 @@ module ActiveRecord
666
680
  if database_version < 9_03_00 # < 9.3
667
681
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
668
682
  end
669
- if database_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0")
670
- warn "pg gem version #{PG::VERSION} is known to be incompatible with PostgreSQL 18+. Please upgrade to pg 1.6.0 or later."
671
- end
672
683
  end
673
684
 
674
685
  class << self
@@ -100,7 +100,8 @@ module ActiveRecord
100
100
  db_config = resolve_config_for_connection(database_key)
101
101
 
102
102
  self.connection_class = true
103
- connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym)
103
+ shard = shard.to_sym unless shard.is_a? Integer
104
+ connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
104
105
  end
105
106
  end
106
107
 
@@ -71,7 +71,10 @@ module ActiveRecord
71
71
  end
72
72
 
73
73
  def max_connections
74
- (configuration_hash[:max_connections] || configuration_hash[:pool] || 5).to_i
74
+ max_connections = configuration_hash.fetch(:max_connections) {
75
+ configuration_hash.fetch(:pool, 5)
76
+ }&.to_i
77
+ max_connections if max_connections && max_connections >= 0
75
78
  end
76
79
 
77
80
  def min_connections
@@ -86,7 +89,7 @@ module ActiveRecord
86
89
  end
87
90
 
88
91
  def max_threads
89
- (configuration_hash[:max_threads] || max_connections).to_i
92
+ (configuration_hash[:max_threads] || (max_connections || 5).clamp(0, 5)).to_i
90
93
  end
91
94
 
92
95
  def max_age
@@ -94,6 +94,18 @@ module ActiveRecord
94
94
  private
95
95
  DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
96
96
  ENCODING_ERRORS = [EncodingError, Errors::Encoding]
97
+
98
+ # This threshold cannot be changed.
99
+ #
100
+ # Users can search for attributes encrypted with `deterministic: true`.
101
+ # That is possible because we are able to generate the message for the
102
+ # given clear text deterministically, and with that perform a regular
103
+ # string lookup in SQL.
104
+ #
105
+ # Problem is, messages may have a "c" header that is present or not
106
+ # depending on whether compression was applied on encryption. If this
107
+ # threshold was modified, the message generated for lookup could vary
108
+ # for the same clear text, and searches on exisiting data could fail.
97
109
  THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
98
110
 
99
111
  def default_key_provider
@@ -12,7 +12,7 @@ module ActiveRecord
12
12
 
13
13
  # Raised when the single-table inheritance mechanism fails to locate the subclass
14
14
  # (for example due to improper usage of column that
15
- # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
15
+ # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema.inheritance_column]
16
16
  # points to).
17
17
  class SubclassNotFound < ActiveRecordError
18
18
  end
@@ -451,7 +451,7 @@ module ActiveRecord
451
451
  UnknownAttributeError = ActiveModel::UnknownAttributeError
452
452
 
453
453
  # Raised when an error occurred while doing a mass assignment to an attribute through the
454
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
454
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
455
455
  # The exception has an +attribute+ property that is the name of the offending attribute.
456
456
  class AttributeAssignmentError < ActiveRecordError
457
457
  attr_reader :exception, :attribute
@@ -464,7 +464,7 @@ module ActiveRecord
464
464
  end
465
465
 
466
466
  # Raised when there are multiple errors while doing a mass assignment through the
467
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
467
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=]
468
468
  # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
469
469
  # objects, each corresponding to the error while assigning to an attribute.
470
470
  class MultiparameterAssignmentErrors < ActiveRecordError
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  # Executes the block with the collect flag enabled. Queries are collected
8
8
  # asynchronously by the subscriber and returned.
9
9
  def collecting_queries_for_explain # :nodoc:
10
- ExplainRegistry.collect = true
10
+ ExplainRegistry.start
11
11
  yield
12
12
  ExplainRegistry.queries
13
13
  ensure
@@ -8,8 +8,53 @@ module ActiveRecord
8
8
  #
9
9
  # returns the collected queries local to the current thread.
10
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
+
11
56
  class << self
12
- delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
57
+ delegate :start, :reset, :collect, :collect=, :collect?, :queries, to: :instance
13
58
 
14
59
  private
15
60
  def instance
@@ -24,6 +69,11 @@ module ActiveRecord
24
69
  reset
25
70
  end
26
71
 
72
+ def start
73
+ Subscriber.ensure_subscribed
74
+ @collect = true
75
+ end
76
+
27
77
  def collect?
28
78
  @collect
29
79
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  MAJOR = 8
11
11
  MINOR = 1
12
12
  TINY = 0
13
- PRE = "beta1"
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
 
34
34
  class V8_0 < V8_1
35
35
  module RemoveForeignKeyColumnMatch
36
- def remove_foreign_key(from_table, to_table = nil, **options)
36
+ def remove_foreign_key(*args, **options)
37
37
  options[:_skip_column_match] = true
38
38
  super
39
39
  end
@@ -48,7 +48,7 @@ module ActiveRecord
48
48
  # way of creating a namespace for tables in a shared database. By default, the prefix is the
49
49
  # empty string.
50
50
  #
51
- # If you are organising your models within modules you can add a prefix to the models within
51
+ # If you are organizing your models within modules you can add a prefix to the models within
52
52
  # a namespace by defining a singleton method in the parent module called table_name_prefix which
53
53
  # returns your chosen prefix.
54
54
 
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
66
66
  # "people_basecamp"). By default, the suffix is the empty string.
67
67
  #
68
- # If you are organising your models within modules, you can add a suffix to the models within
68
+ # If you are organizing your models within modules, you can add a suffix to the models within
69
69
  # a namespace by defining a singleton method in the parent module called table_name_suffix which
70
70
  # returns your chosen suffix.
71
71
 
@@ -181,6 +181,7 @@ module ActiveRecord
181
181
  self.protected_environments = ["production"]
182
182
 
183
183
  self.ignored_columns = [].freeze
184
+ self.only_columns = [].freeze
184
185
 
185
186
  delegate :type_for_attribute, :column_for_attribute, to: :class
186
187
 
@@ -334,6 +335,12 @@ module ActiveRecord
334
335
  @ignored_columns || superclass.ignored_columns
335
336
  end
336
337
 
338
+ # The list of columns names the model should allow. Only columns are used to define
339
+ # attribute accessors, and are referenced in SQL queries.
340
+ def only_columns
341
+ @only_columns || superclass.only_columns
342
+ end
343
+
337
344
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
338
345
  # accessors defined, and won't be referenced in SQL queries.
339
346
  #
@@ -366,10 +373,17 @@ module ActiveRecord
366
373
  # user = Project.create!(name: "First Project")
367
374
  # user.category # => raises NoMethodError
368
375
  def ignored_columns=(columns)
376
+ check_model_columns(@only_columns.present?)
369
377
  reload_schema_from_cache
370
378
  @ignored_columns = columns.map(&:to_s).freeze
371
379
  end
372
380
 
381
+ def only_columns=(columns)
382
+ check_model_columns(@ignored_columns.present?)
383
+ reload_schema_from_cache
384
+ @only_columns = columns.map(&:to_s).freeze
385
+ end
386
+
373
387
  def sequence_name
374
388
  if base_class?
375
389
  @sequence_name ||= reset_sequence_name
@@ -579,6 +593,7 @@ module ActiveRecord
579
593
  child_class.reload_schema_from_cache(false)
580
594
  child_class.class_eval do
581
595
  @ignored_columns = nil
596
+ @only_columns = nil
582
597
  end
583
598
  end
584
599
 
@@ -592,7 +607,11 @@ module ActiveRecord
592
607
  end
593
608
 
594
609
  columns_hash = schema_cache.columns_hash(table_name)
595
- columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
610
+ if only_columns.present?
611
+ columns_hash = columns_hash.slice(*only_columns)
612
+ elsif ignored_columns.present?
613
+ columns_hash = columns_hash.except(*ignored_columns)
614
+ end
596
615
  @columns_hash = columns_hash.freeze
597
616
 
598
617
  _default_attributes # Precompute to cache DB-dependent attribute types
@@ -631,6 +650,10 @@ module ActiveRecord
631
650
 
632
651
  type
633
652
  end
653
+
654
+ def check_model_columns(columns_present)
655
+ raise ArgumentError, "You can not use both only_columns and ignored_columns in the same model." if columns_present
656
+ end
634
657
  end
635
658
  end
636
659
  end
@@ -41,11 +41,14 @@ module ActiveRecord
41
41
 
42
42
  def cleanup_view_runtime
43
43
  if logger && logger.info?
44
- db_rt_before_render = ActiveRecord::RuntimeRegistry.reset_runtimes
44
+ runtime_stats = ActiveRecord::RuntimeRegistry.stats
45
+ db_rt_before_render = runtime_stats.reset_runtimes
45
46
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
47
+
46
48
  runtime = super
47
- queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
48
- db_rt_after_render = ActiveRecord::RuntimeRegistry.reset_runtimes
49
+
50
+ queries_rt = runtime_stats.sql_runtime - runtime_stats.async_sql_runtime
51
+ db_rt_after_render = runtime_stats.reset_runtimes
49
52
  self.db_runtime += db_rt_after_render
50
53
  runtime - queries_rt
51
54
  else
@@ -56,9 +59,11 @@ module ActiveRecord
56
59
  def append_info_to_payload(payload)
57
60
  super
58
61
 
59
- payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::RuntimeRegistry.reset_runtimes
60
- payload[:queries_count] = ActiveRecord::RuntimeRegistry.reset_queries_count
61
- payload[:cached_queries_count] = ActiveRecord::RuntimeRegistry.reset_cached_queries_count
62
+ runtime_stats = ActiveRecord::RuntimeRegistry.stats
63
+ payload[:db_runtime] = (db_runtime || 0) + runtime_stats.sql_runtime
64
+ payload[:queries_count] = runtime_stats.queries_count
65
+ payload[:cached_queries_count] = runtime_stats.cached_queries_count
66
+ runtime_stats.reset
62
67
  end
63
68
  end
64
69
  end
@@ -466,7 +466,7 @@ db_namespace = namespace :db do
466
466
 
467
467
  desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
468
468
  task load: [:load_config, :check_protected_environments] do
469
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(nil, ENV["SCHEMA"])
469
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ENV["SCHEMA_FORMAT"], ENV["SCHEMA"])
470
470
  end
471
471
 
472
472
  namespace :dump do
@@ -8,9 +8,9 @@ module ActiveRecord
8
8
  def instrument(operation, payload = {}, &block) # :nodoc:
9
9
  if operation == :perform && block
10
10
  super(operation, payload) do
11
- db_runtime_before_perform = ActiveRecord::RuntimeRegistry.sql_runtime
11
+ db_runtime_before_perform = ActiveRecord::RuntimeRegistry.stats.sql_runtime
12
12
  result = block.call
13
- payload[:db_runtime] = ActiveRecord::RuntimeRegistry.sql_runtime - db_runtime_before_perform
13
+ payload[:db_runtime] = ActiveRecord::RuntimeRegistry.stats.sql_runtime - db_runtime_before_perform
14
14
  result
15
15
  end
16
16
  else
@@ -437,7 +437,7 @@ module ActiveRecord
437
437
  values = records.pluck(*cursor)
438
438
  values_size = values.size
439
439
  values_last = values.last
440
- yielded_relation = where(cursor => values).order(batch_orders.to_h)
440
+ yielded_relation = rewhere(cursor => values)
441
441
  yielded_relation.load_records(records)
442
442
  elsif (empty_scope && use_ranges != false) || use_ranges
443
443
  # Efficiently peak at the last value for the next batch using offset and limit.
@@ -455,14 +455,14 @@ module ActiveRecord
455
455
  # Finally, build the yielded relation if at least one value found.
456
456
  if values_last
457
457
  yielded_relation = apply_finish_limit(batch_relation, cursor, values_last, batch_orders)
458
- yielded_relation = yielded_relation.except(:limit).reorder(batch_orders.to_h)
458
+ yielded_relation = yielded_relation.except(:limit, :order)
459
459
  yielded_relation.skip_query_cache!(false)
460
460
  end
461
461
  else
462
462
  values = batch_relation.pluck(*cursor)
463
463
  values_size = values.size
464
464
  values_last = values.last
465
- yielded_relation = where(cursor => values).order(batch_orders.to_h)
465
+ yielded_relation = rewhere(cursor => values)
466
466
  end
467
467
 
468
468
  break if values_size == 0
@@ -85,9 +85,9 @@ module ActiveRecord
85
85
  return if other.select_values.empty?
86
86
 
87
87
  if other.model == relation.model
88
- relation.select_values += other.select_values if relation.select_values != other.select_values
88
+ relation.select_values |= other.select_values
89
89
  else
90
- relation.select_values += other.instance_eval do
90
+ relation.select_values |= other.instance_eval do
91
91
  arel_columns(select_values)
92
92
  end
93
93
  end
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class AssociationQueryValue # :nodoc:
6
- def initialize(associated_table, value)
7
- @associated_table = associated_table
6
+ def initialize(reflection, value)
7
+ @reflection = reflection
8
8
  @value = value
9
9
  end
10
10
 
11
11
  def queries
12
- if associated_table.join_foreign_key.is_a?(Array)
12
+ if reflection.join_foreign_key.is_a?(Array)
13
13
  id_list = ids
14
14
  id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
15
 
16
- id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
16
+ id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
17
17
  else
18
- [ associated_table.join_foreign_key => ids ]
18
+ [ reflection.join_foreign_key => ids ]
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :value
23
+ attr_reader :reflection, :value
24
24
 
25
25
  def ids
26
26
  case value
@@ -37,15 +37,15 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  def primary_key
40
- associated_table.join_primary_key
40
+ reflection.join_primary_key
41
41
  end
42
42
 
43
43
  def primary_type
44
- associated_table.join_primary_type
44
+ reflection.join_primary_type
45
45
  end
46
46
 
47
47
  def polymorphic_name
48
- associated_table.polymorphic_name_association
48
+ reflection.polymorphic_name
49
49
  end
50
50
 
51
51
  def select_clause?
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class PolymorphicArrayValue # :nodoc:
6
- def initialize(associated_table, values)
7
- @associated_table = associated_table
6
+ def initialize(reflection, values)
7
+ @reflection = reflection
8
8
  @values = values
9
9
  end
10
10
 
11
11
  def queries
12
- return [ associated_table.join_foreign_key => values ] if values.empty?
12
+ return [ reflection.join_foreign_key => values ] if values.empty?
13
13
 
14
14
  type_to_ids_mapping.map do |type, ids|
15
15
  query = {}
16
- query[associated_table.join_foreign_type] = type if type
17
- query[associated_table.join_foreign_key] = ids
16
+ query[reflection.join_foreign_type] = type if type
17
+ query[reflection.join_foreign_key] = ids
18
18
  query
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :values
23
+ attr_reader :reflection, :values
24
24
 
25
25
  def type_to_ids_mapping
26
26
  default_hash = Hash.new { |hsh, key| hsh[key] = [] }
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def primary_key(value)
33
- associated_table.join_primary_key(klass(value))
33
+ reflection.join_primary_key(klass(value))
34
34
  end
35
35
 
36
36
  def klass(value)
@@ -99,24 +99,26 @@ module ActiveRecord
99
99
  elsif value.is_a?(Hash) && !table.has_column?(key)
100
100
  table.associated_table(key, &block)
101
101
  .predicate_builder.expand_from_hash(value.stringify_keys)
102
- elsif table.associated_with?(key)
102
+ elsif (associated_reflection = table.associated_with(key))
103
103
  # Find the foreign key when using queries such as:
104
104
  # Post.where(author: author)
105
105
  #
106
106
  # For polymorphic relationships, find the foreign key and type:
107
107
  # PriceEstimate.where(estimate_of: treasure)
108
- associated_table = table.associated_table(key)
109
- if associated_table.polymorphic_association?
108
+
109
+ if associated_reflection.polymorphic?
110
110
  value = [value] unless value.is_a?(Array)
111
111
  klass = PolymorphicArrayValue
112
- elsif associated_table.through_association?
112
+ elsif associated_reflection.through_reflection?
113
+ associated_table = table.associated_table(key)
114
+
113
115
  next associated_table.predicate_builder.expand_from_hash(
114
116
  associated_table.primary_key => value
115
117
  )
116
118
  end
117
119
 
118
120
  klass ||= AssociationQueryValue
119
- queries = klass.new(associated_table, value).queries.map! do |query|
121
+ queries = klass.new(associated_reflection, value).queries.map! do |query|
120
122
  # If the query produced is identical to attributes don't go any deeper.
121
123
  # Prevents stack level too deep errors when association and foreign_key are identical.
122
124
  query == attributes ? self[key, value] : expand_from_hash(query)
@@ -426,7 +426,7 @@ module ActiveRecord
426
426
  end
427
427
 
428
428
  def _select!(*fields) # :nodoc:
429
- self.select_values += fields
429
+ self.select_values |= fields
430
430
  self
431
431
  end
432
432