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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -4
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +32 -13
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +9 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -11
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +0 -2
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +16 -5
- data/lib/active_record/connection_handling.rb +2 -1
- data/lib/active_record/database_configurations/hash_config.rb +5 -2
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/errors.rb +3 -3
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +1 -1
- data/lib/active_record/model_schema.rb +26 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/railties/job_runtime.rb +2 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +7 -5
- data/lib/active_record/relation/query_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +2 -0
- data/lib/active_record/relation.rb +1 -1
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/database_tasks.rb +24 -14
- data/lib/active_record/test_databases.rb +4 -2
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +9 -9
- data/lib/active_record/explain_subscriber.rb +0 -34
@@ -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
|
-
|
130
|
+
stored = options[:stored]
|
130
131
|
|
131
|
-
if
|
132
|
-
sql << " STORED"
|
133
|
-
else
|
132
|
+
if stored != true && database_version < 18_00_00
|
134
133
|
raise ArgumentError, <<~MSG
|
135
|
-
PostgreSQL
|
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
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/active_record/errors.rb
CHANGED
@@ -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
|
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.
|
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
|
@@ -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
|
@@ -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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
60
|
-
payload[:
|
61
|
-
payload[:
|
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(
|
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 =
|
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)
|
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 =
|
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
|
88
|
+
relation.select_values |= other.select_values
|
89
89
|
else
|
90
|
-
relation.select_values
|
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(
|
7
|
-
@
|
6
|
+
def initialize(reflection, value)
|
7
|
+
@reflection = reflection
|
8
8
|
@value = value
|
9
9
|
end
|
10
10
|
|
11
11
|
def queries
|
12
|
-
if
|
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|
|
16
|
+
id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
|
17
17
|
else
|
18
|
-
[
|
18
|
+
[ reflection.join_foreign_key => ids ]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
|
-
attr_reader :
|
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
|
-
|
40
|
+
reflection.join_primary_key
|
41
41
|
end
|
42
42
|
|
43
43
|
def primary_type
|
44
|
-
|
44
|
+
reflection.join_primary_type
|
45
45
|
end
|
46
46
|
|
47
47
|
def polymorphic_name
|
48
|
-
|
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(
|
7
|
-
@
|
6
|
+
def initialize(reflection, values)
|
7
|
+
@reflection = reflection
|
8
8
|
@values = values
|
9
9
|
end
|
10
10
|
|
11
11
|
def queries
|
12
|
-
return [
|
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[
|
17
|
-
query[
|
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 :
|
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
|
-
|
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
|
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
|
-
|
109
|
-
if
|
108
|
+
|
109
|
+
if associated_reflection.polymorphic?
|
110
110
|
value = [value] unless value.is_a?(Array)
|
111
111
|
klass = PolymorphicArrayValue
|
112
|
-
elsif
|
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(
|
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)
|