activerecord 7.0.8.6 → 7.2.2.1
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 +631 -1939
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +56 -14
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Promise < BasicObject
|
5
|
+
undef_method :==, :!, :!=
|
6
|
+
|
7
|
+
def initialize(future_result, block) # :nodoc:
|
8
|
+
@future_result = future_result
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns whether the associated query is still being executed or not.
|
13
|
+
def pending?
|
14
|
+
@future_result.pending?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the query result.
|
18
|
+
# If the query wasn't completed yet, accessing +#value+ will block until the query completes.
|
19
|
+
# If the query failed, +#value+ will raise the corresponding error.
|
20
|
+
def value
|
21
|
+
return @value if defined? @value
|
22
|
+
|
23
|
+
result = @future_result.result
|
24
|
+
@value = if @block
|
25
|
+
@block.call(result)
|
26
|
+
else
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a new +ActiveRecord::Promise+ that will apply the passed block
|
32
|
+
# when the value is accessed:
|
33
|
+
#
|
34
|
+
# Post.async_pick(:title).then { |title| title.upcase }.value
|
35
|
+
# # => "POST TITLE"
|
36
|
+
def then(&block)
|
37
|
+
Promise.new(@future_result, @block ? @block >> block : block)
|
38
|
+
end
|
39
|
+
|
40
|
+
[:class, :respond_to?, :is_a?].each do |method|
|
41
|
+
define_method(method, ::Object.instance_method(method))
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect # :nodoc:
|
45
|
+
"#<ActiveRecord::Promise status=#{status}>"
|
46
|
+
end
|
47
|
+
|
48
|
+
def pretty_print(q) # :nodoc:
|
49
|
+
q.text(inspect)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def status
|
54
|
+
if @future_result.pending?
|
55
|
+
:pending
|
56
|
+
elsif @future_result.canceled?
|
57
|
+
:canceled
|
58
|
+
else
|
59
|
+
:complete
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Complete < self # :nodoc:
|
64
|
+
attr_reader :value
|
65
|
+
|
66
|
+
def initialize(value)
|
67
|
+
@value = value
|
68
|
+
end
|
69
|
+
|
70
|
+
def then
|
71
|
+
Complete.new(yield @value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def pending?
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def status
|
80
|
+
:complete
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -8,7 +8,13 @@ module ActiveRecord
|
|
8
8
|
# If it's not, it will execute the given block.
|
9
9
|
def cache(&block)
|
10
10
|
if connected? || !configurations.empty?
|
11
|
-
|
11
|
+
pool = connection_pool
|
12
|
+
was_enabled = pool.query_cache_enabled
|
13
|
+
begin
|
14
|
+
pool.enable_query_cache(&block)
|
15
|
+
ensure
|
16
|
+
pool.clear_query_cache unless was_enabled
|
17
|
+
end
|
12
18
|
else
|
13
19
|
yield
|
14
20
|
end
|
@@ -16,9 +22,12 @@ module ActiveRecord
|
|
16
22
|
|
17
23
|
# Disable the query cache within the block if Active Record is configured.
|
18
24
|
# If it's not, it will execute the given block.
|
19
|
-
|
25
|
+
#
|
26
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
27
|
+
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
28
|
+
def uncached(dirties: true, &block)
|
20
29
|
if connected? || !configurations.empty?
|
21
|
-
|
30
|
+
connection_pool.disable_query_cache(dirties: dirties, &block)
|
22
31
|
else
|
23
32
|
yield
|
24
33
|
end
|
@@ -26,32 +35,16 @@ module ActiveRecord
|
|
26
35
|
end
|
27
36
|
|
28
37
|
def self.run
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
ActiveRecord::Base.connection_handlers.each do |key, handler|
|
33
|
-
pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
|
34
|
-
end
|
35
|
-
else
|
36
|
-
pools.concat(ActiveRecord::Base.connection_handler.all_connection_pools.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
|
38
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool|
|
39
|
+
next if pool.db_config&.query_cache == false
|
40
|
+
pool.enable_query_cache!
|
37
41
|
end
|
38
|
-
|
39
|
-
pools
|
40
42
|
end
|
41
43
|
|
42
44
|
def self.complete(pools)
|
43
|
-
pools.each
|
44
|
-
|
45
|
-
|
46
|
-
ActiveRecord::Base.connection_handlers.each do |_, handler|
|
47
|
-
handler.connection_pool_list.each do |pool|
|
48
|
-
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
|
49
|
-
end
|
50
|
-
end
|
51
|
-
else
|
52
|
-
ActiveRecord::Base.connection_handler.all_connection_pools.each do |pool|
|
53
|
-
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
|
54
|
-
end
|
45
|
+
pools.each do |pool|
|
46
|
+
pool.disable_query_cache!
|
47
|
+
pool.clear_query_cache
|
55
48
|
end
|
56
49
|
end
|
57
50
|
|
@@ -1,62 +1,67 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
4
|
+
require "active_record/query_logs_formatter"
|
4
5
|
|
5
6
|
module ActiveRecord
|
6
7
|
# = Active Record Query Logs
|
7
8
|
#
|
8
|
-
# Automatically
|
9
|
+
# Automatically append comments to SQL queries with runtime information tags. This can be used to trace troublesome
|
10
|
+
# SQL statements back to the application code that generated these statements.
|
9
11
|
#
|
10
|
-
#
|
12
|
+
# Query logs can be enabled via \Rails configuration in <tt>config/application.rb</tt> or an initializer:
|
13
|
+
#
|
14
|
+
# config.active_record.query_log_tags_enabled = true
|
15
|
+
#
|
16
|
+
# By default the name of the application, the name and action of the controller, or the name of the job are logged.
|
17
|
+
# The default format is {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/].
|
18
|
+
# The tags shown in a query comment can be configured via \Rails configuration:
|
19
|
+
#
|
20
|
+
# config.active_record.query_log_tags = [ :application, :controller, :action, :job ]
|
21
|
+
#
|
22
|
+
# Active Record defines default tags available for use:
|
11
23
|
#
|
12
24
|
# * +application+
|
13
25
|
# * +pid+
|
14
26
|
# * +socket+
|
15
27
|
# * +db_host+
|
16
28
|
# * +database+
|
29
|
+
# * +source_location+
|
17
30
|
#
|
18
|
-
#
|
31
|
+
# Action Controller adds default tags when loaded:
|
19
32
|
#
|
20
33
|
# * +controller+
|
21
34
|
# * +action+
|
22
|
-
# * +
|
23
|
-
#
|
24
|
-
# The tags used in a query can be configured directly:
|
25
|
-
#
|
26
|
-
# ActiveRecord::QueryLogs.tags = [ :application, :controller, :action, :job ]
|
35
|
+
# * +namespaced_controller+
|
27
36
|
#
|
28
|
-
#
|
37
|
+
# Active Job adds default tags when loaded:
|
29
38
|
#
|
30
|
-
#
|
39
|
+
# * +job+
|
31
40
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
41
|
+
# New comment tags can be defined by adding them in a +Hash+ to the tags +Array+. Tags can have dynamic content by
|
42
|
+
# setting a +Proc+ or lambda value in the +Hash+, and can reference any value stored by \Rails in the +context+ object.
|
43
|
+
# ActiveSupport::CurrentAttributes can be used to store application values. Tags with +nil+ values are
|
44
|
+
# omitted from the query comment.
|
35
45
|
#
|
36
46
|
# Escaping is performed on the string returned, however untrusted user input should not be used.
|
37
47
|
#
|
38
48
|
# Example:
|
39
49
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# end
|
56
|
-
#
|
57
|
-
# Direct updates to a context value:
|
58
|
-
#
|
59
|
-
# ActiveSupport::ExecutionContext[:foo] = Bar.new
|
50
|
+
# config.active_record.query_log_tags = [
|
51
|
+
# :namespaced_controller,
|
52
|
+
# :action,
|
53
|
+
# :job,
|
54
|
+
# {
|
55
|
+
# request_id: ->(context) { context[:controller]&.request&.request_id },
|
56
|
+
# job_id: ->(context) { context[:job]&.job_id },
|
57
|
+
# tenant_id: -> { Current.tenant&.id },
|
58
|
+
# static: "value",
|
59
|
+
# },
|
60
|
+
# ]
|
61
|
+
#
|
62
|
+
# By default the name of the application, the name and action of the controller, or the name of the job are logged
|
63
|
+
# using the {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/] format. This can be changed
|
64
|
+
# via {config.active_record.query_log_tags_format}[https://guides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-format]
|
60
65
|
#
|
61
66
|
# Tag comments can be prepended to the query:
|
62
67
|
#
|
@@ -65,46 +70,79 @@ module ActiveRecord
|
|
65
70
|
# For applications where the content will not change during the lifetime of
|
66
71
|
# the request or job execution, the tags can be cached for reuse in every query:
|
67
72
|
#
|
68
|
-
# ActiveRecord::QueryLogs.cache_query_log_tags = true
|
69
|
-
#
|
70
|
-
# This option can be set during application configuration or in a Rails initializer:
|
71
|
-
#
|
72
73
|
# config.active_record.cache_query_log_tags = true
|
73
74
|
module QueryLogs
|
74
75
|
mattr_accessor :taggings, instance_accessor: false, default: {}
|
75
76
|
mattr_accessor :tags, instance_accessor: false, default: [ :application ]
|
76
77
|
mattr_accessor :prepend_comment, instance_accessor: false, default: false
|
77
78
|
mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
|
79
|
+
mattr_accessor :tags_formatter, instance_accessor: false
|
78
80
|
thread_mattr_accessor :cached_comment, instance_accessor: false
|
79
81
|
|
80
82
|
class << self
|
81
|
-
def call(sql) # :nodoc:
|
82
|
-
|
83
|
-
|
83
|
+
def call(sql, connection) # :nodoc:
|
84
|
+
comment = self.comment(connection)
|
85
|
+
|
86
|
+
if comment.blank?
|
87
|
+
sql
|
88
|
+
elsif prepend_comment
|
89
|
+
"#{comment} #{sql}"
|
84
90
|
else
|
85
|
-
"#{sql} #{
|
86
|
-
end
|
91
|
+
"#{sql} #{comment}"
|
92
|
+
end
|
87
93
|
end
|
88
94
|
|
89
95
|
def clear_cache # :nodoc:
|
90
96
|
self.cached_comment = nil
|
91
97
|
end
|
92
98
|
|
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
|
+
if Thread.respond_to?(:each_caller_location)
|
113
|
+
def query_source_location # :nodoc:
|
114
|
+
Thread.each_caller_location do |location|
|
115
|
+
frame = LogSubscriber.backtrace_cleaner.clean_frame(location)
|
116
|
+
return frame if frame
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
else
|
121
|
+
def query_source_location # :nodoc:
|
122
|
+
LogSubscriber.backtrace_cleaner.clean(caller_locations(1).each).first
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
93
126
|
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
|
94
127
|
|
95
128
|
private
|
96
129
|
# Returns an SQL comment +String+ containing the query log tags.
|
97
130
|
# Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
|
98
|
-
def comment
|
131
|
+
def comment(connection)
|
99
132
|
if cache_query_log_tags
|
100
|
-
self.cached_comment ||= uncached_comment
|
133
|
+
self.cached_comment ||= uncached_comment(connection)
|
101
134
|
else
|
102
|
-
uncached_comment
|
135
|
+
uncached_comment(connection)
|
103
136
|
end
|
104
137
|
end
|
105
138
|
|
106
|
-
def
|
107
|
-
|
139
|
+
def formatter
|
140
|
+
self.tags_formatter || self.update_formatter(:legacy)
|
141
|
+
end
|
142
|
+
|
143
|
+
def uncached_comment(connection)
|
144
|
+
content = tag_content(connection)
|
145
|
+
|
108
146
|
if content.present?
|
109
147
|
"/*#{escape_sql_comment(content)}*/"
|
110
148
|
end
|
@@ -113,7 +151,7 @@ module ActiveRecord
|
|
113
151
|
def escape_sql_comment(content)
|
114
152
|
# Sanitize a string to appear within a SQL comment
|
115
153
|
# For compatibility, this also surrounding "/*+", "/*", and "*/"
|
116
|
-
#
|
154
|
+
# characters, possibly with single surrounding space.
|
117
155
|
# Then follows that by replacing any internal "*/" or "/ *" with
|
118
156
|
# "* /" or "/ *"
|
119
157
|
comment = content.to_s.dup
|
@@ -123,10 +161,11 @@ module ActiveRecord
|
|
123
161
|
comment
|
124
162
|
end
|
125
163
|
|
126
|
-
def tag_content
|
164
|
+
def tag_content(connection)
|
127
165
|
context = ActiveSupport::ExecutionContext.to_h
|
166
|
+
context[:connection] ||= connection
|
128
167
|
|
129
|
-
tags.flat_map { |i| [*i] }.filter_map do |tag|
|
168
|
+
pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
|
130
169
|
key, handler = tag
|
131
170
|
handler ||= taggings[key]
|
132
171
|
|
@@ -141,8 +180,9 @@ module ActiveRecord
|
|
141
180
|
else
|
142
181
|
handler
|
143
182
|
end
|
144
|
-
|
145
|
-
end
|
183
|
+
[key, val] unless val.nil?
|
184
|
+
end
|
185
|
+
self.formatter.format(pairs)
|
146
186
|
end
|
147
187
|
end
|
148
188
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
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
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SQLCommenter < LegacyFormatter # :nodoc:
|
26
|
+
def initialize
|
27
|
+
@key_value_separator = "="
|
28
|
+
end
|
29
|
+
|
30
|
+
def format(pairs)
|
31
|
+
pairs.sort_by! { |pair| pair.first.to_s }
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def format_value(value)
|
37
|
+
"'#{ERB::Util.url_encode(value)}'"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -10,14 +10,16 @@ module ActiveRecord
|
|
10
10
|
:first_or_create, :first_or_create!, :first_or_initialize,
|
11
11
|
:find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
|
12
12
|
:create_or_find_by, :create_or_find_by!,
|
13
|
-
:destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
|
13
|
+
:destroy, :destroy_all, :delete, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
|
14
14
|
:find_each, :find_in_batches, :in_batches,
|
15
|
-
:select, :reselect, :order, :in_order_of, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
|
15
|
+
:select, :reselect, :order, :regroup, :in_order_of, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
|
16
16
|
:where, :rewhere, :invert_where, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly,
|
17
17
|
:and, :or, :annotate, :optimizer_hints, :extending,
|
18
18
|
:having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
|
19
19
|
:count, :average, :minimum, :maximum, :sum, :calculate,
|
20
|
-
:pluck, :pick, :ids, :strict_loading, :excluding, :without
|
20
|
+
:pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with, :with_recursive,
|
21
|
+
:async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick,
|
22
|
+
:insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
|
21
23
|
].freeze # :nodoc:
|
22
24
|
delegate(*QUERYING_METHODS, to: :all)
|
23
25
|
|
@@ -46,12 +48,26 @@ module ActiveRecord
|
|
46
48
|
#
|
47
49
|
# Note that building your own SQL query string from user input may expose your application to
|
48
50
|
# injection attacks (https://guides.rubyonrails.org/security.html#sql-injection).
|
49
|
-
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
50
|
-
|
51
|
+
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
|
52
|
+
result = with_connection do |c|
|
53
|
+
_query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
|
54
|
+
end
|
55
|
+
_load_from_sql(result, &block)
|
56
|
+
end
|
57
|
+
|
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|
|
65
|
+
_load_from_sql(result, &block)
|
66
|
+
end
|
51
67
|
end
|
52
68
|
|
53
|
-
def _query_by_sql(sql, binds = [], preparable: nil, async: false) # :nodoc:
|
54
|
-
connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async)
|
69
|
+
def _query_by_sql(connection, sql, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
|
70
|
+
connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
55
71
|
end
|
56
72
|
|
57
73
|
def _load_from_sql(result_set, &block) # :nodoc:
|
@@ -91,7 +107,16 @@ module ActiveRecord
|
|
91
107
|
#
|
92
108
|
# * +sql+ - An SQL statement which should return a count query from the database, see the example above.
|
93
109
|
def count_by_sql(sql)
|
94
|
-
|
110
|
+
with_connection do |c|
|
111
|
+
c.select_value(sanitize_sql(sql), "#{name} Count").to_i
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Same as <tt>#count_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
116
|
+
def async_count_by_sql(sql)
|
117
|
+
with_connection do |c|
|
118
|
+
c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
|
119
|
+
end
|
95
120
|
end
|
96
121
|
end
|
97
122
|
end
|