activerecord 8.0.3 → 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 +520 -514
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +2 -1
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +53 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +36 -10
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- 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 +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -492,6 +492,7 @@ module ActiveRecord
|
|
|
492
492
|
becoming.instance_variable_set(:@attributes, @attributes)
|
|
493
493
|
becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
|
|
494
494
|
becoming.instance_variable_set(:@new_record, new_record?)
|
|
495
|
+
becoming.instance_variable_set(:@previously_new_record, previously_new_record?)
|
|
495
496
|
becoming.instance_variable_set(:@destroyed, destroyed?)
|
|
496
497
|
becoming.errors.copy!(errors)
|
|
497
498
|
end
|
|
@@ -581,8 +582,8 @@ module ActiveRecord
|
|
|
581
582
|
end
|
|
582
583
|
|
|
583
584
|
# Equivalent to <code>update_columns(name => value)</code>.
|
|
584
|
-
def update_column(name, value)
|
|
585
|
-
update_columns(name => value)
|
|
585
|
+
def update_column(name, value, touch: nil)
|
|
586
|
+
update_columns(name => value, touch: touch)
|
|
586
587
|
end
|
|
587
588
|
|
|
588
589
|
# Updates the attributes directly in the database issuing an UPDATE SQL
|
|
@@ -596,11 +597,25 @@ module ActiveRecord
|
|
|
596
597
|
#
|
|
597
598
|
# * \Validations are skipped.
|
|
598
599
|
# * \Callbacks are skipped.
|
|
599
|
-
# * +updated_at+/+updated_on+ are
|
|
600
|
+
# * +updated_at+/+updated_on+ are updated if the +touch+ option is set to +true+.
|
|
600
601
|
# * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
|
|
601
602
|
#
|
|
602
603
|
# This method raises an ActiveRecord::ActiveRecordError when called on new
|
|
603
604
|
# objects, or when at least one of the attributes is marked as readonly.
|
|
605
|
+
#
|
|
606
|
+
# ==== Parameters
|
|
607
|
+
#
|
|
608
|
+
# * <tt>:touch</tt> option - Touch the timestamp columns when updating.
|
|
609
|
+
# * If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
|
610
|
+
#
|
|
611
|
+
# ==== Examples
|
|
612
|
+
#
|
|
613
|
+
# # Update a single attribute.
|
|
614
|
+
# user.update_columns(last_request_at: Time.current)
|
|
615
|
+
#
|
|
616
|
+
# # Update with touch option.
|
|
617
|
+
# user.update_columns(last_request_at: Time.current, touch: true)
|
|
618
|
+
|
|
604
619
|
def update_columns(attributes)
|
|
605
620
|
raise ActiveRecordError, "cannot update a new record" if new_record?
|
|
606
621
|
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
|
@@ -612,6 +627,15 @@ module ActiveRecord
|
|
|
612
627
|
verify_readonly_attribute(name) || name
|
|
613
628
|
end
|
|
614
629
|
|
|
630
|
+
touch = attributes.delete("touch")
|
|
631
|
+
if touch
|
|
632
|
+
names = touch if touch != true
|
|
633
|
+
names = Array.wrap(names)
|
|
634
|
+
options = names.extract_options!
|
|
635
|
+
touch_updates = self.class.touch_attributes_with_time(*names, **options)
|
|
636
|
+
attributes.with_defaults!(touch_updates) unless touch_updates.empty?
|
|
637
|
+
end
|
|
638
|
+
|
|
615
639
|
update_constraints = _query_constraints_hash
|
|
616
640
|
attributes = attributes.each_with_object({}) do |(k, v), h|
|
|
617
641
|
h[k] = @attributes.write_cast_value(k, v)
|
|
@@ -640,8 +664,15 @@ module ActiveRecord
|
|
|
640
664
|
# This means that any other modified attributes will still be dirty.
|
|
641
665
|
# Validations and callbacks are skipped. Supports the +touch+ option from
|
|
642
666
|
# +update_counters+, see that for more.
|
|
667
|
+
#
|
|
668
|
+
# This method raises an ActiveRecord::ActiveRecordError when called on new
|
|
669
|
+
# objects, or when at least one of the attributes is marked as readonly.
|
|
670
|
+
#
|
|
643
671
|
# Returns +self+.
|
|
644
672
|
def increment!(attribute, by = 1, touch: nil)
|
|
673
|
+
raise ActiveRecordError, "cannot update a new record" if new_record?
|
|
674
|
+
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
|
675
|
+
|
|
645
676
|
increment(attribute, by)
|
|
646
677
|
change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
|
|
647
678
|
self.class.update_counters(id, attribute => change, touch: touch)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# = Active Record Query Cache
|
|
5
5
|
class QueryCache
|
|
6
|
+
# ActiveRecord::Base extends this module, so these methods are available in models.
|
|
6
7
|
module ClassMethods
|
|
7
8
|
# Enable the query cache within the block if Active Record is configured.
|
|
8
9
|
# If it's not, it will execute the given block.
|
|
@@ -20,11 +21,15 @@ module ActiveRecord
|
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
24
|
+
# Runs the block with the query cache disabled.
|
|
25
|
+
#
|
|
26
|
+
# If the query cache was enabled before the block was executed, it is
|
|
27
|
+
# enabled again after it.
|
|
25
28
|
#
|
|
26
|
-
# Set <tt>dirties: false</tt> to prevent query caches on all connections
|
|
27
|
-
# (By default, write operations
|
|
29
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections
|
|
30
|
+
# from being cleared by write operations. (By default, write operations
|
|
31
|
+
# dirty all connections' query caches in case they are replicas whose
|
|
32
|
+
# cache would now be outdated.)
|
|
28
33
|
def uncached(dirties: true, &block)
|
|
29
34
|
if connected? || !configurations.empty?
|
|
30
35
|
connection_pool.disable_query_cache(dirties: dirties, &block)
|
|
@@ -34,22 +39,24 @@ module ActiveRecord
|
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
module ExecutorHooks # :nodoc:
|
|
43
|
+
def self.run
|
|
44
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool|
|
|
45
|
+
next if pool.db_config&.query_cache == false
|
|
46
|
+
pool.enable_query_cache!
|
|
47
|
+
end
|
|
41
48
|
end
|
|
42
|
-
end
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
def self.complete(pools)
|
|
51
|
+
pools.each do |pool|
|
|
52
|
+
pool.disable_query_cache!
|
|
53
|
+
pool.clear_query_cache
|
|
54
|
+
end
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
|
|
51
|
-
def self.install_executor_hooks(executor = ActiveSupport::Executor)
|
|
52
|
-
executor.register_hook(
|
|
58
|
+
def self.install_executor_hooks(executor = ActiveSupport::Executor) # :nodoc:
|
|
59
|
+
executor.register_hook(ExecutorHooks)
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
62
|
end
|
|
@@ -69,7 +69,7 @@ module ActiveRecord
|
|
|
69
69
|
#
|
|
70
70
|
# Tag comments can be prepended to the query:
|
|
71
71
|
#
|
|
72
|
-
#
|
|
72
|
+
# config.active_record.query_log_tags_prepend_comment = true
|
|
73
73
|
#
|
|
74
74
|
# For applications where the content will not change during the lifetime of
|
|
75
75
|
# the request or job execution, the tags can be cached for reuse in every query:
|
|
@@ -157,11 +157,7 @@ module ActiveRecord
|
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def query_source_location # :nodoc:
|
|
160
|
-
|
|
161
|
-
frame = LogSubscriber.backtrace_cleaner.clean_frame(location)
|
|
162
|
-
return frame if frame
|
|
163
|
-
end
|
|
164
|
-
nil
|
|
160
|
+
LogSubscriber.backtrace_cleaner.first_clean_frame
|
|
165
161
|
end
|
|
166
162
|
|
|
167
163
|
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
|
|
@@ -215,7 +211,7 @@ module ActiveRecord
|
|
|
215
211
|
end
|
|
216
212
|
|
|
217
213
|
def escape_sql_comment(content)
|
|
218
|
-
# Sanitize a string to appear within
|
|
214
|
+
# Sanitize a string to appear within an SQL comment
|
|
219
215
|
# For compatibility, this also surrounding "/*+", "/*", and "*/"
|
|
220
216
|
# characters, possibly with single surrounding space.
|
|
221
217
|
# Then follows that by replacing any internal "*/" or "/ *" with
|
|
@@ -35,9 +35,12 @@ module ActiveRecord
|
|
|
35
35
|
config.active_record.query_log_tags = [ :application ]
|
|
36
36
|
config.active_record.query_log_tags_format = :legacy
|
|
37
37
|
config.active_record.cache_query_log_tags = false
|
|
38
|
+
config.active_record.query_log_tags_prepend_comment = false
|
|
38
39
|
config.active_record.raise_on_assign_to_attr_readonly = false
|
|
39
40
|
config.active_record.belongs_to_required_validates_foreign_key = true
|
|
40
41
|
config.active_record.generate_secure_token_on = :create
|
|
42
|
+
config.active_record.use_legacy_signed_id_verifier = :generate_and_verify
|
|
43
|
+
config.active_record.deprecated_associations_options = { mode: :warn, backtrace: false }
|
|
41
44
|
|
|
42
45
|
config.active_record.queues = ActiveSupport::InheritableOptions.new
|
|
43
46
|
|
|
@@ -229,10 +232,12 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
229
232
|
:query_log_tags,
|
|
230
233
|
:query_log_tags_format,
|
|
231
234
|
:cache_query_log_tags,
|
|
235
|
+
:query_log_tags_prepend_comment,
|
|
232
236
|
:sqlite3_adapter_strict_strings_by_default,
|
|
233
237
|
:check_schema_cache_dump_version,
|
|
234
238
|
:use_schema_cache_dump,
|
|
235
239
|
:postgresql_adapter_decode_dates,
|
|
240
|
+
:use_legacy_signed_id_verifier,
|
|
236
241
|
)
|
|
237
242
|
|
|
238
243
|
configs_used_in_other_initializers.each do |k, v|
|
|
@@ -274,6 +279,13 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
274
279
|
end
|
|
275
280
|
end
|
|
276
281
|
|
|
282
|
+
initializer "active_record.job_checkpoints" do
|
|
283
|
+
require "active_record/railties/job_checkpoints"
|
|
284
|
+
ActiveSupport.on_load(:active_job_continuable) do
|
|
285
|
+
prepend ActiveRecord::Railties::JobCheckpoints
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
277
289
|
initializer "active_record.set_reloader_hooks" do
|
|
278
290
|
ActiveSupport.on_load(:active_record) do
|
|
279
291
|
ActiveSupport::Reloader.before_class_unload do
|
|
@@ -319,9 +331,22 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
319
331
|
end
|
|
320
332
|
end
|
|
321
333
|
|
|
322
|
-
initializer "active_record.
|
|
323
|
-
|
|
324
|
-
|
|
334
|
+
initializer "active_record.filter_attributes_as_log_parameters" do |app|
|
|
335
|
+
ActiveRecord::FilterAttributeHandler.new(app).enable
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
initializer "active_record.configure_message_verifiers" do |app|
|
|
339
|
+
ActiveRecord.message_verifiers = app.message_verifiers
|
|
340
|
+
|
|
341
|
+
use_legacy_signed_id_verifier = app.config.active_record.use_legacy_signed_id_verifier
|
|
342
|
+
legacy_options = { digest: "SHA256", serializer: JSON, url_safe: true }
|
|
343
|
+
|
|
344
|
+
if use_legacy_signed_id_verifier == :generate_and_verify
|
|
345
|
+
app.message_verifiers.prepend { |salt| legacy_options if salt == "active_record/signed_id" }
|
|
346
|
+
elsif use_legacy_signed_id_verifier == :verify
|
|
347
|
+
app.message_verifiers.rotate { |salt| legacy_options if salt == "active_record/signed_id" }
|
|
348
|
+
elsif use_legacy_signed_id_verifier
|
|
349
|
+
raise ArgumentError, "Unrecognized value for config.active_record.use_legacy_signed_id_verifier: #{use_legacy_signed_id_verifier.inspect}"
|
|
325
350
|
end
|
|
326
351
|
end
|
|
327
352
|
|
|
@@ -387,6 +412,10 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
387
412
|
if app.config.active_record.cache_query_log_tags
|
|
388
413
|
ActiveRecord::QueryLogs.cache_query_log_tags = true
|
|
389
414
|
end
|
|
415
|
+
|
|
416
|
+
if app.config.active_record.query_log_tags_prepend_comment
|
|
417
|
+
ActiveRecord::QueryLogs.prepend_comment = true
|
|
418
|
+
end
|
|
390
419
|
end
|
|
391
420
|
end
|
|
392
421
|
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
|
|
@@ -163,6 +163,18 @@ db_namespace = namespace :db do
|
|
|
163
163
|
desc "Resets your database using your migrations for the current environment"
|
|
164
164
|
task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"]
|
|
165
165
|
|
|
166
|
+
namespace :reset do
|
|
167
|
+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
168
|
+
desc "Drop and recreate the #{name} database using migrations"
|
|
169
|
+
task name => :load_config do
|
|
170
|
+
db_namespace["drop:#{name}"].invoke
|
|
171
|
+
db_namespace["create:#{name}"].invoke
|
|
172
|
+
db_namespace["schema:dump:#{name}"].invoke
|
|
173
|
+
db_namespace["migrate:#{name}"].invoke
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
166
178
|
desc 'Run the "up" for a given migration VERSION.'
|
|
167
179
|
task up: :load_config do
|
|
168
180
|
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
|
|
@@ -333,7 +345,7 @@ db_namespace = namespace :db do
|
|
|
333
345
|
pending_migrations << pool.migration_context.open.pending_migrations
|
|
334
346
|
end
|
|
335
347
|
|
|
336
|
-
pending_migrations
|
|
348
|
+
pending_migrations.flatten!
|
|
337
349
|
|
|
338
350
|
if pending_migrations.any?
|
|
339
351
|
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
|
|
@@ -459,7 +471,7 @@ db_namespace = namespace :db do
|
|
|
459
471
|
|
|
460
472
|
namespace :dump do
|
|
461
473
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
462
|
-
desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on
|
|
474
|
+
desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) for #{name} database"
|
|
463
475
|
task name => :load_config do
|
|
464
476
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
|
|
465
477
|
db_config = pool.db_config
|
|
@@ -474,7 +486,7 @@ db_namespace = namespace :db do
|
|
|
474
486
|
namespace :load do
|
|
475
487
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
476
488
|
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) into the #{name} database"
|
|
477
|
-
task name =>
|
|
489
|
+
task name => [:load_config, :check_protected_environments] do
|
|
478
490
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
|
|
479
491
|
db_config = pool.db_config
|
|
480
492
|
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Railties # :nodoc:
|
|
5
|
+
module JobCheckpoints # :nodoc:
|
|
6
|
+
def checkpoint!
|
|
7
|
+
if ActiveRecord.all_open_transactions.any?
|
|
8
|
+
raise ActiveJob::Continuation::CheckpointError, "Cannot checkpoint job with open transactions"
|
|
9
|
+
else
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -5,19 +5,18 @@ require "active_record/runtime_registry"
|
|
|
5
5
|
module ActiveRecord
|
|
6
6
|
module Railties # :nodoc:
|
|
7
7
|
module JobRuntime # :nodoc:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
result
|
|
16
|
-
end
|
|
17
|
-
else
|
|
18
|
-
super
|
|
8
|
+
def instrument(operation, payload = {}, &block) # :nodoc:
|
|
9
|
+
if operation == :perform && block
|
|
10
|
+
super(operation, payload) do
|
|
11
|
+
db_runtime_before_perform = ActiveRecord::RuntimeRegistry.stats.sql_runtime
|
|
12
|
+
result = block.call
|
|
13
|
+
payload[:db_runtime] = ActiveRecord::RuntimeRegistry.stats.sql_runtime - db_runtime_before_perform
|
|
14
|
+
result
|
|
19
15
|
end
|
|
16
|
+
else
|
|
17
|
+
super
|
|
20
18
|
end
|
|
19
|
+
end
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
end
|
|
@@ -425,10 +425,14 @@ module ActiveRecord
|
|
|
425
425
|
|
|
426
426
|
def _klass(class_name) # :nodoc:
|
|
427
427
|
if active_record.name.demodulize == class_name
|
|
428
|
-
|
|
428
|
+
begin
|
|
429
|
+
compute_class("::#{class_name}")
|
|
430
|
+
rescue NameError
|
|
431
|
+
compute_class(class_name)
|
|
432
|
+
end
|
|
433
|
+
else
|
|
434
|
+
compute_class(class_name)
|
|
429
435
|
end
|
|
430
|
-
|
|
431
|
-
compute_class(class_name)
|
|
432
436
|
end
|
|
433
437
|
|
|
434
438
|
def compute_class(name)
|
|
@@ -516,6 +520,8 @@ module ActiveRecord
|
|
|
516
520
|
|
|
517
521
|
def initialize(name, scope, options, active_record)
|
|
518
522
|
super
|
|
523
|
+
|
|
524
|
+
@validated = false
|
|
519
525
|
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
|
520
526
|
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
|
521
527
|
@join_table = nil
|
|
@@ -534,6 +540,8 @@ module ActiveRecord
|
|
|
534
540
|
options[:query_constraints] = options.delete(:foreign_key)
|
|
535
541
|
end
|
|
536
542
|
|
|
543
|
+
@deprecated = !!options[:deprecated]
|
|
544
|
+
|
|
537
545
|
ensure_option_not_given_as_class!(:class_name)
|
|
538
546
|
end
|
|
539
547
|
|
|
@@ -616,6 +624,8 @@ module ActiveRecord
|
|
|
616
624
|
end
|
|
617
625
|
|
|
618
626
|
def check_validity!
|
|
627
|
+
return if @validated
|
|
628
|
+
|
|
619
629
|
check_validity_of_inverse!
|
|
620
630
|
|
|
621
631
|
if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
|
|
@@ -625,6 +635,8 @@ module ActiveRecord
|
|
|
625
635
|
raise CompositePrimaryKeyMismatchError.new(self)
|
|
626
636
|
end
|
|
627
637
|
end
|
|
638
|
+
|
|
639
|
+
@validated = true
|
|
628
640
|
end
|
|
629
641
|
|
|
630
642
|
def check_eager_loadable!
|
|
@@ -742,6 +754,10 @@ module ActiveRecord
|
|
|
742
754
|
Array(options[:extend])
|
|
743
755
|
end
|
|
744
756
|
|
|
757
|
+
def deprecated?
|
|
758
|
+
@deprecated
|
|
759
|
+
end
|
|
760
|
+
|
|
745
761
|
private
|
|
746
762
|
# Attempts to find the inverse association name automatically.
|
|
747
763
|
# If it cannot find a suitable inverse association name, it returns
|
|
@@ -975,6 +991,8 @@ module ActiveRecord
|
|
|
975
991
|
|
|
976
992
|
def initialize(delegate_reflection)
|
|
977
993
|
super()
|
|
994
|
+
|
|
995
|
+
@validated = false
|
|
978
996
|
@delegate_reflection = delegate_reflection
|
|
979
997
|
@klass = delegate_reflection.options[:anonymous_class]
|
|
980
998
|
@source_reflection_name = delegate_reflection.options[:source]
|
|
@@ -1138,6 +1156,8 @@ module ActiveRecord
|
|
|
1138
1156
|
end
|
|
1139
1157
|
|
|
1140
1158
|
def check_validity!
|
|
1159
|
+
return if @validated
|
|
1160
|
+
|
|
1141
1161
|
if through_reflection.nil?
|
|
1142
1162
|
raise HasManyThroughAssociationNotFoundError.new(active_record, self)
|
|
1143
1163
|
end
|
|
@@ -1175,6 +1195,8 @@ module ActiveRecord
|
|
|
1175
1195
|
end
|
|
1176
1196
|
|
|
1177
1197
|
check_validity_of_inverse!
|
|
1198
|
+
|
|
1199
|
+
@validated = true
|
|
1178
1200
|
end
|
|
1179
1201
|
|
|
1180
1202
|
def constraints
|
|
@@ -1195,6 +1217,10 @@ module ActiveRecord
|
|
|
1195
1217
|
collect_join_reflections(seed + [self])
|
|
1196
1218
|
end
|
|
1197
1219
|
|
|
1220
|
+
def deprecated_nested_reflections
|
|
1221
|
+
@deprecated_nested_reflections ||= collect_deprecated_nested_reflections
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1198
1224
|
protected
|
|
1199
1225
|
def actual_source_reflection # FIXME: this is a horrible name
|
|
1200
1226
|
source_reflection.actual_source_reflection
|
|
@@ -1219,6 +1245,19 @@ module ActiveRecord
|
|
|
1219
1245
|
options[:source_type] || source_reflection.class_name
|
|
1220
1246
|
end
|
|
1221
1247
|
|
|
1248
|
+
def collect_deprecated_nested_reflections
|
|
1249
|
+
result = []
|
|
1250
|
+
[through_reflection, source_reflection].each do |reflection|
|
|
1251
|
+
result << reflection if reflection.deprecated?
|
|
1252
|
+
# Both the through and the source reflections could be through
|
|
1253
|
+
# themselves. Nesting can go an arbitrary number of levels down.
|
|
1254
|
+
if reflection.through_reflection?
|
|
1255
|
+
result.concat(reflection.deprecated_nested_reflections)
|
|
1256
|
+
end
|
|
1257
|
+
end
|
|
1258
|
+
result
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1222
1261
|
delegate_methods = AssociationReflection.public_instance_methods -
|
|
1223
1262
|
public_instance_methods
|
|
1224
1263
|
|
|
@@ -435,35 +435,49 @@ module ActiveRecord
|
|
|
435
435
|
if load
|
|
436
436
|
records = batch_relation.records
|
|
437
437
|
values = records.pluck(*cursor)
|
|
438
|
-
|
|
438
|
+
values_size = values.size
|
|
439
|
+
values_last = values.last
|
|
440
|
+
yielded_relation = rewhere(cursor => values)
|
|
439
441
|
yielded_relation.load_records(records)
|
|
440
442
|
elsif (empty_scope && use_ranges != false) || use_ranges
|
|
441
|
-
|
|
443
|
+
# Efficiently peak at the last value for the next batch using offset and limit.
|
|
444
|
+
values_size = batch_limit
|
|
445
|
+
values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)
|
|
446
|
+
|
|
447
|
+
# If the last value is not found using offset, there is at most one more batch of size < batch_limit.
|
|
448
|
+
# Retry by getting the whole list of remaining values so that we have the exact size and last value.
|
|
449
|
+
unless values_last
|
|
450
|
+
values = batch_relation.pluck(*cursor)
|
|
451
|
+
values_size = values.size
|
|
452
|
+
values_last = values.last
|
|
453
|
+
end
|
|
442
454
|
|
|
443
|
-
|
|
444
|
-
if
|
|
445
|
-
yielded_relation = apply_finish_limit(batch_relation, cursor,
|
|
455
|
+
# Finally, build the yielded relation if at least one value found.
|
|
456
|
+
if values_last
|
|
457
|
+
yielded_relation = apply_finish_limit(batch_relation, cursor, values_last, batch_orders)
|
|
446
458
|
yielded_relation = yielded_relation.except(:limit, :order)
|
|
447
459
|
yielded_relation.skip_query_cache!(false)
|
|
448
460
|
end
|
|
449
461
|
else
|
|
450
462
|
values = batch_relation.pluck(*cursor)
|
|
451
|
-
|
|
463
|
+
values_size = values.size
|
|
464
|
+
values_last = values.last
|
|
465
|
+
yielded_relation = rewhere(cursor => values)
|
|
452
466
|
end
|
|
453
467
|
|
|
454
|
-
break if
|
|
468
|
+
break if values_size == 0
|
|
455
469
|
|
|
456
|
-
if
|
|
470
|
+
if [values_last].flatten.any?(nil)
|
|
457
471
|
raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
|
|
458
472
|
"or some columns contain nil."
|
|
459
473
|
end
|
|
460
474
|
|
|
461
475
|
yield yielded_relation
|
|
462
476
|
|
|
463
|
-
break if
|
|
477
|
+
break if values_size < batch_limit
|
|
464
478
|
|
|
465
479
|
if limit_value
|
|
466
|
-
remaining -=
|
|
480
|
+
remaining -= values_size
|
|
467
481
|
|
|
468
482
|
if remaining == 0
|
|
469
483
|
# Saves a useless iteration when the limit is a multiple of the
|
|
@@ -481,7 +495,7 @@ module ActiveRecord
|
|
|
481
495
|
end
|
|
482
496
|
operators << (last_order == :desc ? :lt : :gt)
|
|
483
497
|
|
|
484
|
-
cursor_value =
|
|
498
|
+
cursor_value = values_last
|
|
485
499
|
batch_relation = batch_condition(relation, cursor, cursor_value, operators)
|
|
486
500
|
end
|
|
487
501
|
|
|
@@ -286,6 +286,11 @@ module ActiveRecord
|
|
|
286
286
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
287
287
|
# # => ['0', '27761', '173']
|
|
288
288
|
#
|
|
289
|
+
# Be aware that #pluck ignores any previous select clauses
|
|
290
|
+
#
|
|
291
|
+
# Person.select(:name).pluck(:id)
|
|
292
|
+
# # SELECT people.id FROM people
|
|
293
|
+
#
|
|
289
294
|
# See also #ids.
|
|
290
295
|
def pluck(*column_names)
|
|
291
296
|
if @none
|
|
@@ -314,7 +319,7 @@ module ActiveRecord
|
|
|
314
319
|
columns = relation.arel_columns(column_names)
|
|
315
320
|
relation.select_values = columns
|
|
316
321
|
result = skip_query_cache_if_necessary do
|
|
317
|
-
if where_clause.contradiction?
|
|
322
|
+
if where_clause.contradiction? && !possible_aggregation?(column_names)
|
|
318
323
|
ActiveRecord::Result.empty(async: @async)
|
|
319
324
|
else
|
|
320
325
|
model.with_connection do |c|
|
|
@@ -417,7 +422,7 @@ module ActiveRecord
|
|
|
417
422
|
when :all
|
|
418
423
|
Arel.star
|
|
419
424
|
else
|
|
420
|
-
arel_column(column_name)
|
|
425
|
+
arel_column(column_name.to_s)
|
|
421
426
|
end
|
|
422
427
|
end
|
|
423
428
|
|
|
@@ -461,6 +466,16 @@ module ActiveRecord
|
|
|
461
466
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
462
467
|
end
|
|
463
468
|
|
|
469
|
+
def possible_aggregation?(column_names)
|
|
470
|
+
column_names.all? do |column_name|
|
|
471
|
+
if column_name.is_a?(String)
|
|
472
|
+
column_name.include?("(")
|
|
473
|
+
else
|
|
474
|
+
Arel.arel_node?(column_name)
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
464
479
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
465
480
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
466
481
|
end
|
|
@@ -512,7 +527,6 @@ module ActiveRecord
|
|
|
512
527
|
|
|
513
528
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
514
529
|
group_fields = group_values
|
|
515
|
-
group_fields = group_fields.uniq if group_fields.size > 1
|
|
516
530
|
|
|
517
531
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
518
532
|
association = model._reflect_on_association(group_fields.first)
|
|
@@ -535,7 +549,7 @@ module ActiveRecord
|
|
|
535
549
|
column = relation.aggregate_column(column_name)
|
|
536
550
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
537
551
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
538
|
-
select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
552
|
+
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
539
553
|
|
|
540
554
|
select_values = [select_value]
|
|
541
555
|
select_values += self.select_values unless having_clause.empty?
|
|
@@ -662,6 +676,7 @@ module ActiveRecord
|
|
|
662
676
|
if column_name == :all
|
|
663
677
|
column_alias = Arel.star
|
|
664
678
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
679
|
+
relation.unscope!(:order)
|
|
665
680
|
else
|
|
666
681
|
column_alias = Arel.sql("count_column")
|
|
667
682
|
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
@@ -670,11 +685,7 @@ module ActiveRecord
|
|
|
670
685
|
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
671
686
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
672
687
|
|
|
673
|
-
|
|
674
|
-
relation.unscope(:order).build_subquery(subquery_alias, select_value)
|
|
675
|
-
else
|
|
676
|
-
relation.build_subquery(subquery_alias, select_value)
|
|
677
|
-
end
|
|
688
|
+
relation.build_subquery(subquery_alias, select_value)
|
|
678
689
|
end
|
|
679
690
|
end
|
|
680
691
|
end
|