activerecord 6.1.6 → 7.0.4
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 +1314 -975
- data/README.rdoc +1 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/association_relation.rb +0 -10
- data/lib/active_record/associations/association.rb +33 -17
- data/lib/active_record/associations/association_scope.rb +1 -3
- data/lib/active_record/associations/belongs_to_association.rb +15 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +8 -2
- data/lib/active_record/associations/builder/belongs_to.rb +19 -6
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +19 -21
- data/lib/active_record/associations/collection_proxy.rb +10 -5
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/has_many_association.rb +8 -5
- data/lib/active_record/associations/has_many_through_association.rb +2 -1
- data/lib/active_record/associations/has_one_association.rb +10 -7
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +23 -15
- data/lib/active_record/associations/preloader/association.rb +186 -52
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +49 -13
- data/lib/active_record/associations/preloader.rb +39 -113
- data/lib/active_record/associations/singular_association.rb +8 -2
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +124 -95
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -1
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +49 -16
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +7 -5
- data/lib/active_record/attribute_methods/serialization.rb +57 -19
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
- data/lib/active_record/attribute_methods/write.rb +7 -10
- data/lib/active_record/attribute_methods.rb +14 -15
- data/lib/active_record/attributes.rb +24 -35
- data/lib/active_record/autosave_association.rb +8 -23
- data/lib/active_record/base.rb +19 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/yaml_column.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
- data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
- data/lib/active_record/connection_adapters/column.rb +4 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
- data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
- data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
- data/lib/active_record/connection_adapters.rb +6 -5
- data/lib/active_record/connection_handling.rb +49 -55
- data/lib/active_record/core.rb +124 -134
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +12 -9
- data/lib/active_record/database_configurations/hash_config.rb +63 -5
- data/lib/active_record/database_configurations/url_config.rb +2 -2
- data/lib/active_record/database_configurations.rb +15 -32
- data/lib/active_record/delegated_type.rb +53 -12
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +44 -0
- data/lib/active_record/encryption/configurable.rb +67 -0
- data/lib/active_record/encryption/context.rb +35 -0
- data/lib/active_record/encryption/contexts.rb +72 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +206 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +42 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +90 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +99 -0
- data/lib/active_record/encryption.rb +55 -0
- data/lib/active_record/enum.rb +50 -43
- data/lib/active_record/errors.rb +67 -4
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/table_row.rb +41 -6
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +20 -23
- data/lib/active_record/future_result.rb +139 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +55 -17
- data/lib/active_record/insert_all.rb +80 -14
- data/lib/active_record/integration.rb +4 -3
- data/lib/active_record/internal_metadata.rb +1 -5
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +10 -9
- data/lib/active_record/locking/pessimistic.rb +10 -4
- data/lib/active_record/log_subscriber.rb +23 -7
- data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
- data/lib/active_record/middleware/database_selector.rb +18 -6
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/migration/compatibility.rb +84 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +114 -83
- data/lib/active_record/model_schema.rb +58 -59
- data/lib/active_record/nested_attributes.rb +13 -12
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/null_relation.rb +2 -6
- data/lib/active_record/persistence.rb +228 -60
- data/lib/active_record/query_cache.rb +2 -2
- data/lib/active_record/query_logs.rb +138 -0
- data/lib/active_record/querying.rb +16 -6
- data/lib/active_record/railtie.rb +136 -22
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +78 -136
- data/lib/active_record/readonly_attributes.rb +11 -0
- data/lib/active_record/reflection.rb +73 -50
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +6 -6
- data/lib/active_record/relation/calculations.rb +43 -38
- data/lib/active_record/relation/delegation.rb +7 -7
- data/lib/active_record/relation/finder_methods.rb +31 -35
- data/lib/active_record/relation/merger.rb +20 -13
- data/lib/active_record/relation/predicate_builder.rb +1 -6
- data/lib/active_record/relation/query_attribute.rb +5 -11
- data/lib/active_record/relation/query_methods.rb +276 -67
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +2 -2
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +189 -88
- data/lib/active_record/result.rb +17 -7
- data/lib/active_record/runtime_registry.rb +9 -13
- data/lib/active_record/sanitization.rb +17 -12
- data/lib/active_record/schema.rb +38 -23
- data/lib/active_record/schema_dumper.rb +25 -19
- data/lib/active_record/schema_migration.rb +4 -4
- data/lib/active_record/scoping/default.rb +60 -13
- data/lib/active_record/scoping/named.rb +3 -11
- data/lib/active_record/scoping.rb +64 -34
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +3 -3
- data/lib/active_record/store.rb +7 -2
- data/lib/active_record/suppressor.rb +11 -15
- data/lib/active_record/tasks/database_tasks.rb +127 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +16 -9
- data/lib/active_record/timestamp.rb +3 -4
- data/lib/active_record/transactions.rb +9 -14
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +32 -7
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +2 -2
- data/lib/active_record/type/serialized.rb +1 -1
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/associated.rb +4 -4
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +217 -27
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/predications.rb +11 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +0 -1
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +8 -2
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +58 -2
- data/lib/arel.rb +2 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- 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 +55 -11
@@ -5,13 +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
|
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
|
16
|
+
|
17
|
+
disallow_raw_sql!(on_duplicate)
|
18
|
+
disallow_raw_sql!(returning)
|
19
|
+
|
20
|
+
configure_on_duplicate_update_logic
|
15
21
|
|
16
22
|
if model.scope_attributes?
|
17
23
|
@scope_attributes = model.scope_attributes
|
@@ -36,7 +42,7 @@ module ActiveRecord
|
|
36
42
|
end
|
37
43
|
|
38
44
|
def updatable_columns
|
39
|
-
keys - readonly_columns - unique_by_columns
|
45
|
+
@updatable_columns ||= keys - readonly_columns - unique_by_columns
|
40
46
|
end
|
41
47
|
|
42
48
|
def primary_keys
|
@@ -56,18 +62,50 @@ module ActiveRecord
|
|
56
62
|
inserts.map do |attributes|
|
57
63
|
attributes = attributes.stringify_keys
|
58
64
|
attributes.merge!(scope_attributes) if scope_attributes
|
65
|
+
attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
|
59
66
|
|
60
67
|
verify_attributes(attributes)
|
61
68
|
|
62
|
-
|
69
|
+
keys_including_timestamps.map do |key|
|
63
70
|
yield key, attributes[key]
|
64
71
|
end
|
65
72
|
end
|
66
73
|
end
|
67
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
|
+
|
68
88
|
private
|
69
89
|
attr_reader :scope_attributes
|
70
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
|
+
|
71
109
|
def find_unique_index_for(unique_by)
|
72
110
|
if !connection.supports_insert_conflict_target?
|
73
111
|
return if unique_by.nil?
|
@@ -126,15 +164,28 @@ module ActiveRecord
|
|
126
164
|
|
127
165
|
|
128
166
|
def verify_attributes(attributes)
|
129
|
-
if
|
167
|
+
if keys_including_timestamps != attributes.keys.to_set
|
130
168
|
raise ArgumentError, "All objects being inserted must have the same keys"
|
131
169
|
end
|
132
170
|
end
|
133
171
|
|
172
|
+
def disallow_raw_sql!(value)
|
173
|
+
return if !value.is_a?(String) || Arel.arel_node?(value)
|
174
|
+
|
175
|
+
raise ArgumentError, "Dangerous query method (method whose arguments are used as raw " \
|
176
|
+
"SQL) called: #{value}. " \
|
177
|
+
"Known-safe values can be passed " \
|
178
|
+
"by wrapping them in Arel.sql()."
|
179
|
+
end
|
180
|
+
|
181
|
+
def timestamps_for_create
|
182
|
+
model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
|
183
|
+
end
|
184
|
+
|
134
185
|
class Builder # :nodoc:
|
135
186
|
attr_reader :model
|
136
187
|
|
137
|
-
delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
|
188
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
|
138
189
|
|
139
190
|
def initialize(insert_all)
|
140
191
|
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
@@ -145,9 +196,10 @@ module ActiveRecord
|
|
145
196
|
end
|
146
197
|
|
147
198
|
def values_list
|
148
|
-
types = extract_types_from_columns_on(model.table_name, keys:
|
199
|
+
types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
|
149
200
|
|
150
201
|
values_list = insert_all.map_key_with_value do |key, value|
|
202
|
+
next value if Arel::Nodes::SqlLiteral === value
|
151
203
|
connection.with_yaml_fallback(types[key].serialize(value))
|
152
204
|
end
|
153
205
|
|
@@ -155,7 +207,13 @@ module ActiveRecord
|
|
155
207
|
end
|
156
208
|
|
157
209
|
def returning
|
158
|
-
|
210
|
+
return unless insert_all.returning
|
211
|
+
|
212
|
+
if insert_all.returning.is_a?(String)
|
213
|
+
insert_all.returning
|
214
|
+
else
|
215
|
+
format_columns(insert_all.returning)
|
216
|
+
end
|
159
217
|
end
|
160
218
|
|
161
219
|
def conflict_target
|
@@ -173,22 +231,30 @@ module ActiveRecord
|
|
173
231
|
end
|
174
232
|
|
175
233
|
def touch_model_timestamps_unless(&block)
|
176
|
-
|
234
|
+
return "" unless update_duplicates? && record_timestamps?
|
235
|
+
|
236
|
+
model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
|
177
237
|
if touch_timestamp_attribute?(column_name)
|
178
|
-
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE
|
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),"
|
179
239
|
end
|
180
|
-
end.
|
240
|
+
end.join
|
181
241
|
end
|
182
242
|
|
243
|
+
def raw_update_sql
|
244
|
+
insert_all.update_sql
|
245
|
+
end
|
246
|
+
|
247
|
+
alias raw_update_sql? raw_update_sql
|
248
|
+
|
183
249
|
private
|
184
250
|
attr_reader :connection, :insert_all
|
185
251
|
|
186
252
|
def touch_timestamp_attribute?(column_name)
|
187
|
-
|
253
|
+
insert_all.updatable_columns.exclude?(column_name)
|
188
254
|
end
|
189
255
|
|
190
256
|
def columns_list
|
191
|
-
format_columns(insert_all.
|
257
|
+
format_columns(insert_all.keys_including_timestamps)
|
192
258
|
end
|
193
259
|
|
194
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_fs(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_fs(cache_timestamp_format)
|
106
107
|
end
|
107
108
|
elsif self.class.has_attribute?("updated_at")
|
108
109
|
raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
|
@@ -177,7 +178,7 @@ module ActiveRecord
|
|
177
178
|
def can_use_fast_cache_version?(timestamp)
|
178
179
|
timestamp.is_a?(String) &&
|
179
180
|
cache_timestamp_format == :usec &&
|
180
|
-
default_timezone == :utc &&
|
181
|
+
ActiveRecord.default_timezone == :utc &&
|
181
182
|
!updated_at_came_from_user?
|
182
183
|
end
|
183
184
|
|
@@ -17,10 +17,6 @@ module ActiveRecord
|
|
17
17
|
ActiveRecord::Base.connection.use_metadata_table?
|
18
18
|
end
|
19
19
|
|
20
|
-
def _internal?
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
20
|
def primary_key
|
25
21
|
"key"
|
26
22
|
end
|
@@ -38,7 +34,7 @@ module ActiveRecord
|
|
38
34
|
def [](key)
|
39
35
|
return unless enabled?
|
40
36
|
|
41
|
-
where(key: key).
|
37
|
+
where(key: key).pick(:value)
|
42
38
|
end
|
43
39
|
|
44
40
|
# Creates an internal metadata table with columns +key+ and +value+
|
@@ -2,50 +2,13 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module LegacyYamlAdapter # :nodoc:
|
5
|
-
def self.convert(
|
5
|
+
def self.convert(coder)
|
6
6
|
return coder unless coder.is_a?(Psych::Coder)
|
7
7
|
|
8
8
|
case coder["active_record_yaml_version"]
|
9
9
|
when 1, 2 then coder
|
10
10
|
else
|
11
|
-
|
12
|
-
YAML loading from legacy format older than Rails 5.0 is deprecated
|
13
|
-
and will be removed in Rails 7.0.
|
14
|
-
MSG
|
15
|
-
if coder["attributes"].is_a?(ActiveModel::AttributeSet)
|
16
|
-
Rails420.convert(klass, coder)
|
17
|
-
else
|
18
|
-
Rails41.convert(klass, coder)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
module Rails420 # :nodoc:
|
24
|
-
def self.convert(klass, coder)
|
25
|
-
attribute_set = coder["attributes"]
|
26
|
-
|
27
|
-
klass.attribute_names.each do |attr_name|
|
28
|
-
attribute = attribute_set[attr_name]
|
29
|
-
if attribute.type.is_a?(Delegator)
|
30
|
-
type_from_klass = klass.type_for_attribute(attr_name)
|
31
|
-
attribute_set[attr_name] = attribute.with_type(type_from_klass)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
coder
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
module Rails41 # :nodoc:
|
40
|
-
def self.convert(klass, coder)
|
41
|
-
attributes = klass.attributes_builder
|
42
|
-
.build_from_database(coder["attributes"])
|
43
|
-
new_record = coder["attributes"][klass.primary_key].blank?
|
44
|
-
|
45
|
-
{
|
46
|
-
"attributes" => attributes,
|
47
|
-
"new_record" => new_record,
|
48
|
-
}
|
11
|
+
raise("Active Record doesn't know how to load YAML with this format.")
|
49
12
|
end
|
50
13
|
end
|
51
14
|
end
|
@@ -56,11 +56,11 @@ module ActiveRecord
|
|
56
56
|
class_attribute :lock_optimistically, instance_writer: false, default: true
|
57
57
|
end
|
58
58
|
|
59
|
-
def locking_enabled?
|
59
|
+
def locking_enabled? # :nodoc:
|
60
60
|
self.class.locking_enabled?
|
61
61
|
end
|
62
62
|
|
63
|
-
def increment!(*, **)
|
63
|
+
def increment!(*, **) # :nodoc:
|
64
64
|
super.tap do
|
65
65
|
if locking_enabled?
|
66
66
|
self[self.class.locking_column] += 1
|
@@ -90,7 +90,9 @@ module ActiveRecord
|
|
90
90
|
begin
|
91
91
|
locking_column = self.class.locking_column
|
92
92
|
lock_attribute_was = @attributes[locking_column]
|
93
|
-
|
93
|
+
|
94
|
+
update_constraints = _primary_key_constraints_hash
|
95
|
+
update_constraints[locking_column] = _lock_value_for_database(locking_column)
|
94
96
|
|
95
97
|
attribute_names = attribute_names.dup if attribute_names.frozen?
|
96
98
|
attribute_names << locking_column
|
@@ -99,8 +101,7 @@ module ActiveRecord
|
|
99
101
|
|
100
102
|
affected_rows = self.class._update_record(
|
101
103
|
attributes_with_values(attribute_names),
|
102
|
-
|
103
|
-
locking_column => lock_value_for_database
|
104
|
+
update_constraints
|
104
105
|
)
|
105
106
|
|
106
107
|
if affected_rows != 1
|
@@ -121,10 +122,10 @@ module ActiveRecord
|
|
121
122
|
|
122
123
|
locking_column = self.class.locking_column
|
123
124
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
)
|
125
|
+
delete_constraints = _primary_key_constraints_hash
|
126
|
+
delete_constraints[locking_column] = _lock_value_for_database(locking_column)
|
127
|
+
|
128
|
+
affected_rows = self.class._delete_record(delete_constraints)
|
128
129
|
|
129
130
|
if affected_rows != 1
|
130
131
|
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
# Locking::Pessimistic provides support for row-level locking using
|
6
6
|
# SELECT ... FOR UPDATE and other lock types.
|
7
7
|
#
|
8
|
-
# Chain <tt>ActiveRecord::Base#find</tt> to
|
8
|
+
# Chain <tt>ActiveRecord::Base#find</tt> to ActiveRecord::QueryMethods#lock to obtain an exclusive
|
9
9
|
# lock on the selected rows:
|
10
10
|
# # select * from accounts where id=1 for update
|
11
11
|
# Account.lock.find(1)
|
@@ -81,9 +81,15 @@ module ActiveRecord
|
|
81
81
|
|
82
82
|
# Wraps the passed block in a transaction, locking the object
|
83
83
|
# before yielding. You can pass the SQL locking clause
|
84
|
-
# as argument (see
|
85
|
-
|
86
|
-
|
84
|
+
# as an optional argument (see #lock!).
|
85
|
+
#
|
86
|
+
# You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
|
87
|
+
# and <tt>joinable:</tt> to the wrapping transaction (see
|
88
|
+
# ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction).
|
89
|
+
def with_lock(*args)
|
90
|
+
transaction_opts = args.extract_options!
|
91
|
+
lock = args.present? ? args.first : true
|
92
|
+
transaction(**transaction_opts) do
|
87
93
|
lock!(lock)
|
88
94
|
yield
|
89
95
|
end
|
@@ -22,10 +22,8 @@ module ActiveRecord
|
|
22
22
|
def strict_loading_violation(event)
|
23
23
|
debug do
|
24
24
|
owner = event.payload[:owner]
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED)
|
25
|
+
reflection = event.payload[:reflection]
|
26
|
+
color(reflection.strict_loading_violation_message(owner), RED)
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
@@ -37,7 +35,11 @@ module ActiveRecord
|
|
37
35
|
|
38
36
|
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
39
37
|
|
40
|
-
name
|
38
|
+
name = if payload[:async]
|
39
|
+
"ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
|
40
|
+
else
|
41
|
+
"#{payload[:name]} (#{event.duration.round(1)}ms)"
|
42
|
+
end
|
41
43
|
name = "CACHE #{name}" if payload[:cached]
|
42
44
|
sql = payload[:sql]
|
43
45
|
binds = nil
|
@@ -47,7 +49,17 @@ module ActiveRecord
|
|
47
49
|
|
48
50
|
binds = []
|
49
51
|
payload[:binds].each_with_index do |attr, i|
|
50
|
-
|
52
|
+
attribute_name = if attr.respond_to?(:name)
|
53
|
+
attr.name
|
54
|
+
elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
|
55
|
+
attr[i].name
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
filtered_params = filter(attribute_name, casted_params[i])
|
61
|
+
|
62
|
+
binds << render_bind(attr, filtered_params)
|
51
63
|
end
|
52
64
|
binds = binds.inspect
|
53
65
|
binds.prepend(" ")
|
@@ -115,7 +127,7 @@ module ActiveRecord
|
|
115
127
|
def debug(progname = nil, &block)
|
116
128
|
return unless super
|
117
129
|
|
118
|
-
if ActiveRecord
|
130
|
+
if ActiveRecord.verbose_query_logs
|
119
131
|
log_query_source
|
120
132
|
end
|
121
133
|
end
|
@@ -131,6 +143,10 @@ module ActiveRecord
|
|
131
143
|
def extract_query_source_location(locations)
|
132
144
|
backtrace_cleaner.clean(locations.lazy).first
|
133
145
|
end
|
146
|
+
|
147
|
+
def filter(name, value)
|
148
|
+
ActiveRecord::Base.inspection_filter.filter_param(name, value)
|
149
|
+
end
|
134
150
|
end
|
135
151
|
end
|
136
152
|
|
@@ -50,23 +50,19 @@ module ActiveRecord
|
|
50
50
|
|
51
51
|
private
|
52
52
|
def read_from_primary(&blk)
|
53
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
54
|
-
instrumenter.instrument("database_selector.active_record.read_from_primary")
|
55
|
-
yield
|
56
|
-
end
|
53
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do
|
54
|
+
instrumenter.instrument("database_selector.active_record.read_from_primary", &blk)
|
57
55
|
end
|
58
56
|
end
|
59
57
|
|
60
58
|
def read_from_replica(&blk)
|
61
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
62
|
-
instrumenter.instrument("database_selector.active_record.read_from_replica")
|
63
|
-
yield
|
64
|
-
end
|
59
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.reading_role, prevent_writes: true) do
|
60
|
+
instrumenter.instrument("database_selector.active_record.read_from_replica", &blk)
|
65
61
|
end
|
66
62
|
end
|
67
63
|
|
68
|
-
def write_to_primary
|
69
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
64
|
+
def write_to_primary
|
65
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: false) do
|
70
66
|
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
|
71
67
|
yield
|
72
68
|
ensure
|
@@ -19,14 +19,22 @@ module ActiveRecord
|
|
19
19
|
# that informs the application when to read from a primary or read from a
|
20
20
|
# replica.
|
21
21
|
#
|
22
|
-
# To use the DatabaseSelector in your application with default settings
|
23
|
-
# the
|
22
|
+
# To use the DatabaseSelector in your application with default settings,
|
23
|
+
# run the provided generator.
|
24
24
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
25
|
+
# bin/rails g active_record:multi_db
|
26
|
+
#
|
27
|
+
# This will create a file named +config/initializers/multi_db.rb+ with the
|
28
|
+
# following contents:
|
28
29
|
#
|
29
|
-
#
|
30
|
+
# Rails.application.configure do
|
31
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
32
|
+
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
33
|
+
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Alternatively you can set the options in your environment config or
|
37
|
+
# any other config file loaded on boot.
|
30
38
|
#
|
31
39
|
# The default behavior can be changed by setting the config options to a
|
32
40
|
# custom class:
|
@@ -34,6 +42,10 @@ module ActiveRecord
|
|
34
42
|
# config.active_record.database_selector = { delay: 2.seconds }
|
35
43
|
# config.active_record.database_resolver = MyResolver
|
36
44
|
# config.active_record.database_resolver_context = MyResolver::MySession
|
45
|
+
#
|
46
|
+
# Note: If you are using `rails new my_app --minimal` you will need to call
|
47
|
+
# `require "active_support/core_ext/integer/time"` to load the libraries
|
48
|
+
# for +Time+.
|
37
49
|
class DatabaseSelector
|
38
50
|
def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
|
39
51
|
@app = app
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Middleware
|
5
|
+
# The ShardSelector Middleware provides a framework for automatically
|
6
|
+
# swapping shards. Rails provides a basic framework to determine which
|
7
|
+
# shard to switch to and allows for applications to write custom strategies
|
8
|
+
# for swapping if needed.
|
9
|
+
#
|
10
|
+
# The ShardSelector takes a set of options (currently only +lock+ is supported)
|
11
|
+
# that can be used by the middleware to alter behavior. +lock+ is
|
12
|
+
# true by default and will prohibit the request from switching shards once
|
13
|
+
# inside the block. If +lock+ is false, then shard swapping will be allowed.
|
14
|
+
# For tenant based sharding, +lock+ should always be true to prevent application
|
15
|
+
# code from mistakenly switching between tenants.
|
16
|
+
#
|
17
|
+
# Options can be set in the config:
|
18
|
+
#
|
19
|
+
# config.active_record.shard_selector = { lock: true }
|
20
|
+
#
|
21
|
+
# Applications must also provide the code for the resolver as it depends on application
|
22
|
+
# specific models. An example resolver would look like this:
|
23
|
+
#
|
24
|
+
# config.active_record.shard_resolver = ->(request) {
|
25
|
+
# subdomain = request.subdomain
|
26
|
+
# tenant = Tenant.find_by_subdomain!(subdomain)
|
27
|
+
# tenant.shard
|
28
|
+
# }
|
29
|
+
class ShardSelector
|
30
|
+
def initialize(app, resolver, options = {})
|
31
|
+
@app = app
|
32
|
+
@resolver = resolver
|
33
|
+
@options = options
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :resolver, :options
|
37
|
+
|
38
|
+
def call(env)
|
39
|
+
request = ActionDispatch::Request.new(env)
|
40
|
+
|
41
|
+
shard = selected_shard(request)
|
42
|
+
|
43
|
+
set_shard(shard) do
|
44
|
+
@app.call(env)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def selected_shard(request)
|
50
|
+
resolver.call(request)
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_shard(shard, &block)
|
54
|
+
ActiveRecord::Base.connected_to(shard: shard.to_sym) do
|
55
|
+
ActiveRecord::Base.prohibit_shard_swapping(options.fetch(:lock, true), &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -13,10 +13,10 @@ module ActiveRecord
|
|
13
13
|
# * add_reference
|
14
14
|
# * add_timestamps
|
15
15
|
# * change_column
|
16
|
-
# * change_column_default (must supply a
|
16
|
+
# * change_column_default (must supply a +:from+ and +:to+ option)
|
17
17
|
# * change_column_null
|
18
|
-
# * change_column_comment (must supply a
|
19
|
-
# * change_table_comment (must supply a
|
18
|
+
# * change_column_comment (must supply a +:from+ and +:to+ option)
|
19
|
+
# * change_table_comment (must supply a +:from+ and +:to+ option)
|
20
20
|
# * create_join_table
|
21
21
|
# * create_table
|
22
22
|
# * disable_extension
|
@@ -112,7 +112,7 @@ module ActiveRecord
|
|
112
112
|
record(:"#{method}", args, &block) # record(:create_table, args, &block)
|
113
113
|
end # end
|
114
114
|
EOV
|
115
|
-
ruby2_keywords(method)
|
115
|
+
ruby2_keywords(method)
|
116
116
|
end
|
117
117
|
alias :add_belongs_to :add_reference
|
118
118
|
alias :remove_belongs_to :remove_reference
|
@@ -154,9 +154,9 @@ module ActiveRecord
|
|
154
154
|
|
155
155
|
include StraightReversions
|
156
156
|
|
157
|
-
def invert_transaction(args)
|
157
|
+
def invert_transaction(args, &block)
|
158
158
|
sub_recorder = CommandRecorder.new(delegate)
|
159
|
-
sub_recorder.revert
|
159
|
+
sub_recorder.revert(&block)
|
160
160
|
|
161
161
|
invertions_proc = proc {
|
162
162
|
sub_recorder.replay(self)
|
@@ -286,7 +286,7 @@ module ActiveRecord
|
|
286
286
|
super
|
287
287
|
end
|
288
288
|
end
|
289
|
-
ruby2_keywords(:method_missing)
|
289
|
+
ruby2_keywords(:method_missing)
|
290
290
|
end
|
291
291
|
end
|
292
292
|
end
|