activerecord 8.0.3 → 8.1.0.rc1
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 +520 -514
- 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 +2 -0
- 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 +2 -0
- 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/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- 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 +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- 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 +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- 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 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- 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 +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- 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_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- 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 +2 -1
- 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 +53 -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/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -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 +12 -7
- 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 +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- 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 +36 -10
- 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/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- 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 +25 -11
- 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 +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- 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/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- 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 +14 -4
- 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 -4
- 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/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
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -61,6 +61,14 @@ module ActiveRecord
|
|
|
61
61
|
@previous_read_uncommitted = nil
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def default_insert_value(column) # :nodoc:
|
|
65
|
+
if column.default_function
|
|
66
|
+
Arel.sql(column.default_function)
|
|
67
|
+
else
|
|
68
|
+
column.default
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
64
72
|
private
|
|
65
73
|
def internal_begin_transaction(mode, isolation)
|
|
66
74
|
if isolation
|
|
@@ -76,39 +84,51 @@ module ActiveRecord
|
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
87
|
+
total_changes_before_query = raw_connection.total_changes
|
|
88
|
+
affected_rows = nil
|
|
89
|
+
|
|
79
90
|
if batch
|
|
80
91
|
raw_connection.execute_batch2(sql)
|
|
81
|
-
|
|
82
|
-
stmt =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
result = if stmt.column_count.zero? # No return
|
|
87
|
-
stmt.step
|
|
88
|
-
ActiveRecord::Result.empty
|
|
92
|
+
else
|
|
93
|
+
stmt = if prepare
|
|
94
|
+
@statements[sql] ||= raw_connection.prepare(sql)
|
|
95
|
+
@statements[sql].reset!
|
|
89
96
|
else
|
|
90
|
-
|
|
97
|
+
# Don't cache statements if they are not prepared.
|
|
98
|
+
raw_connection.prepare(sql)
|
|
91
99
|
end
|
|
92
|
-
else
|
|
93
|
-
# Don't cache statements if they are not prepared.
|
|
94
|
-
stmt = raw_connection.prepare(sql)
|
|
95
100
|
begin
|
|
96
101
|
unless binds.nil? || binds.empty?
|
|
97
102
|
stmt.bind_params(type_casted_binds)
|
|
98
103
|
end
|
|
99
104
|
result = if stmt.column_count.zero? # No return
|
|
100
105
|
stmt.step
|
|
101
|
-
|
|
106
|
+
|
|
107
|
+
affected_rows = if raw_connection.total_changes > total_changes_before_query
|
|
108
|
+
raw_connection.changes
|
|
109
|
+
else
|
|
110
|
+
0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
ActiveRecord::Result.empty(affected_rows: affected_rows)
|
|
102
114
|
else
|
|
103
|
-
|
|
115
|
+
rows = stmt.to_a
|
|
116
|
+
|
|
117
|
+
affected_rows = if raw_connection.total_changes > total_changes_before_query
|
|
118
|
+
raw_connection.changes
|
|
119
|
+
else
|
|
120
|
+
0
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
ActiveRecord::Result.new(stmt.columns, rows, stmt.types.map { |t| type_map.lookup(t) }, affected_rows: affected_rows)
|
|
104
124
|
end
|
|
105
125
|
ensure
|
|
106
|
-
stmt.close
|
|
126
|
+
stmt.close unless prepare
|
|
107
127
|
end
|
|
108
128
|
end
|
|
109
|
-
@last_affected_rows = raw_connection.changes
|
|
110
129
|
verified!
|
|
111
130
|
|
|
131
|
+
notification_payload[:affected_rows] = affected_rows
|
|
112
132
|
notification_payload[:row_count] = result&.length || 0
|
|
113
133
|
result
|
|
114
134
|
end
|
|
@@ -120,7 +140,7 @@ module ActiveRecord
|
|
|
120
140
|
end
|
|
121
141
|
|
|
122
142
|
def affected_rows(result)
|
|
123
|
-
|
|
143
|
+
result.affected_rows
|
|
124
144
|
end
|
|
125
145
|
|
|
126
146
|
def execute_batch(statements, name = nil, **kwargs)
|
|
@@ -135,14 +155,6 @@ module ActiveRecord
|
|
|
135
155
|
def returning_column_values(result)
|
|
136
156
|
result.rows.first
|
|
137
157
|
end
|
|
138
|
-
|
|
139
|
-
def default_insert_value(column)
|
|
140
|
-
if column.default_function
|
|
141
|
-
Arel.sql(column.default_function)
|
|
142
|
-
else
|
|
143
|
-
column.default
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
158
|
end
|
|
147
159
|
end
|
|
148
160
|
end
|
|
@@ -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
|
|
@@ -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.
|
|
@@ -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
|
|
@@ -100,7 +100,8 @@ module ActiveRecord
|
|
|
100
100
|
db_config = resolve_config_for_connection(database_key)
|
|
101
101
|
|
|
102
102
|
self.connection_class = true
|
|
103
|
-
|
|
103
|
+
shard = shard.to_sym unless shard.is_a? Integer
|
|
104
|
+
connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
107
|
|
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,35 @@ module ActiveRecord
|
|
|
69
70
|
@configuration_hash = configuration_hash.merge(database: database).freeze
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
def
|
|
73
|
-
(
|
|
73
|
+
def max_connections
|
|
74
|
+
max_connections = configuration_hash.fetch(:max_connections) {
|
|
75
|
+
configuration_hash.fetch(:pool, 5)
|
|
76
|
+
}&.to_i
|
|
77
|
+
max_connections if max_connections && max_connections >= 0
|
|
74
78
|
end
|
|
75
79
|
|
|
80
|
+
def min_connections
|
|
81
|
+
(configuration_hash[:min_connections] || 0).to_i
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
alias :pool :max_connections
|
|
85
|
+
deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
|
|
86
|
+
|
|
76
87
|
def min_threads
|
|
77
88
|
(configuration_hash[:min_threads] || 0).to_i
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def max_threads
|
|
81
|
-
(configuration_hash[:max_threads] ||
|
|
92
|
+
(configuration_hash[:max_threads] || (max_connections || 5).clamp(0, 5)).to_i
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def max_age
|
|
96
|
+
v = configuration_hash[:max_age]&.to_i
|
|
97
|
+
if v && v > 0
|
|
98
|
+
v
|
|
99
|
+
else
|
|
100
|
+
Float::INFINITY
|
|
101
|
+
end
|
|
82
102
|
end
|
|
83
103
|
|
|
84
104
|
def query_cache
|
|
@@ -93,10 +113,8 @@ module ActiveRecord
|
|
|
93
113
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
|
94
114
|
end
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def reaping_frequency
|
|
99
|
-
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
|
|
116
|
+
def reaping_frequency # :nodoc:
|
|
117
|
+
configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
|
|
100
118
|
end
|
|
101
119
|
|
|
102
120
|
def idle_timeout
|
|
@@ -104,6 +122,11 @@ module ActiveRecord
|
|
|
104
122
|
timeout if timeout > 0
|
|
105
123
|
end
|
|
106
124
|
|
|
125
|
+
def keepalive
|
|
126
|
+
keepalive = (configuration_hash[:keepalive] || 600).to_f
|
|
127
|
+
keepalive if keepalive > 0
|
|
128
|
+
end
|
|
129
|
+
|
|
107
130
|
def adapter
|
|
108
131
|
configuration_hash[:adapter]&.to_s
|
|
109
132
|
end
|
|
@@ -159,8 +182,8 @@ module ActiveRecord
|
|
|
159
182
|
end
|
|
160
183
|
|
|
161
184
|
def schema_format # :nodoc:
|
|
162
|
-
format = configuration_hash
|
|
163
|
-
raise "Invalid schema format" unless [
|
|
185
|
+
format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
|
|
186
|
+
raise "Invalid schema format" unless [:ruby, :sql].include?(format)
|
|
164
187
|
format
|
|
165
188
|
end
|
|
166
189
|
|
|
@@ -181,6 +204,27 @@ module ActiveRecord
|
|
|
181
204
|
"structure.sql"
|
|
182
205
|
end
|
|
183
206
|
end
|
|
207
|
+
|
|
208
|
+
def default_reaping_frequency
|
|
209
|
+
# Reap every 20 seconds by default, but run more often as necessary to
|
|
210
|
+
# meet other configured timeouts.
|
|
211
|
+
[20, idle_timeout, max_age, keepalive].compact.min
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def validate_configuration!
|
|
215
|
+
if configuration_hash[:pool] && configuration_hash[:max_connections]
|
|
216
|
+
pool_val = configuration_hash[:pool].to_i
|
|
217
|
+
max_conn_val = configuration_hash[:max_connections].to_i
|
|
218
|
+
|
|
219
|
+
if pool_val != max_conn_val
|
|
220
|
+
raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if configuration_hash[:pool] && configuration_hash[:min_connections]
|
|
225
|
+
raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
|
|
226
|
+
end
|
|
227
|
+
end
|
|
184
228
|
end
|
|
185
229
|
end
|
|
186
230
|
end
|