activerecord 7.1.6 → 7.2.3
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 +839 -2248
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- 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/collection_association.rb +16 -8
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- 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/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +59 -292
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +5 -25
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +51 -60
- data/lib/active_record/attributes.rb +93 -68
- data/lib/active_record/autosave_association.rb +25 -32
- data/lib/active_record/base.rb +4 -5
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +68 -49
- data/lib/active_record/core.rb +112 -44
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +42 -18
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
- data/lib/active_record/encryption/encryptor.rb +35 -19
- 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 +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/enum.rb +31 -13
- data/lib/active_record/errors.rb +49 -23
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +87 -77
- data/lib/active_record/model_schema.rb +31 -68
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +19 -0
- data/lib/active_record/querying.rb +25 -13
- data/lib/active_record/railtie.rb +39 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -44
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +127 -89
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +26 -12
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +238 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +15 -21
- data/lib/active_record/relation.rb +508 -74
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +48 -20
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +27 -7
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +69 -41
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +73 -15
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +3 -1
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +31 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +16 -10
|
@@ -14,7 +14,7 @@ module ActiveRecord
|
|
|
14
14
|
sql
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
|
|
17
|
+
def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_retry = false) # :nodoc:
|
|
18
18
|
# Arel::TreeManager -> Arel::Node
|
|
19
19
|
if arel_or_sql_string.respond_to?(:ast)
|
|
20
20
|
arel_or_sql_string = arel_or_sql_string.ast
|
|
@@ -27,6 +27,7 @@ module ActiveRecord
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
collector = collector()
|
|
30
|
+
collector.retryable = true
|
|
30
31
|
|
|
31
32
|
if prepared_statements
|
|
32
33
|
collector.preparable = true
|
|
@@ -41,10 +42,11 @@ module ActiveRecord
|
|
|
41
42
|
else
|
|
42
43
|
sql = visitor.compile(arel_or_sql_string, collector)
|
|
43
44
|
end
|
|
44
|
-
|
|
45
|
+
allow_retry = collector.retryable
|
|
46
|
+
[sql.freeze, binds, preparable, allow_retry]
|
|
45
47
|
else
|
|
46
48
|
arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
|
|
47
|
-
[arel_or_sql_string, binds, preparable]
|
|
49
|
+
[arel_or_sql_string, binds, preparable, allow_retry]
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
private :to_sql_and_binds
|
|
@@ -64,11 +66,15 @@ module ActiveRecord
|
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
# Returns an ActiveRecord::Result instance.
|
|
67
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
|
|
69
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
|
|
68
70
|
arel = arel_from_relation(arel)
|
|
69
|
-
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
|
71
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
|
|
70
72
|
|
|
71
|
-
select(sql, name, binds,
|
|
73
|
+
select(sql, name, binds,
|
|
74
|
+
prepare: prepared_statements && preparable,
|
|
75
|
+
async: async && FutureResult::SelectAll,
|
|
76
|
+
allow_retry: allow_retry
|
|
77
|
+
)
|
|
72
78
|
rescue ::RangeError
|
|
73
79
|
ActiveRecord::Result.empty(async: async)
|
|
74
80
|
end
|
|
@@ -214,7 +220,7 @@ module ActiveRecord
|
|
|
214
220
|
end
|
|
215
221
|
|
|
216
222
|
def truncate_tables(*table_names) # :nodoc:
|
|
217
|
-
table_names -= [schema_migration.table_name, internal_metadata.table_name]
|
|
223
|
+
table_names -= [pool.schema_migration.table_name, pool.internal_metadata.table_name]
|
|
218
224
|
|
|
219
225
|
return if table_names.empty?
|
|
220
226
|
|
|
@@ -229,6 +235,17 @@ module ActiveRecord
|
|
|
229
235
|
# Runs the given block in a database transaction, and returns the result
|
|
230
236
|
# of the block.
|
|
231
237
|
#
|
|
238
|
+
# == Transaction callbacks
|
|
239
|
+
#
|
|
240
|
+
# #transaction yields an ActiveRecord::Transaction object on which it is
|
|
241
|
+
# possible to register callback:
|
|
242
|
+
#
|
|
243
|
+
# ActiveRecord::Base.transaction do |transaction|
|
|
244
|
+
# transaction.before_commit { puts "before commit!" }
|
|
245
|
+
# transaction.after_commit { puts "after commit!" }
|
|
246
|
+
# transaction.after_rollback { puts "after rollback!" }
|
|
247
|
+
# end
|
|
248
|
+
#
|
|
232
249
|
# == Nested transactions support
|
|
233
250
|
#
|
|
234
251
|
# #transaction calls can be nested. By default, this makes all database
|
|
@@ -296,9 +313,9 @@ module ActiveRecord
|
|
|
296
313
|
# #transaction will raise exceptions when it tries to release the
|
|
297
314
|
# already-automatically-released savepoints:
|
|
298
315
|
#
|
|
299
|
-
# Model.
|
|
300
|
-
# Model.
|
|
301
|
-
# Model.
|
|
316
|
+
# Model.lease_connection.transaction do # BEGIN
|
|
317
|
+
# Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
|
318
|
+
# Model.lease_connection.create_table(...)
|
|
302
319
|
# # active_record_1 now automatically released
|
|
303
320
|
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
|
304
321
|
# end
|
|
@@ -339,7 +356,7 @@ module ActiveRecord
|
|
|
339
356
|
if isolation
|
|
340
357
|
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
|
341
358
|
end
|
|
342
|
-
yield
|
|
359
|
+
yield current_transaction.user_transaction
|
|
343
360
|
else
|
|
344
361
|
transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
|
|
345
362
|
end
|
|
@@ -457,8 +474,8 @@ module ActiveRecord
|
|
|
457
474
|
statements = table_deletes + fixture_inserts
|
|
458
475
|
|
|
459
476
|
with_multi_statements do
|
|
460
|
-
|
|
461
|
-
|
|
477
|
+
transaction(requires_new: true) do
|
|
478
|
+
disable_referential_integrity do
|
|
462
479
|
execute_batch(statements, "Fixtures Load")
|
|
463
480
|
end
|
|
464
481
|
end
|
|
@@ -495,7 +512,7 @@ module ActiveRecord
|
|
|
495
512
|
end
|
|
496
513
|
|
|
497
514
|
# This is a safe default, even if not high precision on all databases
|
|
498
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
|
515
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
|
|
499
516
|
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
|
500
517
|
|
|
501
518
|
# Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with
|
|
@@ -507,7 +524,7 @@ module ActiveRecord
|
|
|
507
524
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
|
508
525
|
end
|
|
509
526
|
|
|
510
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
|
527
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
|
511
528
|
raise NotImplementedError
|
|
512
529
|
end
|
|
513
530
|
|
|
@@ -606,7 +623,7 @@ module ActiveRecord
|
|
|
606
623
|
end
|
|
607
624
|
|
|
608
625
|
# Returns an ActiveRecord::Result instance.
|
|
609
|
-
def select(sql, name = nil, binds = [], prepare: false, async: false)
|
|
626
|
+
def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
|
|
610
627
|
if async && async_enabled?
|
|
611
628
|
if current_transaction.joinable?
|
|
612
629
|
raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
|
|
@@ -627,7 +644,7 @@ module ActiveRecord
|
|
|
627
644
|
return future_result
|
|
628
645
|
end
|
|
629
646
|
|
|
630
|
-
result = internal_exec_query(sql, name, binds, prepare: prepare)
|
|
647
|
+
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
|
631
648
|
if async
|
|
632
649
|
FutureResult.wrap(result)
|
|
633
650
|
else
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "concurrent/map"
|
|
4
|
+
require "concurrent/atomic/atomic_fixnum"
|
|
4
5
|
|
|
5
6
|
module ActiveRecord
|
|
6
7
|
module ConnectionAdapters # :nodoc:
|
|
@@ -13,15 +14,16 @@ module ActiveRecord
|
|
|
13
14
|
:truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
|
|
14
15
|
:exec_insert_all
|
|
15
16
|
|
|
16
|
-
base.set_callback :
|
|
17
|
-
base.set_callback :checkin, :after, :disable_query_cache!
|
|
17
|
+
base.set_callback :checkin, :after, :unset_query_cache!
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def dirties_query_cache(base, *method_names)
|
|
21
21
|
method_names.each do |method_name|
|
|
22
22
|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
|
23
23
|
def #{method_name}(...)
|
|
24
|
-
|
|
24
|
+
if pool.dirties_query_cache
|
|
25
|
+
ActiveRecord::Base.clear_query_caches_for_current_thread
|
|
26
|
+
end
|
|
25
27
|
super
|
|
26
28
|
end
|
|
27
29
|
end_code
|
|
@@ -29,60 +31,207 @@ module ActiveRecord
|
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
class Store # :nodoc:
|
|
35
|
+
attr_accessor :enabled, :dirties
|
|
36
|
+
alias_method :enabled?, :enabled
|
|
37
|
+
alias_method :dirties?, :dirties
|
|
38
|
+
|
|
39
|
+
def initialize(version, max_size)
|
|
40
|
+
@version = version
|
|
41
|
+
@current_version = version.value
|
|
42
|
+
@map = {}
|
|
43
|
+
@max_size = max_size
|
|
44
|
+
@enabled = false
|
|
45
|
+
@dirties = true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def size
|
|
49
|
+
check_version
|
|
50
|
+
@map.size
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def empty?
|
|
54
|
+
check_version
|
|
55
|
+
@map.empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def [](key)
|
|
59
|
+
check_version
|
|
60
|
+
return unless @enabled
|
|
61
|
+
|
|
62
|
+
if entry = @map.delete(key)
|
|
63
|
+
@map[key] = entry
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def compute_if_absent(key)
|
|
68
|
+
check_version
|
|
69
|
+
|
|
70
|
+
return yield unless @enabled
|
|
71
|
+
|
|
72
|
+
if entry = @map.delete(key)
|
|
73
|
+
return @map[key] = entry
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if @max_size && @map.size >= @max_size
|
|
77
|
+
@map.shift # evict the oldest entry
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@map[key] ||= yield
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def clear
|
|
84
|
+
@map.clear
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
def check_version
|
|
90
|
+
if @current_version != @version.value
|
|
91
|
+
@map.clear
|
|
92
|
+
@current_version = @version.value
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class QueryCacheRegistry # :nodoc:
|
|
98
|
+
def initialize
|
|
99
|
+
@mutex = Mutex.new
|
|
100
|
+
@map = ConnectionPool::WeakThreadKeyMap.new
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def compute_if_absent(context)
|
|
104
|
+
@map[context] || @mutex.synchronize do
|
|
105
|
+
@map[context] ||= yield
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def clear
|
|
110
|
+
@map.synchronize do
|
|
111
|
+
@map.clear
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
module ConnectionPoolConfiguration # :nodoc:
|
|
117
|
+
def initialize(...)
|
|
118
|
+
super
|
|
119
|
+
@query_cache_version = Concurrent::AtomicFixnum.new
|
|
120
|
+
@thread_query_caches = QueryCacheRegistry.new
|
|
121
|
+
@query_cache_max_size = \
|
|
122
|
+
case query_cache = db_config&.query_cache
|
|
123
|
+
when 0, false
|
|
124
|
+
nil
|
|
125
|
+
when Integer
|
|
126
|
+
query_cache
|
|
127
|
+
when nil
|
|
128
|
+
DEFAULT_SIZE
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def checkout_and_verify(connection)
|
|
34
133
|
super
|
|
35
|
-
|
|
134
|
+
connection.query_cache ||= query_cache
|
|
135
|
+
connection
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Disable the query cache within the block.
|
|
139
|
+
def disable_query_cache(dirties: true)
|
|
140
|
+
cache = query_cache
|
|
141
|
+
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
|
|
142
|
+
begin
|
|
143
|
+
yield
|
|
144
|
+
ensure
|
|
145
|
+
cache.enabled, cache.dirties = old_enabled, old_dirties
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def enable_query_cache
|
|
150
|
+
cache = query_cache
|
|
151
|
+
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
|
|
152
|
+
begin
|
|
153
|
+
yield
|
|
154
|
+
ensure
|
|
155
|
+
cache.enabled, cache.dirties = old_enabled, old_dirties
|
|
156
|
+
end
|
|
36
157
|
end
|
|
37
158
|
|
|
38
159
|
def enable_query_cache!
|
|
39
|
-
|
|
40
|
-
|
|
160
|
+
query_cache.enabled = true
|
|
161
|
+
query_cache.dirties = true
|
|
41
162
|
end
|
|
42
163
|
|
|
43
164
|
def disable_query_cache!
|
|
44
|
-
|
|
45
|
-
|
|
165
|
+
query_cache.enabled = false
|
|
166
|
+
query_cache.dirties = true
|
|
46
167
|
end
|
|
47
168
|
|
|
48
169
|
def query_cache_enabled
|
|
49
|
-
|
|
170
|
+
query_cache.enabled
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def dirties_query_cache
|
|
174
|
+
query_cache.dirties
|
|
50
175
|
end
|
|
51
|
-
end
|
|
52
176
|
|
|
53
|
-
|
|
177
|
+
def clear_query_cache
|
|
178
|
+
if @pinned_connection
|
|
179
|
+
# With transactional fixtures, and especially systems test
|
|
180
|
+
# another thread may use the same connection, but with a different
|
|
181
|
+
# query cache. So we must clear them all.
|
|
182
|
+
@query_cache_version.increment
|
|
183
|
+
end
|
|
184
|
+
query_cache.clear
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def query_cache
|
|
188
|
+
@thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
|
|
189
|
+
Store.new(@query_cache_version, @query_cache_max_size)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
54
193
|
|
|
55
194
|
def initialize(*)
|
|
56
195
|
super
|
|
57
|
-
@query_cache
|
|
58
|
-
|
|
59
|
-
|
|
196
|
+
@query_cache = nil
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
attr_writer :query_cache
|
|
200
|
+
|
|
201
|
+
def query_cache
|
|
202
|
+
if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
|
|
203
|
+
# With transactional tests, if the connection is pinned, any thread
|
|
204
|
+
# other than the one that pinned the connection need to go through the
|
|
205
|
+
# query cache pool, so each thread get a different cache.
|
|
206
|
+
pool.query_cache
|
|
207
|
+
else
|
|
208
|
+
@query_cache
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def query_cache_enabled
|
|
213
|
+
query_cache&.enabled?
|
|
60
214
|
end
|
|
61
215
|
|
|
62
216
|
# Enable the query cache within the block.
|
|
63
|
-
def cache
|
|
64
|
-
|
|
65
|
-
yield
|
|
66
|
-
ensure
|
|
67
|
-
@query_cache_enabled = old
|
|
68
|
-
clear_query_cache unless @query_cache_enabled
|
|
217
|
+
def cache(&block)
|
|
218
|
+
pool.enable_query_cache(&block)
|
|
69
219
|
end
|
|
70
220
|
|
|
71
221
|
def enable_query_cache!
|
|
72
|
-
|
|
222
|
+
pool.enable_query_cache!
|
|
73
223
|
end
|
|
74
224
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
225
|
+
# Disable the query cache within the block.
|
|
226
|
+
#
|
|
227
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
|
228
|
+
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
|
229
|
+
def uncached(dirties: true, &block)
|
|
230
|
+
pool.disable_query_cache(dirties: dirties, &block)
|
|
78
231
|
end
|
|
79
232
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
old, @query_cache_enabled = @query_cache_enabled, false
|
|
83
|
-
yield
|
|
84
|
-
ensure
|
|
85
|
-
@query_cache_enabled = old
|
|
233
|
+
def disable_query_cache!
|
|
234
|
+
pool.disable_query_cache!
|
|
86
235
|
end
|
|
87
236
|
|
|
88
237
|
# Clears the query cache.
|
|
@@ -92,24 +241,22 @@ module ActiveRecord
|
|
|
92
241
|
# the same SQL query and repeatedly return the same result each time, silently
|
|
93
242
|
# undermining the randomness you were expecting.
|
|
94
243
|
def clear_query_cache
|
|
95
|
-
|
|
96
|
-
@query_cache.clear
|
|
97
|
-
end
|
|
244
|
+
pool.clear_query_cache
|
|
98
245
|
end
|
|
99
246
|
|
|
100
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
|
|
247
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
|
|
101
248
|
arel = arel_from_relation(arel)
|
|
102
249
|
|
|
103
250
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
|
104
251
|
# Such queries should not be cached.
|
|
105
|
-
if
|
|
106
|
-
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
|
252
|
+
if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
|
|
253
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
|
107
254
|
|
|
108
255
|
if async
|
|
109
|
-
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
|
|
256
|
+
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
|
110
257
|
FutureResult.wrap(result)
|
|
111
258
|
else
|
|
112
|
-
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
|
|
259
|
+
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
|
|
113
260
|
end
|
|
114
261
|
else
|
|
115
262
|
super
|
|
@@ -117,42 +264,37 @@ module ActiveRecord
|
|
|
117
264
|
end
|
|
118
265
|
|
|
119
266
|
private
|
|
267
|
+
def unset_query_cache!
|
|
268
|
+
@query_cache = nil
|
|
269
|
+
end
|
|
270
|
+
|
|
120
271
|
def lookup_sql_cache(sql, name, binds)
|
|
121
272
|
key = binds.empty? ? sql : [sql, binds]
|
|
122
|
-
hit = false
|
|
123
|
-
result = nil
|
|
124
273
|
|
|
274
|
+
result = nil
|
|
125
275
|
@lock.synchronize do
|
|
126
|
-
|
|
127
|
-
hit = true
|
|
128
|
-
@query_cache[key] = result
|
|
129
|
-
end
|
|
276
|
+
result = query_cache[key]
|
|
130
277
|
end
|
|
131
278
|
|
|
132
|
-
if
|
|
279
|
+
if result
|
|
133
280
|
ActiveSupport::Notifications.instrument(
|
|
134
281
|
"sql.active_record",
|
|
135
282
|
cache_notification_info(sql, name, binds)
|
|
136
283
|
)
|
|
137
|
-
|
|
138
|
-
result
|
|
139
284
|
end
|
|
285
|
+
|
|
286
|
+
result
|
|
140
287
|
end
|
|
141
288
|
|
|
142
289
|
def cache_sql(sql, name, binds)
|
|
143
290
|
key = binds.empty? ? sql : [sql, binds]
|
|
144
291
|
result = nil
|
|
145
|
-
hit =
|
|
292
|
+
hit = true
|
|
146
293
|
|
|
147
294
|
@lock.synchronize do
|
|
148
|
-
|
|
149
|
-
hit =
|
|
150
|
-
|
|
151
|
-
else
|
|
152
|
-
result = @query_cache[key] = yield
|
|
153
|
-
if @query_cache_max_size && @query_cache.size > @query_cache_max_size
|
|
154
|
-
@query_cache.shift
|
|
155
|
-
end
|
|
295
|
+
result = query_cache.compute_if_absent(key) do
|
|
296
|
+
hit = false
|
|
297
|
+
yield
|
|
156
298
|
end
|
|
157
299
|
end
|
|
158
300
|
|
|
@@ -175,26 +317,10 @@ module ActiveRecord
|
|
|
175
317
|
type_casted_binds: -> { type_casted_binds(binds) },
|
|
176
318
|
name: name,
|
|
177
319
|
connection: self,
|
|
320
|
+
transaction: current_transaction.user_transaction.presence,
|
|
178
321
|
cached: true
|
|
179
322
|
}
|
|
180
323
|
end
|
|
181
|
-
|
|
182
|
-
def configure_query_cache!
|
|
183
|
-
case query_cache = pool.db_config.query_cache
|
|
184
|
-
when 0, false
|
|
185
|
-
return
|
|
186
|
-
when Integer
|
|
187
|
-
@query_cache_max_size = query_cache
|
|
188
|
-
when nil
|
|
189
|
-
@query_cache_max_size = DEFAULT_SIZE
|
|
190
|
-
else
|
|
191
|
-
@query_cache_max_size = nil # no limit
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
if pool.query_cache_enabled
|
|
195
|
-
enable_query_cache!
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
324
|
end
|
|
199
325
|
end
|
|
200
326
|
end
|