activerecord 7.2.3 → 8.0.4
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 +391 -958
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +16 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +31 -11
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +11 -35
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +40 -39
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation.rb +79 -61
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/table.rb +3 -7
- metadata +9 -10
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -5,6 +5,19 @@ module ActiveRecord
|
|
|
5
5
|
module SQLite3
|
|
6
6
|
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
|
7
7
|
private
|
|
8
|
+
def virtual_tables(stream)
|
|
9
|
+
virtual_tables = @connection.virtual_tables
|
|
10
|
+
if virtual_tables.any?
|
|
11
|
+
stream.puts
|
|
12
|
+
stream.puts " # Virtual tables defined in this database."
|
|
13
|
+
stream.puts " # Note that virtual tables may not work with other database engines. Be careful if changing database."
|
|
14
|
+
virtual_tables.sort.each do |table_name, options|
|
|
15
|
+
module_name, arguments = options
|
|
16
|
+
stream.puts " create_virtual_table #{table_name.inspect}, #{module_name.inspect}, #{arguments.split(", ").inspect}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
8
21
|
def default_primary_key?(column)
|
|
9
22
|
schema_type(column) == :integer
|
|
10
23
|
end
|
|
@@ -27,6 +27,7 @@ module ActiveRecord
|
|
|
27
27
|
col["name"]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
where = where.sub(/\s*\/\*.*\*\/\z/, "") if where
|
|
30
31
|
orders = {}
|
|
31
32
|
|
|
32
33
|
if columns.any?(&:nil?) # index created with an expression
|
|
@@ -83,6 +84,10 @@ module ActiveRecord
|
|
|
83
84
|
alter_table(from_table, foreign_keys)
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
def virtual_table_exists?(table_name)
|
|
88
|
+
query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
|
|
89
|
+
end
|
|
90
|
+
|
|
86
91
|
def check_constraints(table_name)
|
|
87
92
|
table_sql = query_value(<<-SQL, "SCHEMA")
|
|
88
93
|
SELECT sql
|
|
@@ -177,7 +182,8 @@ module ActiveRecord
|
|
|
177
182
|
scope = quoted_scope(name, type: type)
|
|
178
183
|
scope[:type] ||= "'table','view'"
|
|
179
184
|
|
|
180
|
-
sql = +"SELECT name FROM
|
|
185
|
+
sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
|
|
186
|
+
sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
|
|
181
187
|
sql << " AND name = #{scope[:name]}" if scope[:name]
|
|
182
188
|
sql << " AND type IN (#{scope[:type]})"
|
|
183
189
|
sql
|
|
@@ -190,6 +196,8 @@ module ActiveRecord
|
|
|
190
196
|
"'table'"
|
|
191
197
|
when "VIEW"
|
|
192
198
|
"'view'"
|
|
199
|
+
when "VIRTUAL TABLE"
|
|
200
|
+
"'virtual'"
|
|
193
201
|
end
|
|
194
202
|
scope = {}
|
|
195
203
|
scope[:name] = quote(name) if name
|
|
@@ -11,9 +11,12 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
|
|
|
11
11
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
|
12
12
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
|
13
13
|
|
|
14
|
-
gem "sqlite3", ">= 1
|
|
14
|
+
gem "sqlite3", ">= 2.1"
|
|
15
15
|
require "sqlite3"
|
|
16
16
|
|
|
17
|
+
# Suppress the warning that SQLite3 issues when open writable connections are carried across fork()
|
|
18
|
+
SQLite3::ForkSafety.suppress_warnings!
|
|
19
|
+
|
|
17
20
|
module ActiveRecord
|
|
18
21
|
module ConnectionAdapters # :nodoc:
|
|
19
22
|
# = Active Record SQLite3 Adapter
|
|
@@ -45,7 +48,7 @@ module ActiveRecord
|
|
|
45
48
|
args << "-header" if options[:header]
|
|
46
49
|
args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
|
|
47
50
|
|
|
48
|
-
find_cmd_and_exec(
|
|
51
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
|
49
52
|
end
|
|
50
53
|
end
|
|
51
54
|
|
|
@@ -119,9 +122,14 @@ module ActiveRecord
|
|
|
119
122
|
end
|
|
120
123
|
end
|
|
121
124
|
|
|
125
|
+
@last_affected_rows = nil
|
|
126
|
+
@previous_read_uncommitted = nil
|
|
122
127
|
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
|
123
|
-
@connection_parameters = @config.merge(
|
|
124
|
-
|
|
128
|
+
@connection_parameters = @config.merge(
|
|
129
|
+
database: @config[:database].to_s,
|
|
130
|
+
results_as_hash: true,
|
|
131
|
+
default_transaction_mode: :immediate,
|
|
132
|
+
)
|
|
125
133
|
end
|
|
126
134
|
|
|
127
135
|
def database_exists?
|
|
@@ -283,6 +291,38 @@ module ActiveRecord
|
|
|
283
291
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
|
284
292
|
end
|
|
285
293
|
|
|
294
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
|
295
|
+
|
|
296
|
+
# Returns a list of defined virtual tables
|
|
297
|
+
def virtual_tables
|
|
298
|
+
query = <<~SQL
|
|
299
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
|
300
|
+
SQL
|
|
301
|
+
|
|
302
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
|
303
|
+
table_name, sql = row[0], row[1]
|
|
304
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
|
305
|
+
memo[table_name] = [module_name, arguments]
|
|
306
|
+
end.to_a
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Creates a virtual table
|
|
310
|
+
#
|
|
311
|
+
# Example:
|
|
312
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title', 'body']
|
|
313
|
+
def create_virtual_table(table_name, module_name, values)
|
|
314
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Drops a virtual table
|
|
318
|
+
#
|
|
319
|
+
# Although this command ignores +module_name+ and +values+,
|
|
320
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
|
321
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
|
322
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
|
323
|
+
drop_table(table_name)
|
|
324
|
+
end
|
|
325
|
+
|
|
286
326
|
# Renames a table.
|
|
287
327
|
#
|
|
288
328
|
# Example:
|
|
@@ -433,10 +473,6 @@ module ActiveRecord
|
|
|
433
473
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
|
434
474
|
end
|
|
435
475
|
|
|
436
|
-
def use_insert_returning?
|
|
437
|
-
@use_insert_returning
|
|
438
|
-
end
|
|
439
|
-
|
|
440
476
|
def get_database_version # :nodoc:
|
|
441
477
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
|
442
478
|
end
|
|
@@ -666,6 +702,8 @@ module ActiveRecord
|
|
|
666
702
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
667
703
|
elsif exception.message.match?(/called on a closed database/i)
|
|
668
704
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
705
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
|
706
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
669
707
|
else
|
|
670
708
|
super
|
|
671
709
|
end
|
|
@@ -783,12 +821,15 @@ module ActiveRecord
|
|
|
783
821
|
if @config[:timeout] && @config[:retries]
|
|
784
822
|
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
|
785
823
|
elsif @config[:timeout]
|
|
786
|
-
|
|
824
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
|
825
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
|
826
|
+
@raw_connection.busy_handler_timeout = timeout
|
|
787
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
|
|
788
831
|
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
|
789
|
-
raw_connection.busy_handler
|
|
790
|
-
count <= retries
|
|
791
|
-
end
|
|
832
|
+
raw_connection.busy_handler { |count| count <= retries }
|
|
792
833
|
end
|
|
793
834
|
|
|
794
835
|
super
|
|
@@ -4,93 +4,63 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module Trilogy
|
|
6
6
|
module DatabaseStatements
|
|
7
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
|
8
|
-
sql = transform_query(sql)
|
|
9
|
-
check_if_write_query(sql)
|
|
10
|
-
mark_transaction_written_if_write(sql)
|
|
11
|
-
|
|
12
|
-
result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
|
|
13
|
-
ActiveRecord::Result.new(result.fields, result.to_a)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
7
|
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
|
17
|
-
sql = transform_query(sql)
|
|
18
|
-
check_if_write_query(sql)
|
|
19
|
-
mark_transaction_written_if_write(sql)
|
|
20
|
-
|
|
21
8
|
sql, _binds = sql_for_insert(sql, pk, binds, returning)
|
|
22
|
-
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
26
|
-
sql = transform_query(sql)
|
|
27
|
-
check_if_write_query(sql)
|
|
28
|
-
mark_transaction_written_if_write(sql)
|
|
29
|
-
|
|
30
|
-
result = raw_execute(to_sql(sql, binds), name)
|
|
31
|
-
result.affected_rows
|
|
9
|
+
internal_execute(sql, name)
|
|
32
10
|
end
|
|
33
11
|
|
|
34
|
-
alias :exec_update :exec_delete # :nodoc:
|
|
35
|
-
|
|
36
12
|
private
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
result = conn.query(sql)
|
|
42
|
-
while conn.more_results_exist?
|
|
43
|
-
conn.next_result
|
|
44
|
-
end
|
|
45
|
-
verified!
|
|
46
|
-
handle_warnings(sql)
|
|
47
|
-
notification_payload[:row_count] = result.count
|
|
48
|
-
result
|
|
49
|
-
end
|
|
13
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
14
|
+
reset_multi_statement = if batch && !@config[:multi_statement]
|
|
15
|
+
raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
|
16
|
+
true
|
|
50
17
|
end
|
|
51
|
-
end
|
|
52
18
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
19
|
+
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
|
20
|
+
# made since we established the connection
|
|
21
|
+
if default_timezone == :local
|
|
22
|
+
raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
|
56
23
|
else
|
|
57
|
-
|
|
24
|
+
raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
|
58
25
|
end
|
|
59
|
-
end
|
|
60
26
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
27
|
+
result = raw_connection.query(sql)
|
|
28
|
+
while raw_connection.more_results_exist?
|
|
29
|
+
raw_connection.next_result
|
|
30
|
+
end
|
|
31
|
+
verified!
|
|
32
|
+
handle_warnings(sql)
|
|
33
|
+
notification_payload[:row_count] = result.count
|
|
34
|
+
result
|
|
35
|
+
ensure
|
|
36
|
+
if reset_multi_statement && active?
|
|
37
|
+
raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
|
67
38
|
end
|
|
68
39
|
end
|
|
69
40
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
end
|
|
41
|
+
def cast_result(result)
|
|
42
|
+
if result.fields.empty?
|
|
43
|
+
ActiveRecord::Result.empty
|
|
44
|
+
else
|
|
45
|
+
ActiveRecord::Result.new(result.fields, result.rows)
|
|
76
46
|
end
|
|
77
47
|
end
|
|
78
48
|
|
|
79
|
-
def
|
|
80
|
-
|
|
49
|
+
def affected_rows(result)
|
|
50
|
+
result.affected_rows
|
|
81
51
|
end
|
|
82
52
|
|
|
83
|
-
def
|
|
84
|
-
if
|
|
85
|
-
|
|
53
|
+
def last_inserted_id(result)
|
|
54
|
+
if supports_insert_returning?
|
|
55
|
+
super
|
|
56
|
+
else
|
|
57
|
+
result.last_insert_id
|
|
86
58
|
end
|
|
59
|
+
end
|
|
87
60
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
yield
|
|
92
|
-
ensure
|
|
93
|
-
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
|
|
61
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
62
|
+
combine_multi_statements(statements).each do |statement|
|
|
63
|
+
raw_execute(statement, name, batch: true, **kwargs)
|
|
94
64
|
end
|
|
95
65
|
end
|
|
96
66
|
end
|
|
@@ -149,23 +149,6 @@ module ActiveRecord
|
|
|
149
149
|
TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
-
def each_hash(result)
|
|
153
|
-
return to_enum(:each_hash, result) unless block_given?
|
|
154
|
-
|
|
155
|
-
keys = result.fields.map(&:to_sym)
|
|
156
|
-
result.rows.each do |row|
|
|
157
|
-
hash = {}
|
|
158
|
-
idx = 0
|
|
159
|
-
row.each do |value|
|
|
160
|
-
hash[keys[idx]] = value
|
|
161
|
-
idx += 1
|
|
162
|
-
end
|
|
163
|
-
yield hash
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
nil
|
|
167
|
-
end
|
|
168
|
-
|
|
169
152
|
def error_number(exception)
|
|
170
153
|
exception.error_code if exception.respond_to?(:error_code)
|
|
171
154
|
end
|
|
@@ -31,62 +31,6 @@ module ActiveRecord
|
|
|
31
31
|
class_name, path_to_adapter = @adapters[adapter_name.to_s]
|
|
32
32
|
|
|
33
33
|
unless class_name
|
|
34
|
-
# To provide better error messages for adapters expecting the pre-7.2 adapter registration API, we attempt
|
|
35
|
-
# to load the adapter file from the old location which was required by convention, and then raise an error
|
|
36
|
-
# describing how to upgrade the adapter to the new API.
|
|
37
|
-
legacy_adapter_path = "active_record/connection_adapters/#{adapter_name}_adapter"
|
|
38
|
-
legacy_adapter_connection_method_name = "#{adapter_name}_connection".to_sym
|
|
39
|
-
|
|
40
|
-
begin
|
|
41
|
-
require legacy_adapter_path
|
|
42
|
-
# If we reach here it means we found the found a file that may be the legacy adapter and should raise.
|
|
43
|
-
if ActiveRecord::ConnectionHandling.method_defined?(legacy_adapter_connection_method_name)
|
|
44
|
-
# If we find the connection method then we care certain it is a legacy adapter.
|
|
45
|
-
deprecation_message = <<~MSG.squish
|
|
46
|
-
Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
|
|
47
|
-
Rails 7.2 has changed the way Active Record database adapters are loaded. The adapter needs to be
|
|
48
|
-
updated to register itself rather than being loaded by convention.
|
|
49
|
-
Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
|
|
50
|
-
be modified.
|
|
51
|
-
See:
|
|
52
|
-
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
|
|
53
|
-
MSG
|
|
54
|
-
|
|
55
|
-
exception_message = <<~MSG.squish
|
|
56
|
-
Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
|
|
57
|
-
Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
|
|
58
|
-
be modified.
|
|
59
|
-
MSG
|
|
60
|
-
else
|
|
61
|
-
# If we do not find the connection method we are much less certain it is a legacy adapter. Even though the
|
|
62
|
-
# file exists in the location defined by convenntion, it does not necessarily mean that file is supposed
|
|
63
|
-
# to define the adapter the legacy way. So raise an error that explains both possibilities.
|
|
64
|
-
deprecation_message = <<~MSG.squish
|
|
65
|
-
Database configuration specifies nonexistent '#{adapter_name}' adapter.
|
|
66
|
-
Available adapters are: #{@adapters.keys.sort.join(", ")}.
|
|
67
|
-
Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
|
|
68
|
-
adapter gem to your Gemfile if it's not in the list of available adapters.
|
|
69
|
-
Rails 7.2 has changed the way Active Record database adapters are loaded. Ensure that the adapter in
|
|
70
|
-
the Gemfile is at the latest version. If it is up to date, the adapter may need to be modified.
|
|
71
|
-
See:
|
|
72
|
-
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
|
|
73
|
-
MSG
|
|
74
|
-
|
|
75
|
-
exception_message = <<~MSG.squish
|
|
76
|
-
Database configuration specifies nonexistent '#{adapter_name}' adapter.
|
|
77
|
-
Available adapters are: #{@adapters.keys.sort.join(", ")}.
|
|
78
|
-
Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
|
|
79
|
-
adapter gem to your Gemfile and that it is at its latest version. If it is up to date, the adapter may
|
|
80
|
-
need to be modified.
|
|
81
|
-
MSG
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
ActiveRecord.deprecator.warn(deprecation_message)
|
|
85
|
-
raise AdapterNotFound, exception_message
|
|
86
|
-
rescue LoadError => error
|
|
87
|
-
# The adapter was not found in the legacy location so fall through to the error handling for a missing adapter.
|
|
88
|
-
end
|
|
89
|
-
|
|
90
34
|
raise AdapterNotFound, <<~MSG.squish
|
|
91
35
|
Database configuration specifies nonexistent '#{adapter_name}' adapter.
|
|
92
36
|
Available adapters are: #{@adapters.keys.sort.join(", ")}.
|
|
@@ -87,6 +87,8 @@ module ActiveRecord
|
|
|
87
87
|
|
|
88
88
|
connections = []
|
|
89
89
|
|
|
90
|
+
@shard_keys = shards.keys
|
|
91
|
+
|
|
90
92
|
if shards.empty?
|
|
91
93
|
shards[:default] = database
|
|
92
94
|
end
|
|
@@ -177,6 +179,18 @@ module ActiveRecord
|
|
|
177
179
|
end
|
|
178
180
|
end
|
|
179
181
|
|
|
182
|
+
# Passes the block to +connected_to+ for every +shard+ the
|
|
183
|
+
# model is configured to connect to (if any), and returns the
|
|
184
|
+
# results in an array.
|
|
185
|
+
#
|
|
186
|
+
# Optionally, +role+ and/or +prevent_writes+ can be passed which
|
|
187
|
+
# will be forwarded to each +connected_to+ call.
|
|
188
|
+
def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
|
|
189
|
+
shard_keys.map do |shard|
|
|
190
|
+
connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
180
194
|
# Use a specified connection.
|
|
181
195
|
#
|
|
182
196
|
# This method is useful for ensuring that a specific connection is
|
|
@@ -289,7 +303,7 @@ module ActiveRecord
|
|
|
289
303
|
|
|
290
304
|
# Checkouts a connection from the pool, yield it and then check it back in.
|
|
291
305
|
# If a connection was already leased via #lease_connection or a parent call to
|
|
292
|
-
# #with_connection, that same connection is
|
|
306
|
+
# #with_connection, that same connection is yielded.
|
|
293
307
|
# If #lease_connection is called inside the block, the connection won't be checked
|
|
294
308
|
# back in.
|
|
295
309
|
# If #connection is called inside the block, the connection won't be checked back in
|
|
@@ -361,6 +375,14 @@ module ActiveRecord
|
|
|
361
375
|
connection_pool.schema_cache.clear!
|
|
362
376
|
end
|
|
363
377
|
|
|
378
|
+
def shard_keys
|
|
379
|
+
connection_class_for_self.instance_variable_get(:@shard_keys) || []
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def sharded?
|
|
383
|
+
shard_keys.any?
|
|
384
|
+
end
|
|
385
|
+
|
|
364
386
|
private
|
|
365
387
|
def resolve_config_for_connection(config_or_env)
|
|
366
388
|
raise "Anonymous class is not allowed." unless name
|
data/lib/active_record/core.rb
CHANGED
|
@@ -89,6 +89,7 @@ module ActiveRecord
|
|
|
89
89
|
class_attribute :belongs_to_required_by_default, instance_accessor: false
|
|
90
90
|
|
|
91
91
|
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
|
|
92
|
+
class_attribute :strict_loading_mode, instance_accessor: false, default: :all
|
|
92
93
|
|
|
93
94
|
class_attribute :has_many_inversing, instance_accessor: false, default: false
|
|
94
95
|
|
|
@@ -201,6 +202,17 @@ module ActiveRecord
|
|
|
201
202
|
false
|
|
202
203
|
end
|
|
203
204
|
|
|
205
|
+
# Intended to behave like `.current_preventing_writes` given the class name as input.
|
|
206
|
+
# See PoolConfig and ConnectionHandler::ConnectionDescriptor.
|
|
207
|
+
def self.preventing_writes?(class_name) # :nodoc:
|
|
208
|
+
connected_to_stack.reverse_each do |hash|
|
|
209
|
+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
|
|
210
|
+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
false
|
|
214
|
+
end
|
|
215
|
+
|
|
204
216
|
def self.connected_to_stack # :nodoc:
|
|
205
217
|
if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
|
|
206
218
|
connected_to_stack
|
|
@@ -381,7 +393,7 @@ module ActiveRecord
|
|
|
381
393
|
end
|
|
382
394
|
|
|
383
395
|
def predicate_builder # :nodoc:
|
|
384
|
-
@predicate_builder ||= PredicateBuilder.new(
|
|
396
|
+
@predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table))
|
|
385
397
|
end
|
|
386
398
|
|
|
387
399
|
def type_caster # :nodoc:
|
|
@@ -426,10 +438,6 @@ module ActiveRecord
|
|
|
426
438
|
end
|
|
427
439
|
end
|
|
428
440
|
|
|
429
|
-
def table_metadata
|
|
430
|
-
TableMetadata.new(self, arel_table)
|
|
431
|
-
end
|
|
432
|
-
|
|
433
441
|
def cached_find_by(keys, values)
|
|
434
442
|
with_connection do |connection|
|
|
435
443
|
statement = cached_find_by_statement(connection, keys) { |params|
|
|
@@ -443,8 +451,8 @@ module ActiveRecord
|
|
|
443
451
|
where(wheres).limit(1)
|
|
444
452
|
}
|
|
445
453
|
|
|
446
|
-
|
|
447
|
-
|
|
454
|
+
statement.execute(values.flatten, connection, allow_retry: true).then do |r|
|
|
455
|
+
r.first
|
|
448
456
|
rescue TypeError
|
|
449
457
|
raise ActiveRecord::StatementInvalid
|
|
450
458
|
end
|
|
@@ -540,12 +548,7 @@ module ActiveRecord
|
|
|
540
548
|
|
|
541
549
|
##
|
|
542
550
|
def initialize_dup(other) # :nodoc:
|
|
543
|
-
@attributes =
|
|
544
|
-
if self.class.composite_primary_key?
|
|
545
|
-
@primary_key.each { |key| @attributes.reset(key) }
|
|
546
|
-
else
|
|
547
|
-
@attributes.reset(@primary_key)
|
|
548
|
-
end
|
|
551
|
+
@attributes = init_attributes(other)
|
|
549
552
|
|
|
550
553
|
_run_initialize_callbacks
|
|
551
554
|
|
|
@@ -557,6 +560,18 @@ module ActiveRecord
|
|
|
557
560
|
super
|
|
558
561
|
end
|
|
559
562
|
|
|
563
|
+
def init_attributes(_) # :nodoc:
|
|
564
|
+
attrs = @attributes.deep_dup
|
|
565
|
+
|
|
566
|
+
if self.class.composite_primary_key?
|
|
567
|
+
@primary_key.each { |key| attrs.reset(key) }
|
|
568
|
+
else
|
|
569
|
+
attrs.reset(@primary_key)
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
attrs
|
|
573
|
+
end
|
|
574
|
+
|
|
560
575
|
# Populate +coder+ with attributes about this record that should be
|
|
561
576
|
# serialized. The structure of +coder+ defined in this method is
|
|
562
577
|
# guaranteed to match the structure of +coder+ passed to the #init_with
|
|
@@ -830,7 +845,7 @@ module ActiveRecord
|
|
|
830
845
|
|
|
831
846
|
@primary_key = klass.primary_key
|
|
832
847
|
@strict_loading = klass.strict_loading_by_default
|
|
833
|
-
@strict_loading_mode =
|
|
848
|
+
@strict_loading_mode = klass.strict_loading_mode
|
|
834
849
|
|
|
835
850
|
klass.define_attribute_methods
|
|
836
851
|
end
|
|
@@ -130,6 +130,14 @@ module ActiveRecord
|
|
|
130
130
|
Base.configurations.primary?(name)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
# Determines whether the db:prepare task should seed the database from db/seeds.rb.
|
|
134
|
+
#
|
|
135
|
+
# If the `seeds` key is present in the config, `seeds?` will return its value. Otherwise, it
|
|
136
|
+
# will return `true` for the primary database and `false` for all other configs.
|
|
137
|
+
def seeds?
|
|
138
|
+
configuration_hash.fetch(:seeds, primary?)
|
|
139
|
+
end
|
|
140
|
+
|
|
133
141
|
# Determines whether to dump the schema/structure files and the filename that
|
|
134
142
|
# should be used.
|
|
135
143
|
#
|
|
@@ -138,7 +146,7 @@ module ActiveRecord
|
|
|
138
146
|
#
|
|
139
147
|
# If the config option is set that will be used. Otherwise Rails will generate
|
|
140
148
|
# the filename from the database config name.
|
|
141
|
-
def schema_dump(format =
|
|
149
|
+
def schema_dump(format = schema_format)
|
|
142
150
|
if configuration_hash.key?(:schema_dump)
|
|
143
151
|
if config = configuration_hash[:schema_dump]
|
|
144
152
|
config
|
|
@@ -150,6 +158,12 @@ module ActiveRecord
|
|
|
150
158
|
end
|
|
151
159
|
end
|
|
152
160
|
|
|
161
|
+
def schema_format # :nodoc:
|
|
162
|
+
format = configuration_hash[:schema_format]&.to_sym || ActiveRecord.schema_format
|
|
163
|
+
raise "Invalid schema format" unless [ :ruby, :sql ].include? format
|
|
164
|
+
format
|
|
165
|
+
end
|
|
166
|
+
|
|
153
167
|
def database_tasks? # :nodoc:
|
|
154
168
|
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
|
155
169
|
end
|
|
@@ -160,7 +174,7 @@ module ActiveRecord
|
|
|
160
174
|
|
|
161
175
|
private
|
|
162
176
|
def schema_file_type(format)
|
|
163
|
-
case format
|
|
177
|
+
case format.to_sym
|
|
164
178
|
when :ruby
|
|
165
179
|
"schema.rb"
|
|
166
180
|
when :sql
|
|
@@ -8,7 +8,8 @@ module ActiveRecord
|
|
|
8
8
|
class Config
|
|
9
9
|
attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
|
|
10
10
|
:support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
|
|
11
|
-
:excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
|
|
11
|
+
:excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption,
|
|
12
|
+
:compressor
|
|
12
13
|
|
|
13
14
|
def initialize
|
|
14
15
|
set_defaults
|
|
@@ -55,6 +56,7 @@ module ActiveRecord
|
|
|
55
56
|
self.previous_schemes = []
|
|
56
57
|
self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
|
|
57
58
|
self.hash_digest_class = OpenSSL::Digest::SHA1
|
|
59
|
+
self.compressor = Zlib
|
|
58
60
|
|
|
59
61
|
# TODO: Setting to false for now as the implementation is a bit experimental
|
|
60
62
|
self.extend_queries = false
|
|
@@ -46,11 +46,11 @@ module ActiveRecord
|
|
|
46
46
|
# * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
|
|
47
47
|
# the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
|
|
48
48
|
# encryption is used, they will be used to generate additional ciphertexts to check in the queries.
|
|
49
|
-
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
|
49
|
+
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties)
|
|
50
50
|
self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
|
|
51
51
|
|
|
52
52
|
names.each do |name|
|
|
53
|
-
encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
|
|
53
|
+
encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -81,12 +81,12 @@ module ActiveRecord
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
|
84
|
+
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties)
|
|
85
85
|
encrypted_attributes << name.to_sym
|
|
86
86
|
|
|
87
87
|
decorate_attributes([name]) do |name, cast_type|
|
|
88
88
|
scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
|
|
89
|
-
downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
|
|
89
|
+
downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties
|
|
90
90
|
|
|
91
91
|
ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type, default: columns_hash[name.to_s]&.default)
|
|
92
92
|
end
|