activerecord 7.1.5.1 → 8.0.2
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 +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- 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 +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -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 +10 -3
- 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 +4 -3
- 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 +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- 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 +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- 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 +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -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 +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- 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/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -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 +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- 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/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -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 +72 -17
- 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 +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -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 +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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,196 @@ 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(...)
|
34
118
|
super
|
35
|
-
@
|
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)
|
133
|
+
super
|
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
|
175
|
+
end
|
176
|
+
|
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
|
50
191
|
end
|
51
192
|
end
|
52
193
|
|
53
|
-
|
194
|
+
attr_accessor :query_cache
|
54
195
|
|
55
196
|
def initialize(*)
|
56
197
|
super
|
57
|
-
@query_cache
|
58
|
-
|
59
|
-
|
198
|
+
@query_cache = nil
|
199
|
+
end
|
200
|
+
|
201
|
+
def query_cache_enabled
|
202
|
+
@query_cache&.enabled?
|
60
203
|
end
|
61
204
|
|
62
205
|
# 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
|
206
|
+
def cache(&block)
|
207
|
+
pool.enable_query_cache(&block)
|
69
208
|
end
|
70
209
|
|
71
210
|
def enable_query_cache!
|
72
|
-
|
211
|
+
pool.enable_query_cache!
|
73
212
|
end
|
74
213
|
|
75
|
-
|
76
|
-
|
77
|
-
|
214
|
+
# Disable the query cache within the block.
|
215
|
+
#
|
216
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
217
|
+
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
218
|
+
def uncached(dirties: true, &block)
|
219
|
+
pool.disable_query_cache(dirties: dirties, &block)
|
78
220
|
end
|
79
221
|
|
80
|
-
|
81
|
-
|
82
|
-
old, @query_cache_enabled = @query_cache_enabled, false
|
83
|
-
yield
|
84
|
-
ensure
|
85
|
-
@query_cache_enabled = old
|
222
|
+
def disable_query_cache!
|
223
|
+
pool.disable_query_cache!
|
86
224
|
end
|
87
225
|
|
88
226
|
# Clears the query cache.
|
@@ -92,24 +230,22 @@ module ActiveRecord
|
|
92
230
|
# the same SQL query and repeatedly return the same result each time, silently
|
93
231
|
# undermining the randomness you were expecting.
|
94
232
|
def clear_query_cache
|
95
|
-
|
96
|
-
@query_cache.clear
|
97
|
-
end
|
233
|
+
pool.clear_query_cache
|
98
234
|
end
|
99
235
|
|
100
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
|
236
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
|
101
237
|
arel = arel_from_relation(arel)
|
102
238
|
|
103
239
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
104
240
|
# Such queries should not be cached.
|
105
|
-
if @
|
106
|
-
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
241
|
+
if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
|
242
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
107
243
|
|
108
244
|
if async
|
109
|
-
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
|
245
|
+
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
110
246
|
FutureResult.wrap(result)
|
111
247
|
else
|
112
|
-
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
|
248
|
+
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
|
113
249
|
end
|
114
250
|
else
|
115
251
|
super
|
@@ -117,55 +253,56 @@ module ActiveRecord
|
|
117
253
|
end
|
118
254
|
|
119
255
|
private
|
256
|
+
def unset_query_cache!
|
257
|
+
@query_cache = nil
|
258
|
+
end
|
259
|
+
|
120
260
|
def lookup_sql_cache(sql, name, binds)
|
121
261
|
key = binds.empty? ? sql : [sql, binds]
|
122
|
-
hit = false
|
123
|
-
result = nil
|
124
262
|
|
263
|
+
result = nil
|
125
264
|
@lock.synchronize do
|
126
|
-
|
127
|
-
hit = true
|
128
|
-
@query_cache[key] = result
|
129
|
-
end
|
265
|
+
result = @query_cache[key]
|
130
266
|
end
|
131
267
|
|
132
|
-
if
|
268
|
+
if result
|
133
269
|
ActiveSupport::Notifications.instrument(
|
134
270
|
"sql.active_record",
|
135
|
-
|
271
|
+
cache_notification_info_result(sql, name, binds, result)
|
136
272
|
)
|
137
|
-
|
138
|
-
result
|
139
273
|
end
|
274
|
+
|
275
|
+
result
|
140
276
|
end
|
141
277
|
|
142
278
|
def cache_sql(sql, name, binds)
|
143
279
|
key = binds.empty? ? sql : [sql, binds]
|
144
280
|
result = nil
|
145
|
-
hit =
|
281
|
+
hit = true
|
146
282
|
|
147
283
|
@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
|
284
|
+
result = @query_cache.compute_if_absent(key) do
|
285
|
+
hit = false
|
286
|
+
yield
|
156
287
|
end
|
157
288
|
end
|
158
289
|
|
159
290
|
if hit
|
160
291
|
ActiveSupport::Notifications.instrument(
|
161
292
|
"sql.active_record",
|
162
|
-
|
293
|
+
cache_notification_info_result(sql, name, binds, result)
|
163
294
|
)
|
164
295
|
end
|
165
296
|
|
166
297
|
result.dup
|
167
298
|
end
|
168
299
|
|
300
|
+
def cache_notification_info_result(sql, name, binds, result)
|
301
|
+
payload = cache_notification_info(sql, name, binds)
|
302
|
+
payload[:row_count] = result.length
|
303
|
+
payload
|
304
|
+
end
|
305
|
+
|
169
306
|
# Database adapters can override this method to
|
170
307
|
# provide custom cache information.
|
171
308
|
def cache_notification_info(sql, name, binds)
|
@@ -175,26 +312,10 @@ module ActiveRecord
|
|
175
312
|
type_casted_binds: -> { type_casted_binds(binds) },
|
176
313
|
name: name,
|
177
314
|
connection: self,
|
315
|
+
transaction: current_transaction.user_transaction.presence,
|
178
316
|
cached: true
|
179
317
|
}
|
180
318
|
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
319
|
end
|
199
320
|
end
|
200
321
|
end
|
@@ -7,6 +7,67 @@ module ActiveRecord
|
|
7
7
|
module ConnectionAdapters # :nodoc:
|
8
8
|
# = Active Record Connection Adapters \Quoting
|
9
9
|
module Quoting
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
module ClassMethods # :nodoc:
|
13
|
+
# Regexp for column names (with or without a table name prefix).
|
14
|
+
# Matches the following:
|
15
|
+
#
|
16
|
+
# "#{table_name}.#{column_name}"
|
17
|
+
# "#{column_name}"
|
18
|
+
def column_name_matcher
|
19
|
+
/
|
20
|
+
\A
|
21
|
+
(
|
22
|
+
(?:
|
23
|
+
# table_name.column_name | function(one or no argument)
|
24
|
+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
25
|
+
)
|
26
|
+
(?:(?:\s+AS)?\s+\w+)?
|
27
|
+
)
|
28
|
+
(?:\s*,\s*\g<1>)*
|
29
|
+
\z
|
30
|
+
/ix
|
31
|
+
end
|
32
|
+
|
33
|
+
# Regexp for column names with order (with or without a table name prefix,
|
34
|
+
# with or without various order modifiers). Matches the following:
|
35
|
+
#
|
36
|
+
# "#{table_name}.#{column_name}"
|
37
|
+
# "#{table_name}.#{column_name} #{direction}"
|
38
|
+
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
39
|
+
# "#{table_name}.#{column_name} NULLS LAST"
|
40
|
+
# "#{column_name}"
|
41
|
+
# "#{column_name} #{direction}"
|
42
|
+
# "#{column_name} #{direction} NULLS FIRST"
|
43
|
+
# "#{column_name} NULLS LAST"
|
44
|
+
def column_name_with_order_matcher
|
45
|
+
/
|
46
|
+
\A
|
47
|
+
(
|
48
|
+
(?:
|
49
|
+
# table_name.column_name | function(one or no argument)
|
50
|
+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
51
|
+
)
|
52
|
+
(?:\s+ASC|\s+DESC)?
|
53
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
54
|
+
)
|
55
|
+
(?:\s*,\s*\g<1>)*
|
56
|
+
\z
|
57
|
+
/ix
|
58
|
+
end
|
59
|
+
|
60
|
+
# Quotes the column name. Must be implemented by subclasses
|
61
|
+
def quote_column_name(column_name)
|
62
|
+
raise NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
# Quotes the table name. Defaults to column name quoting.
|
66
|
+
def quote_table_name(table_name)
|
67
|
+
quote_column_name(table_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
10
71
|
# Quotes the column value to help prevent
|
11
72
|
# {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
|
12
73
|
def quote(value)
|
@@ -23,9 +84,6 @@ module ActiveRecord
|
|
23
84
|
when Type::Time::Value then "'#{quoted_time(value)}'"
|
24
85
|
when Date, Time then "'#{quoted_date(value)}'"
|
25
86
|
when Class then "'#{value}'"
|
26
|
-
when ActiveSupport::Duration
|
27
|
-
warn_quote_duration_deprecated
|
28
|
-
value.to_s
|
29
87
|
else raise TypeError, "can't quote #{value.class.name}"
|
30
88
|
end
|
31
89
|
end
|
@@ -48,21 +106,6 @@ module ActiveRecord
|
|
48
106
|
end
|
49
107
|
end
|
50
108
|
|
51
|
-
# Quote a value to be used as a bound parameter of unknown type. For example,
|
52
|
-
# MySQL might perform dangerous castings when comparing a string to a number,
|
53
|
-
# so this method will cast numbers to string.
|
54
|
-
#
|
55
|
-
# Deprecated: Consider `Arel.sql("... ? ...", value)` or
|
56
|
-
# +sanitize_sql+ instead.
|
57
|
-
def quote_bound_value(value)
|
58
|
-
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
59
|
-
#quote_bound_value is deprecated and will be removed in Rails 7.2.
|
60
|
-
Consider Arel.sql(".. ? ..", value) or #sanitize_sql instead.
|
61
|
-
MSG
|
62
|
-
|
63
|
-
quote(cast_bound_value(value))
|
64
|
-
end
|
65
|
-
|
66
109
|
# Cast a value to be used as a bound parameter of unknown type. For example,
|
67
110
|
# MySQL might perform dangerous castings when comparing a string to a number,
|
68
111
|
# so this method will cast numbers to string.
|
@@ -89,14 +132,14 @@ module ActiveRecord
|
|
89
132
|
s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
|
90
133
|
end
|
91
134
|
|
92
|
-
# Quotes the column name.
|
135
|
+
# Quotes the column name.
|
93
136
|
def quote_column_name(column_name)
|
94
|
-
column_name
|
137
|
+
self.class.quote_column_name(column_name)
|
95
138
|
end
|
96
139
|
|
97
|
-
# Quotes the table name.
|
140
|
+
# Quotes the table name.
|
98
141
|
def quote_table_name(table_name)
|
99
|
-
|
142
|
+
self.class.quote_table_name(table_name)
|
100
143
|
end
|
101
144
|
|
102
145
|
# Override to return the quoted table name for assignment. Defaults to
|
@@ -177,62 +220,9 @@ module ActiveRecord
|
|
177
220
|
comment
|
178
221
|
end
|
179
222
|
|
180
|
-
def column_name_matcher # :nodoc:
|
181
|
-
COLUMN_NAME
|
182
|
-
end
|
183
|
-
|
184
|
-
def column_name_with_order_matcher # :nodoc:
|
185
|
-
COLUMN_NAME_WITH_ORDER
|
186
|
-
end
|
187
|
-
|
188
|
-
# Regexp for column names (with or without a table name prefix).
|
189
|
-
# Matches the following:
|
190
|
-
#
|
191
|
-
# "#{table_name}.#{column_name}"
|
192
|
-
# "#{column_name}"
|
193
|
-
COLUMN_NAME = /
|
194
|
-
\A
|
195
|
-
(
|
196
|
-
(?:
|
197
|
-
# table_name.column_name | function(one or no argument)
|
198
|
-
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
199
|
-
)
|
200
|
-
(?:(?:\s+AS)?\s+\w+)?
|
201
|
-
)
|
202
|
-
(?:\s*,\s*\g<1>)*
|
203
|
-
\z
|
204
|
-
/ix
|
205
|
-
|
206
|
-
# Regexp for column names with order (with or without a table name prefix,
|
207
|
-
# with or without various order modifiers). Matches the following:
|
208
|
-
#
|
209
|
-
# "#{table_name}.#{column_name}"
|
210
|
-
# "#{table_name}.#{column_name} #{direction}"
|
211
|
-
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
212
|
-
# "#{table_name}.#{column_name} NULLS LAST"
|
213
|
-
# "#{column_name}"
|
214
|
-
# "#{column_name} #{direction}"
|
215
|
-
# "#{column_name} #{direction} NULLS FIRST"
|
216
|
-
# "#{column_name} NULLS LAST"
|
217
|
-
COLUMN_NAME_WITH_ORDER = /
|
218
|
-
\A
|
219
|
-
(
|
220
|
-
(?:
|
221
|
-
# table_name.column_name | function(one or no argument)
|
222
|
-
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
223
|
-
)
|
224
|
-
(?:\s+ASC|\s+DESC)?
|
225
|
-
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
226
|
-
)
|
227
|
-
(?:\s*,\s*\g<1>)*
|
228
|
-
\z
|
229
|
-
/ix
|
230
|
-
|
231
|
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
232
|
-
|
233
223
|
private
|
234
224
|
def type_casted_binds(binds)
|
235
|
-
binds
|
225
|
+
binds&.map do |value|
|
236
226
|
if ActiveModel::Attribute === value
|
237
227
|
type_cast(value.value_for_database)
|
238
228
|
else
|
@@ -244,22 +234,6 @@ module ActiveRecord
|
|
244
234
|
def lookup_cast_type(sql_type)
|
245
235
|
type_map.lookup(sql_type)
|
246
236
|
end
|
247
|
-
|
248
|
-
def warn_quote_duration_deprecated
|
249
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
250
|
-
Using ActiveSupport::Duration as an interpolated bind parameter in a SQL
|
251
|
-
string template is deprecated. To avoid this warning, you should explicitly
|
252
|
-
convert the duration to a more specific database type. For example, if you
|
253
|
-
want to use a duration as an integer number of seconds:
|
254
|
-
```
|
255
|
-
Record.where("duration = ?", 1.hour.to_i)
|
256
|
-
```
|
257
|
-
If you want to use a duration as an ISO 8601 string:
|
258
|
-
```
|
259
|
-
Record.where("duration = ?", 1.hour.iso8601)
|
260
|
-
```
|
261
|
-
MSG
|
262
|
-
end
|
263
237
|
end
|
264
238
|
end
|
265
239
|
end
|
@@ -28,6 +28,7 @@ module ActiveRecord
|
|
28
28
|
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
|
29
29
|
sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
|
30
30
|
sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
|
31
|
+
sql << o.constraint_drops.map { |con| visit_DropConstraint con }.join(" ")
|
31
32
|
end
|
32
33
|
|
33
34
|
def visit_ColumnDefinition(o)
|
@@ -96,9 +97,11 @@ module ActiveRecord
|
|
96
97
|
"ADD #{accept(o)}"
|
97
98
|
end
|
98
99
|
|
99
|
-
def
|
100
|
+
def visit_DropConstraint(name)
|
100
101
|
"DROP CONSTRAINT #{quote_column_name(name)}"
|
101
102
|
end
|
103
|
+
alias :visit_DropForeignKey :visit_DropConstraint
|
104
|
+
alias :visit_DropCheckConstraint :visit_DropConstraint
|
102
105
|
|
103
106
|
def visit_CreateIndexDefinition(o)
|
104
107
|
index = o.index
|
@@ -127,10 +130,6 @@ module ActiveRecord
|
|
127
130
|
"ADD #{accept(o)}"
|
128
131
|
end
|
129
132
|
|
130
|
-
def visit_DropCheckConstraint(name)
|
131
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
132
|
-
end
|
133
|
-
|
134
133
|
def quoted_columns(o)
|
135
134
|
String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
|
136
135
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
3
|
module ActiveRecord
|
5
4
|
module ConnectionAdapters # :nodoc:
|
6
5
|
# Abstract representation of an index definition on a table. Instances of
|
@@ -160,6 +159,8 @@ module ActiveRecord
|
|
160
159
|
end
|
161
160
|
|
162
161
|
def defined_for?(to_table: nil, validate: nil, **options)
|
162
|
+
options = options.slice(*self.options.keys)
|
163
|
+
|
163
164
|
(to_table.nil? || to_table.to_s == self.to_table) &&
|
164
165
|
(validate.nil? || validate == self.options.fetch(:validate, validate)) &&
|
165
166
|
options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
|
@@ -186,6 +187,8 @@ module ActiveRecord
|
|
186
187
|
end
|
187
188
|
|
188
189
|
def defined_for?(name:, expression: nil, validate: nil, **options)
|
190
|
+
options = options.slice(*self.options.keys)
|
191
|
+
|
189
192
|
self.name == name.to_s &&
|
190
193
|
(validate.nil? || validate == self.options.fetch(:validate, validate)) &&
|
191
194
|
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
@@ -348,7 +351,7 @@ module ActiveRecord
|
|
348
351
|
# Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
|
349
352
|
# is actually of this type:
|
350
353
|
#
|
351
|
-
# class SomeMigration < ActiveRecord::Migration[
|
354
|
+
# class SomeMigration < ActiveRecord::Migration[8.0]
|
352
355
|
# def up
|
353
356
|
# create_table :foo do |t|
|
354
357
|
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
|
@@ -431,7 +434,7 @@ module ActiveRecord
|
|
431
434
|
#
|
432
435
|
# == Examples
|
433
436
|
#
|
434
|
-
# # Assuming
|
437
|
+
# # Assuming `td` is an instance of TableDefinition
|
435
438
|
# td.column(:granted, :boolean, index: true)
|
436
439
|
#
|
437
440
|
# == Short-hand examples
|
@@ -622,6 +625,7 @@ module ActiveRecord
|
|
622
625
|
attr_reader :adds
|
623
626
|
attr_reader :foreign_key_adds, :foreign_key_drops
|
624
627
|
attr_reader :check_constraint_adds, :check_constraint_drops
|
628
|
+
attr_reader :constraint_drops
|
625
629
|
|
626
630
|
def initialize(td)
|
627
631
|
@td = td
|
@@ -630,6 +634,7 @@ module ActiveRecord
|
|
630
634
|
@foreign_key_drops = []
|
631
635
|
@check_constraint_adds = []
|
632
636
|
@check_constraint_drops = []
|
637
|
+
@constraint_drops = []
|
633
638
|
end
|
634
639
|
|
635
640
|
def name; @td.name; end
|
@@ -650,6 +655,10 @@ module ActiveRecord
|
|
650
655
|
@check_constraint_drops << constraint_name
|
651
656
|
end
|
652
657
|
|
658
|
+
def drop_constraint(constraint_name)
|
659
|
+
@constraint_drops << constraint_name
|
660
|
+
end
|
661
|
+
|
653
662
|
def add_column(name, type, **options)
|
654
663
|
name = name.to_s
|
655
664
|
type = type.to_sym
|