activerecord 7.1.3.2 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +507 -2123
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- 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 +4 -2
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +5 -7
- 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 +34 -11
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- 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 +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +87 -58
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +14 -30
- data/lib/active_record/base.rb +2 -3
- 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 +248 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
- 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 +7 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- 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/cidr.rb +6 -0
- 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 +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- 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 +44 -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 +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +59 -38
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- 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 +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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 -4
- data/lib/active_record/enum.rb +11 -2
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- 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 +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -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 +85 -76
- data/lib/active_record/model_schema.rb +34 -69
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +52 -64
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +98 -37
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- 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/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -43
- 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 +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- 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 +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- 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 +70 -42
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +82 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +4 -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 +68 -0
- data/lib/active_record/transactions.rb +43 -14
- 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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- 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/nodes/binary.rb +0 -6
- 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 +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- 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/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 +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- metadata +17 -12
@@ -13,15 +13,16 @@ module ActiveRecord
|
|
13
13
|
:truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
|
14
14
|
:exec_insert_all
|
15
15
|
|
16
|
-
base.set_callback :
|
17
|
-
base.set_callback :checkin, :after, :disable_query_cache!
|
16
|
+
base.set_callback :checkin, :after, :unset_query_cache!
|
18
17
|
end
|
19
18
|
|
20
19
|
def dirties_query_cache(base, *method_names)
|
21
20
|
method_names.each do |method_name|
|
22
21
|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
23
22
|
def #{method_name}(...)
|
24
|
-
|
23
|
+
if pool.dirties_query_cache
|
24
|
+
ActiveRecord::Base.clear_query_caches_for_current_thread
|
25
|
+
end
|
25
26
|
super
|
26
27
|
end
|
27
28
|
end_code
|
@@ -29,60 +30,168 @@ module ActiveRecord
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
class Store # :nodoc:
|
34
|
+
attr_accessor :enabled, :dirties
|
35
|
+
alias_method :enabled?, :enabled
|
36
|
+
alias_method :dirties?, :dirties
|
37
|
+
|
38
|
+
def initialize(max_size)
|
39
|
+
@map = {}
|
40
|
+
@max_size = max_size
|
41
|
+
@enabled = false
|
42
|
+
@dirties = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
@map.size
|
47
|
+
end
|
48
|
+
|
49
|
+
def empty?
|
50
|
+
@map.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def [](key)
|
54
|
+
return unless @enabled
|
55
|
+
|
56
|
+
if entry = @map.delete(key)
|
57
|
+
@map[key] = entry
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def compute_if_absent(key)
|
62
|
+
return yield unless @enabled
|
63
|
+
|
64
|
+
if entry = @map.delete(key)
|
65
|
+
return @map[key] = entry
|
66
|
+
end
|
67
|
+
|
68
|
+
if @max_size && @map.size >= @max_size
|
69
|
+
@map.shift # evict the oldest entry
|
70
|
+
end
|
71
|
+
|
72
|
+
@map[key] ||= yield
|
73
|
+
end
|
74
|
+
|
75
|
+
def clear
|
76
|
+
@map.clear
|
77
|
+
self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module ConnectionPoolConfiguration # :nodoc:
|
82
|
+
def initialize(...)
|
83
|
+
super
|
84
|
+
@thread_query_caches = Concurrent::Map.new(initial_capacity: @size)
|
85
|
+
@query_cache_max_size = \
|
86
|
+
case query_cache = db_config&.query_cache
|
87
|
+
when 0, false
|
88
|
+
nil
|
89
|
+
when Integer
|
90
|
+
query_cache
|
91
|
+
when nil
|
92
|
+
DEFAULT_SIZE
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def checkout_and_verify(connection)
|
34
97
|
super
|
35
|
-
|
98
|
+
connection.query_cache ||= query_cache
|
99
|
+
connection
|
100
|
+
end
|
101
|
+
|
102
|
+
# Disable the query cache within the block.
|
103
|
+
def disable_query_cache(dirties: true)
|
104
|
+
cache = query_cache
|
105
|
+
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
|
106
|
+
begin
|
107
|
+
yield
|
108
|
+
ensure
|
109
|
+
cache.enabled, cache.dirties = old_enabled, old_dirties
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def enable_query_cache
|
114
|
+
cache = query_cache
|
115
|
+
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
|
116
|
+
begin
|
117
|
+
yield
|
118
|
+
ensure
|
119
|
+
cache.enabled, cache.dirties = old_enabled, old_dirties
|
120
|
+
end
|
36
121
|
end
|
37
122
|
|
38
123
|
def enable_query_cache!
|
39
|
-
|
40
|
-
connection.enable_query_cache! if active_connection?
|
124
|
+
query_cache.enabled, query_cache.dirties = true, true
|
41
125
|
end
|
42
126
|
|
43
127
|
def disable_query_cache!
|
44
|
-
|
45
|
-
connection.disable_query_cache! if active_connection?
|
128
|
+
query_cache.enabled, query_cache.dirties = false, true
|
46
129
|
end
|
47
130
|
|
48
131
|
def query_cache_enabled
|
49
|
-
|
132
|
+
query_cache.enabled
|
133
|
+
end
|
134
|
+
|
135
|
+
def dirties_query_cache
|
136
|
+
query_cache.dirties
|
50
137
|
end
|
138
|
+
|
139
|
+
def clear_query_cache
|
140
|
+
if @pinned_connection
|
141
|
+
# With transactional fixtures, and especially systems test
|
142
|
+
# another thread may use the same connection, but with a different
|
143
|
+
# query cache. So we must clear them all.
|
144
|
+
@thread_query_caches.each_value(&:clear)
|
145
|
+
else
|
146
|
+
query_cache.clear
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def query_cache
|
151
|
+
@thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
|
152
|
+
Store.new(@query_cache_max_size)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def prune_thread_cache
|
158
|
+
dead_threads = @thread_query_caches.keys.reject(&:alive?)
|
159
|
+
dead_threads.each do |dead_thread|
|
160
|
+
@thread_query_caches.delete(dead_thread)
|
161
|
+
end
|
162
|
+
end
|
51
163
|
end
|
52
164
|
|
53
|
-
|
165
|
+
attr_accessor :query_cache
|
54
166
|
|
55
167
|
def initialize(*)
|
56
168
|
super
|
57
|
-
@query_cache
|
58
|
-
|
59
|
-
|
169
|
+
@query_cache = nil
|
170
|
+
end
|
171
|
+
|
172
|
+
def query_cache_enabled
|
173
|
+
@query_cache&.enabled?
|
60
174
|
end
|
61
175
|
|
62
176
|
# 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
|
177
|
+
def cache(&block)
|
178
|
+
pool.enable_query_cache(&block)
|
69
179
|
end
|
70
180
|
|
71
181
|
def enable_query_cache!
|
72
|
-
|
182
|
+
pool.enable_query_cache!
|
73
183
|
end
|
74
184
|
|
75
|
-
|
76
|
-
|
77
|
-
|
185
|
+
# Disable the query cache within the block.
|
186
|
+
#
|
187
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
188
|
+
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
189
|
+
def uncached(dirties: true, &block)
|
190
|
+
pool.disable_query_cache(dirties: dirties, &block)
|
78
191
|
end
|
79
192
|
|
80
|
-
|
81
|
-
|
82
|
-
old, @query_cache_enabled = @query_cache_enabled, false
|
83
|
-
yield
|
84
|
-
ensure
|
85
|
-
@query_cache_enabled = old
|
193
|
+
def disable_query_cache!
|
194
|
+
pool.disable_query_cache!
|
86
195
|
end
|
87
196
|
|
88
197
|
# Clears the query cache.
|
@@ -92,24 +201,22 @@ module ActiveRecord
|
|
92
201
|
# the same SQL query and repeatedly return the same result each time, silently
|
93
202
|
# undermining the randomness you were expecting.
|
94
203
|
def clear_query_cache
|
95
|
-
|
96
|
-
@query_cache.clear
|
97
|
-
end
|
204
|
+
pool.clear_query_cache
|
98
205
|
end
|
99
206
|
|
100
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
|
207
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
|
101
208
|
arel = arel_from_relation(arel)
|
102
209
|
|
103
210
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
104
211
|
# Such queries should not be cached.
|
105
|
-
if @
|
106
|
-
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
212
|
+
if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
|
213
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
107
214
|
|
108
215
|
if async
|
109
|
-
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
|
110
|
-
FutureResult
|
216
|
+
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
217
|
+
FutureResult.wrap(result)
|
111
218
|
else
|
112
|
-
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
|
219
|
+
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
|
113
220
|
end
|
114
221
|
else
|
115
222
|
super
|
@@ -117,42 +224,37 @@ module ActiveRecord
|
|
117
224
|
end
|
118
225
|
|
119
226
|
private
|
227
|
+
def unset_query_cache!
|
228
|
+
@query_cache = nil
|
229
|
+
end
|
230
|
+
|
120
231
|
def lookup_sql_cache(sql, name, binds)
|
121
232
|
key = binds.empty? ? sql : [sql, binds]
|
122
|
-
hit = false
|
123
|
-
result = nil
|
124
233
|
|
234
|
+
result = nil
|
125
235
|
@lock.synchronize do
|
126
|
-
|
127
|
-
hit = true
|
128
|
-
@query_cache[key] = result
|
129
|
-
end
|
236
|
+
result = @query_cache[key]
|
130
237
|
end
|
131
238
|
|
132
|
-
if
|
239
|
+
if result
|
133
240
|
ActiveSupport::Notifications.instrument(
|
134
241
|
"sql.active_record",
|
135
242
|
cache_notification_info(sql, name, binds)
|
136
243
|
)
|
137
|
-
|
138
|
-
result
|
139
244
|
end
|
245
|
+
|
246
|
+
result
|
140
247
|
end
|
141
248
|
|
142
249
|
def cache_sql(sql, name, binds)
|
143
250
|
key = binds.empty? ? sql : [sql, binds]
|
144
251
|
result = nil
|
145
|
-
hit =
|
252
|
+
hit = true
|
146
253
|
|
147
254
|
@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
|
255
|
+
result = @query_cache.compute_if_absent(key) do
|
256
|
+
hit = false
|
257
|
+
yield
|
156
258
|
end
|
157
259
|
end
|
158
260
|
|
@@ -178,23 +280,6 @@ module ActiveRecord
|
|
178
280
|
cached: true
|
179
281
|
}
|
180
282
|
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
283
|
end
|
199
284
|
end
|
200
285
|
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,59 +220,6 @@ 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
225
|
binds.map do |value|
|
@@ -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
|
@@ -348,7 +348,7 @@ module ActiveRecord
|
|
348
348
|
# Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
|
349
349
|
# is actually of this type:
|
350
350
|
#
|
351
|
-
# class SomeMigration < ActiveRecord::Migration[7.
|
351
|
+
# class SomeMigration < ActiveRecord::Migration[7.2]
|
352
352
|
# def up
|
353
353
|
# create_table :foo do |t|
|
354
354
|
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
|
@@ -293,6 +293,11 @@ module ActiveRecord
|
|
293
293
|
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block)
|
294
294
|
validate_create_table_options!(options)
|
295
295
|
validate_table_length!(table_name) unless options[:_uses_legacy_table_name]
|
296
|
+
|
297
|
+
if force && options.key?(:if_not_exists)
|
298
|
+
raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
|
299
|
+
end
|
300
|
+
|
296
301
|
td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
|
297
302
|
|
298
303
|
if force
|
@@ -876,9 +881,12 @@ module ActiveRecord
|
|
876
881
|
# ====== Creating an index with a specific algorithm
|
877
882
|
#
|
878
883
|
# add_index(:developers, :name, algorithm: :concurrently)
|
879
|
-
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
|
884
|
+
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
|
880
885
|
#
|
881
|
-
#
|
886
|
+
# add_index(:developers, :name, algorithm: :inplace)
|
887
|
+
# # CREATE INDEX `index_developers_on_name` ON `developers` (`name`) ALGORITHM = INPLACE -- MySQL
|
888
|
+
#
|
889
|
+
# Note: only supported by PostgreSQL and MySQL.
|
882
890
|
#
|
883
891
|
# Concurrently adding an index is not supported in a transaction.
|
884
892
|
#
|
@@ -963,7 +971,11 @@ module ActiveRecord
|
|
963
971
|
def index_name(table_name, options) # :nodoc:
|
964
972
|
if Hash === options
|
965
973
|
if options[:column]
|
966
|
-
|
974
|
+
if options[:_uses_legacy_index_name]
|
975
|
+
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
|
976
|
+
else
|
977
|
+
generate_index_name(table_name, options[:column])
|
978
|
+
end
|
967
979
|
elsif options[:name]
|
968
980
|
options[:name]
|
969
981
|
else
|
@@ -1314,7 +1326,7 @@ module ActiveRecord
|
|
1314
1326
|
end
|
1315
1327
|
|
1316
1328
|
def dump_schema_information # :nodoc:
|
1317
|
-
versions = schema_migration.versions
|
1329
|
+
versions = pool.schema_migration.versions
|
1318
1330
|
insert_versions_sql(versions) if versions.any?
|
1319
1331
|
end
|
1320
1332
|
|
@@ -1324,8 +1336,9 @@ module ActiveRecord
|
|
1324
1336
|
|
1325
1337
|
def assume_migrated_upto_version(version)
|
1326
1338
|
version = version.to_i
|
1327
|
-
sm_table = quote_table_name(schema_migration.table_name)
|
1339
|
+
sm_table = quote_table_name(pool.schema_migration.table_name)
|
1328
1340
|
|
1341
|
+
migration_context = pool.migration_context
|
1329
1342
|
migrated = migration_context.get_all_versions
|
1330
1343
|
versions = migration_context.migrations.map(&:version)
|
1331
1344
|
|
@@ -1636,11 +1649,11 @@ module ActiveRecord
|
|
1636
1649
|
end
|
1637
1650
|
end
|
1638
1651
|
|
1639
|
-
def rename_table_indexes(table_name, new_name)
|
1652
|
+
def rename_table_indexes(table_name, new_name, **options)
|
1640
1653
|
indexes(new_name).each do |index|
|
1641
|
-
generated_index_name = index_name(table_name, column: index.columns)
|
1654
|
+
generated_index_name = index_name(table_name, column: index.columns, **options)
|
1642
1655
|
if generated_index_name == index.name
|
1643
|
-
rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
|
1656
|
+
rename_index new_name, generated_index_name, index_name(new_name, column: index.columns, **options)
|
1644
1657
|
end
|
1645
1658
|
end
|
1646
1659
|
end
|
@@ -1835,7 +1848,7 @@ module ActiveRecord
|
|
1835
1848
|
end
|
1836
1849
|
|
1837
1850
|
def insert_versions_sql(versions)
|
1838
|
-
sm_table = quote_table_name(schema_migration.table_name)
|
1851
|
+
sm_table = quote_table_name(pool.schema_migration.table_name)
|
1839
1852
|
|
1840
1853
|
if versions.is_a?(Array)
|
1841
1854
|
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
|