activerecord 7.0.0.alpha2 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +539 -11
- data/lib/active_record/associations/association.rb +2 -8
- data/lib/active_record/associations/builder/collection_association.rb +9 -2
- data/lib/active_record/associations/collection_association.rb +10 -2
- data/lib/active_record/associations/join_dependency.rb +6 -2
- data/lib/active_record/associations/preloader/association.rb +68 -48
- data/lib/active_record/associations/preloader/batch.rb +3 -6
- data/lib/active_record/associations/preloader/through_association.rb +19 -9
- data/lib/active_record/associations/preloader.rb +14 -24
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/associations.rb +16 -3
- data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
- data/lib/active_record/attribute_methods/dirty.rb +9 -1
- data/lib/active_record/attribute_methods.rb +7 -5
- data/lib/active_record/autosave_association.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
- data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/column.rb +4 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/pool_config.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
- data/lib/active_record/connection_handling.rb +31 -19
- data/lib/active_record/core.rb +13 -24
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +0 -9
- data/lib/active_record/database_configurations/hash_config.rb +40 -8
- data/lib/active_record/database_configurations.rb +2 -27
- data/lib/active_record/delegated_type.rb +19 -0
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
- data/lib/active_record/encryption/message_serializer.rb +11 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +8 -1
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/fixture_set/table_row.rb +1 -1
- data/lib/active_record/fixtures.rb +1 -9
- data/lib/active_record/future_result.rb +2 -2
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +52 -15
- data/lib/active_record/integration.rb +3 -2
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/pessimistic.rb +9 -3
- data/lib/active_record/log_subscriber.rb +8 -1
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration.rb +2 -2
- data/lib/active_record/model_schema.rb +1 -28
- data/lib/active_record/nested_attributes.rb +11 -10
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +99 -21
- data/lib/active_record/query_logs.rb +18 -83
- data/lib/active_record/railtie.rb +11 -1
- data/lib/active_record/railties/databases.rake +4 -91
- data/lib/active_record/reflection.rb +22 -6
- data/lib/active_record/relation/calculations.rb +1 -10
- data/lib/active_record/relation/finder_methods.rb +0 -13
- data/lib/active_record/relation/query_methods.rb +5 -14
- data/lib/active_record/relation/record_fetch_warning.rb +5 -7
- data/lib/active_record/relation/where_clause.rb +2 -15
- data/lib/active_record/relation.rb +11 -15
- data/lib/active_record/result.rb +0 -5
- data/lib/active_record/runtime_registry.rb +10 -12
- data/lib/active_record/schema_dumper.rb +7 -0
- data/lib/active_record/schema_migration.rb +4 -0
- data/lib/active_record/scoping.rb +34 -22
- data/lib/active_record/suppressor.rb +11 -15
- data/lib/active_record/tasks/database_tasks.rb +18 -44
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record.rb +41 -33
- data/lib/arel/crud.rb +12 -2
- data/lib/arel/delete_manager.rb +16 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/nodes/delete_statement.rb +5 -1
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/update_statement.rb +5 -1
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/predications.rb +10 -2
- data/lib/arel/update_manager.rb +16 -0
- data/lib/arel/visitors/mysql.rb +2 -1
- data/lib/arel/visitors/to_sql.rb +15 -0
- data/lib/arel.rb +1 -0
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +18 -12
data/lib/active_record/core.rb
CHANGED
@@ -77,6 +77,8 @@ module ActiveRecord
|
|
77
77
|
|
78
78
|
class_attribute :default_shard, instance_writer: false
|
79
79
|
|
80
|
+
class_attribute :shard_selector, instance_accessor: false, default: nil
|
81
|
+
|
80
82
|
def self.application_record_class? # :nodoc:
|
81
83
|
if ActiveRecord.application_record_class
|
82
84
|
self == ActiveRecord.application_record_class
|
@@ -90,11 +92,11 @@ module ActiveRecord
|
|
90
92
|
self.filter_attributes = []
|
91
93
|
|
92
94
|
def self.connection_handler
|
93
|
-
|
95
|
+
ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler
|
94
96
|
end
|
95
97
|
|
96
98
|
def self.connection_handler=(handler)
|
97
|
-
|
99
|
+
ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
|
98
100
|
end
|
99
101
|
|
100
102
|
def self.connection_handlers
|
@@ -129,8 +131,8 @@ module ActiveRecord
|
|
129
131
|
end
|
130
132
|
|
131
133
|
def self.asynchronous_queries_tracker # :nodoc:
|
132
|
-
|
133
|
-
|
134
|
+
ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \
|
135
|
+
AsynchronousQueriesTracker.new
|
134
136
|
end
|
135
137
|
|
136
138
|
# Returns the symbol representing the current connected role.
|
@@ -148,7 +150,7 @@ module ActiveRecord
|
|
148
150
|
else
|
149
151
|
connected_to_stack.reverse_each do |hash|
|
150
152
|
return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
|
151
|
-
return hash[:role] if hash[:role] && hash[:klasses].include?(
|
153
|
+
return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
|
152
154
|
end
|
153
155
|
|
154
156
|
default_role
|
@@ -167,7 +169,7 @@ module ActiveRecord
|
|
167
169
|
def self.current_shard
|
168
170
|
connected_to_stack.reverse_each do |hash|
|
169
171
|
return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
|
170
|
-
return hash[:shard] if hash[:shard] && hash[:klasses].include?(
|
172
|
+
return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
|
171
173
|
end
|
172
174
|
|
173
175
|
default_shard
|
@@ -189,7 +191,7 @@ module ActiveRecord
|
|
189
191
|
else
|
190
192
|
connected_to_stack.reverse_each do |hash|
|
191
193
|
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
|
192
|
-
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(
|
194
|
+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
|
193
195
|
end
|
194
196
|
|
195
197
|
false
|
@@ -197,11 +199,11 @@ module ActiveRecord
|
|
197
199
|
end
|
198
200
|
|
199
201
|
def self.connected_to_stack # :nodoc:
|
200
|
-
if connected_to_stack =
|
202
|
+
if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
|
201
203
|
connected_to_stack
|
202
204
|
else
|
203
205
|
connected_to_stack = Concurrent::Array.new
|
204
|
-
|
206
|
+
ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack
|
205
207
|
connected_to_stack
|
206
208
|
end
|
207
209
|
end
|
@@ -218,7 +220,7 @@ module ActiveRecord
|
|
218
220
|
self.connection_class
|
219
221
|
end
|
220
222
|
|
221
|
-
def self.
|
223
|
+
def self.connection_class_for_self # :nodoc:
|
222
224
|
klass = self
|
223
225
|
|
224
226
|
until klass == Base
|
@@ -229,14 +231,6 @@ module ActiveRecord
|
|
229
231
|
klass
|
230
232
|
end
|
231
233
|
|
232
|
-
def self.allow_unsafe_raw_sql # :nodoc:
|
233
|
-
ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 7.0")
|
234
|
-
end
|
235
|
-
|
236
|
-
def self.allow_unsafe_raw_sql=(value) # :nodoc:
|
237
|
-
ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 7.0")
|
238
|
-
end
|
239
|
-
|
240
234
|
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
|
241
235
|
self.default_role = ActiveRecord.writing_role
|
242
236
|
self.default_shard = :default
|
@@ -427,11 +421,6 @@ module ActiveRecord
|
|
427
421
|
@arel_table ||= Arel::Table.new(table_name, klass: self)
|
428
422
|
end
|
429
423
|
|
430
|
-
def arel_attribute(name, table = arel_table) # :nodoc:
|
431
|
-
table[name]
|
432
|
-
end
|
433
|
-
deprecate :arel_attribute
|
434
|
-
|
435
424
|
def predicate_builder # :nodoc:
|
436
425
|
@predicate_builder ||= PredicateBuilder.new(table_metadata)
|
437
426
|
end
|
@@ -497,7 +486,7 @@ module ActiveRecord
|
|
497
486
|
# post.init_with(coder)
|
498
487
|
# post.title # => 'hello world'
|
499
488
|
def init_with(coder, &block)
|
500
|
-
coder = LegacyYamlAdapter.convert(
|
489
|
+
coder = LegacyYamlAdapter.convert(coder)
|
501
490
|
attributes = self.class.yaml_encoder.decode(coder)
|
502
491
|
init_with_attributes(attributes, coder["new_record"], &block)
|
503
492
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "uri"
|
3
4
|
require "active_support/core_ext/enumerable"
|
4
5
|
|
5
6
|
module ActiveRecord
|
@@ -67,7 +68,7 @@ module ActiveRecord
|
|
67
68
|
database: uri.opaque
|
68
69
|
)
|
69
70
|
else
|
70
|
-
query_hash.
|
71
|
+
query_hash.reverse_merge(
|
71
72
|
adapter: @adapter,
|
72
73
|
username: uri.user,
|
73
74
|
password: uri.password,
|
@@ -32,11 +32,6 @@ module ActiveRecord
|
|
32
32
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
33
33
|
end
|
34
34
|
|
35
|
-
def config
|
36
|
-
ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
|
37
|
-
configuration_hash.stringify_keys
|
38
|
-
end
|
39
|
-
|
40
35
|
# Determines whether a database configuration is for a replica / readonly
|
41
36
|
# connection. If the +replica+ key is present in the config, +replica?+ will
|
42
37
|
# return +true+.
|
@@ -109,14 +104,51 @@ module ActiveRecord
|
|
109
104
|
configuration_hash[:schema_cache_path]
|
110
105
|
end
|
111
106
|
|
112
|
-
|
113
|
-
|
114
|
-
|
107
|
+
def default_schema_cache_path
|
108
|
+
"db/schema_cache.yml"
|
109
|
+
end
|
110
|
+
|
111
|
+
def lazy_schema_cache_path
|
112
|
+
schema_cache_path || default_schema_cache_path
|
113
|
+
end
|
114
|
+
|
115
|
+
def primary? # :nodoc:
|
116
|
+
Base.configurations.primary?(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Determines whether to dump the schema/structure files and the
|
120
|
+
# filename that should be used.
|
121
|
+
#
|
122
|
+
# If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
|
123
|
+
# the schema will not be dumped.
|
124
|
+
#
|
125
|
+
# If the config option is set that will be used. Otherwise Rails
|
126
|
+
# will generate the filename from the database config name.
|
127
|
+
def schema_dump(format = ActiveRecord.schema_format)
|
128
|
+
if configuration_hash.key?(:schema_dump)
|
129
|
+
if config = configuration_hash[:schema_dump]
|
130
|
+
config
|
131
|
+
end
|
132
|
+
elsif primary?
|
133
|
+
schema_file_type(format)
|
134
|
+
else
|
135
|
+
"#{name}_#{schema_file_type(format)}"
|
136
|
+
end
|
115
137
|
end
|
116
138
|
|
117
139
|
def database_tasks? # :nodoc:
|
118
140
|
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
119
141
|
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def schema_file_type(format)
|
145
|
+
case format
|
146
|
+
when :ruby
|
147
|
+
"schema.rb"
|
148
|
+
when :sql
|
149
|
+
"structure.sql"
|
150
|
+
end
|
151
|
+
end
|
120
152
|
end
|
121
153
|
end
|
122
154
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "uri"
|
3
4
|
require "active_record/database_configurations/database_config"
|
4
5
|
require "active_record/database_configurations/hash_config"
|
5
6
|
require "active_record/database_configurations/url_config"
|
@@ -41,12 +42,7 @@ module ActiveRecord
|
|
41
42
|
# hidden by +database_tasks: false+ in the returned list. Most of the time we're only
|
42
43
|
# iterating over the primary connections (i.e. migrations don't need to run for the
|
43
44
|
# write and read connection). Defaults to +false+.
|
44
|
-
def configs_for(env_name: nil,
|
45
|
-
if spec_name
|
46
|
-
name = spec_name
|
47
|
-
ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0")
|
48
|
-
end
|
49
|
-
|
45
|
+
def configs_for(env_name: nil, name: nil, include_replicas: false, include_hidden: false)
|
50
46
|
if include_replicas
|
51
47
|
include_hidden = include_replicas
|
52
48
|
ActiveSupport::Deprecation.warn("The kwarg `include_replicas` is deprecated in favor of `include_hidden`. When `include_hidden` is passed, configurations with `replica: true` or `database_tasks: false` will be returned. `include_replicas` will be removed in Rails 7.1.")
|
@@ -70,19 +66,6 @@ module ActiveRecord
|
|
70
66
|
end
|
71
67
|
end
|
72
68
|
|
73
|
-
# Returns the config hash that corresponds with the environment
|
74
|
-
#
|
75
|
-
# If the application has multiple databases +default_hash+ will
|
76
|
-
# return the first config hash for the environment.
|
77
|
-
#
|
78
|
-
# { database: "my_db", adapter: "mysql2" }
|
79
|
-
def default_hash(env = default_env)
|
80
|
-
default = find_db_config(env)
|
81
|
-
default.configuration_hash if default
|
82
|
-
end
|
83
|
-
alias :[] :default_hash
|
84
|
-
deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
|
85
|
-
|
86
69
|
# Returns a single DatabaseConfig object based on the requested environment.
|
87
70
|
#
|
88
71
|
# If the application has multiple databases +find_db_config+ will return
|
@@ -109,14 +92,6 @@ module ActiveRecord
|
|
109
92
|
first_config && name == first_config.name
|
110
93
|
end
|
111
94
|
|
112
|
-
# Returns the DatabaseConfigurations object as a Hash.
|
113
|
-
def to_h
|
114
|
-
configurations.inject({}) do |memo, db_config|
|
115
|
-
memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
|
119
|
-
|
120
95
|
# Checks if the application's configurations are empty.
|
121
96
|
#
|
122
97
|
# Aliased to blank?
|
@@ -137,6 +137,21 @@ module ActiveRecord
|
|
137
137
|
# end
|
138
138
|
#
|
139
139
|
# Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
|
140
|
+
#
|
141
|
+
# == Nested Attributes
|
142
|
+
#
|
143
|
+
# Enabling nested attributes on a delegated_type association allows you to
|
144
|
+
# create the entry and message in one go:
|
145
|
+
#
|
146
|
+
# class Entry < ApplicationRecord
|
147
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
148
|
+
# accepts_nested_attributes_for :entryable
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } }
|
152
|
+
# entry = Entry.create(params[:entry])
|
153
|
+
# entry.entryable.id # => 2
|
154
|
+
# entry.entryable.subject # => 'Smiling'
|
140
155
|
module DelegatedType
|
141
156
|
# Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
|
142
157
|
# That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
|
@@ -207,6 +222,10 @@ module ActiveRecord
|
|
207
222
|
public_send("#{role}_class").model_name.singular.inquiry
|
208
223
|
end
|
209
224
|
|
225
|
+
define_method "build_#{role}" do |*params|
|
226
|
+
public_send("#{role}=", public_send("#{role}_class").new(*params))
|
227
|
+
end
|
228
|
+
|
210
229
|
types.each do |type|
|
211
230
|
scope_name = type.tableize.tr("/", "_")
|
212
231
|
singular = scope_name.singularize
|
@@ -37,7 +37,7 @@ module ActiveRecord
|
|
37
37
|
# in preserving it.
|
38
38
|
# * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
|
39
39
|
# designated column +original_<name>+. When reading the encrypted content, the version with the original case is
|
40
|
-
#
|
40
|
+
# served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
|
41
41
|
# is true.
|
42
42
|
# * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
|
43
43
|
# encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
|
18
18
|
# === Options
|
19
19
|
#
|
20
|
-
# * <tt>:scheme</tt> -
|
20
|
+
# * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
|
21
21
|
# * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
|
22
22
|
# (after decrypting). +ActiveModel::Type::String+ by default.
|
23
23
|
def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false)
|
@@ -12,7 +12,7 @@ module ActiveRecord
|
|
12
12
|
super(record, attribute, value)
|
13
13
|
|
14
14
|
klass = record.class
|
15
|
-
|
15
|
+
klass.deterministic_encrypted_attributes&.each do |attribute_name|
|
16
16
|
encrypted_type = klass.type_for_attribute(attribute_name)
|
17
17
|
[ encrypted_type, *encrypted_type.previous_types ].each do |type|
|
18
18
|
encrypted_value = type.serialize(value)
|
@@ -21,7 +21,6 @@ module ActiveRecord
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
|
-
end
|
25
24
|
end
|
26
25
|
end
|
27
26
|
end
|
@@ -33,10 +33,20 @@ module ActiveRecord
|
|
33
33
|
|
34
34
|
private
|
35
35
|
def parse_message(data, level)
|
36
|
-
|
36
|
+
validate_message_data_format(data, level)
|
37
37
|
ActiveRecord::Encryption::Message.new(payload: decode_if_needed(data["p"]), headers: parse_properties(data["h"], level))
|
38
38
|
end
|
39
39
|
|
40
|
+
def validate_message_data_format(data, level)
|
41
|
+
if level > 2
|
42
|
+
raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
43
|
+
end
|
44
|
+
|
45
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
46
|
+
raise ActiveRecord::Encryption::Errors::Decryption, "Invalid data format: hash without payload"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
40
50
|
def parse_properties(headers, level)
|
41
51
|
ActiveRecord::Encryption::Properties.new.tap do |properties|
|
42
52
|
headers&.each do |key, value|
|
@@ -82,7 +82,7 @@ module ActiveRecord
|
|
82
82
|
|
83
83
|
def validate_credential(key, error_message = "is not configured")
|
84
84
|
unless ActiveRecord::Encryption.config.public_send(key).present?
|
85
|
-
raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential"\
|
85
|
+
raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential "\
|
86
86
|
"active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
|
87
87
|
end
|
88
88
|
end
|
data/lib/active_record/enum.rb
CHANGED
@@ -57,13 +57,20 @@ module ActiveRecord
|
|
57
57
|
# conversation = Conversation.new
|
58
58
|
# conversation.status # => "active"
|
59
59
|
#
|
60
|
-
#
|
60
|
+
# It's possible to explicitly map the relation between attribute and
|
61
61
|
# database integer with a hash:
|
62
62
|
#
|
63
63
|
# class Conversation < ActiveRecord::Base
|
64
64
|
# enum :status, active: 0, archived: 1
|
65
65
|
# end
|
66
66
|
#
|
67
|
+
# Finally it's also possible to use a string column to persist the enumerated value.
|
68
|
+
# Note that this will likely lead to slower database queries:
|
69
|
+
#
|
70
|
+
# class Conversation < ActiveRecord::Base
|
71
|
+
# enum :status, active: "active", archived: "archived"
|
72
|
+
# end
|
73
|
+
#
|
67
74
|
# Note that when an array is used, the implicit mapping from the values to database
|
68
75
|
# integers is derived from the order the values appear in the array. In the example,
|
69
76
|
# <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
|
data/lib/active_record/errors.rb
CHANGED
@@ -326,7 +326,7 @@ module ActiveRecord
|
|
326
326
|
# # The system must fail on Friday so that our support department
|
327
327
|
# # won't be out of job. We silently rollback this transaction
|
328
328
|
# # without telling the user.
|
329
|
-
# raise ActiveRecord::Rollback
|
329
|
+
# raise ActiveRecord::Rollback
|
330
330
|
# end
|
331
331
|
# end
|
332
332
|
# # ActiveRecord::Rollback is the only exception that won't be passed on
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
# This is a thread locals registry for EXPLAIN. For example
|
@@ -8,13 +8,18 @@ module ActiveRecord
|
|
8
8
|
# ActiveRecord::ExplainRegistry.queries
|
9
9
|
#
|
10
10
|
# returns the collected queries local to the current thread.
|
11
|
-
#
|
12
|
-
# See the documentation of ActiveSupport::PerThreadRegistry
|
13
|
-
# for further details.
|
14
11
|
class ExplainRegistry # :nodoc:
|
15
|
-
|
12
|
+
class << self
|
13
|
+
delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
|
14
|
+
|
15
|
+
private
|
16
|
+
def instance
|
17
|
+
ActiveSupport::IsolatedExecutionState[:active_record_explain_registry] ||= new
|
18
|
+
end
|
19
|
+
end
|
16
20
|
|
17
|
-
attr_accessor :
|
21
|
+
attr_accessor :collect
|
22
|
+
attr_reader :queries
|
18
23
|
|
19
24
|
def initialize
|
20
25
|
reset
|
@@ -126,7 +126,7 @@ module ActiveRecord
|
|
126
126
|
end
|
127
127
|
|
128
128
|
def resolve_enums
|
129
|
-
|
129
|
+
reflection_class.defined_enums.each do |name, values|
|
130
130
|
if @row.include?(name)
|
131
131
|
@row[name] = values.fetch(@row[name], @row[name])
|
132
132
|
end
|
@@ -407,7 +407,7 @@ module ActiveRecord
|
|
407
407
|
# defaults:
|
408
408
|
#
|
409
409
|
# DEFAULTS: &DEFAULTS
|
410
|
-
# created_on: <%= 3.weeks.ago.
|
410
|
+
# created_on: <%= 3.weeks.ago.to_formatted_s(:db) %>
|
411
411
|
#
|
412
412
|
# first:
|
413
413
|
# name: Smurf
|
@@ -585,14 +585,6 @@ module ActiveRecord
|
|
585
585
|
end
|
586
586
|
end
|
587
587
|
|
588
|
-
def signed_global_id(fixture_set_name, label, column_type: :integer, **options)
|
589
|
-
identifier = identify(label, column_type)
|
590
|
-
model_name = default_fixture_model_name(fixture_set_name)
|
591
|
-
uri = URI::GID.build([GlobalID.app, model_name, identifier, {}])
|
592
|
-
|
593
|
-
SignedGlobalID.new(uri, **options)
|
594
|
-
end
|
595
|
-
|
596
588
|
# Superclass for the evaluation contexts used by ERB fixtures.
|
597
589
|
def context_class
|
598
590
|
@context_class ||= Class.new
|
@@ -102,12 +102,12 @@ module ActiveRecord
|
|
102
102
|
|
103
103
|
def execute_or_wait
|
104
104
|
if pending?
|
105
|
-
start =
|
105
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
106
106
|
@mutex.synchronize do
|
107
107
|
if pending?
|
108
108
|
execute_query(@pool.connection)
|
109
109
|
else
|
110
|
-
@lock_wait = (
|
110
|
+
@lock_wait = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start)
|
111
111
|
end
|
112
112
|
end
|
113
113
|
else
|
@@ -5,21 +5,19 @@ require "active_support/core_ext/enumerable"
|
|
5
5
|
module ActiveRecord
|
6
6
|
class InsertAll # :nodoc:
|
7
7
|
attr_reader :model, :connection, :inserts, :keys
|
8
|
-
attr_reader :on_duplicate, :returning, :unique_by, :update_sql
|
8
|
+
attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
|
9
9
|
|
10
|
-
def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
|
10
|
+
def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
11
11
|
raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
|
12
12
|
|
13
13
|
@model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
|
14
|
-
@on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
|
14
|
+
@on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
|
15
|
+
@record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
|
15
16
|
|
16
|
-
disallow_raw_sql!(returning)
|
17
17
|
disallow_raw_sql!(on_duplicate)
|
18
|
+
disallow_raw_sql!(returning)
|
18
19
|
|
19
|
-
|
20
|
-
@update_sql = on_duplicate
|
21
|
-
@on_duplicate = :update
|
22
|
-
end
|
20
|
+
configure_on_duplicate_update_logic
|
23
21
|
|
24
22
|
if model.scope_attributes?
|
25
23
|
@scope_attributes = model.scope_attributes
|
@@ -44,7 +42,7 @@ module ActiveRecord
|
|
44
42
|
end
|
45
43
|
|
46
44
|
def updatable_columns
|
47
|
-
keys - readonly_columns - unique_by_columns
|
45
|
+
@updatable_columns ||= keys - readonly_columns - unique_by_columns
|
48
46
|
end
|
49
47
|
|
50
48
|
def primary_keys
|
@@ -64,18 +62,50 @@ module ActiveRecord
|
|
64
62
|
inserts.map do |attributes|
|
65
63
|
attributes = attributes.stringify_keys
|
66
64
|
attributes.merge!(scope_attributes) if scope_attributes
|
65
|
+
attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
|
67
66
|
|
68
67
|
verify_attributes(attributes)
|
69
68
|
|
70
|
-
|
69
|
+
keys_including_timestamps.map do |key|
|
71
70
|
yield key, attributes[key]
|
72
71
|
end
|
73
72
|
end
|
74
73
|
end
|
75
74
|
|
75
|
+
def record_timestamps?
|
76
|
+
@record_timestamps
|
77
|
+
end
|
78
|
+
|
79
|
+
# TODO: Consider remaining this method, as it only conditionally extends keys, not always
|
80
|
+
def keys_including_timestamps
|
81
|
+
@keys_including_timestamps ||= if record_timestamps?
|
82
|
+
keys + model.all_timestamp_attributes_in_model
|
83
|
+
else
|
84
|
+
keys
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
76
88
|
private
|
77
89
|
attr_reader :scope_attributes
|
78
90
|
|
91
|
+
def configure_on_duplicate_update_logic
|
92
|
+
if custom_update_sql_provided? && update_only.present?
|
93
|
+
raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
|
94
|
+
end
|
95
|
+
|
96
|
+
if update_only.present?
|
97
|
+
@updatable_columns = Array(update_only)
|
98
|
+
@on_duplicate = :update
|
99
|
+
elsif custom_update_sql_provided?
|
100
|
+
@update_sql = on_duplicate
|
101
|
+
@on_duplicate = :update
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def custom_update_sql_provided?
|
106
|
+
@custom_update_sql_provided ||= Arel.arel_node?(on_duplicate)
|
107
|
+
end
|
108
|
+
|
79
109
|
def find_unique_index_for(unique_by)
|
80
110
|
if !connection.supports_insert_conflict_target?
|
81
111
|
return if unique_by.nil?
|
@@ -134,7 +164,7 @@ module ActiveRecord
|
|
134
164
|
|
135
165
|
|
136
166
|
def verify_attributes(attributes)
|
137
|
-
if
|
167
|
+
if keys_including_timestamps != attributes.keys.to_set
|
138
168
|
raise ArgumentError, "All objects being inserted must have the same keys"
|
139
169
|
end
|
140
170
|
end
|
@@ -148,10 +178,14 @@ module ActiveRecord
|
|
148
178
|
"by wrapping them in Arel.sql()."
|
149
179
|
end
|
150
180
|
|
181
|
+
def timestamps_for_create
|
182
|
+
model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
|
183
|
+
end
|
184
|
+
|
151
185
|
class Builder # :nodoc:
|
152
186
|
attr_reader :model
|
153
187
|
|
154
|
-
delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
|
188
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
|
155
189
|
|
156
190
|
def initialize(insert_all)
|
157
191
|
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
@@ -162,9 +196,10 @@ module ActiveRecord
|
|
162
196
|
end
|
163
197
|
|
164
198
|
def values_list
|
165
|
-
types = extract_types_from_columns_on(model.table_name, keys:
|
199
|
+
types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
|
166
200
|
|
167
201
|
values_list = insert_all.map_key_with_value do |key, value|
|
202
|
+
next value if Arel::Nodes::SqlLiteral === value
|
168
203
|
connection.with_yaml_fallback(types[key].serialize(value))
|
169
204
|
end
|
170
205
|
|
@@ -196,6 +231,8 @@ module ActiveRecord
|
|
196
231
|
end
|
197
232
|
|
198
233
|
def touch_model_timestamps_unless(&block)
|
234
|
+
return "" unless update_duplicates? && record_timestamps?
|
235
|
+
|
199
236
|
model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
|
200
237
|
if touch_timestamp_attribute?(column_name)
|
201
238
|
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END),"
|
@@ -213,11 +250,11 @@ module ActiveRecord
|
|
213
250
|
attr_reader :connection, :insert_all
|
214
251
|
|
215
252
|
def touch_timestamp_attribute?(column_name)
|
216
|
-
|
253
|
+
insert_all.updatable_columns.exclude?(column_name)
|
217
254
|
end
|
218
255
|
|
219
256
|
def columns_list
|
220
|
-
format_columns(insert_all.
|
257
|
+
format_columns(insert_all.keys_including_timestamps)
|
221
258
|
end
|
222
259
|
|
223
260
|
def extract_types_from_columns_on(table_name, keys:)
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
79
79
|
timestamp = max_updated_column_timestamp
|
80
80
|
|
81
81
|
if timestamp
|
82
|
-
timestamp = timestamp.utc.
|
82
|
+
timestamp = timestamp.utc.to_formatted_s(cache_timestamp_format)
|
83
83
|
"#{model_name.cache_key}/#{id}-#{timestamp}"
|
84
84
|
else
|
85
85
|
"#{model_name.cache_key}/#{id}"
|
@@ -101,8 +101,9 @@ module ActiveRecord
|
|
101
101
|
timestamp = updated_at_before_type_cast
|
102
102
|
if can_use_fast_cache_version?(timestamp)
|
103
103
|
raw_timestamp_to_cache_version(timestamp)
|
104
|
+
|
104
105
|
elsif timestamp = updated_at
|
105
|
-
timestamp.utc.
|
106
|
+
timestamp.utc.to_formatted_s(cache_timestamp_format)
|
106
107
|
end
|
107
108
|
elsif self.class.has_attribute?("updated_at")
|
108
109
|
raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
|