activerecord 7.2.3 → 8.1.3
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 +612 -1055
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- 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 +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- 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 +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- 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/quoting.rb +21 -10
- 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 +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +45 -12
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- 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 +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +52 -40
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +40 -24
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- 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 +22 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +140 -86
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +107 -75
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +18 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +24 -20
- 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 +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +37 -16
- 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 +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +84 -49
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -13
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
data/lib/active_record/result.rb
CHANGED
|
@@ -29,6 +29,11 @@ module ActiveRecord
|
|
|
29
29
|
# ...
|
|
30
30
|
# ]
|
|
31
31
|
#
|
|
32
|
+
# # Get the number of rows affected by the query:
|
|
33
|
+
# result = ActiveRecord::Base.lease_connection.exec_query('INSERT INTO posts (title, body) VALUES ("title_3", "body_3"), ("title_4", "body_4")')
|
|
34
|
+
# result.affected_rows
|
|
35
|
+
# # => 2
|
|
36
|
+
#
|
|
32
37
|
# # ActiveRecord::Result also includes Enumerable.
|
|
33
38
|
# result.each do |row|
|
|
34
39
|
# puts row['title'] + " " + row['body']
|
|
@@ -36,24 +41,79 @@ module ActiveRecord
|
|
|
36
41
|
class Result
|
|
37
42
|
include Enumerable
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
class IndexedRow
|
|
45
|
+
def initialize(column_indexes, row)
|
|
46
|
+
@column_indexes = column_indexes
|
|
47
|
+
@row = row
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def size
|
|
51
|
+
@column_indexes.size
|
|
52
|
+
end
|
|
53
|
+
alias_method :length, :size
|
|
54
|
+
|
|
55
|
+
def each_key(&block)
|
|
56
|
+
@column_indexes.each_key(&block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def keys
|
|
60
|
+
@column_indexes.keys
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ==(other)
|
|
64
|
+
if other.is_a?(Hash)
|
|
65
|
+
to_hash == other
|
|
66
|
+
else
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def key?(column)
|
|
72
|
+
@column_indexes.key?(column)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fetch(column)
|
|
76
|
+
if index = @column_indexes[column]
|
|
77
|
+
@row[index]
|
|
78
|
+
elsif block_given?
|
|
79
|
+
yield
|
|
80
|
+
else
|
|
81
|
+
raise KeyError, "key not found: #{column.inspect}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def [](column)
|
|
86
|
+
if index = @column_indexes[column]
|
|
87
|
+
@row[index]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_h
|
|
92
|
+
@column_indexes.transform_values { |index| @row[index] }
|
|
93
|
+
end
|
|
94
|
+
alias_method :to_hash, :to_h
|
|
95
|
+
end
|
|
40
96
|
|
|
41
|
-
|
|
97
|
+
attr_reader :columns, :rows, :affected_rows
|
|
98
|
+
|
|
99
|
+
def self.empty(async: false, affected_rows: nil) # :nodoc:
|
|
42
100
|
if async
|
|
43
|
-
|
|
101
|
+
FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
|
|
44
102
|
else
|
|
45
|
-
|
|
103
|
+
new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
|
|
46
104
|
end
|
|
47
105
|
end
|
|
48
106
|
|
|
49
|
-
def initialize(columns, rows, column_types = nil)
|
|
107
|
+
def initialize(columns, rows, column_types = nil, affected_rows: nil)
|
|
50
108
|
# We freeze the strings to prevent them getting duped when
|
|
51
109
|
# used as keys in ActiveRecord::Base's @attributes hash
|
|
52
110
|
@columns = columns.each(&:-@).freeze
|
|
53
111
|
@rows = rows
|
|
54
112
|
@hash_rows = nil
|
|
55
|
-
@column_types = column_types
|
|
113
|
+
@column_types = column_types.freeze
|
|
114
|
+
@types_hash = nil
|
|
56
115
|
@column_indexes = nil
|
|
116
|
+
@affected_rows = affected_rows
|
|
57
117
|
end
|
|
58
118
|
|
|
59
119
|
# Returns true if this result set includes the column named +name+
|
|
@@ -67,7 +127,9 @@ module ActiveRecord
|
|
|
67
127
|
end
|
|
68
128
|
|
|
69
129
|
# Calls the given block once for each element in row collection, passing
|
|
70
|
-
# row as parameter.
|
|
130
|
+
# row as parameter. Each row is a Hash-like, read only object.
|
|
131
|
+
#
|
|
132
|
+
# To get real hashes, use +.to_a.each+.
|
|
71
133
|
#
|
|
72
134
|
# Returns an +Enumerator+ if no block is given.
|
|
73
135
|
def each(&block)
|
|
@@ -99,6 +161,24 @@ module ActiveRecord
|
|
|
99
161
|
n ? hash_rows.last(n) : hash_rows.last
|
|
100
162
|
end
|
|
101
163
|
|
|
164
|
+
# Returns the +ActiveRecord::Type+ type of all columns.
|
|
165
|
+
# Note that not all database adapters return the result types,
|
|
166
|
+
# so the hash may be empty.
|
|
167
|
+
def column_types
|
|
168
|
+
if @column_types
|
|
169
|
+
@types_hash ||= begin
|
|
170
|
+
types = {}
|
|
171
|
+
@columns.each_with_index do |name, index|
|
|
172
|
+
type = @column_types[index] || Type.default_value
|
|
173
|
+
types[name] = types[index] = type
|
|
174
|
+
end
|
|
175
|
+
types.freeze
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
EMPTY_HASH
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
102
182
|
def result # :nodoc:
|
|
103
183
|
self
|
|
104
184
|
end
|
|
@@ -107,7 +187,7 @@ module ActiveRecord
|
|
|
107
187
|
self
|
|
108
188
|
end
|
|
109
189
|
|
|
110
|
-
def cast_values(type_overrides =
|
|
190
|
+
def cast_values(type_overrides = nil) # :nodoc:
|
|
111
191
|
if columns.one?
|
|
112
192
|
# Separated to avoid allocating an array per row
|
|
113
193
|
|
|
@@ -134,14 +214,14 @@ module ActiveRecord
|
|
|
134
214
|
end
|
|
135
215
|
|
|
136
216
|
def initialize_copy(other)
|
|
137
|
-
@
|
|
138
|
-
@rows = rows.dup
|
|
139
|
-
@column_types = column_types.dup
|
|
217
|
+
@rows = rows.dup
|
|
140
218
|
@hash_rows = nil
|
|
141
219
|
end
|
|
142
220
|
|
|
143
221
|
def freeze # :nodoc:
|
|
144
222
|
hash_rows.freeze
|
|
223
|
+
indexed_rows
|
|
224
|
+
column_types
|
|
145
225
|
super
|
|
146
226
|
end
|
|
147
227
|
|
|
@@ -149,21 +229,32 @@ module ActiveRecord
|
|
|
149
229
|
@column_indexes ||= begin
|
|
150
230
|
index = 0
|
|
151
231
|
hash = {}
|
|
152
|
-
length
|
|
232
|
+
length = columns.length
|
|
153
233
|
while index < length
|
|
154
234
|
hash[columns[index]] = index
|
|
155
235
|
index += 1
|
|
156
236
|
end
|
|
157
|
-
hash
|
|
237
|
+
hash.freeze
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def indexed_rows # :nodoc:
|
|
242
|
+
@indexed_rows ||= begin
|
|
243
|
+
columns = column_indexes
|
|
244
|
+
@rows.map { |row| IndexedRow.new(columns, row) }.freeze
|
|
158
245
|
end
|
|
159
246
|
end
|
|
160
247
|
|
|
161
248
|
private
|
|
162
249
|
def column_type(name, index, type_overrides)
|
|
163
|
-
type_overrides
|
|
164
|
-
|
|
165
|
-
|
|
250
|
+
if type_overrides
|
|
251
|
+
type_overrides.fetch(name) do
|
|
252
|
+
column_type(name, index, nil)
|
|
166
253
|
end
|
|
254
|
+
elsif @column_types
|
|
255
|
+
@column_types[index] || Type.default_value
|
|
256
|
+
else
|
|
257
|
+
Type.default_value
|
|
167
258
|
end
|
|
168
259
|
end
|
|
169
260
|
|
|
@@ -175,14 +266,8 @@ module ActiveRecord
|
|
|
175
266
|
end
|
|
176
267
|
end
|
|
177
268
|
|
|
178
|
-
|
|
269
|
+
EMPTY_ARRAY = [].freeze
|
|
179
270
|
EMPTY_HASH = {}.freeze
|
|
180
|
-
private_constant :EMPTY_HASH
|
|
181
|
-
|
|
182
|
-
EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
|
|
183
|
-
private_constant :EMPTY
|
|
184
|
-
|
|
185
|
-
EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
|
|
186
|
-
private_constant :EMPTY_ASYNC
|
|
271
|
+
private_constant :EMPTY_ARRAY, :EMPTY_HASH
|
|
187
272
|
end
|
|
188
273
|
end
|
|
@@ -3,80 +3,64 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# This is a thread locals registry for Active Record. For example:
|
|
5
5
|
#
|
|
6
|
-
# ActiveRecord::RuntimeRegistry.sql_runtime
|
|
6
|
+
# ActiveRecord::RuntimeRegistry.stats.sql_runtime
|
|
7
7
|
#
|
|
8
8
|
# returns the connection handler local to the current unit of execution (either thread of fiber).
|
|
9
9
|
module RuntimeRegistry # :nodoc:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
class Stats
|
|
11
|
+
attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@sql_runtime = 0.0
|
|
15
|
+
@async_sql_runtime = 0.0
|
|
16
|
+
@queries_count = 0
|
|
17
|
+
@cached_queries_count = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_runtimes
|
|
21
|
+
sql_runtime_was = @sql_runtime
|
|
22
|
+
@sql_runtime = 0.0
|
|
23
|
+
@async_sql_runtime = 0.0
|
|
24
|
+
sql_runtime_was
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
public alias_method :reset, :initialize
|
|
14
28
|
end
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def async_sql_runtime
|
|
21
|
-
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
|
|
22
|
-
end
|
|
30
|
+
extend self
|
|
23
31
|
|
|
24
|
-
def
|
|
25
|
-
|
|
32
|
+
def call(name, start, finish, id, payload)
|
|
33
|
+
record(
|
|
34
|
+
payload[:name],
|
|
35
|
+
(finish - start) * 1_000.0,
|
|
36
|
+
cached: payload[:cached],
|
|
37
|
+
async: payload[:async],
|
|
38
|
+
lock_wait: payload[:lock_wait],
|
|
39
|
+
)
|
|
26
40
|
end
|
|
27
41
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
end
|
|
42
|
+
def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
|
|
43
|
+
stats = self.stats
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
unless query_name == "TRANSACTION" || query_name == "SCHEMA"
|
|
46
|
+
stats.queries_count += 1
|
|
47
|
+
stats.cached_queries_count += 1 if cached
|
|
48
|
+
end
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
if async
|
|
51
|
+
stats.async_sql_runtime += (runtime - lock_wait)
|
|
52
|
+
end
|
|
53
|
+
stats.sql_runtime += runtime
|
|
38
54
|
end
|
|
39
55
|
|
|
40
|
-
def
|
|
41
|
-
ActiveSupport::IsolatedExecutionState[:
|
|
56
|
+
def stats
|
|
57
|
+
ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
|
|
42
58
|
end
|
|
43
59
|
|
|
44
60
|
def reset
|
|
45
|
-
|
|
46
|
-
reset_queries_count
|
|
47
|
-
reset_cached_queries_count
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def reset_runtimes
|
|
51
|
-
rt, self.sql_runtime = sql_runtime, 0.0
|
|
52
|
-
self.async_sql_runtime = 0.0
|
|
53
|
-
rt
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def reset_queries_count
|
|
57
|
-
qc = queries_count
|
|
58
|
-
self.queries_count = 0
|
|
59
|
-
qc
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def reset_cached_queries_count
|
|
63
|
-
qc = cached_queries_count
|
|
64
|
-
self.cached_queries_count = 0
|
|
65
|
-
qc
|
|
61
|
+
stats.reset
|
|
66
62
|
end
|
|
67
63
|
end
|
|
68
64
|
end
|
|
69
65
|
|
|
70
|
-
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record"
|
|
71
|
-
unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
|
|
72
|
-
ActiveRecord::RuntimeRegistry.queries_count += 1
|
|
73
|
-
ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
runtime = (finish - start) * 1_000.0
|
|
77
|
-
|
|
78
|
-
if payload[:async]
|
|
79
|
-
ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
|
|
80
|
-
end
|
|
81
|
-
ActiveRecord::RuntimeRegistry.sql_runtime += runtime
|
|
82
|
-
end
|
|
66
|
+
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
|
|
@@ -105,12 +105,13 @@ module ActiveRecord
|
|
|
105
105
|
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
|
|
106
106
|
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
|
|
107
107
|
def sanitize_sql_hash_for_assignment(attrs, table)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
with_connection do |c|
|
|
109
|
+
attrs.map do |attr, value|
|
|
110
|
+
type = type_for_attribute(attr)
|
|
111
|
+
value = type.serialize(type.cast(value))
|
|
112
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
|
113
|
+
end.join(", ")
|
|
114
|
+
end
|
|
114
115
|
end
|
|
115
116
|
|
|
116
117
|
# Sanitizes a +string+ so that it is safe to use within an SQL
|
|
@@ -160,6 +161,8 @@ module ActiveRecord
|
|
|
160
161
|
#
|
|
161
162
|
# sanitize_sql_array(["role = ?", 0])
|
|
162
163
|
# # => "role = '0'"
|
|
164
|
+
#
|
|
165
|
+
# Before using this method, please consider if Arel.sql would be better for your use-case
|
|
163
166
|
def sanitize_sql_array(ary)
|
|
164
167
|
statement, *values = ary
|
|
165
168
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
|
@@ -63,6 +63,7 @@ module ActiveRecord
|
|
|
63
63
|
extensions(stream)
|
|
64
64
|
types(stream)
|
|
65
65
|
tables(stream)
|
|
66
|
+
virtual_tables(stream)
|
|
66
67
|
trailer(stream)
|
|
67
68
|
stream
|
|
68
69
|
end
|
|
@@ -126,6 +127,10 @@ module ActiveRecord
|
|
|
126
127
|
def schemas(stream)
|
|
127
128
|
end
|
|
128
129
|
|
|
130
|
+
# virtual tables are only supported by SQLite
|
|
131
|
+
def virtual_tables(stream)
|
|
132
|
+
end
|
|
133
|
+
|
|
129
134
|
def tables(stream)
|
|
130
135
|
sorted_tables = @connection.tables.sort
|
|
131
136
|
|
|
@@ -160,7 +165,7 @@ module ActiveRecord
|
|
|
160
165
|
# first dump primary key column
|
|
161
166
|
pk = @connection.primary_key(table)
|
|
162
167
|
|
|
163
|
-
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
168
|
+
tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
|
|
164
169
|
|
|
165
170
|
case pk
|
|
166
171
|
when String
|
|
@@ -187,7 +192,7 @@ module ActiveRecord
|
|
|
187
192
|
tbl.puts ", force: :cascade do |t|"
|
|
188
193
|
|
|
189
194
|
# then dump all non-primary key columns
|
|
190
|
-
columns.each do |column|
|
|
195
|
+
columns.sort_by(&:name).each do |column|
|
|
191
196
|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
192
197
|
next if column.name == pk
|
|
193
198
|
|
|
@@ -227,8 +232,8 @@ module ActiveRecord
|
|
|
227
232
|
def indexes(table, stream)
|
|
228
233
|
if (indexes = @connection.indexes(table)).any?
|
|
229
234
|
add_index_statements = indexes.map do |index|
|
|
230
|
-
table_name = remove_prefix_and_suffix(index.table)
|
|
231
|
-
" add_index #{([table_name] + index_parts(index)).join(', ')}"
|
|
235
|
+
table_name = remove_prefix_and_suffix(index.table)
|
|
236
|
+
" add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}"
|
|
232
237
|
end
|
|
233
238
|
|
|
234
239
|
stream.puts add_index_statements.sort.join("\n")
|
|
@@ -272,6 +277,7 @@ module ActiveRecord
|
|
|
272
277
|
index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
|
|
273
278
|
index_parts << "type: #{index.type.inspect}" if index.type
|
|
274
279
|
index_parts << "comment: #{index.comment.inspect}" if index.comment
|
|
280
|
+
index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
|
|
275
281
|
index_parts
|
|
276
282
|
end
|
|
277
283
|
|
|
@@ -312,8 +318,8 @@ module ActiveRecord
|
|
|
312
318
|
if (foreign_keys = @connection.foreign_keys(table)).any?
|
|
313
319
|
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
|
314
320
|
parts = [
|
|
315
|
-
|
|
316
|
-
remove_prefix_and_suffix(foreign_key.to_table).inspect,
|
|
321
|
+
relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
|
|
322
|
+
relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
|
|
317
323
|
]
|
|
318
324
|
|
|
319
325
|
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
|
|
@@ -324,16 +330,13 @@ module ActiveRecord
|
|
|
324
330
|
parts << "primary_key: #{foreign_key.primary_key.inspect}"
|
|
325
331
|
end
|
|
326
332
|
|
|
327
|
-
if foreign_key.export_name_on_schema_dump?
|
|
328
|
-
parts << "name: #{foreign_key.name.inspect}"
|
|
329
|
-
end
|
|
330
|
-
|
|
333
|
+
parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
|
|
331
334
|
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
|
|
332
335
|
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
|
|
333
336
|
parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
|
|
334
337
|
parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
|
|
335
338
|
|
|
336
|
-
" #{parts.join(', ')}"
|
|
339
|
+
" add_foreign_key #{parts.join(', ')}"
|
|
337
340
|
end
|
|
338
341
|
|
|
339
342
|
stream.puts add_foreign_key_statements.sort.join("\n")
|
|
@@ -358,6 +361,10 @@ module ActiveRecord
|
|
|
358
361
|
end
|
|
359
362
|
end
|
|
360
363
|
|
|
364
|
+
def relation_name(name)
|
|
365
|
+
name
|
|
366
|
+
end
|
|
367
|
+
|
|
361
368
|
def remove_prefix_and_suffix(table)
|
|
362
369
|
# This method appears at the top when profiling active_record test cases run.
|
|
363
370
|
# Avoid costly calculation when there are no prefix and suffix.
|
|
@@ -34,7 +34,8 @@ module ActiveRecord
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def delete_all_versions
|
|
37
|
-
|
|
37
|
+
# Eagerly check in connection to avoid checking in/out many times in the called method.
|
|
38
|
+
@pool.with_connection do
|
|
38
39
|
versions.each do |version|
|
|
39
40
|
delete_version(version)
|
|
40
41
|
end
|
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
|
23
23
|
scope = current_scope
|
|
24
24
|
|
|
25
25
|
if scope
|
|
26
|
-
if self == scope.
|
|
26
|
+
if self == scope.model
|
|
27
27
|
scope.clone
|
|
28
28
|
else
|
|
29
29
|
relation.merge!(scope)
|
|
@@ -191,7 +191,10 @@ module ActiveRecord
|
|
|
191
191
|
private
|
|
192
192
|
def singleton_method_added(name)
|
|
193
193
|
super
|
|
194
|
-
|
|
194
|
+
# Most Kernel extends are both singleton and instance methods so
|
|
195
|
+
# respond_to is a fast check, but we don't want to define methods
|
|
196
|
+
# only on the module (ex. Module#name)
|
|
197
|
+
generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
|
|
195
198
|
end
|
|
196
199
|
end
|
|
197
200
|
end
|
|
@@ -6,11 +6,27 @@ module ActiveRecord
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
+
class_attribute :_signed_id_verifier, instance_accessor: false, instance_predicate: false
|
|
10
|
+
|
|
9
11
|
##
|
|
10
12
|
# :singleton-method:
|
|
11
13
|
# Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
|
|
12
14
|
# Within \Rails, this is automatically set using the \Rails application key generator.
|
|
13
15
|
class_attribute :signed_id_verifier_secret, instance_writer: false
|
|
16
|
+
module DeprecateSignedIdVerifierSecret
|
|
17
|
+
def signed_id_verifier_secret=(secret)
|
|
18
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
19
|
+
ActiveRecord::Base.signed_id_verifier_secret is deprecated and will be removed in Rails 8.2.
|
|
20
|
+
|
|
21
|
+
If the secret is model-specific, set Model.signed_id_verifier instead.
|
|
22
|
+
|
|
23
|
+
Otherwise, configure Rails.application.message_verifiers (or ActiveRecord.message_verifiers) with the secret.
|
|
24
|
+
MSG
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
singleton_class.prepend DeprecateSignedIdVerifierSecret
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
module RelationMethods # :nodoc:
|
|
@@ -49,10 +65,11 @@ module ActiveRecord
|
|
|
49
65
|
#
|
|
50
66
|
# travel_back
|
|
51
67
|
# User.find_signed signed_id, purpose: :password_reset # => User.first
|
|
52
|
-
def find_signed(signed_id, purpose: nil)
|
|
68
|
+
def find_signed(signed_id, purpose: nil, on_rotation: nil)
|
|
53
69
|
raise UnknownPrimaryKey.new(self) if primary_key.nil?
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
options = { on_rotation: on_rotation }.compact
|
|
72
|
+
if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
56
73
|
find_by primary_key => id
|
|
57
74
|
end
|
|
58
75
|
end
|
|
@@ -69,26 +86,33 @@ module ActiveRecord
|
|
|
69
86
|
# signed_id = User.first.signed_id
|
|
70
87
|
# User.first.destroy
|
|
71
88
|
# User.find_signed! signed_id # => ActiveRecord::RecordNotFound
|
|
72
|
-
def find_signed!(signed_id, purpose: nil)
|
|
73
|
-
|
|
89
|
+
def find_signed!(signed_id, purpose: nil, on_rotation: nil)
|
|
90
|
+
options = { on_rotation: on_rotation }.compact
|
|
91
|
+
if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
74
92
|
find(id)
|
|
75
93
|
end
|
|
76
94
|
end
|
|
77
95
|
|
|
78
|
-
# The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
|
|
79
|
-
# with the class-level +signed_id_verifier_secret+, which within Rails comes from
|
|
80
|
-
# {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
|
|
81
|
-
# By default, it's SHA256 for the digest and JSON for the serialization.
|
|
82
96
|
def signed_id_verifier
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
if signed_id_verifier_secret
|
|
98
|
+
@signed_id_verifier ||= begin
|
|
99
|
+
secret = signed_id_verifier_secret
|
|
100
|
+
secret = secret.call if secret.respond_to?(:call)
|
|
101
|
+
|
|
102
|
+
if secret.nil?
|
|
103
|
+
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed IDs"
|
|
104
|
+
end
|
|
86
105
|
|
|
87
|
-
if secret.nil?
|
|
88
|
-
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
|
|
89
|
-
else
|
|
90
106
|
ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
|
|
91
107
|
end
|
|
108
|
+
else
|
|
109
|
+
return _signed_id_verifier if _signed_id_verifier
|
|
110
|
+
|
|
111
|
+
if ActiveRecord.message_verifiers.nil?
|
|
112
|
+
raise "You must set ActiveRecord.message_verifiers to use signed IDs"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
ActiveRecord.message_verifiers["active_record/signed_id"]
|
|
92
116
|
end
|
|
93
117
|
end
|
|
94
118
|
|
|
@@ -96,7 +120,11 @@ module ActiveRecord
|
|
|
96
120
|
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
|
|
97
121
|
# your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
|
|
98
122
|
def signed_id_verifier=(verifier)
|
|
99
|
-
|
|
123
|
+
if signed_id_verifier_secret
|
|
124
|
+
@signed_id_verifier = verifier
|
|
125
|
+
else
|
|
126
|
+
self._signed_id_verifier = verifier
|
|
127
|
+
end
|
|
100
128
|
end
|
|
101
129
|
|
|
102
130
|
# :nodoc:
|