activerecord 8.0.0.1 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +208 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/belongs_to_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods.rb +22 -17
- data/lib/active_record/attributes.rb +2 -2
- data/lib/active_record/autosave_association.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +17 -14
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -34
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +39 -22
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -7
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +57 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +7 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +7 -2
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/core.rb +31 -2
- 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/delegated_type.rb +17 -17
- data/lib/active_record/future_result.rb +3 -3
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +5 -2
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/relation/calculations.rb +23 -17
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +1 -1
- data/lib/active_record/relation.rb +1 -1
- data/lib/active_record/schema_dumper.rb +29 -11
- data/lib/active_record/signed_id.rb +4 -3
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record.rb +4 -2
- data/lib/arel/collectors/bind.rb +1 -1
- 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/visitors/to_sql.rb +1 -1
- metadata +10 -13
@@ -36,7 +36,7 @@ module ActiveRecord
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def schema_cache; end
|
39
|
-
def
|
39
|
+
def connection_descriptor; end
|
40
40
|
def checkin(_); end
|
41
41
|
def remove(_); end
|
42
42
|
def async_executor; end
|
@@ -117,24 +117,30 @@ module ActiveRecord
|
|
117
117
|
# * private methods that require being called in a +synchronize+ blocks
|
118
118
|
# are now explicitly documented
|
119
119
|
class ConnectionPool
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
120
|
+
# Prior to 3.3.5, WeakKeyMap had a use after free bug
|
121
|
+
# https://bugs.ruby-lang.org/issues/20688
|
122
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
|
123
|
+
WeakThreadKeyMap = ObjectSpace::WeakKeyMap
|
124
|
+
else
|
125
|
+
class WeakThreadKeyMap # :nodoc:
|
126
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
127
|
+
# but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
|
128
|
+
def initialize
|
129
|
+
@map = {}
|
130
|
+
end
|
131
|
+
|
132
|
+
def clear
|
133
|
+
@map.clear
|
134
|
+
end
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
|
136
|
+
def [](key)
|
137
|
+
@map[key]
|
138
|
+
end
|
134
139
|
|
135
|
-
|
136
|
-
|
137
|
-
|
140
|
+
def []=(key, value)
|
141
|
+
@map.select! { |c, _| c&.alive? }
|
142
|
+
@map[key] = value
|
143
|
+
end
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
@@ -358,8 +364,8 @@ module ActiveRecord
|
|
358
364
|
clean
|
359
365
|
end
|
360
366
|
|
361
|
-
def
|
362
|
-
pool_config.
|
367
|
+
def connection_descriptor # :nodoc:
|
368
|
+
pool_config.connection_descriptor
|
363
369
|
end
|
364
370
|
|
365
371
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -539,20 +545,25 @@ module ActiveRecord
|
|
539
545
|
# Raises:
|
540
546
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
541
547
|
def checkout(checkout_timeout = @checkout_timeout)
|
542
|
-
|
543
|
-
|
544
|
-
|
548
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
549
|
+
|
550
|
+
@pinned_connection.lock.synchronize do
|
551
|
+
synchronize do
|
552
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
553
|
+
if @pinned_connection
|
545
554
|
@pinned_connection.verify!
|
555
|
+
|
546
556
|
# Any leased connection must be in @connections otherwise
|
547
557
|
# some methods like #connected? won't behave correctly
|
548
558
|
unless @connections.include?(@pinned_connection)
|
549
559
|
@connections << @pinned_connection
|
550
560
|
end
|
561
|
+
|
562
|
+
@pinned_connection
|
563
|
+
else
|
564
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
551
565
|
end
|
552
566
|
end
|
553
|
-
@pinned_connection
|
554
|
-
else
|
555
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
556
567
|
end
|
557
568
|
end
|
558
569
|
|
@@ -687,6 +698,14 @@ module ActiveRecord
|
|
687
698
|
Thread.pass
|
688
699
|
end
|
689
700
|
|
701
|
+
def new_connection # :nodoc:
|
702
|
+
connection = db_config.new_connection
|
703
|
+
connection.pool = self
|
704
|
+
connection
|
705
|
+
rescue ConnectionNotEstablished => ex
|
706
|
+
raise ex.set_pool(self)
|
707
|
+
end
|
708
|
+
|
690
709
|
private
|
691
710
|
def connection_lease
|
692
711
|
@leases[ActiveSupport::IsolatedExecutionState.context]
|
@@ -866,18 +885,12 @@ module ActiveRecord
|
|
866
885
|
#--
|
867
886
|
# if owner_thread param is omitted, this must be called in synchronize block
|
868
887
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
869
|
-
|
888
|
+
if owner_thread
|
889
|
+
@leases[owner_thread].clear(conn)
|
890
|
+
end
|
870
891
|
end
|
871
892
|
alias_method :release, :remove_connection_from_thread_cache
|
872
893
|
|
873
|
-
def new_connection
|
874
|
-
connection = db_config.new_connection
|
875
|
-
connection.pool = self
|
876
|
-
connection
|
877
|
-
rescue ConnectionNotEstablished => ex
|
878
|
-
raise ex.set_pool(self)
|
879
|
-
end
|
880
|
-
|
881
894
|
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
882
895
|
# to the DB is done outside main synchronized section.
|
883
896
|
#--
|
@@ -159,6 +159,8 @@ module ActiveRecord
|
|
159
159
|
end
|
160
160
|
|
161
161
|
def defined_for?(to_table: nil, validate: nil, **options)
|
162
|
+
options = options.slice(*self.options.keys)
|
163
|
+
|
162
164
|
(to_table.nil? || to_table.to_s == self.to_table) &&
|
163
165
|
(validate.nil? || validate == self.options.fetch(:validate, validate)) &&
|
164
166
|
options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
|
@@ -185,6 +187,8 @@ module ActiveRecord
|
|
185
187
|
end
|
186
188
|
|
187
189
|
def defined_for?(name:, expression: nil, validate: nil, **options)
|
190
|
+
options = options.slice(*self.options.keys)
|
191
|
+
|
188
192
|
self.name == name.to_s &&
|
189
193
|
(validate.nil? || validate == self.options.fetch(:validate, validate)) &&
|
190
194
|
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
@@ -430,7 +434,7 @@ module ActiveRecord
|
|
430
434
|
#
|
431
435
|
# == Examples
|
432
436
|
#
|
433
|
-
# # Assuming
|
437
|
+
# # Assuming `td` is an instance of TableDefinition
|
434
438
|
# td.column(:granted, :boolean, index: true)
|
435
439
|
#
|
436
440
|
# == Short-hand examples
|
@@ -150,7 +150,6 @@ module ActiveRecord
|
|
150
150
|
end
|
151
151
|
|
152
152
|
@owner = nil
|
153
|
-
@instrumenter = ActiveSupport::Notifications.instrumenter
|
154
153
|
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
155
154
|
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
156
155
|
@visitor = arel_visitor
|
@@ -168,6 +167,7 @@ module ActiveRecord
|
|
168
167
|
@default_timezone = self.class.validate_default_timezone(@config[:default_timezone])
|
169
168
|
|
170
169
|
@raw_connection_dirty = false
|
170
|
+
@last_activity = nil
|
171
171
|
@verified = false
|
172
172
|
end
|
173
173
|
|
@@ -190,19 +190,6 @@ module ActiveRecord
|
|
190
190
|
end
|
191
191
|
end
|
192
192
|
|
193
|
-
EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
|
194
|
-
EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc:
|
195
|
-
private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
|
196
|
-
def with_instrumenter(instrumenter, &block) # :nodoc:
|
197
|
-
Thread.handle_interrupt(EXCEPTION_NEVER) do
|
198
|
-
previous_instrumenter = @instrumenter
|
199
|
-
@instrumenter = instrumenter
|
200
|
-
Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
|
201
|
-
ensure
|
202
|
-
@instrumenter = previous_instrumenter
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
193
|
def check_if_write_query(sql) # :nodoc:
|
207
194
|
if preventing_writes? && write_query?(sql)
|
208
195
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
@@ -217,6 +204,10 @@ module ActiveRecord
|
|
217
204
|
(@config[:connection_retries] || 1).to_i
|
218
205
|
end
|
219
206
|
|
207
|
+
def verify_timeout
|
208
|
+
(@config[:verify_timeout] || 2).to_i
|
209
|
+
end
|
210
|
+
|
220
211
|
def retry_deadline
|
221
212
|
if @config[:retry_deadline]
|
222
213
|
@config[:retry_deadline].to_f
|
@@ -235,9 +226,9 @@ module ActiveRecord
|
|
235
226
|
# the value of +current_preventing_writes+.
|
236
227
|
def preventing_writes?
|
237
228
|
return true if replica?
|
238
|
-
return false if
|
229
|
+
return false if connection_descriptor.nil?
|
239
230
|
|
240
|
-
|
231
|
+
connection_descriptor.current_preventing_writes
|
241
232
|
end
|
242
233
|
|
243
234
|
def prepared_statements?
|
@@ -288,8 +279,8 @@ module ActiveRecord
|
|
288
279
|
@owner = ActiveSupport::IsolatedExecutionState.context
|
289
280
|
end
|
290
281
|
|
291
|
-
def
|
292
|
-
@pool.
|
282
|
+
def connection_descriptor # :nodoc:
|
283
|
+
@pool.connection_descriptor
|
293
284
|
end
|
294
285
|
|
295
286
|
# The role (e.g. +:writing+) for the current connection. In a
|
@@ -343,6 +334,13 @@ module ActiveRecord
|
|
343
334
|
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since
|
344
335
|
end
|
345
336
|
|
337
|
+
# Seconds since this connection last communicated with the server
|
338
|
+
def seconds_since_last_activity # :nodoc:
|
339
|
+
if @raw_connection && @last_activity
|
340
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_activity
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
346
344
|
def unprepared_statement
|
347
345
|
cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
|
348
346
|
yield
|
@@ -670,11 +668,12 @@ module ActiveRecord
|
|
670
668
|
|
671
669
|
enable_lazy_transactions!
|
672
670
|
@raw_connection_dirty = false
|
671
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
673
672
|
@verified = true
|
674
673
|
|
675
674
|
reset_transaction(restore: restore_transactions) do
|
676
675
|
clear_cache!(new_connection: true)
|
677
|
-
|
676
|
+
attempt_configure_connection
|
678
677
|
end
|
679
678
|
rescue => original_exception
|
680
679
|
translated_exception = translate_exception_class(original_exception, nil, nil)
|
@@ -689,6 +688,7 @@ module ActiveRecord
|
|
689
688
|
end
|
690
689
|
end
|
691
690
|
|
691
|
+
@last_activity = nil
|
692
692
|
@verified = false
|
693
693
|
|
694
694
|
raise translated_exception
|
@@ -726,7 +726,7 @@ module ActiveRecord
|
|
726
726
|
def reset!
|
727
727
|
clear_cache!(new_connection: true)
|
728
728
|
reset_transaction
|
729
|
-
|
729
|
+
attempt_configure_connection
|
730
730
|
end
|
731
731
|
|
732
732
|
# Removes the connection from the pool and disconnect it.
|
@@ -762,7 +762,8 @@ module ActiveRecord
|
|
762
762
|
if @unconfigured_connection
|
763
763
|
@raw_connection = @unconfigured_connection
|
764
764
|
@unconfigured_connection = nil
|
765
|
-
|
765
|
+
attempt_configure_connection
|
766
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
766
767
|
@verified = true
|
767
768
|
return
|
768
769
|
end
|
@@ -992,6 +993,9 @@ module ActiveRecord
|
|
992
993
|
if @verified
|
993
994
|
# Cool, we're confident the connection's ready to use. (Note this might have
|
994
995
|
# become true during the above #materialize_transactions.)
|
996
|
+
elsif (last_activity = seconds_since_last_activity) && last_activity < verify_timeout
|
997
|
+
# We haven't actually verified the connection since we acquired it, but it
|
998
|
+
# has been used very recently. We're going to assume it's still okay.
|
995
999
|
elsif reconnectable
|
996
1000
|
if allow_retry
|
997
1001
|
# Not sure about the connection yet, but if anything goes wrong we can
|
@@ -1033,6 +1037,7 @@ module ActiveRecord
|
|
1033
1037
|
# Barring a known-retryable error inside the query (regardless of
|
1034
1038
|
# whether we were in a _position_ to retry it), we should infer that
|
1035
1039
|
# there's likely a real problem with the connection.
|
1040
|
+
@last_activity = nil
|
1036
1041
|
@verified = false
|
1037
1042
|
end
|
1038
1043
|
|
@@ -1047,6 +1052,7 @@ module ActiveRecord
|
|
1047
1052
|
# `with_raw_connection` block only when the block is guaranteed to
|
1048
1053
|
# exercise the raw connection.
|
1049
1054
|
def verified!
|
1055
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
1050
1056
|
@verified = true
|
1051
1057
|
end
|
1052
1058
|
|
@@ -1126,7 +1132,7 @@ module ActiveRecord
|
|
1126
1132
|
end
|
1127
1133
|
|
1128
1134
|
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
|
1129
|
-
|
1135
|
+
instrumenter.instrument(
|
1130
1136
|
"sql.active_record",
|
1131
1137
|
sql: sql,
|
1132
1138
|
name: name,
|
@@ -1142,6 +1148,10 @@ module ActiveRecord
|
|
1142
1148
|
raise ex.set_query(sql, binds)
|
1143
1149
|
end
|
1144
1150
|
|
1151
|
+
def instrumenter # :nodoc:
|
1152
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] ||= ActiveSupport::Notifications.instrumenter
|
1153
|
+
end
|
1154
|
+
|
1145
1155
|
def translate_exception(exception, message:, sql:, binds:)
|
1146
1156
|
# override in derived class
|
1147
1157
|
case exception
|
@@ -1203,6 +1213,13 @@ module ActiveRecord
|
|
1203
1213
|
check_version
|
1204
1214
|
end
|
1205
1215
|
|
1216
|
+
def attempt_configure_connection
|
1217
|
+
configure_connection
|
1218
|
+
rescue
|
1219
|
+
disconnect!
|
1220
|
+
raise
|
1221
|
+
end
|
1222
|
+
|
1206
1223
|
def default_prepared_statements
|
1207
1224
|
true
|
1208
1225
|
end
|
@@ -174,6 +174,10 @@ module ActiveRecord
|
|
174
174
|
mariadb? && database_version >= "10.5.0"
|
175
175
|
end
|
176
176
|
|
177
|
+
def return_value_after_insert?(column) # :nodoc:
|
178
|
+
supports_insert_returning? ? column.auto_populated? : column.auto_increment?
|
179
|
+
end
|
180
|
+
|
177
181
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
178
182
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
179
183
|
end
|
@@ -403,7 +407,11 @@ module ActiveRecord
|
|
403
407
|
type ||= column.sql_type
|
404
408
|
|
405
409
|
unless options.key?(:default)
|
406
|
-
options[:default] = column.
|
410
|
+
options[:default] = if column.default_function
|
411
|
+
-> { column.default_function }
|
412
|
+
else
|
413
|
+
column.default
|
414
|
+
end
|
407
415
|
end
|
408
416
|
|
409
417
|
unless options.key?(:null)
|
@@ -628,24 +636,26 @@ module ActiveRecord
|
|
628
636
|
end
|
629
637
|
|
630
638
|
def build_insert_sql(insert) # :nodoc:
|
639
|
+
# Can use any column as it will be assigned to itself.
|
631
640
|
no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
|
632
641
|
|
633
642
|
# MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
|
634
643
|
# then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
|
635
644
|
if supports_insert_raw_alias_syntax?
|
645
|
+
quoted_table_name = insert.model.quoted_table_name
|
636
646
|
values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
|
637
647
|
sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
|
638
648
|
|
639
649
|
if insert.skip_duplicates?
|
640
650
|
if no_op_column
|
641
|
-
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{
|
651
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
|
642
652
|
end
|
643
653
|
elsif insert.update_duplicates?
|
644
654
|
if insert.raw_update_sql?
|
645
655
|
sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
|
646
656
|
else
|
647
657
|
sql << " ON DUPLICATE KEY UPDATE "
|
648
|
-
sql << insert.touch_model_timestamps_unless { |column| "#{
|
658
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
|
649
659
|
sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
|
650
660
|
end
|
651
661
|
end
|
@@ -746,9 +756,7 @@ module ActiveRecord
|
|
746
756
|
|
747
757
|
private
|
748
758
|
def strip_whitespace_characters(expression)
|
749
|
-
expression
|
750
|
-
expression = expression.gsub(/\s{2,}/, " ")
|
751
|
-
expression
|
759
|
+
expression.gsub('\\\n', "").gsub("x0A", "").squish
|
752
760
|
end
|
753
761
|
|
754
762
|
def extended_type_map_key
|
@@ -762,7 +770,6 @@ module ActiveRecord
|
|
762
770
|
def handle_warnings(sql)
|
763
771
|
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
764
772
|
|
765
|
-
@affected_rows_before_warnings = @raw_connection.affected_rows
|
766
773
|
warning_count = @raw_connection.warning_count
|
767
774
|
result = @raw_connection.query("SHOW WARNINGS")
|
768
775
|
result = [
|
@@ -102,7 +102,13 @@ module ActiveRecord
|
|
102
102
|
else
|
103
103
|
value.getlocal
|
104
104
|
end
|
105
|
-
when
|
105
|
+
when Time
|
106
|
+
if default_timezone == :utc
|
107
|
+
value.utc? ? value : value.getutc
|
108
|
+
else
|
109
|
+
value.utc? ? value.getlocal : value
|
110
|
+
end
|
111
|
+
when Date
|
106
112
|
value
|
107
113
|
else
|
108
114
|
super
|
@@ -85,6 +85,13 @@ module ActiveRecord
|
|
85
85
|
super
|
86
86
|
end
|
87
87
|
|
88
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
89
|
+
# RESTRICT is by default in MySQL.
|
90
|
+
options.delete(:on_update) if options[:on_update] == :restrict
|
91
|
+
options.delete(:on_delete) if options[:on_delete] == :restrict
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
88
95
|
def internal_string_options_for_primary_key
|
89
96
|
super.tap do |options|
|
90
97
|
if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
|
@@ -48,26 +48,55 @@ module ActiveRecord
|
|
48
48
|
# made since we established the connection
|
49
49
|
raw_connection.query_options[:database_timezone] = default_timezone
|
50
50
|
|
51
|
-
result =
|
51
|
+
result = nil
|
52
|
+
if binds.nil? || binds.empty?
|
53
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
54
|
+
result = raw_connection.query(sql)
|
55
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
56
|
+
# As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
|
57
|
+
# from that same connection was GCed while `#query` released the GVL.
|
58
|
+
# By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
|
59
|
+
# of hitting the bug.
|
60
|
+
@affected_rows_before_warnings = result&.size || raw_connection.affected_rows
|
61
|
+
end
|
62
|
+
elsif prepare
|
52
63
|
stmt = @statements[sql] ||= raw_connection.prepare(sql)
|
53
|
-
|
54
64
|
begin
|
55
65
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
56
|
-
stmt.execute(*type_casted_binds)
|
66
|
+
result = stmt.execute(*type_casted_binds)
|
67
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
57
68
|
end
|
58
69
|
rescue ::Mysql2::Error
|
59
70
|
@statements.delete(sql)
|
60
|
-
stmt.close
|
61
71
|
raise
|
62
72
|
end
|
63
|
-
verified!
|
64
73
|
else
|
65
|
-
raw_connection.
|
74
|
+
stmt = raw_connection.prepare(sql)
|
75
|
+
|
76
|
+
begin
|
77
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
78
|
+
result = stmt.execute(*type_casted_binds)
|
79
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
80
|
+
end
|
81
|
+
|
82
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
83
|
+
# by eagerly closing uncached prepared statements, we also reduce the chances of
|
84
|
+
# that bug happening. It can still happen if `#execute` is used as we have no callback
|
85
|
+
# to eagerly close the statement.
|
86
|
+
if result
|
87
|
+
result.instance_variable_set(:@_ar_stmt_to_close, stmt)
|
88
|
+
else
|
89
|
+
stmt.close
|
90
|
+
end
|
91
|
+
rescue ::Mysql2::Error
|
92
|
+
stmt.close
|
93
|
+
raise
|
94
|
+
end
|
66
95
|
end
|
67
96
|
|
97
|
+
notification_payload[:affected_rows] = @affected_rows_before_warnings
|
68
98
|
notification_payload[:row_count] = result&.size || 0
|
69
99
|
|
70
|
-
@affected_rows_before_warnings = raw_connection.affected_rows
|
71
100
|
raw_connection.abandon_results!
|
72
101
|
|
73
102
|
verified!
|
@@ -79,17 +108,34 @@ module ActiveRecord
|
|
79
108
|
end
|
80
109
|
end
|
81
110
|
|
82
|
-
def cast_result(
|
83
|
-
|
111
|
+
def cast_result(raw_result)
|
112
|
+
return ActiveRecord::Result.empty if raw_result.nil?
|
113
|
+
|
114
|
+
fields = raw_result.fields
|
115
|
+
|
116
|
+
result = if fields.empty?
|
84
117
|
ActiveRecord::Result.empty
|
85
118
|
else
|
86
|
-
ActiveRecord::Result.new(
|
119
|
+
ActiveRecord::Result.new(fields, raw_result.to_a)
|
87
120
|
end
|
121
|
+
|
122
|
+
free_raw_result(raw_result)
|
123
|
+
|
124
|
+
result
|
88
125
|
end
|
89
126
|
|
90
|
-
def affected_rows(
|
127
|
+
def affected_rows(raw_result)
|
128
|
+
free_raw_result(raw_result) if raw_result
|
129
|
+
|
91
130
|
@affected_rows_before_warnings
|
92
131
|
end
|
132
|
+
|
133
|
+
def free_raw_result(raw_result)
|
134
|
+
raw_result.free
|
135
|
+
if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
|
136
|
+
stmt.close
|
137
|
+
end
|
138
|
+
end
|
93
139
|
end
|
94
140
|
end
|
95
141
|
end
|
@@ -106,7 +106,14 @@ module ActiveRecord
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def active?
|
109
|
-
connected?
|
109
|
+
if connected?
|
110
|
+
@lock.synchronize do
|
111
|
+
if @raw_connection&.ping
|
112
|
+
verified!
|
113
|
+
true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end || false
|
110
117
|
end
|
111
118
|
|
112
119
|
alias :reset! :reconnect!
|
@@ -5,9 +5,8 @@ module ActiveRecord
|
|
5
5
|
class PoolConfig # :nodoc:
|
6
6
|
include MonitorMixin
|
7
7
|
|
8
|
-
attr_reader :db_config, :role, :shard
|
8
|
+
attr_reader :db_config, :role, :shard, :connection_descriptor
|
9
9
|
attr_writer :schema_reflection, :server_version
|
10
|
-
attr_accessor :connection_class
|
11
10
|
|
12
11
|
def schema_reflection
|
13
12
|
@schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
|
@@ -29,7 +28,7 @@ module ActiveRecord
|
|
29
28
|
def initialize(connection_class, db_config, role, shard)
|
30
29
|
super()
|
31
30
|
@server_version = nil
|
32
|
-
|
31
|
+
self.connection_descriptor = connection_class
|
33
32
|
@db_config = db_config
|
34
33
|
@role = role
|
35
34
|
@shard = shard
|
@@ -41,11 +40,12 @@ module ActiveRecord
|
|
41
40
|
@server_version || synchronize { @server_version ||= connection.get_database_version }
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
43
|
+
def connection_descriptor=(connection_descriptor)
|
44
|
+
case connection_descriptor
|
45
|
+
when ConnectionHandler::ConnectionDescriptor
|
46
|
+
@connection_descriptor = connection_descriptor
|
47
47
|
else
|
48
|
-
|
48
|
+
@connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -233,6 +233,8 @@ module ActiveRecord
|
|
233
233
|
end
|
234
234
|
|
235
235
|
def defined_for?(name: nil, column: nil, **options)
|
236
|
+
options = options.slice(*self.options.keys)
|
237
|
+
|
236
238
|
(name.nil? || self.name == name.to_s) &&
|
237
239
|
(column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
|
238
240
|
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
@@ -306,8 +308,8 @@ module ActiveRecord
|
|
306
308
|
# t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check")
|
307
309
|
#
|
308
310
|
# See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint]
|
309
|
-
def exclusion_constraint(
|
310
|
-
@base.add_exclusion_constraint(name,
|
311
|
+
def exclusion_constraint(...)
|
312
|
+
@base.add_exclusion_constraint(name, ...)
|
311
313
|
end
|
312
314
|
|
313
315
|
# Removes the given exclusion constraint from the table.
|
@@ -315,8 +317,8 @@ module ActiveRecord
|
|
315
317
|
# t.remove_exclusion_constraint(name: "price_check")
|
316
318
|
#
|
317
319
|
# See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
|
318
|
-
def remove_exclusion_constraint(
|
319
|
-
@base.remove_exclusion_constraint(name,
|
320
|
+
def remove_exclusion_constraint(...)
|
321
|
+
@base.remove_exclusion_constraint(name, ...)
|
320
322
|
end
|
321
323
|
|
322
324
|
# Adds a unique constraint.
|
@@ -324,8 +326,8 @@ module ActiveRecord
|
|
324
326
|
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
|
325
327
|
#
|
326
328
|
# See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
|
327
|
-
def unique_constraint(
|
328
|
-
@base.add_unique_constraint(name,
|
329
|
+
def unique_constraint(...)
|
330
|
+
@base.add_unique_constraint(name, ...)
|
329
331
|
end
|
330
332
|
|
331
333
|
# Removes the given unique constraint from the table.
|
@@ -333,8 +335,8 @@ module ActiveRecord
|
|
333
335
|
# t.remove_unique_constraint(name: "unique_position")
|
334
336
|
#
|
335
337
|
# See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
|
336
|
-
def remove_unique_constraint(
|
337
|
-
@base.remove_unique_constraint(name,
|
338
|
+
def remove_unique_constraint(...)
|
339
|
+
@base.remove_unique_constraint(name, ...)
|
338
340
|
end
|
339
341
|
|
340
342
|
# Validates the given constraint on the table.
|
@@ -343,8 +345,8 @@ module ActiveRecord
|
|
343
345
|
# t.validate_constraint "price_check"
|
344
346
|
#
|
345
347
|
# See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
|
346
|
-
def validate_constraint(
|
347
|
-
@base.validate_constraint(name,
|
348
|
+
def validate_constraint(...)
|
349
|
+
@base.validate_constraint(name, ...)
|
348
350
|
end
|
349
351
|
|
350
352
|
# Validates the given check constraint on the table
|
@@ -353,8 +355,8 @@ module ActiveRecord
|
|
353
355
|
# t.validate_check_constraint name: "price_check"
|
354
356
|
#
|
355
357
|
# See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
|
356
|
-
def validate_check_constraint(
|
357
|
-
@base.validate_check_constraint(name,
|
358
|
+
def validate_check_constraint(...)
|
359
|
+
@base.validate_check_constraint(name, ...)
|
358
360
|
end
|
359
361
|
end
|
360
362
|
|