activerecord 7.2.3.1 → 8.0.5.1
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 +475 -946
- 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/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- 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 +8 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- 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 +37 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -74
- 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/column.rb +4 -2
- 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 +53 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- 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 +27 -2
- 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 +15 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +3 -3
- 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 +32 -12
- 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/array_handler.rb +3 -1
- 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/where_clause.rb +1 -1
- data/lib/active_record/relation.rb +80 -62
- 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/type/serialized.rb +5 -0
- 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/predications.rb +1 -3
- data/lib/arel/table.rb +3 -7
- metadata +10 -11
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -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
|
|
@@ -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,13 +11,22 @@ 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
|
# [+:compress+]
|
|
18
20
|
# Boolean indicating whether records should be compressed before
|
|
19
21
|
# encryption. Defaults to +true+.
|
|
20
|
-
|
|
22
|
+
#
|
|
23
|
+
# [+:compressor+]
|
|
24
|
+
# The compressor to use. It must respond to +deflate+ and +inflate+.
|
|
25
|
+
# If not provided, will default to +ActiveRecord::Encryption.config.compressor+,
|
|
26
|
+
# which itself defaults to +Zlib+.
|
|
27
|
+
def initialize(compress: true, compressor: nil)
|
|
21
28
|
@compress = compress
|
|
29
|
+
@compressor = compressor || ActiveRecord::Encryption.config.compressor
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
# Encrypts +clean_text+ and returns the encrypted result.
|
|
@@ -79,6 +87,10 @@ module ActiveRecord
|
|
|
79
87
|
serializer.binary?
|
|
80
88
|
end
|
|
81
89
|
|
|
90
|
+
def compress? # :nodoc:
|
|
91
|
+
@compress
|
|
92
|
+
end
|
|
93
|
+
|
|
82
94
|
private
|
|
83
95
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
|
84
96
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
|
@@ -131,12 +143,8 @@ module ActiveRecord
|
|
|
131
143
|
end
|
|
132
144
|
end
|
|
133
145
|
|
|
134
|
-
def compress?
|
|
135
|
-
@compress
|
|
136
|
-
end
|
|
137
|
-
|
|
138
146
|
def compress(data)
|
|
139
|
-
|
|
147
|
+
@compressor.deflate(data).tap do |compressed_data|
|
|
140
148
|
compressed_data.force_encoding(data.encoding)
|
|
141
149
|
end
|
|
142
150
|
end
|
|
@@ -150,7 +158,7 @@ module ActiveRecord
|
|
|
150
158
|
end
|
|
151
159
|
|
|
152
160
|
def uncompress(data)
|
|
153
|
-
|
|
161
|
+
@compressor.inflate(data).tap do |uncompressed_data|
|
|
154
162
|
uncompressed_data.force_encoding(data.encoding)
|
|
155
163
|
end
|
|
156
164
|
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)
|
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
|
11
11
|
attr_accessor :previous_schemes
|
|
12
12
|
|
|
13
13
|
def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
|
|
14
|
-
previous_schemes: nil, **context_properties)
|
|
14
|
+
previous_schemes: nil, compress: true, compressor: nil, **context_properties)
|
|
15
15
|
# Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
|
|
16
16
|
# can merge schemes without overriding values with defaults. See +#merge+
|
|
17
17
|
|
|
@@ -24,8 +24,13 @@ module ActiveRecord
|
|
|
24
24
|
@previous_schemes_param = previous_schemes
|
|
25
25
|
@previous_schemes = Array.wrap(previous_schemes)
|
|
26
26
|
@context_properties = context_properties
|
|
27
|
+
@compress = compress
|
|
28
|
+
@compressor = compressor
|
|
27
29
|
|
|
28
30
|
validate_config!
|
|
31
|
+
|
|
32
|
+
@context_properties[:encryptor] = Encryptor.new(compress: @compress) unless @compress
|
|
33
|
+
@context_properties[:encryptor] = Encryptor.new(compressor: compressor) if compressor
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def ignore_case?
|
|
@@ -78,6 +83,8 @@ module ActiveRecord
|
|
|
78
83
|
def validate_config!
|
|
79
84
|
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
|
|
80
85
|
raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
|
|
86
|
+
raise Errors::Configuration, "compressor: can't be used with compress: false" if !@compress && @compressor
|
|
87
|
+
raise Errors::Configuration, "compressor: can't be used with encryptor" if @compressor && @context_properties[:encryptor]
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def key_provider_from_key
|
data/lib/active_record/enum.rb
CHANGED
|
@@ -214,34 +214,16 @@ module ActiveRecord
|
|
|
214
214
|
attr_reader :name, :mapping
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
def enum(name
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return _enum(name, values, **options)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
|
|
224
|
-
options.transform_keys! { |key| :"#{key[1..-1]}" }
|
|
225
|
-
|
|
226
|
-
definitions.each { |name, values| _enum(name, values, **options) }
|
|
227
|
-
|
|
228
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
|
229
|
-
Defining enums with keyword arguments is deprecated and will be removed
|
|
230
|
-
in Rails 8.0. Positional arguments should be used instead:
|
|
231
|
-
|
|
232
|
-
#{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
|
|
233
|
-
MSG
|
|
217
|
+
def enum(name, values = nil, **options)
|
|
218
|
+
values, options = options, {} unless values
|
|
219
|
+
_enum(name, values, **options)
|
|
234
220
|
end
|
|
235
221
|
|
|
236
222
|
private
|
|
237
|
-
def inherited(base)
|
|
238
|
-
base.defined_enums = defined_enums.deep_dup
|
|
239
|
-
super
|
|
240
|
-
end
|
|
241
|
-
|
|
242
223
|
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
|
|
243
224
|
assert_valid_enum_definition_values(values)
|
|
244
225
|
assert_valid_enum_options(options)
|
|
226
|
+
|
|
245
227
|
# statuses = { }
|
|
246
228
|
enum_values = ActiveSupport::HashWithIndifferentAccess.new
|
|
247
229
|
name = name.to_s
|
|
@@ -305,6 +287,11 @@ module ActiveRecord
|
|
|
305
287
|
enum_values.freeze
|
|
306
288
|
end
|
|
307
289
|
|
|
290
|
+
def inherited(base)
|
|
291
|
+
base.defined_enums = defined_enums.deep_dup
|
|
292
|
+
super
|
|
293
|
+
end
|
|
294
|
+
|
|
308
295
|
class EnumMethods < Module # :nodoc:
|
|
309
296
|
def initialize(klass)
|
|
310
297
|
@klass = klass
|
data/lib/active_record/errors.rb
CHANGED
|
@@ -84,6 +84,19 @@ module ActiveRecord
|
|
|
84
84
|
class ConnectionTimeoutError < ConnectionNotEstablished
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
# Raised when a database connection pool is requested but
|
|
88
|
+
# has not been defined.
|
|
89
|
+
class ConnectionNotDefined < ConnectionNotEstablished
|
|
90
|
+
def initialize(message = nil, connection_name: nil, role: nil, shard: nil)
|
|
91
|
+
super(message)
|
|
92
|
+
@connection_name = connection_name
|
|
93
|
+
@role = role
|
|
94
|
+
@shard = shard
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
attr_reader :connection_name, :role, :shard
|
|
98
|
+
end
|
|
99
|
+
|
|
87
100
|
# Raised when connection to the database could not been established because it was not
|
|
88
101
|
# able to connect to the host or when the authorization failed.
|
|
89
102
|
class DatabaseConnectionError < ConnectionNotEstablished
|
|
@@ -484,11 +497,6 @@ module ActiveRecord
|
|
|
484
497
|
# relation.limit!(5) # => ActiveRecord::UnmodifiableRelation
|
|
485
498
|
class UnmodifiableRelation < ActiveRecordError
|
|
486
499
|
end
|
|
487
|
-
deprecate_constant(
|
|
488
|
-
:ImmutableRelation,
|
|
489
|
-
"ActiveRecord::UnmodifiableRelation",
|
|
490
|
-
deprecator: ActiveRecord.deprecator
|
|
491
|
-
)
|
|
492
500
|
|
|
493
501
|
# TransactionIsolationError will be raised under the following conditions:
|
|
494
502
|
#
|
|
@@ -100,17 +100,23 @@ module ActiveRecord
|
|
|
100
100
|
def execute_or_skip
|
|
101
101
|
return unless pending?
|
|
102
102
|
|
|
103
|
-
@
|
|
104
|
-
return unless
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
@session.synchronize do
|
|
104
|
+
return unless pending?
|
|
105
|
+
|
|
106
|
+
@pool.with_connection do |connection|
|
|
107
|
+
return unless @mutex.try_lock
|
|
108
|
+
previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
|
|
109
|
+
begin
|
|
110
|
+
if pending?
|
|
111
|
+
@event_buffer = EventBuffer.new(self, @instrumenter)
|
|
112
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
|
|
113
|
+
|
|
109
114
|
execute_query(connection, async: true)
|
|
110
115
|
end
|
|
116
|
+
ensure
|
|
117
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
|
|
118
|
+
@mutex.unlock
|
|
111
119
|
end
|
|
112
|
-
ensure
|
|
113
|
-
@mutex.unlock
|
|
114
120
|
end
|
|
115
121
|
end
|
|
116
122
|
end
|
|
@@ -163,7 +169,7 @@ module ActiveRecord
|
|
|
163
169
|
end
|
|
164
170
|
|
|
165
171
|
def exec_query(connection, *args, **kwargs)
|
|
166
|
-
connection.
|
|
172
|
+
connection.raw_exec_query(*args, **kwargs)
|
|
167
173
|
end
|
|
168
174
|
|
|
169
175
|
class SelectAll < FutureResult # :nodoc:
|
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
|
39
39
|
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
|
40
40
|
@returning = false if @returning == []
|
|
41
41
|
|
|
42
|
-
@unique_by = find_unique_index_for(@unique_by)
|
|
42
|
+
@unique_by = find_unique_index_for(@unique_by) if @on_duplicate != :raise
|
|
43
43
|
|
|
44
44
|
configure_on_duplicate_update_logic
|
|
45
45
|
ensure_valid_options_for_connection!
|
|
@@ -48,7 +48,7 @@ module ActiveRecord
|
|
|
48
48
|
def execute
|
|
49
49
|
return ActiveRecord::Result.empty if inserts.empty?
|
|
50
50
|
|
|
51
|
-
message = +"#{model} "
|
|
51
|
+
message = +"#{model.name} "
|
|
52
52
|
message << "Bulk " if inserts.many?
|
|
53
53
|
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
|
54
54
|
connection.exec_insert_all to_sql, message
|
|
@@ -240,7 +240,7 @@ module ActiveRecord
|
|
|
240
240
|
|
|
241
241
|
values_list = insert_all.map_key_with_value do |key, value|
|
|
242
242
|
next value if Arel::Nodes::SqlLiteral === value
|
|
243
|
-
|
|
243
|
+
ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
|
|
244
244
|
end
|
|
245
245
|
|
|
246
246
|
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
|
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
|
9
9
|
# it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
|
|
10
10
|
# and the update is ignored.
|
|
11
11
|
#
|
|
12
|
-
# Check out
|
|
12
|
+
# Check out ActiveRecord::Locking::Pessimistic for an alternative.
|
|
13
13
|
#
|
|
14
14
|
# == Usage
|
|
15
15
|
#
|
|
@@ -126,18 +126,12 @@ module ActiveRecord
|
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return frame if frame
|
|
134
|
-
end
|
|
135
|
-
nil
|
|
136
|
-
end
|
|
137
|
-
else
|
|
138
|
-
def query_source_location
|
|
139
|
-
backtrace_cleaner.clean(caller(1).lazy).first
|
|
129
|
+
def query_source_location
|
|
130
|
+
Thread.each_caller_location do |location|
|
|
131
|
+
frame = backtrace_cleaner.clean_frame(location)
|
|
132
|
+
return frame if frame
|
|
140
133
|
end
|
|
134
|
+
nil
|
|
141
135
|
end
|
|
142
136
|
|
|
143
137
|
def filter(name, value)
|