activerecord 7.1.5.2 → 8.1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -32,8 +32,11 @@ module ActiveRecord
|
|
|
32
32
|
|
|
33
33
|
def instrument(name, payload = {}, &block)
|
|
34
34
|
event = @instrumenter.new_event(name, payload)
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
begin
|
|
36
|
+
event.record(&block)
|
|
37
|
+
ensure
|
|
38
|
+
@events << event
|
|
39
|
+
end
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def flush
|
|
@@ -57,7 +60,6 @@ module ActiveRecord
|
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
delegate :empty?, :to_a, to: :result
|
|
60
|
-
delegate_missing_to :result
|
|
61
63
|
|
|
62
64
|
attr_reader :lock_wait
|
|
63
65
|
|
|
@@ -98,17 +100,21 @@ module ActiveRecord
|
|
|
98
100
|
def execute_or_skip
|
|
99
101
|
return unless pending?
|
|
100
102
|
|
|
101
|
-
@
|
|
102
|
-
return unless
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
@session.synchronize do
|
|
104
|
+
return unless pending?
|
|
105
|
+
|
|
106
|
+
@pool.with_connection do |connection|
|
|
107
|
+
return unless @mutex.try_lock
|
|
108
|
+
begin
|
|
109
|
+
if pending?
|
|
110
|
+
@event_buffer = EventBuffer.new(self, @instrumenter)
|
|
111
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
|
|
112
|
+
|
|
107
113
|
execute_query(connection, async: true)
|
|
108
114
|
end
|
|
115
|
+
ensure
|
|
116
|
+
@mutex.unlock
|
|
109
117
|
end
|
|
110
|
-
ensure
|
|
111
|
-
@mutex.unlock
|
|
112
118
|
end
|
|
113
119
|
end
|
|
114
120
|
end
|
|
@@ -140,7 +146,9 @@ module ActiveRecord
|
|
|
140
146
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
141
147
|
@mutex.synchronize do
|
|
142
148
|
if pending?
|
|
143
|
-
|
|
149
|
+
@pool.with_connection do |connection|
|
|
150
|
+
execute_query(connection)
|
|
151
|
+
end
|
|
144
152
|
else
|
|
145
153
|
@lock_wait = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start)
|
|
146
154
|
end
|
|
@@ -159,7 +167,7 @@ module ActiveRecord
|
|
|
159
167
|
end
|
|
160
168
|
|
|
161
169
|
def exec_query(connection, *args, **kwargs)
|
|
162
|
-
connection.
|
|
170
|
+
connection.raw_exec_query(*args, **kwargs)
|
|
163
171
|
end
|
|
164
172
|
|
|
165
173
|
class SelectAll < FutureResult # :nodoc:
|
|
@@ -97,7 +97,7 @@ module ActiveRecord
|
|
|
97
97
|
# Returns the first class in the inheritance hierarchy that descends from either an
|
|
98
98
|
# abstract class or from <tt>ActiveRecord::Base</tt>.
|
|
99
99
|
#
|
|
100
|
-
# Consider the following
|
|
100
|
+
# Consider the following behavior:
|
|
101
101
|
#
|
|
102
102
|
# class ApplicationRecord < ActiveRecord::Base
|
|
103
103
|
# self.abstract_class = true
|
|
@@ -165,7 +165,7 @@ module ActiveRecord
|
|
|
165
165
|
|
|
166
166
|
# Returns whether this class is an abstract class or not.
|
|
167
167
|
def abstract_class?
|
|
168
|
-
|
|
168
|
+
@abstract_class == true
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# Sets the application record class for Active Record
|
|
@@ -202,7 +202,9 @@ module ActiveRecord
|
|
|
202
202
|
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
|
|
203
203
|
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
|
|
204
204
|
"Please rename this column if you didn't intend it to be used for storing the inheritance class " \
|
|
205
|
-
"or overwrite #{name}.inheritance_column to use another column for that information."
|
|
205
|
+
"or overwrite #{name}.inheritance_column to use another column for that information. " \
|
|
206
|
+
"If you wish to disable single-table inheritance for #{name} set " \
|
|
207
|
+
"#{name}.inheritance_column to nil"
|
|
206
208
|
end
|
|
207
209
|
|
|
208
210
|
# Returns the value to be stored in the polymorphic type column for Polymorphic Associations.
|
|
@@ -7,8 +7,17 @@ module ActiveRecord
|
|
|
7
7
|
attr_reader :model, :connection, :inserts, :keys
|
|
8
8
|
attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
class << self
|
|
11
|
+
def execute(relation, ...)
|
|
12
|
+
relation.model.with_connection do |c|
|
|
13
|
+
new(relation, c, ...).execute
|
|
14
|
+
end.tap { relation.reset }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
19
|
+
@relation = relation
|
|
20
|
+
@model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
|
|
12
21
|
@on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
|
|
13
22
|
@record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
|
|
14
23
|
|
|
@@ -23,10 +32,8 @@ module ActiveRecord
|
|
|
23
32
|
@keys = @inserts.first.keys
|
|
24
33
|
end
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@keys |= @scope_attributes.keys
|
|
29
|
-
end
|
|
35
|
+
@scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
|
|
36
|
+
@keys |= @scope_attributes.keys
|
|
30
37
|
@keys = @keys.to_set
|
|
31
38
|
|
|
32
39
|
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
|
@@ -52,10 +59,9 @@ module ActiveRecord
|
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
def primary_keys
|
|
55
|
-
Array(
|
|
62
|
+
Array(@model.schema_cache.primary_keys(model.table_name))
|
|
56
63
|
end
|
|
57
64
|
|
|
58
|
-
|
|
59
65
|
def skip_duplicates?
|
|
60
66
|
on_duplicate == :skip
|
|
61
67
|
end
|
|
@@ -67,7 +73,7 @@ module ActiveRecord
|
|
|
67
73
|
def map_key_with_value
|
|
68
74
|
inserts.map do |attributes|
|
|
69
75
|
attributes = attributes.stringify_keys
|
|
70
|
-
attributes.merge!(scope_attributes)
|
|
76
|
+
attributes.merge!(@scope_attributes)
|
|
71
77
|
attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
|
|
72
78
|
|
|
73
79
|
verify_attributes(attributes)
|
|
@@ -92,8 +98,6 @@ module ActiveRecord
|
|
|
92
98
|
end
|
|
93
99
|
|
|
94
100
|
private
|
|
95
|
-
attr_reader :scope_attributes
|
|
96
|
-
|
|
97
101
|
def has_attribute_aliases?(attributes)
|
|
98
102
|
attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
|
|
99
103
|
end
|
|
@@ -163,10 +167,9 @@ module ActiveRecord
|
|
|
163
167
|
end
|
|
164
168
|
|
|
165
169
|
def unique_indexes
|
|
166
|
-
|
|
170
|
+
@model.schema_cache.indexes(model.table_name).select(&:unique)
|
|
167
171
|
end
|
|
168
172
|
|
|
169
|
-
|
|
170
173
|
def ensure_valid_options_for_connection!
|
|
171
174
|
if returning && !connection.supports_insert_returning?
|
|
172
175
|
raise ArgumentError, "#{connection.class} does not support :returning"
|
|
@@ -192,7 +195,7 @@ module ActiveRecord
|
|
|
192
195
|
|
|
193
196
|
|
|
194
197
|
def readonly_columns
|
|
195
|
-
primary_keys + model.readonly_attributes
|
|
198
|
+
primary_keys + model.readonly_attributes
|
|
196
199
|
end
|
|
197
200
|
|
|
198
201
|
def unique_by_columns
|
|
@@ -222,7 +225,7 @@ module ActiveRecord
|
|
|
222
225
|
class Builder # :nodoc:
|
|
223
226
|
attr_reader :model
|
|
224
227
|
|
|
225
|
-
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
|
|
228
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, :primary_keys, to: :insert_all
|
|
226
229
|
|
|
227
230
|
def initialize(insert_all)
|
|
228
231
|
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
|
@@ -233,11 +236,16 @@ module ActiveRecord
|
|
|
233
236
|
end
|
|
234
237
|
|
|
235
238
|
def values_list
|
|
236
|
-
types =
|
|
239
|
+
types = extract_types_for(keys_including_timestamps)
|
|
237
240
|
|
|
238
241
|
values_list = insert_all.map_key_with_value do |key, value|
|
|
239
|
-
|
|
240
|
-
|
|
242
|
+
if Arel::Nodes::SqlLiteral === value
|
|
243
|
+
value
|
|
244
|
+
elsif primary_keys.include?(key) && value.nil?
|
|
245
|
+
connection.default_insert_value(model.columns_hash[key])
|
|
246
|
+
else
|
|
247
|
+
ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
|
|
248
|
+
end
|
|
241
249
|
end
|
|
242
250
|
|
|
243
251
|
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
|
|
@@ -300,8 +308,8 @@ module ActiveRecord
|
|
|
300
308
|
format_columns(insert_all.keys_including_timestamps)
|
|
301
309
|
end
|
|
302
310
|
|
|
303
|
-
def
|
|
304
|
-
columns =
|
|
311
|
+
def extract_types_for(keys)
|
|
312
|
+
columns = @model.columns_hash
|
|
305
313
|
|
|
306
314
|
unknown_column = (keys - columns.keys).first
|
|
307
315
|
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
|
|
@@ -178,7 +178,10 @@ 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
|
+
# FIXME: checking out a connection for this is wasteful
|
|
182
|
+
# we should store/cache this information in the schema cache
|
|
183
|
+
# or similar.
|
|
184
|
+
self.class.with_connection(&:default_timezone) == :utc &&
|
|
182
185
|
!updated_at_came_from_user?
|
|
183
186
|
end
|
|
184
187
|
|
|
@@ -13,17 +13,13 @@ module ActiveRecord
|
|
|
13
13
|
class NullInternalMetadata # :nodoc:
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
attr_reader :
|
|
16
|
+
attr_reader :arel_table
|
|
17
17
|
|
|
18
|
-
def initialize(
|
|
19
|
-
@
|
|
18
|
+
def initialize(pool)
|
|
19
|
+
@pool = pool
|
|
20
20
|
@arel_table = Arel::Table.new(table_name)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def enabled?
|
|
24
|
-
connection.use_metadata_table?
|
|
25
|
-
end
|
|
26
|
-
|
|
27
23
|
def primary_key
|
|
28
24
|
"key"
|
|
29
25
|
end
|
|
@@ -36,50 +32,66 @@ module ActiveRecord
|
|
|
36
32
|
"#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
|
|
37
33
|
end
|
|
38
34
|
|
|
35
|
+
def enabled?
|
|
36
|
+
@pool.db_config.use_metadata_table?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
39
|
def []=(key, value)
|
|
40
40
|
return unless enabled?
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
@pool.with_connection do |connection|
|
|
43
|
+
update_or_create_entry(connection, key, value)
|
|
44
|
+
end
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def [](key)
|
|
46
48
|
return unless enabled?
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
entry
|
|
50
|
+
@pool.with_connection do |connection|
|
|
51
|
+
if entry = select_entry(connection, key)
|
|
52
|
+
entry[value_key]
|
|
53
|
+
end
|
|
50
54
|
end
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def delete_all_entries
|
|
54
58
|
dm = Arel::DeleteManager.new(arel_table)
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
@pool.with_connection do |connection|
|
|
61
|
+
connection.delete(dm, "#{self.class} Destroy")
|
|
62
|
+
end
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def count
|
|
60
66
|
sm = Arel::SelectManager.new(arel_table)
|
|
61
67
|
sm.project(*Arel::Nodes::Count.new([Arel.star]))
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
@pool.with_connection do |connection|
|
|
70
|
+
connection.select_values(sm, "#{self.class} Count").first
|
|
71
|
+
end
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
def create_table_and_set_flags(environment, schema_sha1 = nil)
|
|
67
75
|
return unless enabled?
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
@pool.with_connection do |connection|
|
|
78
|
+
create_table
|
|
79
|
+
update_or_create_entry(connection, :environment, environment)
|
|
80
|
+
update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
|
|
81
|
+
end
|
|
72
82
|
end
|
|
73
83
|
|
|
74
84
|
# Creates an internal metadata table with columns +key+ and +value+
|
|
75
85
|
def create_table
|
|
76
86
|
return unless enabled?
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
connection.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
@pool.with_connection do |connection|
|
|
89
|
+
unless connection.table_exists?(table_name)
|
|
90
|
+
connection.create_table(table_name, id: false) do |t|
|
|
91
|
+
t.string :key, **connection.internal_string_options_for_primary_key
|
|
92
|
+
t.string :value
|
|
93
|
+
t.timestamps
|
|
94
|
+
end
|
|
83
95
|
end
|
|
84
96
|
end
|
|
85
97
|
end
|
|
@@ -87,49 +99,51 @@ module ActiveRecord
|
|
|
87
99
|
def drop_table
|
|
88
100
|
return unless enabled?
|
|
89
101
|
|
|
90
|
-
|
|
102
|
+
@pool.with_connection do |connection|
|
|
103
|
+
connection.drop_table table_name, if_exists: true
|
|
104
|
+
end
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
def table_exists?
|
|
94
|
-
|
|
108
|
+
@pool.schema_cache.data_source_exists?(table_name)
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
private
|
|
98
|
-
def update_or_create_entry(key, value)
|
|
99
|
-
entry = select_entry(key)
|
|
112
|
+
def update_or_create_entry(connection, key, value)
|
|
113
|
+
entry = select_entry(connection, key)
|
|
100
114
|
|
|
101
115
|
if entry
|
|
102
116
|
if entry[value_key] != value
|
|
103
|
-
update_entry(key, value)
|
|
117
|
+
update_entry(connection, key, value)
|
|
104
118
|
else
|
|
105
119
|
entry[value_key]
|
|
106
120
|
end
|
|
107
121
|
else
|
|
108
|
-
create_entry(key, value)
|
|
122
|
+
create_entry(connection, key, value)
|
|
109
123
|
end
|
|
110
124
|
end
|
|
111
125
|
|
|
112
|
-
def current_time
|
|
126
|
+
def current_time(connection)
|
|
113
127
|
connection.default_timezone == :utc ? Time.now.utc : Time.now
|
|
114
128
|
end
|
|
115
129
|
|
|
116
|
-
def create_entry(key, value)
|
|
130
|
+
def create_entry(connection, key, value)
|
|
117
131
|
im = Arel::InsertManager.new(arel_table)
|
|
118
132
|
im.insert [
|
|
119
133
|
[arel_table[primary_key], key],
|
|
120
134
|
[arel_table[value_key], value],
|
|
121
|
-
[arel_table[:created_at], current_time],
|
|
122
|
-
[arel_table[:updated_at], current_time]
|
|
135
|
+
[arel_table[:created_at], current_time(connection)],
|
|
136
|
+
[arel_table[:updated_at], current_time(connection)]
|
|
123
137
|
]
|
|
124
138
|
|
|
125
139
|
connection.insert(im, "#{self.class} Create", primary_key, key)
|
|
126
140
|
end
|
|
127
141
|
|
|
128
|
-
def update_entry(key, new_value)
|
|
142
|
+
def update_entry(connection, key, new_value)
|
|
129
143
|
um = Arel::UpdateManager.new(arel_table)
|
|
130
144
|
um.set [
|
|
131
145
|
[arel_table[value_key], new_value],
|
|
132
|
-
[arel_table[:updated_at], current_time]
|
|
146
|
+
[arel_table[:updated_at], current_time(connection)]
|
|
133
147
|
]
|
|
134
148
|
|
|
135
149
|
um.where(arel_table[primary_key].eq(key))
|
|
@@ -137,9 +151,9 @@ module ActiveRecord
|
|
|
137
151
|
connection.update(um, "#{self.class} Update")
|
|
138
152
|
end
|
|
139
153
|
|
|
140
|
-
def select_entry(key)
|
|
154
|
+
def select_entry(connection, key)
|
|
141
155
|
sm = Arel::SelectManager.new(arel_table)
|
|
142
|
-
sm.project(Arel::Nodes::SqlLiteral.new("*"))
|
|
156
|
+
sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
|
|
143
157
|
sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
|
|
144
158
|
sm.order(arel_table[primary_key].asc)
|
|
145
159
|
sm.limit = 1
|
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
|
9
9
|
# it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
|
|
10
10
|
# and the update is ignored.
|
|
11
11
|
#
|
|
12
|
-
# Check out
|
|
12
|
+
# Check out ActiveRecord::Locking::Pessimistic for an alternative.
|
|
13
13
|
#
|
|
14
14
|
# == Usage
|
|
15
15
|
#
|
|
@@ -101,6 +101,13 @@ module ActiveRecord
|
|
|
101
101
|
attribute_names = attribute_names.dup if attribute_names.frozen?
|
|
102
102
|
attribute_names << locking_column
|
|
103
103
|
|
|
104
|
+
if self[locking_column].nil?
|
|
105
|
+
raise(<<-MSG.squish)
|
|
106
|
+
For optimistic locking, locking_column ('#{locking_column}') can't be nil.
|
|
107
|
+
Are you missing a default value or validation on '#{locking_column}'?
|
|
108
|
+
MSG
|
|
109
|
+
end
|
|
110
|
+
|
|
104
111
|
self[locking_column] += 1
|
|
105
112
|
|
|
106
113
|
affected_rows = self.class._update_record(
|
|
@@ -182,14 +189,15 @@ module ActiveRecord
|
|
|
182
189
|
super
|
|
183
190
|
end
|
|
184
191
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
private
|
|
193
|
+
def hook_attribute_type(name, cast_type)
|
|
194
|
+
if lock_optimistically && name == locking_column
|
|
195
|
+
cast_type = LockingType.new(cast_type)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
super
|
|
188
199
|
end
|
|
189
|
-
super
|
|
190
|
-
end
|
|
191
200
|
|
|
192
|
-
private
|
|
193
201
|
def inherited(base)
|
|
194
202
|
super
|
|
195
203
|
base.class_eval do
|
|
@@ -67,6 +67,10 @@ module ActiveRecord
|
|
|
67
67
|
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
|
68
68
|
# the locked record.
|
|
69
69
|
def lock!(lock = true)
|
|
70
|
+
if self.class.current_preventing_writes
|
|
71
|
+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
|
|
72
|
+
end
|
|
73
|
+
|
|
70
74
|
if persisted?
|
|
71
75
|
if has_changes_to_save?
|
|
72
76
|
raise(<<-MSG.squish)
|
|
@@ -79,6 +83,7 @@ module ActiveRecord
|
|
|
79
83
|
|
|
80
84
|
reload(lock: lock)
|
|
81
85
|
end
|
|
86
|
+
|
|
82
87
|
self
|
|
83
88
|
end
|
|
84
89
|
|
|
@@ -1,32 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
5
5
|
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
|
6
6
|
|
|
7
7
|
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
8
8
|
|
|
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
|
|
13
|
-
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def self.runtime
|
|
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
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.reset_runtime
|
|
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
|
|
28
|
-
end
|
|
29
|
-
|
|
30
9
|
def strict_loading_violation(event)
|
|
31
10
|
debug do
|
|
32
11
|
owner = event.payload[:owner]
|
|
@@ -147,18 +126,8 @@ module ActiveRecord
|
|
|
147
126
|
end
|
|
148
127
|
end
|
|
149
128
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
129
|
+
def query_source_location
|
|
130
|
+
backtrace_cleaner.first_clean_frame
|
|
162
131
|
end
|
|
163
132
|
|
|
164
133
|
def filter(name, value)
|
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def add_cached_associations(record, entry)
|
|
82
|
-
record.class.
|
|
82
|
+
record.class.normalized_reflections.each_value do |reflection|
|
|
83
83
|
if record.association_cached?(reflection.name) && record.association(reflection.name).loaded?
|
|
84
84
|
entry << reflection.name << encode(record.association(reflection.name).target)
|
|
85
85
|
end
|
|
@@ -9,25 +9,40 @@ module ActiveRecord
|
|
|
9
9
|
# shard to switch to and allows for applications to write custom strategies
|
|
10
10
|
# for swapping if needed.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
# that can be used by the middleware to alter behavior. +lock+ is
|
|
14
|
-
# true by default and will prohibit the request from switching shards once
|
|
15
|
-
# inside the block. If +lock+ is false, then shard swapping will be allowed.
|
|
16
|
-
# For tenant based sharding, +lock+ should always be true to prevent application
|
|
17
|
-
# code from mistakenly switching between tenants.
|
|
12
|
+
# == Setup
|
|
18
13
|
#
|
|
19
|
-
#
|
|
14
|
+
# Applications must provide a resolver that will provide application-specific logic for
|
|
15
|
+
# selecting the appropriate shard. Setting +config.active_record.shard_resolver+ will cause
|
|
16
|
+
# Rails to add ShardSelector to the default middleware stack.
|
|
20
17
|
#
|
|
21
|
-
#
|
|
18
|
+
# The resolver, along with any configuration options, can be set in the application
|
|
19
|
+
# configuration using an initializer like so:
|
|
22
20
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
21
|
+
# Rails.application.configure do
|
|
22
|
+
# config.active_record.shard_selector = { lock: false, class_name: "AnimalsRecord" }
|
|
23
|
+
# config.active_record.shard_resolver = ->(request) {
|
|
24
|
+
# subdomain = request.subdomain
|
|
25
|
+
# tenant = Tenant.find_by_subdomain!(subdomain)
|
|
26
|
+
# tenant.shard
|
|
27
|
+
# }
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# == Configuration
|
|
31
|
+
#
|
|
32
|
+
# The behavior of ShardSelector can be altered through some configuration options.
|
|
33
|
+
#
|
|
34
|
+
# [+lock:+]
|
|
35
|
+
# +lock+ is true by default and will prohibit the request from switching shards once inside
|
|
36
|
+
# the block. If +lock+ is false, then shard switching will be allowed. For tenant based
|
|
37
|
+
# sharding, +lock+ should always be true to prevent application code from mistakenly switching
|
|
38
|
+
# between tenants.
|
|
39
|
+
#
|
|
40
|
+
# [+class_name:+]
|
|
41
|
+
# +class_name+ is the name of the abstract connection class to switch. By
|
|
42
|
+
# default, the ShardSelector will use ActiveRecord::Base, but if the
|
|
43
|
+
# application has multiple databases, then this option should be set to
|
|
44
|
+
# the name of the sharded database's abstract connection class.
|
|
25
45
|
#
|
|
26
|
-
# config.active_record.shard_resolver = ->(request) {
|
|
27
|
-
# subdomain = request.subdomain
|
|
28
|
-
# tenant = Tenant.find_by_subdomain!(subdomain)
|
|
29
|
-
# tenant.shard
|
|
30
|
-
# }
|
|
31
46
|
class ShardSelector
|
|
32
47
|
def initialize(app, resolver, options = {})
|
|
33
48
|
@app = app
|
|
@@ -53,8 +68,10 @@ module ActiveRecord
|
|
|
53
68
|
end
|
|
54
69
|
|
|
55
70
|
def set_shard(shard, &block)
|
|
56
|
-
ActiveRecord::Base
|
|
57
|
-
|
|
71
|
+
klass = options[:class_name]&.constantize || ActiveRecord::Base
|
|
72
|
+
|
|
73
|
+
klass.connected_to(shard: shard.to_sym) do
|
|
74
|
+
klass.prohibit_shard_swapping(options.fetch(:lock, true), &block)
|
|
58
75
|
end
|
|
59
76
|
end
|
|
60
77
|
end
|