activerecord 7.2.3.1 → 8.0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +475 -946
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +8 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -74
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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 +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +53 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +16 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +11 -35
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +40 -39
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- 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 +13 -0
- data/lib/active_record/relation/query_methods.rb +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +1 -1
- data/lib/active_record/relation.rb +80 -62
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/table.rb +3 -7
- metadata +10 -11
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -221,8 +221,10 @@ module ActiveRecord
|
|
|
221
221
|
if reflection.validate? && !method_defined?(validation_method)
|
|
222
222
|
if reflection.collection?
|
|
223
223
|
method = :validate_collection_association
|
|
224
|
+
elsif reflection.has_one?
|
|
225
|
+
method = :validate_has_one_association
|
|
224
226
|
else
|
|
225
|
-
method = :
|
|
227
|
+
method = :validate_belongs_to_association
|
|
226
228
|
end
|
|
227
229
|
|
|
228
230
|
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
|
@@ -274,6 +276,16 @@ module ActiveRecord
|
|
|
274
276
|
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
|
|
275
277
|
end
|
|
276
278
|
|
|
279
|
+
def validating_belongs_to_for?(association)
|
|
280
|
+
@validating_belongs_to_for ||= {}
|
|
281
|
+
@validating_belongs_to_for[association]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def autosaving_belongs_to_for?(association)
|
|
285
|
+
@autosaving_belongs_to_for ||= {}
|
|
286
|
+
@autosaving_belongs_to_for[association]
|
|
287
|
+
end
|
|
288
|
+
|
|
277
289
|
private
|
|
278
290
|
def init_internals
|
|
279
291
|
super
|
|
@@ -313,11 +325,33 @@ module ActiveRecord
|
|
|
313
325
|
end
|
|
314
326
|
|
|
315
327
|
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
|
316
|
-
# turned on for the association.
|
|
317
|
-
def
|
|
328
|
+
# turned on for the has_one association.
|
|
329
|
+
def validate_has_one_association(reflection)
|
|
330
|
+
association = association_instance_get(reflection.name)
|
|
331
|
+
record = association && association.reader
|
|
332
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
|
333
|
+
|
|
334
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
335
|
+
return if inverse_association && (record.validating_belongs_to_for?(inverse_association) ||
|
|
336
|
+
record.autosaving_belongs_to_for?(inverse_association))
|
|
337
|
+
|
|
338
|
+
association_valid?(association, record)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
|
342
|
+
# turned on for the belongs_to association.
|
|
343
|
+
def validate_belongs_to_association(reflection)
|
|
318
344
|
association = association_instance_get(reflection.name)
|
|
319
345
|
record = association && association.reader
|
|
320
|
-
|
|
346
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
|
347
|
+
|
|
348
|
+
begin
|
|
349
|
+
@validating_belongs_to_for ||= {}
|
|
350
|
+
@validating_belongs_to_for[association] = true
|
|
351
|
+
association_valid?(association, record)
|
|
352
|
+
ensure
|
|
353
|
+
@validating_belongs_to_for[association] = false
|
|
354
|
+
end
|
|
321
355
|
end
|
|
322
356
|
|
|
323
357
|
# Validate the associated records if <tt>:validate</tt> or
|
|
@@ -441,33 +475,34 @@ module ActiveRecord
|
|
|
441
475
|
return unless association && association.loaded?
|
|
442
476
|
|
|
443
477
|
record = association.load_target
|
|
478
|
+
return unless record && !record.destroyed?
|
|
444
479
|
|
|
445
|
-
|
|
446
|
-
autosave = reflection.options[:autosave]
|
|
447
|
-
|
|
448
|
-
if autosave && record.marked_for_destruction?
|
|
449
|
-
record.destroy
|
|
450
|
-
elsif autosave != false
|
|
451
|
-
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
452
|
-
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
480
|
+
autosave = reflection.options[:autosave]
|
|
453
481
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
482
|
+
if autosave && record.marked_for_destruction?
|
|
483
|
+
record.destroy
|
|
484
|
+
elsif autosave != false
|
|
485
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
486
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
487
|
+
return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
|
458
488
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
end
|
|
463
|
-
association.set_inverse_instance(record)
|
|
464
|
-
end
|
|
489
|
+
unless reflection.through_reflection
|
|
490
|
+
foreign_key = Array(reflection.foreign_key)
|
|
491
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
465
492
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
493
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
494
|
+
association_id = _read_attribute(primary_key)
|
|
495
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
|
469
496
|
end
|
|
497
|
+
association.set_inverse_instance(record)
|
|
470
498
|
end
|
|
499
|
+
|
|
500
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
501
|
+
return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
|
|
502
|
+
|
|
503
|
+
saved = record.save(validate: !autosave)
|
|
504
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
|
505
|
+
saved
|
|
471
506
|
end
|
|
472
507
|
end
|
|
473
508
|
|
|
@@ -492,7 +527,6 @@ module ActiveRecord
|
|
|
492
527
|
return false unless reflection.inverse_of&.polymorphic?
|
|
493
528
|
|
|
494
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
495
|
-
|
|
496
530
|
reflection.active_record.polymorphic_name != class_name
|
|
497
531
|
end
|
|
498
532
|
|
|
@@ -512,7 +546,15 @@ module ActiveRecord
|
|
|
512
546
|
foreign_key.each { |key| self[key] = nil }
|
|
513
547
|
record.destroy
|
|
514
548
|
elsif autosave != false
|
|
515
|
-
saved =
|
|
549
|
+
saved = if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
550
|
+
begin
|
|
551
|
+
@autosaving_belongs_to_for ||= {}
|
|
552
|
+
@autosaving_belongs_to_for[association] = true
|
|
553
|
+
record.save(validate: !autosave)
|
|
554
|
+
ensure
|
|
555
|
+
@autosaving_belongs_to_for[association] = false
|
|
556
|
+
end
|
|
557
|
+
end
|
|
516
558
|
|
|
517
559
|
if association.updated?
|
|
518
560
|
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "concurrent/map"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -55,19 +54,22 @@ module ActiveRecord
|
|
|
55
54
|
# about the model. The model needs to pass a connection specification name to the handler,
|
|
56
55
|
# in order to look up the correct connection pool.
|
|
57
56
|
class ConnectionHandler
|
|
58
|
-
class
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def initialize(name)
|
|
57
|
+
class ConnectionDescriptor # :nodoc:
|
|
58
|
+
def initialize(name, primary = false)
|
|
62
59
|
@name = name
|
|
60
|
+
@primary = primary
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def name
|
|
64
|
+
primary_class? ? "ActiveRecord::Base" : @name
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def primary_class?
|
|
66
|
-
|
|
68
|
+
@primary
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def current_preventing_writes
|
|
70
|
-
|
|
72
|
+
ActiveRecord::Base.preventing_writes?(@name)
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -116,7 +118,7 @@ module ActiveRecord
|
|
|
116
118
|
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
|
117
119
|
db_config = pool_config.db_config
|
|
118
120
|
|
|
119
|
-
pool_manager = set_pool_manager(pool_config.
|
|
121
|
+
pool_manager = set_pool_manager(pool_config.connection_descriptor)
|
|
120
122
|
|
|
121
123
|
# If there is an existing pool with the same values as the pool_config
|
|
122
124
|
# don't remove the connection. Connections should only be removed if we are
|
|
@@ -128,8 +130,8 @@ module ActiveRecord
|
|
|
128
130
|
# Update the pool_config's connection class if it differs. This is used
|
|
129
131
|
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
|
|
130
132
|
# the same pool. Without this granular swapping will not work correctly.
|
|
131
|
-
if owner_name.primary_class? && (existing_pool_config.
|
|
132
|
-
existing_pool_config.
|
|
133
|
+
if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
|
|
134
|
+
existing_pool_config.connection_descriptor = owner_name
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
existing_pool_config.pool
|
|
@@ -138,7 +140,7 @@ module ActiveRecord
|
|
|
138
140
|
pool_manager.set_pool_config(role, shard, pool_config)
|
|
139
141
|
|
|
140
142
|
payload = {
|
|
141
|
-
connection_name: pool_config.
|
|
143
|
+
connection_name: pool_config.connection_descriptor.name,
|
|
142
144
|
role: role,
|
|
143
145
|
shard: shard,
|
|
144
146
|
config: db_config.configuration_hash
|
|
@@ -166,7 +168,7 @@ module ActiveRecord
|
|
|
166
168
|
end
|
|
167
169
|
end
|
|
168
170
|
|
|
169
|
-
# Clears
|
|
171
|
+
# Clears reloadable connection caches in all connection pools.
|
|
170
172
|
#
|
|
171
173
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
|
172
174
|
def clear_reloadable_connections!(role = nil)
|
|
@@ -210,18 +212,25 @@ module ActiveRecord
|
|
|
210
212
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
|
211
213
|
# When a connection is established or removed, we invalidate the cache.
|
|
212
214
|
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
|
213
|
-
|
|
215
|
+
pool_manager = get_pool_manager(connection_name)
|
|
216
|
+
pool = pool_manager&.get_pool_config(role, shard)&.pool
|
|
214
217
|
|
|
215
218
|
if strict && !pool
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
selector = [
|
|
220
|
+
("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
|
|
221
|
+
("'#{role}' role" unless role == ActiveRecord::Base.default_role),
|
|
222
|
+
].compact.join(" and ")
|
|
223
|
+
|
|
224
|
+
selector = [
|
|
225
|
+
(connection_name unless connection_name == "ActiveRecord::Base"),
|
|
226
|
+
selector.presence,
|
|
227
|
+
].compact.join(" with ")
|
|
228
|
+
|
|
229
|
+
selector = " for #{selector}" if selector.present?
|
|
230
|
+
|
|
231
|
+
message = "No database connection defined#{selector}."
|
|
223
232
|
|
|
224
|
-
raise
|
|
233
|
+
raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
|
|
225
234
|
end
|
|
226
235
|
|
|
227
236
|
pool
|
|
@@ -236,8 +245,8 @@ module ActiveRecord
|
|
|
236
245
|
end
|
|
237
246
|
|
|
238
247
|
# Get the existing pool manager or initialize and assign a new one.
|
|
239
|
-
def set_pool_manager(
|
|
240
|
-
connection_name_to_pool_manager[
|
|
248
|
+
def set_pool_manager(connection_descriptor)
|
|
249
|
+
connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
|
|
241
250
|
end
|
|
242
251
|
|
|
243
252
|
def pool_managers
|
|
@@ -272,9 +281,9 @@ module ActiveRecord
|
|
|
272
281
|
|
|
273
282
|
def determine_owner_name(owner_name, config)
|
|
274
283
|
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
|
275
|
-
|
|
284
|
+
ConnectionDescriptor.new(owner_name.to_s)
|
|
276
285
|
elsif config.is_a?(Symbol)
|
|
277
|
-
|
|
286
|
+
ConnectionDescriptor.new(config.to_s)
|
|
278
287
|
else
|
|
279
288
|
owner_name
|
|
280
289
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "concurrent/map"
|
|
5
4
|
require "monitor"
|
|
6
5
|
|
|
@@ -37,8 +36,8 @@ module ActiveRecord
|
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
def schema_cache; end
|
|
40
|
-
def connection_class; end
|
|
41
39
|
def query_cache; end
|
|
40
|
+
def connection_descriptor; end
|
|
42
41
|
def checkin(_); end
|
|
43
42
|
def remove(_); end
|
|
44
43
|
def async_executor; end
|
|
@@ -121,12 +120,12 @@ module ActiveRecord
|
|
|
121
120
|
class ConnectionPool
|
|
122
121
|
# Prior to 3.3.5, WeakKeyMap had a use after free bug
|
|
123
122
|
# https://bugs.ruby-lang.org/issues/20688
|
|
124
|
-
if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
|
|
123
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) && Gem::Version.new(RUBY_VERSION) >= "3.3.5"
|
|
125
124
|
WeakThreadKeyMap = ObjectSpace::WeakKeyMap
|
|
126
125
|
else
|
|
127
126
|
class WeakThreadKeyMap # :nodoc:
|
|
128
127
|
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
129
|
-
# but it currently
|
|
128
|
+
# but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
|
|
130
129
|
def initialize
|
|
131
130
|
@map = {}
|
|
132
131
|
end
|
|
@@ -325,14 +324,6 @@ module ActiveRecord
|
|
|
325
324
|
connection_lease.sticky.nil?
|
|
326
325
|
end
|
|
327
326
|
|
|
328
|
-
def connection
|
|
329
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
|
330
|
-
ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
|
|
331
|
-
and will be removed in Rails 8.0. Use #lease_connection instead.
|
|
332
|
-
MSG
|
|
333
|
-
lease_connection
|
|
334
|
-
end
|
|
335
|
-
|
|
336
327
|
def pin_connection!(lock_thread) # :nodoc:
|
|
337
328
|
@pinned_connection ||= (connection_lease&.connection || checkout)
|
|
338
329
|
@pinned_connections_depth += 1
|
|
@@ -377,8 +368,8 @@ module ActiveRecord
|
|
|
377
368
|
clean
|
|
378
369
|
end
|
|
379
370
|
|
|
380
|
-
def
|
|
381
|
-
pool_config.
|
|
371
|
+
def connection_descriptor # :nodoc:
|
|
372
|
+
pool_config.connection_descriptor
|
|
382
373
|
end
|
|
383
374
|
|
|
384
375
|
# Returns true if there is an open connection being used for the current thread.
|
|
@@ -472,7 +463,7 @@ module ActiveRecord
|
|
|
472
463
|
end
|
|
473
464
|
conn.disconnect!
|
|
474
465
|
end
|
|
475
|
-
@connections = []
|
|
466
|
+
@connections = @pinned_connection ? [@pinned_connection] : []
|
|
476
467
|
@leases.clear
|
|
477
468
|
@available.clear
|
|
478
469
|
end
|
|
@@ -508,7 +499,7 @@ module ActiveRecord
|
|
|
508
499
|
@connections.nil?
|
|
509
500
|
end
|
|
510
501
|
|
|
511
|
-
# Clears
|
|
502
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
512
503
|
# require reloading.
|
|
513
504
|
#
|
|
514
505
|
# Raises:
|
|
@@ -531,7 +522,7 @@ module ActiveRecord
|
|
|
531
522
|
end
|
|
532
523
|
end
|
|
533
524
|
|
|
534
|
-
# Clears
|
|
525
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
535
526
|
# require reloading.
|
|
536
527
|
#
|
|
537
528
|
# The pool first tries to gain ownership of all connections. If unable to
|
|
@@ -102,16 +102,16 @@ module ActiveRecord
|
|
|
102
102
|
select_all(arel, name, binds, async: async).then(&:rows)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
def query_value(
|
|
106
|
-
single_value_from_rows(query(
|
|
105
|
+
def query_value(...) # :nodoc:
|
|
106
|
+
single_value_from_rows(query(...))
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
def query_values(
|
|
110
|
-
query(
|
|
109
|
+
def query_values(...) # :nodoc:
|
|
110
|
+
query(...).map(&:first)
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
def query(
|
|
114
|
-
internal_exec_query(
|
|
113
|
+
def query(...) # :nodoc:
|
|
114
|
+
internal_exec_query(...).rows
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
# Determines whether the SQL statement is a write query.
|
|
@@ -163,14 +163,14 @@ module ActiveRecord
|
|
|
163
163
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
164
164
|
# the executed +sql+ statement.
|
|
165
165
|
def exec_delete(sql, name = nil, binds = [])
|
|
166
|
-
|
|
166
|
+
affected_rows(internal_execute(sql, name, binds))
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# Executes update +sql+ statement in the context of this connection using
|
|
170
170
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
171
171
|
# the executed +sql+ statement.
|
|
172
172
|
def exec_update(sql, name = nil, binds = [])
|
|
173
|
-
|
|
173
|
+
affected_rows(internal_execute(sql, name, binds))
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
def exec_insert_all(sql, name) # :nodoc:
|
|
@@ -224,11 +224,9 @@ module ActiveRecord
|
|
|
224
224
|
|
|
225
225
|
return if table_names.empty?
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
execute_batch(statements, "Truncate Tables")
|
|
231
|
-
end
|
|
227
|
+
disable_referential_integrity do
|
|
228
|
+
statements = build_truncate_statements(table_names)
|
|
229
|
+
execute_batch(statements, "Truncate Tables")
|
|
232
230
|
end
|
|
233
231
|
end
|
|
234
232
|
|
|
@@ -358,7 +356,7 @@ module ActiveRecord
|
|
|
358
356
|
end
|
|
359
357
|
yield current_transaction.user_transaction
|
|
360
358
|
else
|
|
361
|
-
|
|
359
|
+
within_new_transaction(isolation: isolation, joinable: joinable, &block)
|
|
362
360
|
end
|
|
363
361
|
rescue ActiveRecord::Rollback
|
|
364
362
|
# rollbacks are silently swallowed
|
|
@@ -411,6 +409,14 @@ module ActiveRecord
|
|
|
411
409
|
# Begins the transaction (and turns off auto-committing).
|
|
412
410
|
def begin_db_transaction() end
|
|
413
411
|
|
|
412
|
+
def begin_deferred_transaction(isolation_level = nil) # :nodoc:
|
|
413
|
+
if isolation_level
|
|
414
|
+
begin_isolated_db_transaction(isolation_level)
|
|
415
|
+
else
|
|
416
|
+
begin_db_transaction
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
414
420
|
def transaction_isolation_levels
|
|
415
421
|
{
|
|
416
422
|
read_uncommitted: "READ UNCOMMITTED",
|
|
@@ -427,6 +433,15 @@ module ActiveRecord
|
|
|
427
433
|
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
|
|
428
434
|
end
|
|
429
435
|
|
|
436
|
+
# Hook point called after an isolated DB transaction is committed
|
|
437
|
+
# or rolled back.
|
|
438
|
+
# Most adapters don't need to implement anything because the isolation
|
|
439
|
+
# level is set on a per transaction basis.
|
|
440
|
+
# But some databases like SQLite set it on a per connection level
|
|
441
|
+
# and need to explicitly reset it after commit or rollback.
|
|
442
|
+
def reset_isolation_level
|
|
443
|
+
end
|
|
444
|
+
|
|
430
445
|
# Commits the transaction (and turns on auto-committing).
|
|
431
446
|
def commit_db_transaction() end
|
|
432
447
|
|
|
@@ -473,11 +488,9 @@ module ActiveRecord
|
|
|
473
488
|
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
|
|
474
489
|
statements = table_deletes + fixture_inserts
|
|
475
490
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
execute_batch(statements, "Fixtures Load")
|
|
480
|
-
end
|
|
491
|
+
transaction(requires_new: true) do
|
|
492
|
+
disable_referential_integrity do
|
|
493
|
+
execute_batch(statements, "Fixtures Load")
|
|
481
494
|
end
|
|
482
495
|
end
|
|
483
496
|
end
|
|
@@ -524,28 +537,64 @@ module ActiveRecord
|
|
|
524
537
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
|
525
538
|
end
|
|
526
539
|
|
|
527
|
-
|
|
528
|
-
|
|
540
|
+
# Same as raw_execute but returns an ActiveRecord::Result object.
|
|
541
|
+
def raw_exec_query(...) # :nodoc:
|
|
542
|
+
cast_result(raw_execute(...))
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Execute a query and returns an ActiveRecord::Result
|
|
546
|
+
def internal_exec_query(...) # :nodoc:
|
|
547
|
+
cast_result(internal_execute(...))
|
|
529
548
|
end
|
|
530
549
|
|
|
531
550
|
private
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
551
|
+
# Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
|
|
552
|
+
def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
|
|
553
|
+
type_casted_binds = type_casted_binds(binds)
|
|
554
|
+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
|
555
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
556
|
+
perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
end
|
|
535
560
|
|
|
536
|
-
|
|
561
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
|
|
562
|
+
raise NotImplementedError
|
|
563
|
+
end
|
|
537
564
|
|
|
538
|
-
|
|
565
|
+
# Receive a native adapter result object and returns an ActiveRecord::Result object.
|
|
566
|
+
def cast_result(raw_result)
|
|
567
|
+
raise NotImplementedError
|
|
539
568
|
end
|
|
540
569
|
|
|
541
|
-
def
|
|
542
|
-
|
|
543
|
-
|
|
570
|
+
def affected_rows(raw_result)
|
|
571
|
+
raise NotImplementedError
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def preprocess_query(sql)
|
|
575
|
+
check_if_write_query(sql)
|
|
576
|
+
mark_transaction_written_if_write(sql)
|
|
577
|
+
|
|
578
|
+
# We call tranformers after the write checks so we don't add extra parsing work.
|
|
579
|
+
# This means we assume no transformer whille change a read for a write
|
|
580
|
+
# but it would be insane to do such a thing.
|
|
581
|
+
ActiveRecord.query_transformers.each do |transformer|
|
|
582
|
+
sql = transformer.call(sql, self)
|
|
544
583
|
end
|
|
584
|
+
|
|
585
|
+
sql
|
|
545
586
|
end
|
|
546
587
|
|
|
547
|
-
|
|
548
|
-
|
|
588
|
+
# Same as #internal_exec_query, but yields a native adapter result
|
|
589
|
+
def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block)
|
|
590
|
+
sql = preprocess_query(sql)
|
|
591
|
+
raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
595
|
+
statements.each do |statement|
|
|
596
|
+
raw_execute(statement, name, **kwargs)
|
|
597
|
+
end
|
|
549
598
|
end
|
|
550
599
|
|
|
551
600
|
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
|
@@ -614,10 +663,6 @@ module ActiveRecord
|
|
|
614
663
|
end
|
|
615
664
|
end
|
|
616
665
|
|
|
617
|
-
def with_multi_statements
|
|
618
|
-
yield
|
|
619
|
-
end
|
|
620
|
-
|
|
621
666
|
def combine_multi_statements(total_sql)
|
|
622
667
|
total_sql.join(";\n")
|
|
623
668
|
end
|
|
@@ -629,6 +674,8 @@ module ActiveRecord
|
|
|
629
674
|
raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
|
|
630
675
|
end
|
|
631
676
|
|
|
677
|
+
# We make sure to run query transformers on the original thread
|
|
678
|
+
sql = preprocess_query(sql)
|
|
632
679
|
future_result = async.new(
|
|
633
680
|
pool,
|
|
634
681
|
sql,
|
|
@@ -636,19 +683,19 @@ module ActiveRecord
|
|
|
636
683
|
binds,
|
|
637
684
|
prepare: prepare,
|
|
638
685
|
)
|
|
639
|
-
if supports_concurrent_connections? && current_transaction.
|
|
686
|
+
if supports_concurrent_connections? && !current_transaction.joinable?
|
|
640
687
|
future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
|
|
641
688
|
else
|
|
642
689
|
future_result.execute!(self)
|
|
643
690
|
end
|
|
644
|
-
|
|
645
|
-
end
|
|
646
|
-
|
|
647
|
-
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
|
648
|
-
if async
|
|
649
|
-
FutureResult.wrap(result)
|
|
691
|
+
future_result
|
|
650
692
|
else
|
|
651
|
-
result
|
|
693
|
+
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
|
694
|
+
if async
|
|
695
|
+
FutureResult.wrap(result)
|
|
696
|
+
else
|
|
697
|
+
result
|
|
698
|
+
end
|
|
652
699
|
end
|
|
653
700
|
end
|
|
654
701
|
|
|
@@ -691,7 +738,7 @@ module ActiveRecord
|
|
|
691
738
|
end
|
|
692
739
|
|
|
693
740
|
def extract_table_ref_from_insert_sql(sql)
|
|
694
|
-
if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
|
|
741
|
+
if sql =~ /into\s("[-A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
|
|
695
742
|
$1.delete('"').strip
|
|
696
743
|
end
|
|
697
744
|
end
|
|
@@ -279,7 +279,7 @@ module ActiveRecord
|
|
|
279
279
|
if result
|
|
280
280
|
ActiveSupport::Notifications.instrument(
|
|
281
281
|
"sql.active_record",
|
|
282
|
-
|
|
282
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
283
283
|
)
|
|
284
284
|
end
|
|
285
285
|
|
|
@@ -301,13 +301,19 @@ module ActiveRecord
|
|
|
301
301
|
if hit
|
|
302
302
|
ActiveSupport::Notifications.instrument(
|
|
303
303
|
"sql.active_record",
|
|
304
|
-
|
|
304
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
305
305
|
)
|
|
306
306
|
end
|
|
307
307
|
|
|
308
308
|
result.dup
|
|
309
309
|
end
|
|
310
310
|
|
|
311
|
+
def cache_notification_info_result(sql, name, binds, result)
|
|
312
|
+
payload = cache_notification_info(sql, name, binds)
|
|
313
|
+
payload[:row_count] = result.length
|
|
314
|
+
payload
|
|
315
|
+
end
|
|
316
|
+
|
|
311
317
|
# Database adapters can override this method to
|
|
312
318
|
# provide custom cache information.
|
|
313
319
|
def cache_notification_info(sql, name, binds)
|