activerecord 7.0.0 → 7.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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- 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 +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- 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 +28 -18
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- 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 +2 -4
- 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 +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- 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 +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- data/lib/active_record/callbacks.rb +16 -32
- 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 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
- 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 +297 -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 +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- 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 +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- 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 +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- 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 +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- 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 +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- 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 +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- 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 +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -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 +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- 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 +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -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 +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- 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/message.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 +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_subscriber.rb +1 -1
- 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 +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- data/lib/active_record/marshalling.rb +56 -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 +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- 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 +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- 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 +278 -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 +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- 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 +38 -4
- 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 +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -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 +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- 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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- 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 +109 -27
- data/lib/active_record/translation.rb +1 -3
- 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 +9 -7
- 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 +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- 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/filter_predications.rb +1 -1
- 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/filter.rb +1 -1
- 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} +9 -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/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 +59 -17
- 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,17 @@ module ActiveRecord
|
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
def self.run
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if ActiveRecord.legacy_connection_handling
|
|
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! })
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
pools
|
|
38
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each(&:enable_query_cache!)
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def self.complete(pools)
|
|
43
|
-
pools.each
|
|
42
|
+
pools.each do |pool|
|
|
43
|
+
pool.disable_query_cache!
|
|
44
|
+
pool.clear_query_cache
|
|
45
|
+
end
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
47
|
+
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
|
48
|
+
pool.release_connection if pool.active_connection? && !pool.lease_connection.transaction_open?
|
|
55
49
|
end
|
|
56
50
|
end
|
|
57
51
|
|
|
@@ -1,60 +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
|
-
# * +
|
|
35
|
+
# * +namespaced_controller+
|
|
23
36
|
#
|
|
24
|
-
#
|
|
37
|
+
# Active Job adds default tags when loaded:
|
|
25
38
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# or via Rails configuration:
|
|
39
|
+
# * +job+
|
|
29
40
|
#
|
|
30
|
-
#
|
|
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.
|
|
31
45
|
#
|
|
32
|
-
#
|
|
33
|
-
# want to add to the comment. Dynamic content can be created by setting a proc or lambda value in a hash,
|
|
34
|
-
# and can reference any value stored in the +context+ object.
|
|
46
|
+
# Escaping is performed on the string returned, however untrusted user input should not be used.
|
|
35
47
|
#
|
|
36
48
|
# Example:
|
|
37
49
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# end
|
|
54
|
-
#
|
|
55
|
-
# Direct updates to a context value:
|
|
56
|
-
#
|
|
57
|
-
# 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]
|
|
58
65
|
#
|
|
59
66
|
# Tag comments can be prepended to the query:
|
|
60
67
|
#
|
|
@@ -63,59 +70,102 @@ module ActiveRecord
|
|
|
63
70
|
# For applications where the content will not change during the lifetime of
|
|
64
71
|
# the request or job execution, the tags can be cached for reuse in every query:
|
|
65
72
|
#
|
|
66
|
-
# ActiveRecord::QueryLogs.cache_query_log_tags = true
|
|
67
|
-
#
|
|
68
|
-
# This option can be set during application configuration or in a Rails initializer:
|
|
69
|
-
#
|
|
70
73
|
# config.active_record.cache_query_log_tags = true
|
|
71
74
|
module QueryLogs
|
|
72
75
|
mattr_accessor :taggings, instance_accessor: false, default: {}
|
|
73
76
|
mattr_accessor :tags, instance_accessor: false, default: [ :application ]
|
|
74
77
|
mattr_accessor :prepend_comment, instance_accessor: false, default: false
|
|
75
78
|
mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
|
|
79
|
+
mattr_accessor :tags_formatter, instance_accessor: false
|
|
76
80
|
thread_mattr_accessor :cached_comment, instance_accessor: false
|
|
77
81
|
|
|
78
82
|
class << self
|
|
79
|
-
def call(sql) # :nodoc:
|
|
80
|
-
|
|
81
|
-
|
|
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}"
|
|
82
90
|
else
|
|
83
|
-
"#{sql} #{
|
|
84
|
-
end
|
|
91
|
+
"#{sql} #{comment}"
|
|
92
|
+
end
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
def clear_cache # :nodoc:
|
|
88
96
|
self.cached_comment = nil
|
|
89
97
|
end
|
|
90
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.path)
|
|
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
|
+
|
|
91
126
|
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
|
|
92
127
|
|
|
93
128
|
private
|
|
94
129
|
# Returns an SQL comment +String+ containing the query log tags.
|
|
95
130
|
# Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
|
|
96
|
-
def comment
|
|
131
|
+
def comment(connection)
|
|
97
132
|
if cache_query_log_tags
|
|
98
|
-
self.cached_comment ||= uncached_comment
|
|
133
|
+
self.cached_comment ||= uncached_comment(connection)
|
|
99
134
|
else
|
|
100
|
-
uncached_comment
|
|
135
|
+
uncached_comment(connection)
|
|
101
136
|
end
|
|
102
137
|
end
|
|
103
138
|
|
|
104
|
-
def
|
|
105
|
-
|
|
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
|
+
|
|
106
146
|
if content.present?
|
|
107
147
|
"/*#{escape_sql_comment(content)}*/"
|
|
108
148
|
end
|
|
109
149
|
end
|
|
110
150
|
|
|
111
151
|
def escape_sql_comment(content)
|
|
112
|
-
|
|
152
|
+
# Sanitize a string to appear within a SQL comment
|
|
153
|
+
# For compatibility, this also surrounding "/*+", "/*", and "*/"
|
|
154
|
+
# characters, possibly with single surrounding space.
|
|
155
|
+
# Then follows that by replacing any internal "*/" or "/ *" with
|
|
156
|
+
# "* /" or "/ *"
|
|
157
|
+
comment = content.to_s.dup
|
|
158
|
+
comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
|
|
159
|
+
comment.gsub!("*/", "* /")
|
|
160
|
+
comment.gsub!("/*", "/ *")
|
|
161
|
+
comment
|
|
113
162
|
end
|
|
114
163
|
|
|
115
|
-
def tag_content
|
|
164
|
+
def tag_content(connection)
|
|
116
165
|
context = ActiveSupport::ExecutionContext.to_h
|
|
166
|
+
context[:connection] ||= connection
|
|
117
167
|
|
|
118
|
-
tags.flat_map { |i| [*i] }.filter_map do |tag|
|
|
168
|
+
pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
|
|
119
169
|
key, handler = tag
|
|
120
170
|
handler ||= taggings[key]
|
|
121
171
|
|
|
@@ -130,8 +180,9 @@ module ActiveRecord
|
|
|
130
180
|
else
|
|
131
181
|
handler
|
|
132
182
|
end
|
|
133
|
-
|
|
134
|
-
end
|
|
183
|
+
[key, val] unless val.nil?
|
|
184
|
+
end
|
|
185
|
+
self.formatter.format(pairs)
|
|
135
186
|
end
|
|
136
187
|
end
|
|
137
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
|
|
|
@@ -39,19 +41,33 @@ module ActiveRecord
|
|
|
39
41
|
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
|
|
40
42
|
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "author"=>"Quentin"}>, ...]
|
|
41
43
|
#
|
|
42
|
-
# You can use the same string replacement techniques as you can with
|
|
44
|
+
# You can use the same string replacement techniques as you can with ActiveRecord::QueryMethods#where :
|
|
43
45
|
#
|
|
44
46
|
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
|
45
47
|
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
|
|
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
|