activerecord 8.0.5.1 → 8.1.0.beta1
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 +421 -657
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +0 -2
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/batch.rb +1 -7
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +88 -25
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -15
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +8 -12
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +50 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +0 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +15 -2
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/databases.rake +16 -4
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +28 -12
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +40 -31
- data/lib/active_record/relation/where_clause.rb +2 -11
- data/lib/active_record/relation.rb +26 -14
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +10 -2
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -9
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- metadata +14 -10
- data/lib/active_record/normalization.rb +0 -163
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
|
7
7
|
private
|
|
8
8
|
def virtual_tables(stream)
|
|
9
|
-
virtual_tables = @connection.virtual_tables
|
|
9
|
+
virtual_tables = @connection.virtual_tables
|
|
10
10
|
if virtual_tables.any?
|
|
11
11
|
stream.puts
|
|
12
12
|
stream.puts " # Virtual tables defined in this database."
|
|
@@ -19,23 +19,11 @@ module ActiveRecord
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def default_primary_key?(column)
|
|
22
|
-
schema_type(column) == :integer
|
|
22
|
+
schema_type(column) == :integer
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def explicit_primary_key_default?(column)
|
|
26
|
-
column.bigint?
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def primary_key_has_autoincrement?
|
|
30
|
-
return false unless table_name
|
|
31
|
-
|
|
32
|
-
table_sql = @connection.query_value(<<~SQL, "SCHEMA")
|
|
33
|
-
SELECT sql FROM sqlite_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
|
|
34
|
-
UNION ALL
|
|
35
|
-
SELECT sql FROM sqlite_temp_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
|
|
36
|
-
SQL
|
|
37
|
-
|
|
38
|
-
table_sql.to_s.match?(/\bAUTOINCREMENT\b/i)
|
|
26
|
+
column.bigint?
|
|
39
27
|
end
|
|
40
28
|
|
|
41
29
|
def prepare_column_options(column)
|
|
@@ -63,23 +63,13 @@ module ActiveRecord
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
|
66
|
-
return if options.delete(:if_exists)
|
|
66
|
+
return if options.delete(:if_exists) && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
|
67
67
|
|
|
68
68
|
to_table ||= options[:to_table]
|
|
69
69
|
options = options.except(:name, :to_table, :validate)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
fkey = foreign_keys.detect do |fk|
|
|
73
|
-
table = to_table || begin
|
|
74
|
-
table = options[:column].to_s.delete_suffix("_id")
|
|
75
|
-
Base.pluralize_table_names ? table.pluralize : table
|
|
76
|
-
end
|
|
77
|
-
table = strip_table_name_prefix_and_suffix(table)
|
|
78
|
-
options = options.slice(*fk.options.keys)
|
|
79
|
-
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
|
80
|
-
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
|
|
81
|
-
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
|
70
|
+
fkey = foreign_key_for!(from_table, to_table: to_table, **options)
|
|
82
71
|
|
|
72
|
+
foreign_keys = foreign_keys(from_table)
|
|
83
73
|
foreign_keys.delete(fkey)
|
|
84
74
|
alter_table(from_table, foreign_keys)
|
|
85
75
|
end
|
|
@@ -157,6 +147,7 @@ module ActiveRecord
|
|
|
157
147
|
|
|
158
148
|
Column.new(
|
|
159
149
|
field["name"],
|
|
150
|
+
lookup_cast_type(field["type"]),
|
|
160
151
|
default_value,
|
|
161
152
|
type_metadata,
|
|
162
153
|
field["notnull"].to_i == 0,
|
|
@@ -19,14 +19,30 @@ SQLite3::ForkSafety.suppress_warnings!
|
|
|
19
19
|
|
|
20
20
|
module ActiveRecord
|
|
21
21
|
module ConnectionAdapters # :nodoc:
|
|
22
|
-
# = Active Record SQLite3 Adapter
|
|
22
|
+
# = Active Record \SQLite3 Adapter
|
|
23
23
|
#
|
|
24
|
-
# The SQLite3 adapter works with the sqlite3-ruby
|
|
25
|
-
#
|
|
24
|
+
# The \SQLite3 adapter works with the sqlite3[https://sparklemotion.github.io/sqlite3-ruby/]
|
|
25
|
+
# driver.
|
|
26
26
|
#
|
|
27
27
|
# ==== Options
|
|
28
28
|
#
|
|
29
|
-
# *
|
|
29
|
+
# * +:database+ (String): Filesystem path to the database file.
|
|
30
|
+
# * +:statement_limit+ (Integer): Maximum number of prepared statements to cache per database connection. (default: 1000)
|
|
31
|
+
# * +:timeout+ (Integer): Timeout in milliseconds to use when waiting for a lock. (default: no wait)
|
|
32
|
+
# * +:strict+ (Boolean): Enable or disable strict mode. When enabled, this will
|
|
33
|
+
# {disallow double-quoted string literals in SQL
|
|
34
|
+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted].
|
|
35
|
+
# (default: see strict_strings_by_default)
|
|
36
|
+
# * +:extensions+ (Array): (<b>requires sqlite3 v2.4.0</b>) Each entry specifies a sqlite extension
|
|
37
|
+
# to load for this database. The entry may be a filesystem path, or the name of a class that
|
|
38
|
+
# responds to +.to_path+ to provide the filesystem path for the extension. See {sqlite3-ruby
|
|
39
|
+
# documentation}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#class-SQLite3::Database-label-SQLite+Extensions]
|
|
40
|
+
# for more information.
|
|
41
|
+
#
|
|
42
|
+
# There may be other options available specific to the SQLite3 driver. Please read the
|
|
43
|
+
# documentation for
|
|
44
|
+
# {SQLite3::Database.new}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#method-c-new]
|
|
45
|
+
#
|
|
30
46
|
class SQLite3Adapter < AbstractAdapter
|
|
31
47
|
ADAPTER_NAME = "SQLite"
|
|
32
48
|
|
|
@@ -46,10 +62,14 @@ module ActiveRecord
|
|
|
46
62
|
|
|
47
63
|
args << "-#{options[:mode]}" if options[:mode]
|
|
48
64
|
args << "-header" if options[:header]
|
|
49
|
-
args << File.expand_path(config.database, Rails.
|
|
65
|
+
args << File.expand_path(config.database, defined?(Rails.root) ? Rails.root : nil)
|
|
50
66
|
|
|
51
67
|
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
|
52
68
|
end
|
|
69
|
+
|
|
70
|
+
def native_database_types # :nodoc:
|
|
71
|
+
NATIVE_DATABASE_TYPES
|
|
72
|
+
end
|
|
53
73
|
end
|
|
54
74
|
|
|
55
75
|
include SQLite3::Quoting
|
|
@@ -58,12 +78,19 @@ module ActiveRecord
|
|
|
58
78
|
|
|
59
79
|
##
|
|
60
80
|
# :singleton-method:
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
81
|
+
#
|
|
82
|
+
# Configure the SQLite3Adapter to be used in a "strict strings" mode. When enabled, this will
|
|
83
|
+
# {disallow double-quoted string literals in SQL
|
|
84
|
+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted],
|
|
85
|
+
# which may prevent some typographical errors like creating an index for a non-existent
|
|
86
|
+
# column. The default is +false+.
|
|
87
|
+
#
|
|
64
88
|
# If you wish to enable this mode you can add the following line to your application.rb file:
|
|
65
89
|
#
|
|
66
90
|
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
|
91
|
+
#
|
|
92
|
+
# This can also be configured on individual databases by setting the +strict:+ option.
|
|
93
|
+
#
|
|
67
94
|
class_attribute :strict_strings_by_default, default: false
|
|
68
95
|
|
|
69
96
|
NATIVE_DATABASE_TYPES = {
|
|
@@ -122,13 +149,18 @@ module ActiveRecord
|
|
|
122
149
|
end
|
|
123
150
|
end
|
|
124
151
|
|
|
125
|
-
@last_affected_rows = nil
|
|
126
152
|
@previous_read_uncommitted = nil
|
|
127
153
|
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
|
154
|
+
|
|
155
|
+
extensions = @config.fetch(:extensions, []).map do |extension|
|
|
156
|
+
extension.safe_constantize || extension
|
|
157
|
+
end
|
|
158
|
+
|
|
128
159
|
@connection_parameters = @config.merge(
|
|
129
160
|
database: @config[:database].to_s,
|
|
130
161
|
results_as_hash: true,
|
|
131
162
|
default_transaction_mode: :immediate,
|
|
163
|
+
extensions: extensions
|
|
132
164
|
)
|
|
133
165
|
end
|
|
134
166
|
|
|
@@ -153,7 +185,7 @@ module ActiveRecord
|
|
|
153
185
|
end
|
|
154
186
|
|
|
155
187
|
def supports_expression_index?
|
|
156
|
-
|
|
188
|
+
true
|
|
157
189
|
end
|
|
158
190
|
|
|
159
191
|
def requires_reloading?
|
|
@@ -181,7 +213,7 @@ module ActiveRecord
|
|
|
181
213
|
end
|
|
182
214
|
|
|
183
215
|
def supports_common_table_expressions?
|
|
184
|
-
|
|
216
|
+
true
|
|
185
217
|
end
|
|
186
218
|
|
|
187
219
|
def supports_insert_returning?
|
|
@@ -229,10 +261,6 @@ module ActiveRecord
|
|
|
229
261
|
true
|
|
230
262
|
end
|
|
231
263
|
|
|
232
|
-
def native_database_types # :nodoc:
|
|
233
|
-
NATIVE_DATABASE_TYPES
|
|
234
|
-
end
|
|
235
|
-
|
|
236
264
|
# Returns the current database encoding format as a string, e.g. 'UTF-8'
|
|
237
265
|
def encoding
|
|
238
266
|
any_raw_connection.encoding.to_s
|
|
@@ -291,7 +319,7 @@ module ActiveRecord
|
|
|
291
319
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
|
292
320
|
end
|
|
293
321
|
|
|
294
|
-
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((
|
|
322
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
|
295
323
|
|
|
296
324
|
# Returns a list of defined virtual tables
|
|
297
325
|
def virtual_tables
|
|
@@ -478,8 +506,8 @@ module ActiveRecord
|
|
|
478
506
|
end
|
|
479
507
|
|
|
480
508
|
def check_version # :nodoc:
|
|
481
|
-
if database_version < "3.
|
|
482
|
-
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.
|
|
509
|
+
if database_version < "3.23.0"
|
|
510
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.23.0."
|
|
483
511
|
end
|
|
484
512
|
end
|
|
485
513
|
|
|
@@ -535,6 +563,8 @@ module ActiveRecord
|
|
|
535
563
|
# Binary columns
|
|
536
564
|
when /x'(.*)'/
|
|
537
565
|
[ $1 ].pack("H*")
|
|
566
|
+
when "TRUE", "FALSE"
|
|
567
|
+
default
|
|
538
568
|
else
|
|
539
569
|
# Anything else is blank or some function
|
|
540
570
|
# and we can't know the value of that, so return nil.
|
|
@@ -583,8 +613,8 @@ module ActiveRecord
|
|
|
583
613
|
yield definition if block_given?
|
|
584
614
|
end
|
|
585
615
|
|
|
586
|
-
|
|
587
|
-
|
|
616
|
+
transaction do
|
|
617
|
+
disable_referential_integrity do
|
|
588
618
|
move_table(table_name, altered_table_name, options.merge(temporary: true))
|
|
589
619
|
move_table(altered_table_name, table_name, &caller)
|
|
590
620
|
end
|
|
@@ -625,8 +655,8 @@ module ActiveRecord
|
|
|
625
655
|
column_options[:stored] = column.virtual_stored?
|
|
626
656
|
column_options[:type] = column.type
|
|
627
657
|
elsif column.has_default?
|
|
628
|
-
|
|
629
|
-
default =
|
|
658
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
659
|
+
default = column.fetch_cast_type(self).deserialize(column.default)
|
|
630
660
|
default = -> { column.default_function } if default.nil?
|
|
631
661
|
|
|
632
662
|
unless column.auto_increment?
|
|
@@ -700,6 +730,8 @@ module ActiveRecord
|
|
|
700
730
|
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
701
731
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
|
702
732
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
733
|
+
elsif exception.message.match?(/CHECK constraint failed: .*/i)
|
|
734
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
703
735
|
elsif exception.message.match?(/called on a closed database/i)
|
|
704
736
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
705
737
|
elsif exception.is_a?(::SQLite3::BusyException)
|
|
@@ -789,9 +821,9 @@ module ActiveRecord
|
|
|
789
821
|
|
|
790
822
|
def table_info(table_name)
|
|
791
823
|
if supports_virtual_columns?
|
|
792
|
-
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
|
824
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
|
|
793
825
|
else
|
|
794
|
-
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
|
826
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
|
|
795
827
|
end
|
|
796
828
|
end
|
|
797
829
|
|
|
@@ -818,18 +850,10 @@ module ActiveRecord
|
|
|
818
850
|
end
|
|
819
851
|
|
|
820
852
|
def configure_connection
|
|
821
|
-
if @config[:timeout]
|
|
822
|
-
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
|
823
|
-
elsif @config[:timeout]
|
|
853
|
+
if @config[:timeout]
|
|
824
854
|
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
|
825
855
|
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
|
826
856
|
@raw_connection.busy_handler_timeout = timeout
|
|
827
|
-
elsif @config[:retries]
|
|
828
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
|
829
|
-
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
|
830
|
-
MSG
|
|
831
|
-
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
|
832
|
-
raw_connection.busy_handler { |count| count <= retries }
|
|
833
857
|
end
|
|
834
858
|
|
|
835
859
|
super
|
|
@@ -29,7 +29,8 @@ module ActiveRecord
|
|
|
29
29
|
raw_connection.next_result
|
|
30
30
|
end
|
|
31
31
|
verified!
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
notification_payload[:affected_rows] = result.affected_rows
|
|
33
34
|
notification_payload[:row_count] = result.count
|
|
34
35
|
result
|
|
35
36
|
ensure
|
|
@@ -40,9 +41,9 @@ module ActiveRecord
|
|
|
40
41
|
|
|
41
42
|
def cast_result(result)
|
|
42
43
|
if result.fields.empty?
|
|
43
|
-
ActiveRecord::Result.empty
|
|
44
|
+
ActiveRecord::Result.empty(affected_rows: result.affected_rows)
|
|
44
45
|
else
|
|
45
|
-
ActiveRecord::Result.new(result.fields, result.rows)
|
|
46
|
+
ActiveRecord::Result.new(result.fields, result.rows, affected_rows: result.affected_rows)
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
|
|
@@ -181,7 +181,7 @@ module ActiveRecord
|
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
case exception
|
|
184
|
-
when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
|
|
184
|
+
when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
|
|
185
185
|
return ConnectionFailed.new(message, connection_pool: @pool)
|
|
186
186
|
when ::Trilogy::Error
|
|
187
187
|
if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
|
|
@@ -84,6 +84,7 @@ module ActiveRecord
|
|
|
84
84
|
autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
|
|
85
85
|
autoload :IndexDefinition
|
|
86
86
|
autoload :ColumnDefinition
|
|
87
|
+
autoload :ColumnMethods
|
|
87
88
|
autoload :ChangeColumnDefinition
|
|
88
89
|
autoload :ChangeColumnDefaultDefinition
|
|
89
90
|
autoload :ForeignKeyDefinition
|
|
@@ -172,11 +172,9 @@ module ActiveRecord
|
|
|
172
172
|
prevent_writes = true if role == ActiveRecord.reading_role
|
|
173
173
|
|
|
174
174
|
append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
connected_to_stack.pop
|
|
179
|
-
end
|
|
175
|
+
yield
|
|
176
|
+
ensure
|
|
177
|
+
connected_to_stack.pop
|
|
180
178
|
end
|
|
181
179
|
|
|
182
180
|
# Passes the block to +connected_to+ for every +shard+ the
|
|
@@ -397,13 +395,11 @@ module ActiveRecord
|
|
|
397
395
|
prevent_writes = true if role == ActiveRecord.reading_role
|
|
398
396
|
|
|
399
397
|
append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
self.connected_to_stack.pop
|
|
406
|
-
end
|
|
398
|
+
return_value = yield
|
|
399
|
+
return_value.load if return_value.is_a? ActiveRecord::Relation
|
|
400
|
+
return_value
|
|
401
|
+
ensure
|
|
402
|
+
self.connected_to_stack.pop
|
|
407
403
|
end
|
|
408
404
|
|
|
409
405
|
def append_to_connected_to_stack(entry)
|
data/lib/active_record/core.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
|
4
|
-
require "active_support/core_ext/module/delegation"
|
|
5
4
|
require "active_support/parameter_filter"
|
|
6
5
|
require "concurrent/map"
|
|
7
6
|
|
|
@@ -112,7 +111,7 @@ module ActiveRecord
|
|
|
112
111
|
# Post.attributes_for_inspect = [:id, :title]
|
|
113
112
|
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
|
|
114
113
|
#
|
|
115
|
-
# When set to
|
|
114
|
+
# When set to +:all+ inspect will list all the record's attributes:
|
|
116
115
|
#
|
|
117
116
|
# Post.attributes_for_inspect = :all
|
|
118
117
|
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
|
|
@@ -358,6 +357,8 @@ module ActiveRecord
|
|
|
358
357
|
def filter_attributes=(filter_attributes)
|
|
359
358
|
@inspection_filter = nil
|
|
360
359
|
@filter_attributes = filter_attributes
|
|
360
|
+
|
|
361
|
+
FilterAttributeHandler.sensitive_attribute_was_declared(self, filter_attributes)
|
|
361
362
|
end
|
|
362
363
|
|
|
363
364
|
def inspection_filter # :nodoc:
|
|
@@ -451,7 +452,7 @@ module ActiveRecord
|
|
|
451
452
|
where(wheres).limit(1)
|
|
452
453
|
}
|
|
453
454
|
|
|
454
|
-
statement.execute(values.flatten, connection
|
|
455
|
+
statement.execute(values.flatten, connection).then do |r|
|
|
455
456
|
r.first
|
|
456
457
|
rescue TypeError
|
|
457
458
|
raise ActiveRecord::StatementInvalid
|
|
@@ -641,7 +642,7 @@ module ActiveRecord
|
|
|
641
642
|
def hash
|
|
642
643
|
id = self.id
|
|
643
644
|
|
|
644
|
-
if primary_key_values_present?
|
|
645
|
+
if self.class.composite_primary_key? ? primary_key_values_present? : id
|
|
645
646
|
self.class.hash ^ id.hash
|
|
646
647
|
else
|
|
647
648
|
super
|
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
|
17
17
|
#
|
|
18
18
|
# ==== Parameters
|
|
19
19
|
#
|
|
20
|
-
# * +id+ - The id of the object you wish to reset a counter on.
|
|
20
|
+
# * +id+ - The id of the object you wish to reset a counter on or an array of ids.
|
|
21
21
|
# * +counters+ - One or more association counters to reset. Association name or counter name can be given.
|
|
22
22
|
# * <tt>:touch</tt> - Touch timestamp columns when updating.
|
|
23
23
|
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
|
|
@@ -28,13 +28,25 @@ module ActiveRecord
|
|
|
28
28
|
# # For the Post with id #1, reset the comments_count
|
|
29
29
|
# Post.reset_counters(1, :comments)
|
|
30
30
|
#
|
|
31
|
+
# # For posts with ids #1 and #2, reset the comments_count
|
|
32
|
+
# Post.reset_counters([1, 2], :comments)
|
|
33
|
+
#
|
|
31
34
|
# # Like above, but also touch the updated_at and/or updated_on
|
|
32
35
|
# # attributes.
|
|
33
36
|
# Post.reset_counters(1, :comments, touch: true)
|
|
34
37
|
def reset_counters(id, *counters, touch: nil)
|
|
35
|
-
|
|
38
|
+
ids = if composite_primary_key?
|
|
39
|
+
if id.first.is_a?(Array)
|
|
40
|
+
id
|
|
41
|
+
else
|
|
42
|
+
[id]
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
Array(id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
updates = Hash.new { |h, k| h[k] = {} }
|
|
36
49
|
|
|
37
|
-
updates = {}
|
|
38
50
|
counters.each do |counter_association|
|
|
39
51
|
has_many_association = _reflect_on_association(counter_association)
|
|
40
52
|
unless has_many_association
|
|
@@ -48,14 +60,22 @@ module ActiveRecord
|
|
|
48
60
|
has_many_association = has_many_association.through_reflection
|
|
49
61
|
end
|
|
50
62
|
|
|
63
|
+
counter_association = counter_association.to_sym
|
|
51
64
|
foreign_key = has_many_association.foreign_key.to_s
|
|
52
65
|
child_class = has_many_association.klass
|
|
53
66
|
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
|
|
54
67
|
counter_name = reflection.counter_cache_column
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
counts =
|
|
70
|
+
unscoped
|
|
71
|
+
.joins(counter_association)
|
|
72
|
+
.where(primary_key => ids)
|
|
73
|
+
.group(primary_key)
|
|
74
|
+
.count(:all)
|
|
75
|
+
|
|
76
|
+
ids.each do |id|
|
|
77
|
+
updates[id].merge!(counter_name => counts[id] || 0)
|
|
78
|
+
end
|
|
59
79
|
end
|
|
60
80
|
|
|
61
81
|
if touch
|
|
@@ -63,10 +83,15 @@ module ActiveRecord
|
|
|
63
83
|
names = Array.wrap(names)
|
|
64
84
|
options = names.extract_options!
|
|
65
85
|
touch_updates = touch_attributes_with_time(*names, **options)
|
|
66
|
-
|
|
86
|
+
|
|
87
|
+
updates.each_value do |record_updates|
|
|
88
|
+
record_updates.merge!(touch_updates)
|
|
89
|
+
end
|
|
67
90
|
end
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
updates.each do |id, record_updates|
|
|
93
|
+
unscoped.where(primary_key => [id]).update_all(record_updates)
|
|
94
|
+
end
|
|
70
95
|
|
|
71
96
|
true
|
|
72
97
|
end
|
|
@@ -38,6 +38,7 @@ module ActiveRecord
|
|
|
38
38
|
def initialize(env_name, name, configuration_hash)
|
|
39
39
|
super(env_name, name)
|
|
40
40
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
|
41
|
+
validate_configuration!
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# Determines whether a database configuration is for a replica / readonly
|
|
@@ -69,16 +70,32 @@ module ActiveRecord
|
|
|
69
70
|
@configuration_hash = configuration_hash.merge(database: database).freeze
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
def
|
|
73
|
-
(configuration_hash[:pool] || 5).to_i
|
|
73
|
+
def max_connections
|
|
74
|
+
(configuration_hash[:max_connections] || configuration_hash[:pool] || 5).to_i
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
def min_connections
|
|
78
|
+
(configuration_hash[:min_connections] || 0).to_i
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias :pool :max_connections
|
|
82
|
+
deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
|
|
83
|
+
|
|
76
84
|
def min_threads
|
|
77
85
|
(configuration_hash[:min_threads] || 0).to_i
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
def max_threads
|
|
81
|
-
(configuration_hash[:max_threads] ||
|
|
89
|
+
(configuration_hash[:max_threads] || max_connections).to_i
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def max_age
|
|
93
|
+
v = configuration_hash[:max_age]&.to_i
|
|
94
|
+
if v && v > 0
|
|
95
|
+
v
|
|
96
|
+
else
|
|
97
|
+
Float::INFINITY
|
|
98
|
+
end
|
|
82
99
|
end
|
|
83
100
|
|
|
84
101
|
def query_cache
|
|
@@ -93,10 +110,8 @@ module ActiveRecord
|
|
|
93
110
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
|
94
111
|
end
|
|
95
112
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def reaping_frequency
|
|
99
|
-
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
|
|
113
|
+
def reaping_frequency # :nodoc:
|
|
114
|
+
configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
|
|
100
115
|
end
|
|
101
116
|
|
|
102
117
|
def idle_timeout
|
|
@@ -104,6 +119,11 @@ module ActiveRecord
|
|
|
104
119
|
timeout if timeout > 0
|
|
105
120
|
end
|
|
106
121
|
|
|
122
|
+
def keepalive
|
|
123
|
+
keepalive = (configuration_hash[:keepalive] || 600).to_f
|
|
124
|
+
keepalive if keepalive > 0
|
|
125
|
+
end
|
|
126
|
+
|
|
107
127
|
def adapter
|
|
108
128
|
configuration_hash[:adapter]&.to_s
|
|
109
129
|
end
|
|
@@ -159,8 +179,8 @@ module ActiveRecord
|
|
|
159
179
|
end
|
|
160
180
|
|
|
161
181
|
def schema_format # :nodoc:
|
|
162
|
-
format = configuration_hash
|
|
163
|
-
raise "Invalid schema format" unless [
|
|
182
|
+
format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
|
|
183
|
+
raise "Invalid schema format" unless [:ruby, :sql].include?(format)
|
|
164
184
|
format
|
|
165
185
|
end
|
|
166
186
|
|
|
@@ -181,6 +201,27 @@ module ActiveRecord
|
|
|
181
201
|
"structure.sql"
|
|
182
202
|
end
|
|
183
203
|
end
|
|
204
|
+
|
|
205
|
+
def default_reaping_frequency
|
|
206
|
+
# Reap every 20 seconds by default, but run more often as necessary to
|
|
207
|
+
# meet other configured timeouts.
|
|
208
|
+
[20, idle_timeout, max_age, keepalive].compact.min
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def validate_configuration!
|
|
212
|
+
if configuration_hash[:pool] && configuration_hash[:max_connections]
|
|
213
|
+
pool_val = configuration_hash[:pool].to_i
|
|
214
|
+
max_conn_val = configuration_hash[:max_connections].to_i
|
|
215
|
+
|
|
216
|
+
if pool_val != max_conn_val
|
|
217
|
+
raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if configuration_hash[:pool] && configuration_hash[:min_connections]
|
|
222
|
+
raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
|
|
223
|
+
end
|
|
224
|
+
end
|
|
184
225
|
end
|
|
185
226
|
end
|
|
186
227
|
end
|
|
@@ -47,9 +47,8 @@ module ActiveRecord
|
|
|
47
47
|
@configuration_hash[:schema_dump] = false
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
50
|
+
query_cache = parse_query_cache
|
|
51
|
+
@configuration_hash[:query_cache] = query_cache unless query_cache.nil?
|
|
53
52
|
|
|
54
53
|
to_boolean!(@configuration_hash, :replica)
|
|
55
54
|
to_boolean!(@configuration_hash, :database_tasks)
|
|
@@ -58,6 +57,17 @@ module ActiveRecord
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
private
|
|
60
|
+
def parse_query_cache
|
|
61
|
+
case value = @configuration_hash[:query_cache]
|
|
62
|
+
when /\A\d+\z/
|
|
63
|
+
value.to_i
|
|
64
|
+
when "false"
|
|
65
|
+
false
|
|
66
|
+
else
|
|
67
|
+
value
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
61
71
|
def to_boolean!(configuration_hash, key)
|
|
62
72
|
if configuration_hash[key].is_a?(String)
|
|
63
73
|
configuration_hash[key] = configuration_hash[key] != "false"
|
|
@@ -36,9 +36,11 @@ module ActiveRecord
|
|
|
36
36
|
# to respond to `sharded?`. To implement this define the following in an
|
|
37
37
|
# initializer:
|
|
38
38
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
39
|
+
# ActiveSupport.on_load(:active_record_database_configurations) do
|
|
40
|
+
# ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
|
|
41
|
+
# next unless config.key?(:vitess)
|
|
42
|
+
# VitessConfig.new(env_name, name, config)
|
|
43
|
+
# end
|
|
42
44
|
# end
|
|
43
45
|
#
|
|
44
46
|
# Note: applications must handle the condition in which custom config should be
|
|
@@ -306,4 +308,6 @@ module ActiveRecord
|
|
|
306
308
|
url
|
|
307
309
|
end
|
|
308
310
|
end
|
|
311
|
+
|
|
312
|
+
ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
|
|
309
313
|
end
|
|
@@ -229,7 +229,7 @@ module ActiveRecord
|
|
|
229
229
|
# @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
|
230
230
|
# @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
|
231
231
|
def delegated_type(role, types:, **options)
|
|
232
|
-
belongs_to role, options.delete(:scope), **options
|
|
232
|
+
belongs_to role, options.delete(:scope), **options, polymorphic: true
|
|
233
233
|
define_delegated_type_methods role, types: types, options: options
|
|
234
234
|
end
|
|
235
235
|
|