activerecord 7.2.1.1 → 8.0.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 +220 -756
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -4
- 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_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/primary_key.rb +2 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -12
- data/lib/active_record/attributes.rb +1 -2
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- 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 +26 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -4
- 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 +24 -26
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -42
- 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 +43 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.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 +0 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +54 -14
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -97
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -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 +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- 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 +22 -0
- data/lib/active_record/core.rb +28 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- 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 +15 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +54 -75
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +4 -4
- 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/marshalling.rb +4 -1
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -38
- data/lib/active_record/model_schema.rb +4 -6
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_cache.rb +0 -4
- 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 +9 -38
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/reflection.rb +23 -23
- 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 +41 -40
- 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 +14 -1
- data/lib/active_record/relation/query_methods.rb +122 -71
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- 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 +12 -12
- 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 +40 -47
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/testing/query_assertions.rb +2 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +9 -8
- data/lib/active_record.rb +15 -45
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/sqlite.rb +25 -0
- metadata +10 -11
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -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?
|
@@ -278,6 +286,38 @@ module ActiveRecord
|
|
278
286
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
279
287
|
end
|
280
288
|
|
289
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
290
|
+
|
291
|
+
# Returns a list of defined virtual tables
|
292
|
+
def virtual_tables
|
293
|
+
query = <<~SQL
|
294
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
295
|
+
SQL
|
296
|
+
|
297
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
298
|
+
table_name, sql = row[0], row[1]
|
299
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
300
|
+
memo[table_name] = [module_name, arguments]
|
301
|
+
end.to_a
|
302
|
+
end
|
303
|
+
|
304
|
+
# Creates a virtual table
|
305
|
+
#
|
306
|
+
# Example:
|
307
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title',' body']
|
308
|
+
def create_virtual_table(table_name, module_name, values)
|
309
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
310
|
+
end
|
311
|
+
|
312
|
+
# Drops a virtual table
|
313
|
+
#
|
314
|
+
# Although this command ignores +module_name+ and +values+,
|
315
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
316
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
317
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
318
|
+
drop_table(table_name)
|
319
|
+
end
|
320
|
+
|
281
321
|
# Renames a table.
|
282
322
|
#
|
283
323
|
# Example:
|
@@ -428,10 +468,6 @@ module ActiveRecord
|
|
428
468
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
429
469
|
end
|
430
470
|
|
431
|
-
def use_insert_returning?
|
432
|
-
@use_insert_returning
|
433
|
-
end
|
434
|
-
|
435
471
|
def get_database_version # :nodoc:
|
436
472
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
437
473
|
end
|
@@ -661,6 +697,8 @@ module ActiveRecord
|
|
661
697
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
662
698
|
elsif exception.message.match?(/called on a closed database/i)
|
663
699
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
700
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
701
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
664
702
|
else
|
665
703
|
super
|
666
704
|
end
|
@@ -778,12 +816,15 @@ module ActiveRecord
|
|
778
816
|
if @config[:timeout] && @config[:retries]
|
779
817
|
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
780
818
|
elsif @config[:timeout]
|
781
|
-
|
819
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
820
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
821
|
+
@raw_connection.busy_handler_timeout = timeout
|
782
822
|
elsif @config[:retries]
|
823
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
824
|
+
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
825
|
+
MSG
|
783
826
|
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
784
|
-
raw_connection.busy_handler
|
785
|
-
count <= retries
|
786
|
-
end
|
827
|
+
raw_connection.busy_handler { |count| count <= retries }
|
787
828
|
end
|
788
829
|
|
789
830
|
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
|
@@ -175,6 +177,18 @@ module ActiveRecord
|
|
175
177
|
connected_to_stack.pop
|
176
178
|
end
|
177
179
|
|
180
|
+
# Passes the block to +connected_to+ for every +shard+ the
|
181
|
+
# model is configured to connect to (if any), and returns the
|
182
|
+
# results in an array.
|
183
|
+
#
|
184
|
+
# Optionally, +role+ and/or +prevent_writes+ can be passed which
|
185
|
+
# will be forwarded to each +connected_to+ call.
|
186
|
+
def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
|
187
|
+
shard_keys.map do |shard|
|
188
|
+
connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
178
192
|
# Use a specified connection.
|
179
193
|
#
|
180
194
|
# This method is useful for ensuring that a specific connection is
|
@@ -359,6 +373,14 @@ module ActiveRecord
|
|
359
373
|
connection_pool.schema_cache.clear!
|
360
374
|
end
|
361
375
|
|
376
|
+
def shard_keys
|
377
|
+
connection_class_for_self.instance_variable_get(:@shard_keys) || []
|
378
|
+
end
|
379
|
+
|
380
|
+
def sharded?
|
381
|
+
shard_keys.any?
|
382
|
+
end
|
383
|
+
|
362
384
|
private
|
363
385
|
def resolve_config_for_connection(config_or_env)
|
364
386
|
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
|
|
@@ -103,7 +104,7 @@ module ActiveRecord
|
|
103
104
|
class_attribute :shard_selector, instance_accessor: false, default: nil
|
104
105
|
|
105
106
|
# Specifies the attributes that will be included in the output of the #inspect method
|
106
|
-
class_attribute :attributes_for_inspect, instance_accessor: false, default:
|
107
|
+
class_attribute :attributes_for_inspect, instance_accessor: false, default: :all
|
107
108
|
|
108
109
|
def self.application_record_class? # :nodoc:
|
109
110
|
if ActiveRecord.application_record_class
|
@@ -349,7 +350,7 @@ module ActiveRecord
|
|
349
350
|
|
350
351
|
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
351
352
|
def inspect # :nodoc:
|
352
|
-
if self == Base
|
353
|
+
if self == Base || singleton_class?
|
353
354
|
super
|
354
355
|
elsif abstract_class?
|
355
356
|
"#{super}(abstract)"
|
@@ -369,7 +370,7 @@ module ActiveRecord
|
|
369
370
|
end
|
370
371
|
|
371
372
|
def predicate_builder # :nodoc:
|
372
|
-
@predicate_builder ||= PredicateBuilder.new(
|
373
|
+
@predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table))
|
373
374
|
end
|
374
375
|
|
375
376
|
def type_caster # :nodoc:
|
@@ -414,10 +415,6 @@ module ActiveRecord
|
|
414
415
|
end
|
415
416
|
end
|
416
417
|
|
417
|
-
def table_metadata
|
418
|
-
TableMetadata.new(self, arel_table)
|
419
|
-
end
|
420
|
-
|
421
418
|
def cached_find_by(keys, values)
|
422
419
|
with_connection do |connection|
|
423
420
|
statement = cached_find_by_statement(connection, keys) { |params|
|
@@ -431,8 +428,8 @@ module ActiveRecord
|
|
431
428
|
where(wheres).limit(1)
|
432
429
|
}
|
433
430
|
|
434
|
-
|
435
|
-
|
431
|
+
statement.execute(values.flatten, connection, allow_retry: true).then do |r|
|
432
|
+
r.first
|
436
433
|
rescue TypeError
|
437
434
|
raise ActiveRecord::StatementInvalid
|
438
435
|
end
|
@@ -528,12 +525,7 @@ module ActiveRecord
|
|
528
525
|
|
529
526
|
##
|
530
527
|
def initialize_dup(other) # :nodoc:
|
531
|
-
@attributes =
|
532
|
-
if self.class.composite_primary_key?
|
533
|
-
@primary_key.each { |key| @attributes.reset(key) }
|
534
|
-
else
|
535
|
-
@attributes.reset(@primary_key)
|
536
|
-
end
|
528
|
+
@attributes = init_attributes(other)
|
537
529
|
|
538
530
|
_run_initialize_callbacks
|
539
531
|
|
@@ -545,6 +537,18 @@ module ActiveRecord
|
|
545
537
|
super
|
546
538
|
end
|
547
539
|
|
540
|
+
def init_attributes(_) # :nodoc:
|
541
|
+
attrs = @attributes.deep_dup
|
542
|
+
|
543
|
+
if self.class.composite_primary_key?
|
544
|
+
@primary_key.each { |key| attrs.reset(key) }
|
545
|
+
else
|
546
|
+
attrs.reset(@primary_key)
|
547
|
+
end
|
548
|
+
|
549
|
+
attrs
|
550
|
+
end
|
551
|
+
|
548
552
|
# Populate +coder+ with attributes about this record that should be
|
549
553
|
# serialized. The structure of +coder+ defined in this method is
|
550
554
|
# guaranteed to match the structure of +coder+ passed to the #init_with
|
@@ -731,7 +735,7 @@ module ActiveRecord
|
|
731
735
|
|
732
736
|
# Returns the full contents of the record as a nicely formatted string.
|
733
737
|
def full_inspect
|
734
|
-
inspect_with_attributes(
|
738
|
+
inspect_with_attributes(all_attributes_for_inspect)
|
735
739
|
end
|
736
740
|
|
737
741
|
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
|
@@ -784,7 +788,7 @@ module ActiveRecord
|
|
784
788
|
|
785
789
|
@primary_key = klass.primary_key
|
786
790
|
@strict_loading = klass.strict_loading_by_default
|
787
|
-
@strict_loading_mode =
|
791
|
+
@strict_loading_mode = klass.strict_loading_mode
|
788
792
|
|
789
793
|
klass.define_attribute_methods
|
790
794
|
end
|
@@ -823,7 +827,13 @@ module ActiveRecord
|
|
823
827
|
end
|
824
828
|
|
825
829
|
def attributes_for_inspect
|
826
|
-
self.class.attributes_for_inspect == :all ?
|
830
|
+
self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
|
831
|
+
end
|
832
|
+
|
833
|
+
def all_attributes_for_inspect
|
834
|
+
return [] unless @attributes
|
835
|
+
|
836
|
+
attribute_names
|
827
837
|
end
|
828
838
|
end
|
829
839
|
end
|
@@ -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
|
@@ -100,7 +100,7 @@ module ActiveRecord
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def decrypt(value)
|
103
|
-
text_to_database_type decrypt_as_text(value)
|
103
|
+
text_to_database_type decrypt_as_text(database_type_to_text(value))
|
104
104
|
end
|
105
105
|
|
106
106
|
def try_to_deserialize_with_previous_encrypted_types(value)
|
@@ -170,6 +170,15 @@ module ActiveRecord
|
|
170
170
|
value
|
171
171
|
end
|
172
172
|
end
|
173
|
+
|
174
|
+
def database_type_to_text(value)
|
175
|
+
if value && cast_type.binary?
|
176
|
+
binary_cast_type = cast_type.serialized? ? cast_type.subtype : cast_type
|
177
|
+
binary_cast_type.deserialize(value)
|
178
|
+
else
|
179
|
+
value
|
180
|
+
end
|
181
|
+
end
|
173
182
|
end
|
174
183
|
end
|
175
184
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "openssl"
|
4
|
-
require "zlib"
|
5
4
|
require "active_support/core_ext/numeric"
|
6
5
|
|
7
6
|
module ActiveRecord
|
@@ -12,12 +11,20 @@ module ActiveRecord
|
|
12
11
|
# It interacts with a KeyProvider for getting the keys, and delegate to
|
13
12
|
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
14
13
|
class Encryptor
|
14
|
+
# The compressor to use for compressing the payload
|
15
|
+
attr_reader :compressor
|
16
|
+
|
15
17
|
# === Options
|
16
18
|
#
|
17
19
|
# * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
|
18
20
|
# Defaults to +true+.
|
19
|
-
|
21
|
+
# * <tt>:compressor</tt> - The compressor to use.
|
22
|
+
# 1. If compressor is provided, it will be used.
|
23
|
+
# 2. If not, it will use ActiveRecord::Encryption.config.compressor which default value is +Zlib+.
|
24
|
+
# If you want to use a custom compressor, it must respond to +deflate+ and +inflate+.
|
25
|
+
def initialize(compress: true, compressor: nil)
|
20
26
|
@compress = compress
|
27
|
+
@compressor = compressor || ActiveRecord::Encryption.config.compressor
|
21
28
|
end
|
22
29
|
|
23
30
|
# Encrypts +clean_text+ and returns the encrypted result
|
@@ -78,6 +85,10 @@ module ActiveRecord
|
|
78
85
|
serializer.binary?
|
79
86
|
end
|
80
87
|
|
88
|
+
def compress? # :nodoc:
|
89
|
+
@compress
|
90
|
+
end
|
91
|
+
|
81
92
|
private
|
82
93
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
83
94
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
@@ -130,12 +141,8 @@ module ActiveRecord
|
|
130
141
|
end
|
131
142
|
end
|
132
143
|
|
133
|
-
def compress?
|
134
|
-
@compress
|
135
|
-
end
|
136
|
-
|
137
144
|
def compress(data)
|
138
|
-
|
145
|
+
@compressor.deflate(data).tap do |compressed_data|
|
139
146
|
compressed_data.force_encoding(data.encoding)
|
140
147
|
end
|
141
148
|
end
|
@@ -149,7 +156,7 @@ module ActiveRecord
|
|
149
156
|
end
|
150
157
|
|
151
158
|
def uncompress(data)
|
152
|
-
|
159
|
+
@compressor.inflate(data).tap do |uncompressed_data|
|
153
160
|
uncompressed_data.force_encoding(data.encoding)
|
154
161
|
end
|
155
162
|
end
|
@@ -41,6 +41,8 @@ module ActiveRecord
|
|
41
41
|
module EncryptedQuery # :nodoc:
|
42
42
|
class << self
|
43
43
|
def process_arguments(owner, args, check_for_additional_values)
|
44
|
+
owner = owner.model if owner.is_a?(Relation)
|
45
|
+
|
44
46
|
return args if owner.deterministic_encrypted_attributes&.empty?
|
45
47
|
|
46
48
|
if args.is_a?(Array) && (options = args.first).is_a?(Hash)
|
@@ -102,12 +104,12 @@ module ActiveRecord
|
|
102
104
|
end
|
103
105
|
|
104
106
|
def scope_for_create
|
105
|
-
return super unless
|
107
|
+
return super unless model.deterministic_encrypted_attributes&.any?
|
106
108
|
|
107
109
|
scope_attributes = super
|
108
110
|
wheres = where_values_hash
|
109
111
|
|
110
|
-
|
112
|
+
model.deterministic_encrypted_attributes.each do |attribute_name|
|
111
113
|
attribute_name = attribute_name.to_s
|
112
114
|
values = wheres[attribute_name]
|
113
115
|
if values.is_a?(Array) && values[1..].all?(AdditionalValue)
|