activerecord 7.0.8 → 7.1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1554 -1452
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +15 -9
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +31 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +139 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +142 -148
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +174 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +352 -63
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +48 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -8,16 +8,20 @@ module ActiveRecord
|
|
8
8
|
attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
|
9
9
|
|
10
10
|
def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
11
|
-
|
12
|
-
|
13
|
-
@model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
|
11
|
+
@model, @connection, @inserts = model, model.connection, inserts.map(&:stringify_keys)
|
14
12
|
@on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
|
15
13
|
@record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
|
16
14
|
|
17
15
|
disallow_raw_sql!(on_duplicate)
|
18
16
|
disallow_raw_sql!(returning)
|
19
17
|
|
20
|
-
|
18
|
+
if @inserts.empty?
|
19
|
+
@keys = []
|
20
|
+
else
|
21
|
+
resolve_sti
|
22
|
+
resolve_attribute_aliases
|
23
|
+
@keys = @inserts.first.keys
|
24
|
+
end
|
21
25
|
|
22
26
|
if model.scope_attributes?
|
23
27
|
@scope_attributes = model.scope_attributes
|
@@ -28,13 +32,15 @@ module ActiveRecord
|
|
28
32
|
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
29
33
|
@returning = false if @returning == []
|
30
34
|
|
31
|
-
@unique_by = find_unique_index_for(unique_by)
|
32
|
-
@on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
|
35
|
+
@unique_by = find_unique_index_for(@unique_by)
|
33
36
|
|
37
|
+
configure_on_duplicate_update_logic
|
34
38
|
ensure_valid_options_for_connection!
|
35
39
|
end
|
36
40
|
|
37
41
|
def execute
|
42
|
+
return ActiveRecord::Result.empty if inserts.empty?
|
43
|
+
|
38
44
|
message = +"#{model} "
|
39
45
|
message << "Bulk " if inserts.many?
|
40
46
|
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
@@ -76,7 +82,7 @@ module ActiveRecord
|
|
76
82
|
@record_timestamps
|
77
83
|
end
|
78
84
|
|
79
|
-
# TODO: Consider
|
85
|
+
# TODO: Consider renaming this method, as it only conditionally extends keys, not always
|
80
86
|
def keys_including_timestamps
|
81
87
|
@keys_including_timestamps ||= if record_timestamps?
|
82
88
|
keys + model.all_timestamp_attributes_in_model
|
@@ -88,6 +94,34 @@ module ActiveRecord
|
|
88
94
|
private
|
89
95
|
attr_reader :scope_attributes
|
90
96
|
|
97
|
+
def has_attribute_aliases?(attributes)
|
98
|
+
attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
|
99
|
+
end
|
100
|
+
|
101
|
+
def resolve_sti
|
102
|
+
return if model.descends_from_active_record?
|
103
|
+
|
104
|
+
sti_type = model.sti_name
|
105
|
+
@inserts = @inserts.map do |insert|
|
106
|
+
insert.reverse_merge(model.inheritance_column.to_s => sti_type)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def resolve_attribute_aliases
|
111
|
+
return unless has_attribute_aliases?(@inserts.first)
|
112
|
+
|
113
|
+
@inserts = @inserts.map do |insert|
|
114
|
+
insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
|
115
|
+
end
|
116
|
+
|
117
|
+
@update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
|
118
|
+
@unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
|
119
|
+
end
|
120
|
+
|
121
|
+
def resolve_attribute_alias(attribute)
|
122
|
+
model.attribute_alias(attribute) || attribute
|
123
|
+
end
|
124
|
+
|
91
125
|
def configure_on_duplicate_update_logic
|
92
126
|
if custom_update_sql_provided? && update_only.present?
|
93
127
|
raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
|
@@ -99,6 +133,8 @@ module ActiveRecord
|
|
99
133
|
elsif custom_update_sql_provided?
|
100
134
|
@update_sql = on_duplicate
|
101
135
|
@on_duplicate = :update
|
136
|
+
elsif @on_duplicate == :update && updatable_columns.empty?
|
137
|
+
@on_duplicate = :skip
|
102
138
|
end
|
103
139
|
end
|
104
140
|
|
@@ -115,8 +151,9 @@ module ActiveRecord
|
|
115
151
|
|
116
152
|
name_or_columns = unique_by || model.primary_key
|
117
153
|
match = Array(name_or_columns).map(&:to_s)
|
154
|
+
sorted_match = match.sort
|
118
155
|
|
119
|
-
if index = unique_indexes.find { |i| match.include?(i.name) || i.columns ==
|
156
|
+
if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
|
120
157
|
index
|
121
158
|
elsif match == primary_keys
|
122
159
|
unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
|
@@ -212,7 +249,13 @@ module ActiveRecord
|
|
212
249
|
if insert_all.returning.is_a?(String)
|
213
250
|
insert_all.returning
|
214
251
|
else
|
215
|
-
|
252
|
+
Array(insert_all.returning).map do |attribute|
|
253
|
+
if model.attribute_alias?(attribute)
|
254
|
+
"#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}"
|
255
|
+
else
|
256
|
+
quote_column(attribute)
|
257
|
+
end
|
258
|
+
end.join(",")
|
216
259
|
end
|
217
260
|
end
|
218
261
|
|
@@ -271,7 +314,11 @@ module ActiveRecord
|
|
271
314
|
end
|
272
315
|
|
273
316
|
def quote_columns(columns)
|
274
|
-
columns.map(
|
317
|
+
columns.map { |column| quote_column(column) }
|
318
|
+
end
|
319
|
+
|
320
|
+
def quote_column(column)
|
321
|
+
connection.quote_column_name(column)
|
275
322
|
end
|
276
323
|
end
|
277
324
|
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
##
|
11
11
|
# :singleton-method:
|
12
12
|
# Indicates the format used to generate the timestamp in the cache key, if
|
13
|
-
# versioning is off. Accepts any of the symbols in
|
13
|
+
# versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+.
|
14
14
|
#
|
15
15
|
# This is +:usec+, by default.
|
16
16
|
class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
# Indicates whether to use a stable #cache_key method that is accompanied
|
21
21
|
# by a changing version in the #cache_version method.
|
22
22
|
#
|
23
|
-
# This is +true+, by default on Rails 5.2 and above.
|
23
|
+
# This is +true+, by default on \Rails 5.2 and above.
|
24
24
|
class_attribute :cache_versioning, instance_writer: false, default: false
|
25
25
|
|
26
26
|
##
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
# Indicates whether to use a stable #cache_key method that is accompanied
|
29
29
|
# by a changing version in the #cache_version method on collections.
|
30
30
|
#
|
31
|
-
# This is +false+, by default until Rails 6.1.
|
31
|
+
# This is +false+, by default until \Rails 6.1.
|
32
32
|
class_attribute :collection_cache_versioning, instance_writer: false, default: false
|
33
33
|
end
|
34
34
|
|
@@ -55,8 +55,8 @@ module ActiveRecord
|
|
55
55
|
# user = User.find_by(name: 'Phusion')
|
56
56
|
# user_path(user) # => "/users/Phusion"
|
57
57
|
def to_param
|
58
|
-
|
59
|
-
id
|
58
|
+
return unless id
|
59
|
+
Array(id).join(self.class.param_delimiter)
|
60
60
|
end
|
61
61
|
|
62
62
|
# Returns a stable cache key that can be used to identify this record.
|
@@ -64,7 +64,7 @@ module ActiveRecord
|
|
64
64
|
# Product.new.cache_key # => "products/new"
|
65
65
|
# Product.find(5).cache_key # => "products/5"
|
66
66
|
#
|
67
|
-
# If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
|
67
|
+
# If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier,
|
68
68
|
# the cache key will also include a version.
|
69
69
|
#
|
70
70
|
# Product.cache_versioning = false
|
@@ -106,7 +106,7 @@ module ActiveRecord
|
|
106
106
|
timestamp.utc.to_fs(cache_timestamp_format)
|
107
107
|
end
|
108
108
|
elsif self.class.has_attribute?("updated_at")
|
109
|
-
raise ActiveModel::MissingAttributeError, "missing attribute
|
109
|
+
raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
@@ -178,7 +178,7 @@ module ActiveRecord
|
|
178
178
|
def can_use_fast_cache_version?(timestamp)
|
179
179
|
timestamp.is_a?(String) &&
|
180
180
|
cache_timestamp_format == :usec &&
|
181
|
-
|
181
|
+
self.class.connection.default_timezone == :utc &&
|
182
182
|
!updated_at_came_from_user?
|
183
183
|
end
|
184
184
|
|
@@ -9,52 +9,142 @@ module ActiveRecord
|
|
9
9
|
#
|
10
10
|
# This is enabled by default. To disable this functionality set
|
11
11
|
# `use_metadata_table` to false in your database configuration.
|
12
|
-
class InternalMetadata
|
13
|
-
|
12
|
+
class InternalMetadata # :nodoc:
|
13
|
+
class NullInternalMetadata # :nodoc:
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
def enabled?
|
17
|
-
ActiveRecord::Base.connection.use_metadata_table?
|
18
|
-
end
|
16
|
+
attr_reader :connection, :arel_table
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
def initialize(connection)
|
19
|
+
@connection = connection
|
20
|
+
@arel_table = Arel::Table.new(table_name)
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def enabled?
|
24
|
+
connection.use_metadata_table?
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
def primary_key
|
28
|
+
"key"
|
29
|
+
end
|
30
|
+
|
31
|
+
def value_key
|
32
|
+
"value"
|
33
|
+
end
|
34
|
+
|
35
|
+
def table_name
|
36
|
+
"#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
|
37
|
+
end
|
30
38
|
|
31
|
-
|
39
|
+
def []=(key, value)
|
40
|
+
return unless enabled?
|
41
|
+
|
42
|
+
update_or_create_entry(key, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](key)
|
46
|
+
return unless enabled?
|
47
|
+
|
48
|
+
if entry = select_entry(key)
|
49
|
+
entry[value_key]
|
32
50
|
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_all_entries
|
54
|
+
dm = Arel::DeleteManager.new(arel_table)
|
55
|
+
|
56
|
+
connection.delete(dm, "#{self.class} Destroy")
|
57
|
+
end
|
58
|
+
|
59
|
+
def count
|
60
|
+
sm = Arel::SelectManager.new(arel_table)
|
61
|
+
sm.project(*Arel::Nodes::Count.new([Arel.star]))
|
62
|
+
|
63
|
+
connection.select_values(sm, "#{self.class} Count").first
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_table_and_set_flags(environment, schema_sha1 = nil)
|
67
|
+
return unless enabled?
|
68
|
+
|
69
|
+
create_table
|
70
|
+
update_or_create_entry(:environment, environment)
|
71
|
+
update_or_create_entry(:schema_sha1, schema_sha1) if schema_sha1
|
72
|
+
end
|
33
73
|
|
34
|
-
|
35
|
-
|
74
|
+
# Creates an internal metadata table with columns +key+ and +value+
|
75
|
+
def create_table
|
76
|
+
return unless enabled?
|
36
77
|
|
37
|
-
|
78
|
+
unless connection.table_exists?(table_name)
|
79
|
+
connection.create_table(table_name, id: false) do |t|
|
80
|
+
t.string :key, **connection.internal_string_options_for_primary_key
|
81
|
+
t.string :value
|
82
|
+
t.timestamps
|
83
|
+
end
|
38
84
|
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def drop_table
|
88
|
+
return unless enabled?
|
89
|
+
|
90
|
+
connection.drop_table table_name, if_exists: true
|
91
|
+
end
|
92
|
+
|
93
|
+
def table_exists?
|
94
|
+
connection.schema_cache.data_source_exists?(table_name)
|
95
|
+
end
|
39
96
|
|
40
|
-
|
41
|
-
def
|
42
|
-
|
97
|
+
private
|
98
|
+
def update_or_create_entry(key, value)
|
99
|
+
entry = select_entry(key)
|
43
100
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
101
|
+
if entry
|
102
|
+
if entry[value_key] != value
|
103
|
+
update_entry(key, value)
|
104
|
+
else
|
105
|
+
entry[value_key]
|
49
106
|
end
|
107
|
+
else
|
108
|
+
create_entry(key, value)
|
50
109
|
end
|
51
110
|
end
|
52
111
|
|
53
|
-
def
|
54
|
-
|
112
|
+
def current_time
|
113
|
+
connection.default_timezone == :utc ? Time.now.utc : Time.now
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_entry(key, value)
|
117
|
+
im = Arel::InsertManager.new(arel_table)
|
118
|
+
im.insert [
|
119
|
+
[arel_table[primary_key], key],
|
120
|
+
[arel_table[value_key], value],
|
121
|
+
[arel_table[:created_at], current_time],
|
122
|
+
[arel_table[:updated_at], current_time]
|
123
|
+
]
|
55
124
|
|
56
|
-
connection.
|
125
|
+
connection.insert(im, "#{self.class} Create", primary_key, key)
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_entry(key, new_value)
|
129
|
+
um = Arel::UpdateManager.new(arel_table)
|
130
|
+
um.set [
|
131
|
+
[arel_table[value_key], new_value],
|
132
|
+
[arel_table[:updated_at], current_time]
|
133
|
+
]
|
134
|
+
|
135
|
+
um.where(arel_table[primary_key].eq(key))
|
136
|
+
|
137
|
+
connection.update(um, "#{self.class} Update")
|
138
|
+
end
|
139
|
+
|
140
|
+
def select_entry(key)
|
141
|
+
sm = Arel::SelectManager.new(arel_table)
|
142
|
+
sm.project(Arel::Nodes::SqlLiteral.new("*"))
|
143
|
+
sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
|
144
|
+
sm.order(arel_table[primary_key].asc)
|
145
|
+
sm.limit = 1
|
146
|
+
|
147
|
+
connection.select_all(sm, "#{self.class} Load").first
|
57
148
|
end
|
58
|
-
end
|
59
149
|
end
|
60
150
|
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Locking
|
5
|
+
# = \Pessimistic \Locking
|
6
|
+
#
|
5
7
|
# Locking::Pessimistic provides support for row-level locking using
|
6
8
|
# SELECT ... FOR UPDATE and other lock types.
|
7
9
|
#
|
@@ -71,6 +73,7 @@ module ActiveRecord
|
|
71
73
|
Locking a record with unpersisted changes is not supported. Use
|
72
74
|
`save` to persist the changes, or `reload` to discard them
|
73
75
|
explicitly.
|
76
|
+
Changed attributes: #{changed.map(&:inspect).join(', ')}.
|
74
77
|
MSG
|
75
78
|
end
|
76
79
|
|
@@ -79,8 +82,8 @@ module ActiveRecord
|
|
79
82
|
self
|
80
83
|
end
|
81
84
|
|
82
|
-
# Wraps the passed block in a transaction,
|
83
|
-
# before yielding. You can pass the SQL locking clause
|
85
|
+
# Wraps the passed block in a transaction, reloading the object with a
|
86
|
+
# lock before yielding. You can pass the SQL locking clause
|
84
87
|
# as an optional argument (see #lock!).
|
85
88
|
#
|
86
89
|
# You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
|
@@ -7,16 +7,24 @@ module ActiveRecord
|
|
7
7
|
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
8
8
|
|
9
9
|
def self.runtime=(value)
|
10
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
11
|
+
ActiveRecord::LogSubscriber.runtime= is deprecated and will be removed in Rails 7.2.
|
12
|
+
MSG
|
10
13
|
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
11
14
|
end
|
12
15
|
|
13
16
|
def self.runtime
|
14
|
-
ActiveRecord
|
17
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
18
|
+
ActiveRecord::LogSubscriber.runtime is deprecated and will be removed in Rails 7.2.
|
19
|
+
MSG
|
20
|
+
ActiveRecord::RuntimeRegistry.sql_runtime
|
15
21
|
end
|
16
22
|
|
17
23
|
def self.reset_runtime
|
18
|
-
|
19
|
-
|
24
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
25
|
+
ActiveRecord::LogSubscriber.reset_runtime is deprecated and will be removed in Rails 7.2.
|
26
|
+
MSG
|
27
|
+
ActiveRecord::RuntimeRegistry.reset
|
20
28
|
end
|
21
29
|
|
22
30
|
def strict_loading_violation(event)
|
@@ -26,11 +34,9 @@ module ActiveRecord
|
|
26
34
|
color(reflection.strict_loading_violation_message(owner), RED)
|
27
35
|
end
|
28
36
|
end
|
37
|
+
subscribe_log_level :strict_loading_violation, :debug
|
29
38
|
|
30
39
|
def sql(event)
|
31
|
-
self.class.runtime += event.duration
|
32
|
-
return unless logger.debug?
|
33
|
-
|
34
40
|
payload = event.payload
|
35
41
|
|
36
42
|
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
@@ -66,10 +72,11 @@ module ActiveRecord
|
|
66
72
|
end
|
67
73
|
|
68
74
|
name = colorize_payload_name(name, payload[:name])
|
69
|
-
sql = color(sql, sql_color(sql), true) if colorize_logging
|
75
|
+
sql = color(sql, sql_color(sql), bold: true) if colorize_logging
|
70
76
|
|
71
77
|
debug " #{name} #{sql}#{binds}"
|
72
78
|
end
|
79
|
+
subscribe_log_level :sql, :debug
|
73
80
|
|
74
81
|
private
|
75
82
|
def type_casted_binds(casted_binds)
|
@@ -93,9 +100,9 @@ module ActiveRecord
|
|
93
100
|
|
94
101
|
def colorize_payload_name(name, payload_name)
|
95
102
|
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
96
|
-
color(name, MAGENTA, true)
|
103
|
+
color(name, MAGENTA, bold: true)
|
97
104
|
else
|
98
|
-
color(name, CYAN, true)
|
105
|
+
color(name, CYAN, bold: true)
|
99
106
|
end
|
100
107
|
end
|
101
108
|
|
@@ -133,15 +140,25 @@ module ActiveRecord
|
|
133
140
|
end
|
134
141
|
|
135
142
|
def log_query_source
|
136
|
-
source =
|
143
|
+
source = query_source_location
|
137
144
|
|
138
145
|
if source
|
139
146
|
logger.debug(" ↳ #{source}")
|
140
147
|
end
|
141
148
|
end
|
142
149
|
|
143
|
-
|
144
|
-
|
150
|
+
if Thread.respond_to?(:each_caller_location)
|
151
|
+
def query_source_location
|
152
|
+
Thread.each_caller_location do |location|
|
153
|
+
frame = backtrace_cleaner.clean_frame(location)
|
154
|
+
return frame if frame
|
155
|
+
end
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
else
|
159
|
+
def query_source_location
|
160
|
+
backtrace_cleaner.clean(caller(1).lazy).first
|
161
|
+
end
|
145
162
|
end
|
146
163
|
|
147
164
|
def filter(name, value)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Marshalling
|
5
|
+
@format_version = 6.1
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :format_version
|
9
|
+
|
10
|
+
def format_version=(version)
|
11
|
+
case version
|
12
|
+
when 6.1
|
13
|
+
Methods.remove_method(:marshal_dump) if Methods.method_defined?(:marshal_dump)
|
14
|
+
when 7.1
|
15
|
+
Methods.alias_method(:marshal_dump, :_marshal_dump_7_1)
|
16
|
+
else
|
17
|
+
raise ArgumentError, "Unknown marshalling format: #{version.inspect}"
|
18
|
+
end
|
19
|
+
@format_version = version
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Methods
|
24
|
+
def _marshal_dump_7_1
|
25
|
+
payload = [attributes_for_database, new_record?]
|
26
|
+
|
27
|
+
cached_associations = self.class.reflect_on_all_associations.select do |reflection|
|
28
|
+
association_cached?(reflection.name)
|
29
|
+
end
|
30
|
+
|
31
|
+
unless cached_associations.empty?
|
32
|
+
payload << cached_associations.map do |reflection|
|
33
|
+
[reflection.name, association(reflection.name).target]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
payload
|
38
|
+
end
|
39
|
+
|
40
|
+
def marshal_load(state)
|
41
|
+
attributes_from_database, new_record, associations = state
|
42
|
+
|
43
|
+
attributes = self.class.attributes_builder.build_from_database(attributes_from_database)
|
44
|
+
init_with_attributes(attributes, new_record)
|
45
|
+
|
46
|
+
if associations
|
47
|
+
associations.each do |name, target|
|
48
|
+
association(name).target = target
|
49
|
+
rescue ActiveRecord::AssociationNotFoundError
|
50
|
+
# the association no longer exist, we can just skip it.
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module MessagePack # :nodoc:
|
5
|
+
FORMAT_VERSION = 1
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def dump(input)
|
9
|
+
encoder = Encoder.new
|
10
|
+
[FORMAT_VERSION, encoder.encode(input), encoder.entries]
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(dumped)
|
14
|
+
format_version, top_level, entries = dumped
|
15
|
+
unless format_version == FORMAT_VERSION
|
16
|
+
raise "Invalid format version: #{format_version.inspect}"
|
17
|
+
end
|
18
|
+
Decoder.new(entries).decode(top_level)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Extensions
|
23
|
+
extend self
|
24
|
+
|
25
|
+
def install(registry)
|
26
|
+
registry.register_type 119, ActiveModel::Type::Binary::Data,
|
27
|
+
packer: :to_s,
|
28
|
+
unpacker: :new
|
29
|
+
|
30
|
+
registry.register_type 120, ActiveRecord::Base,
|
31
|
+
packer: method(:write_record),
|
32
|
+
unpacker: method(:read_record),
|
33
|
+
recursive: true
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_record(record, packer)
|
37
|
+
packer.write(ActiveRecord::MessagePack.dump(record))
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_record(unpacker)
|
41
|
+
ActiveRecord::MessagePack.load(unpacker.read)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Encoder
|
46
|
+
attr_reader :entries
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@entries = []
|
50
|
+
@refs = {}.compare_by_identity
|
51
|
+
end
|
52
|
+
|
53
|
+
def encode(input)
|
54
|
+
if input.is_a?(Array)
|
55
|
+
input.map { |record| encode_record(record) }
|
56
|
+
elsif input
|
57
|
+
encode_record(input)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def encode_record(record)
|
62
|
+
ref = @refs[record]
|
63
|
+
|
64
|
+
if !ref
|
65
|
+
ref = @refs[record] = @entries.size
|
66
|
+
@entries << build_entry(record)
|
67
|
+
add_cached_associations(record, @entries.last)
|
68
|
+
end
|
69
|
+
|
70
|
+
ref
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_entry(record)
|
74
|
+
[
|
75
|
+
ActiveSupport::MessagePack::Extensions.dump_class(record.class),
|
76
|
+
record.attributes_for_database,
|
77
|
+
record.new_record?
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_cached_associations(record, entry)
|
82
|
+
record.class.reflections.each_value do |reflection|
|
83
|
+
if record.association_cached?(reflection.name)
|
84
|
+
entry << reflection.name << encode(record.association(reflection.name).target)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Decoder
|
91
|
+
def initialize(entries)
|
92
|
+
@records = entries.map { |entry| build_record(entry) }
|
93
|
+
@records.zip(entries) { |record, entry| resolve_cached_associations(record, entry) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def decode(ref)
|
97
|
+
if ref.is_a?(Array)
|
98
|
+
ref.map { |r| @records[r] }
|
99
|
+
elsif ref
|
100
|
+
@records[ref]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_record(entry)
|
105
|
+
class_name, attributes_hash, is_new_record, * = entry
|
106
|
+
klass = ActiveSupport::MessagePack::Extensions.load_class(class_name)
|
107
|
+
attributes = klass.attributes_builder.build_from_database(attributes_hash)
|
108
|
+
klass.allocate.init_with_attributes(attributes, is_new_record)
|
109
|
+
end
|
110
|
+
|
111
|
+
def resolve_cached_associations(record, entry)
|
112
|
+
i = 3 # entry == [class_name, attributes_hash, is_new_record, *associations]
|
113
|
+
while i < entry.length
|
114
|
+
begin
|
115
|
+
record.association(entry[i]).target = decode(entry[i + 1])
|
116
|
+
rescue ActiveRecord::AssociationNotFoundError
|
117
|
+
# The association no longer exists, so just skip it.
|
118
|
+
end
|
119
|
+
i += 2
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -48,6 +48,10 @@ module ActiveRecord
|
|
48
48
|
context.save(response)
|
49
49
|
end
|
50
50
|
|
51
|
+
def reading_request?(request)
|
52
|
+
request.get? || request.head?
|
53
|
+
end
|
54
|
+
|
51
55
|
private
|
52
56
|
def read_from_primary(&blk)
|
53
57
|
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do
|