activerecord 7.2.2.1 → 8.0.5
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 +523 -677
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- 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/join_association.rb +25 -27
- 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/serialization.rb +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +2 -2
- 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 +55 -47
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
- 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 +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- 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 +9 -9
- 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 +20 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
- 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/quoting.rb +13 -0
- 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 +10 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
- 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 +1 -18
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +35 -9
- data/lib/active_record/core.rb +57 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- 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/delegated_type.rb +18 -18
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +37 -28
- 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 +22 -34
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +4 -4
- 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 +40 -43
- 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 +106 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +4 -28
- data/lib/active_record/railties/databases.rake +11 -34
- 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 +71 -65
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +32 -31
- 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/association_query_value.rb +2 -0
- 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_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +121 -70
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +9 -3
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +34 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- 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 +9 -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 +8 -7
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +22 -49
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +11 -15
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -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,7 +36,8 @@ module ActiveRecord
|
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
def schema_cache; end
|
|
40
|
-
def
|
|
39
|
+
def query_cache; end
|
|
40
|
+
def connection_descriptor; end
|
|
41
41
|
def checkin(_); end
|
|
42
42
|
def remove(_); end
|
|
43
43
|
def async_executor; end
|
|
@@ -118,24 +118,30 @@ module ActiveRecord
|
|
|
118
118
|
# * private methods that require being called in a +synchronize+ blocks
|
|
119
119
|
# are now explicitly documented
|
|
120
120
|
class ConnectionPool
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
# Prior to 3.3.5, WeakKeyMap had a use after free bug
|
|
122
|
+
# https://bugs.ruby-lang.org/issues/20688
|
|
123
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) && Gem::Version.new(RUBY_VERSION) >= "3.3.5"
|
|
124
|
+
WeakThreadKeyMap = ObjectSpace::WeakKeyMap
|
|
125
|
+
else
|
|
126
|
+
class WeakThreadKeyMap # :nodoc:
|
|
127
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
128
|
+
# but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
|
|
129
|
+
def initialize
|
|
130
|
+
@map = {}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def clear
|
|
134
|
+
@map.clear
|
|
135
|
+
end
|
|
131
136
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
def [](key)
|
|
138
|
+
@map[key]
|
|
139
|
+
end
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
def []=(key, value)
|
|
142
|
+
@map.select! { |c, _| c&.alive? }
|
|
143
|
+
@map[key] = value
|
|
144
|
+
end
|
|
139
145
|
end
|
|
140
146
|
end
|
|
141
147
|
|
|
@@ -309,22 +315,15 @@ module ActiveRecord
|
|
|
309
315
|
# held in a cache keyed by a thread.
|
|
310
316
|
def lease_connection
|
|
311
317
|
lease = connection_lease
|
|
312
|
-
lease.sticky = true
|
|
313
318
|
lease.connection ||= checkout
|
|
319
|
+
lease.sticky = true
|
|
320
|
+
lease.connection
|
|
314
321
|
end
|
|
315
322
|
|
|
316
323
|
def permanent_lease? # :nodoc:
|
|
317
324
|
connection_lease.sticky.nil?
|
|
318
325
|
end
|
|
319
326
|
|
|
320
|
-
def connection
|
|
321
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
|
322
|
-
ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
|
|
323
|
-
and will be removed in Rails 8.0. Use #lease_connection instead.
|
|
324
|
-
MSG
|
|
325
|
-
lease_connection
|
|
326
|
-
end
|
|
327
|
-
|
|
328
327
|
def pin_connection!(lock_thread) # :nodoc:
|
|
329
328
|
@pinned_connection ||= (connection_lease&.connection || checkout)
|
|
330
329
|
@pinned_connections_depth += 1
|
|
@@ -336,6 +335,7 @@ module ActiveRecord
|
|
|
336
335
|
end
|
|
337
336
|
|
|
338
337
|
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
|
338
|
+
@pinned_connection.pinned = true
|
|
339
339
|
@pinned_connection.verify! # eagerly validate the connection
|
|
340
340
|
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
|
341
341
|
end
|
|
@@ -358,6 +358,7 @@ module ActiveRecord
|
|
|
358
358
|
end
|
|
359
359
|
|
|
360
360
|
if @pinned_connection.nil?
|
|
361
|
+
connection.pinned = false
|
|
361
362
|
connection.steal!
|
|
362
363
|
connection.lock_thread = nil
|
|
363
364
|
checkin(connection)
|
|
@@ -367,8 +368,8 @@ module ActiveRecord
|
|
|
367
368
|
clean
|
|
368
369
|
end
|
|
369
370
|
|
|
370
|
-
def
|
|
371
|
-
pool_config.
|
|
371
|
+
def connection_descriptor # :nodoc:
|
|
372
|
+
pool_config.connection_descriptor
|
|
372
373
|
end
|
|
373
374
|
|
|
374
375
|
# Returns true if there is an open connection being used for the current thread.
|
|
@@ -462,7 +463,7 @@ module ActiveRecord
|
|
|
462
463
|
end
|
|
463
464
|
conn.disconnect!
|
|
464
465
|
end
|
|
465
|
-
@connections = []
|
|
466
|
+
@connections = @pinned_connection ? [@pinned_connection] : []
|
|
466
467
|
@leases.clear
|
|
467
468
|
@available.clear
|
|
468
469
|
end
|
|
@@ -498,7 +499,7 @@ module ActiveRecord
|
|
|
498
499
|
@connections.nil?
|
|
499
500
|
end
|
|
500
501
|
|
|
501
|
-
# Clears
|
|
502
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
502
503
|
# require reloading.
|
|
503
504
|
#
|
|
504
505
|
# Raises:
|
|
@@ -521,7 +522,7 @@ module ActiveRecord
|
|
|
521
522
|
end
|
|
522
523
|
end
|
|
523
524
|
|
|
524
|
-
# Clears
|
|
525
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
525
526
|
# require reloading.
|
|
526
527
|
#
|
|
527
528
|
# The pool first tries to gain ownership of all connections. If unable to
|
|
@@ -548,20 +549,25 @@ module ActiveRecord
|
|
|
548
549
|
# Raises:
|
|
549
550
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
|
550
551
|
def checkout(checkout_timeout = @checkout_timeout)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
552
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
|
553
|
+
|
|
554
|
+
@pinned_connection.lock.synchronize do
|
|
555
|
+
synchronize do
|
|
556
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
|
557
|
+
if @pinned_connection
|
|
554
558
|
@pinned_connection.verify!
|
|
559
|
+
|
|
555
560
|
# Any leased connection must be in @connections otherwise
|
|
556
561
|
# some methods like #connected? won't behave correctly
|
|
557
562
|
unless @connections.include?(@pinned_connection)
|
|
558
563
|
@connections << @pinned_connection
|
|
559
564
|
end
|
|
565
|
+
|
|
566
|
+
@pinned_connection
|
|
567
|
+
else
|
|
568
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
560
569
|
end
|
|
561
570
|
end
|
|
562
|
-
@pinned_connection
|
|
563
|
-
else
|
|
564
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
565
571
|
end
|
|
566
572
|
end
|
|
567
573
|
|
|
@@ -696,6 +702,14 @@ module ActiveRecord
|
|
|
696
702
|
Thread.pass
|
|
697
703
|
end
|
|
698
704
|
|
|
705
|
+
def new_connection # :nodoc:
|
|
706
|
+
connection = db_config.new_connection
|
|
707
|
+
connection.pool = self
|
|
708
|
+
connection
|
|
709
|
+
rescue ConnectionNotEstablished => ex
|
|
710
|
+
raise ex.set_pool(self)
|
|
711
|
+
end
|
|
712
|
+
|
|
699
713
|
private
|
|
700
714
|
def connection_lease
|
|
701
715
|
@leases[ActiveSupport::IsolatedExecutionState.context]
|
|
@@ -875,18 +889,12 @@ module ActiveRecord
|
|
|
875
889
|
#--
|
|
876
890
|
# if owner_thread param is omitted, this must be called in synchronize block
|
|
877
891
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
|
878
|
-
|
|
892
|
+
if owner_thread
|
|
893
|
+
@leases[owner_thread].clear(conn)
|
|
894
|
+
end
|
|
879
895
|
end
|
|
880
896
|
alias_method :release, :remove_connection_from_thread_cache
|
|
881
897
|
|
|
882
|
-
def new_connection
|
|
883
|
-
connection = db_config.new_connection
|
|
884
|
-
connection.pool = self
|
|
885
|
-
connection
|
|
886
|
-
rescue ConnectionNotEstablished => ex
|
|
887
|
-
raise ex.set_pool(self)
|
|
888
|
-
end
|
|
889
|
-
|
|
890
898
|
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
|
891
899
|
# to the DB is done outside main synchronized section.
|
|
892
900
|
#--
|
|
@@ -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
|
|
@@ -191,15 +191,26 @@ module ActiveRecord
|
|
|
191
191
|
end
|
|
192
192
|
end
|
|
193
193
|
|
|
194
|
-
attr_accessor :query_cache
|
|
195
|
-
|
|
196
194
|
def initialize(*)
|
|
197
195
|
super
|
|
198
196
|
@query_cache = nil
|
|
199
197
|
end
|
|
200
198
|
|
|
199
|
+
attr_writer :query_cache
|
|
200
|
+
|
|
201
|
+
def query_cache
|
|
202
|
+
if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
|
|
203
|
+
# With transactional tests, if the connection is pinned, any thread
|
|
204
|
+
# other than the one that pinned the connection need to go through the
|
|
205
|
+
# query cache pool, so each thread get a different cache.
|
|
206
|
+
pool.query_cache
|
|
207
|
+
else
|
|
208
|
+
@query_cache
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
201
212
|
def query_cache_enabled
|
|
202
|
-
|
|
213
|
+
query_cache&.enabled?
|
|
203
214
|
end
|
|
204
215
|
|
|
205
216
|
# Enable the query cache within the block.
|
|
@@ -238,7 +249,7 @@ module ActiveRecord
|
|
|
238
249
|
|
|
239
250
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
|
240
251
|
# Such queries should not be cached.
|
|
241
|
-
if
|
|
252
|
+
if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
|
|
242
253
|
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
|
243
254
|
|
|
244
255
|
if async
|
|
@@ -262,13 +273,13 @@ module ActiveRecord
|
|
|
262
273
|
|
|
263
274
|
result = nil
|
|
264
275
|
@lock.synchronize do
|
|
265
|
-
result =
|
|
276
|
+
result = query_cache[key]
|
|
266
277
|
end
|
|
267
278
|
|
|
268
279
|
if result
|
|
269
280
|
ActiveSupport::Notifications.instrument(
|
|
270
281
|
"sql.active_record",
|
|
271
|
-
|
|
282
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
272
283
|
)
|
|
273
284
|
end
|
|
274
285
|
|
|
@@ -281,7 +292,7 @@ module ActiveRecord
|
|
|
281
292
|
hit = true
|
|
282
293
|
|
|
283
294
|
@lock.synchronize do
|
|
284
|
-
result =
|
|
295
|
+
result = query_cache.compute_if_absent(key) do
|
|
285
296
|
hit = false
|
|
286
297
|
yield
|
|
287
298
|
end
|
|
@@ -290,13 +301,19 @@ module ActiveRecord
|
|
|
290
301
|
if hit
|
|
291
302
|
ActiveSupport::Notifications.instrument(
|
|
292
303
|
"sql.active_record",
|
|
293
|
-
|
|
304
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
294
305
|
)
|
|
295
306
|
end
|
|
296
307
|
|
|
297
308
|
result.dup
|
|
298
309
|
end
|
|
299
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
|
+
|
|
300
317
|
# Database adapters can override this method to
|
|
301
318
|
# provide custom cache information.
|
|
302
319
|
def cache_notification_info(sql, name, binds)
|
|
@@ -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
|