activerecord 7.0.8 → 7.1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1554 -1452
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +15 -9
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +31 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +139 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +142 -148
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +174 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +352 -63
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +48 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -6,7 +6,6 @@ require "active_record/relation/merger"
|
|
6
6
|
|
7
7
|
module ActiveRecord
|
8
8
|
module SpawnMethods
|
9
|
-
# This is overridden by Associations::CollectionProxy
|
10
9
|
def spawn # :nodoc:
|
11
10
|
already_in_scope?(klass.scope_registry) ? klass.all : clone
|
12
11
|
end
|
@@ -28,6 +27,9 @@ module ActiveRecord
|
|
28
27
|
# # => Post.where(published: true).joins(:comments)
|
29
28
|
#
|
30
29
|
# This is mainly intended for sharing common conditions between multiple associations.
|
30
|
+
#
|
31
|
+
# For conditions that exist in both relations, those from <tt>other</tt> will take precedence.
|
32
|
+
# To find the intersection of two relations, use QueryMethods#and.
|
31
33
|
def merge(other, *rest)
|
32
34
|
if other.is_a?(Array)
|
33
35
|
records & other
|
@@ -40,6 +42,21 @@ module ActiveRecord
|
|
40
42
|
|
41
43
|
def merge!(other, *rest) # :nodoc:
|
42
44
|
options = rest.extract_options!
|
45
|
+
|
46
|
+
if options.key?(:rewhere)
|
47
|
+
if options[:rewhere]
|
48
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
49
|
+
Specifying `Relation#merge(rewhere: true)` is deprecated, as that has now been
|
50
|
+
the default since Rails 7.0. Setting the rewhere option will error in Rails 7.2
|
51
|
+
MSG
|
52
|
+
else
|
53
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
54
|
+
`Relation#merge(rewhere: false)` is deprecated without replacement,
|
55
|
+
and will be removed in Rails 7.2
|
56
|
+
MSG
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
43
60
|
if other.is_a?(Hash)
|
44
61
|
Relation::HashMerger.new(self, other, options[:rewhere]).merge
|
45
62
|
elsif other.is_a?(Relation)
|
@@ -5,13 +5,14 @@ module ActiveRecord
|
|
5
5
|
class Relation
|
6
6
|
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
|
7
7
|
:order, :joins, :left_outer_joins, :references,
|
8
|
-
:extending, :unscope, :optimizer_hints, :annotate
|
8
|
+
:extending, :unscope, :optimizer_hints, :annotate,
|
9
|
+
:with]
|
9
10
|
|
10
11
|
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
|
11
12
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
12
13
|
|
13
14
|
CLAUSE_METHODS = [:where, :having, :from]
|
14
|
-
INVALID_METHODS_FOR_DELETE_ALL = [:distinct]
|
15
|
+
INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with]
|
15
16
|
|
16
17
|
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
|
17
18
|
|
@@ -33,6 +34,8 @@ module ActiveRecord
|
|
33
34
|
@delegate_to_klass = false
|
34
35
|
@future_result = nil
|
35
36
|
@records = nil
|
37
|
+
@async = false
|
38
|
+
@none = false
|
36
39
|
end
|
37
40
|
|
38
41
|
def initialize_copy(other)
|
@@ -43,7 +46,7 @@ module ActiveRecord
|
|
43
46
|
def bind_attribute(name, value) # :nodoc:
|
44
47
|
if reflection = klass._reflect_on_association(name)
|
45
48
|
name = reflection.foreign_key
|
46
|
-
value = value.read_attribute(reflection.
|
49
|
+
value = value.read_attribute(reflection.association_primary_key) unless value.nil?
|
47
50
|
end
|
48
51
|
|
49
52
|
attr = table[name]
|
@@ -159,21 +162,25 @@ module ActiveRecord
|
|
159
162
|
# failed due to validation errors it won't be persisted, you get what
|
160
163
|
# #create returns in such situation.
|
161
164
|
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
165
|
+
# If creation failed because of a unique constraint, this method will
|
166
|
+
# assume it encountered a race condition and will try finding the record
|
167
|
+
# once more. If somehow the second find still does not find a record
|
168
|
+
# because a concurrent DELETE happened, it will then raise an
|
169
|
+
# ActiveRecord::RecordNotFound exception.
|
166
170
|
#
|
167
|
-
#
|
171
|
+
# Please note <b>this method is not atomic</b>, it runs first a SELECT,
|
172
|
+
# and if there are no results an INSERT is attempted. So if the table
|
173
|
+
# doesn't have a relevant unique constraint it could be the case that
|
174
|
+
# you end up with two or more similar records.
|
168
175
|
def find_or_create_by(attributes, &block)
|
169
|
-
find_by(attributes) ||
|
176
|
+
find_by(attributes) || create_or_find_by(attributes, &block)
|
170
177
|
end
|
171
178
|
|
172
179
|
# Like #find_or_create_by, but calls
|
173
180
|
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
|
174
181
|
# is raised if the created record is invalid.
|
175
182
|
def find_or_create_by!(attributes, &block)
|
176
|
-
find_by(attributes) ||
|
183
|
+
find_by(attributes) || create_or_find_by!(attributes, &block)
|
177
184
|
end
|
178
185
|
|
179
186
|
# Attempts to create a record with the given attributes in a table that has a unique database constraint
|
@@ -181,16 +188,15 @@ module ActiveRecord
|
|
181
188
|
# unique constraints, the exception such an insertion would normally raise is caught,
|
182
189
|
# and the existing record with those attributes is found using #find_by!.
|
183
190
|
#
|
184
|
-
# This is similar to #find_or_create_by, but
|
185
|
-
#
|
186
|
-
# if none is found.
|
191
|
+
# This is similar to #find_or_create_by, but tries to create the record first. As such it is
|
192
|
+
# better suited for cases where the record is most likely not to exist yet.
|
187
193
|
#
|
188
194
|
# There are several drawbacks to #create_or_find_by, though:
|
189
195
|
#
|
190
196
|
# * The underlying table must have the relevant columns defined with unique database constraints.
|
191
197
|
# * A unique constraint violation may be triggered by only one, or at least less than all,
|
192
198
|
# of the given attributes. This means that the subsequent #find_by! may fail to find a
|
193
|
-
# matching record, which will then raise an
|
199
|
+
# matching record, which will then raise an ActiveRecord::RecordNotFound exception,
|
194
200
|
# rather than a record with the given attributes.
|
195
201
|
# * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
|
196
202
|
# we actually have another race condition between INSERT -> SELECT, which can be triggered
|
@@ -199,7 +205,7 @@ module ActiveRecord
|
|
199
205
|
# * It relies on exception handling to handle control flow, which may be marginally slower.
|
200
206
|
# * The primary key may auto-increment on each create, even if it fails. This can accelerate
|
201
207
|
# the problem of running out of integers, if the underlying table is still stuck on a primary
|
202
|
-
# key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
208
|
+
# key of type int (note: All \Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
203
209
|
# to this problem).
|
204
210
|
#
|
205
211
|
# This method will return a record if all given attributes are covered by unique constraints
|
@@ -209,7 +215,11 @@ module ActiveRecord
|
|
209
215
|
def create_or_find_by(attributes, &block)
|
210
216
|
transaction(requires_new: true) { create(attributes, &block) }
|
211
217
|
rescue ActiveRecord::RecordNotUnique
|
212
|
-
|
218
|
+
if connection.transaction_open?
|
219
|
+
where(attributes).lock.find_by!(attributes)
|
220
|
+
else
|
221
|
+
find_by!(attributes)
|
222
|
+
end
|
213
223
|
end
|
214
224
|
|
215
225
|
# Like #create_or_find_by, but calls
|
@@ -218,7 +228,11 @@ module ActiveRecord
|
|
218
228
|
def create_or_find_by!(attributes, &block)
|
219
229
|
transaction(requires_new: true) { create!(attributes, &block) }
|
220
230
|
rescue ActiveRecord::RecordNotUnique
|
221
|
-
|
231
|
+
if connection.transaction_open?
|
232
|
+
where(attributes).lock.find_by!(attributes)
|
233
|
+
else
|
234
|
+
find_by!(attributes)
|
235
|
+
end
|
222
236
|
end
|
223
237
|
|
224
238
|
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
|
@@ -236,8 +250,8 @@ module ActiveRecord
|
|
236
250
|
#
|
237
251
|
# Please see further details in the
|
238
252
|
# {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
|
239
|
-
def explain
|
240
|
-
exec_explain(collecting_queries_for_explain { exec_queries })
|
253
|
+
def explain(*options)
|
254
|
+
exec_explain(collecting_queries_for_explain { exec_queries }, options)
|
241
255
|
end
|
242
256
|
|
243
257
|
# Converts relation objects to Array.
|
@@ -267,6 +281,8 @@ module ActiveRecord
|
|
267
281
|
|
268
282
|
# Returns true if there are no records.
|
269
283
|
def empty?
|
284
|
+
return true if @none
|
285
|
+
|
270
286
|
if loaded?
|
271
287
|
records.empty?
|
272
288
|
else
|
@@ -275,26 +291,49 @@ module ActiveRecord
|
|
275
291
|
end
|
276
292
|
|
277
293
|
# Returns true if there are no records.
|
278
|
-
|
279
|
-
|
294
|
+
#
|
295
|
+
# When a pattern argument is given, this method checks whether elements in
|
296
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
297
|
+
#
|
298
|
+
# posts.none?(Comment) # => true or false
|
299
|
+
def none?(*args)
|
300
|
+
return true if @none
|
301
|
+
|
302
|
+
return super if args.present? || block_given?
|
280
303
|
empty?
|
281
304
|
end
|
282
305
|
|
283
306
|
# Returns true if there are any records.
|
284
|
-
|
285
|
-
|
307
|
+
#
|
308
|
+
# When a pattern argument is given, this method checks whether elements in
|
309
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
310
|
+
#
|
311
|
+
# posts.any?(Post) # => true or false
|
312
|
+
def any?(*args)
|
313
|
+
return false if @none
|
314
|
+
|
315
|
+
return super if args.present? || block_given?
|
286
316
|
!empty?
|
287
317
|
end
|
288
318
|
|
289
319
|
# Returns true if there is exactly one record.
|
290
|
-
|
291
|
-
|
320
|
+
#
|
321
|
+
# When a pattern argument is given, this method checks whether elements in
|
322
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
323
|
+
#
|
324
|
+
# posts.one?(Post) # => true or false
|
325
|
+
def one?(*args)
|
326
|
+
return false if @none
|
327
|
+
|
328
|
+
return super if args.present? || block_given?
|
292
329
|
return records.one? if loaded?
|
293
330
|
limited_count == 1
|
294
331
|
end
|
295
332
|
|
296
333
|
# Returns true if there is more than one record.
|
297
334
|
def many?
|
335
|
+
return false if @none
|
336
|
+
|
298
337
|
return super if block_given?
|
299
338
|
return records.many? if loaded?
|
300
339
|
limited_count > 1
|
@@ -307,7 +346,7 @@ module ActiveRecord
|
|
307
346
|
# # => "products/query-1850ab3d302391b85b8693e941286659"
|
308
347
|
#
|
309
348
|
# If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
|
310
|
-
# in Rails 6.0 and earlier, the cache key will also include a version.
|
349
|
+
# in \Rails 6.0 and earlier, the cache key will also include a version.
|
311
350
|
#
|
312
351
|
# ActiveRecord::Base.collection_cache_versioning = false
|
313
352
|
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
@@ -409,7 +448,7 @@ module ActiveRecord
|
|
409
448
|
# Comment.where(post_id: 1).scoping do
|
410
449
|
# Comment.first
|
411
450
|
# end
|
412
|
-
# #
|
451
|
+
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
|
413
452
|
#
|
414
453
|
# If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
|
415
454
|
# for the relation including +update+ and +delete+ on instances.
|
@@ -446,7 +485,8 @@ module ActiveRecord
|
|
446
485
|
#
|
447
486
|
# ==== Parameters
|
448
487
|
#
|
449
|
-
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
488
|
+
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement. Any strings provided will
|
489
|
+
# be type cast, unless you use +Arel.sql+. (Don't pass user-provided values to +Arel.sql+.)
|
450
490
|
#
|
451
491
|
# ==== Examples
|
452
492
|
#
|
@@ -461,9 +501,14 @@ module ActiveRecord
|
|
461
501
|
#
|
462
502
|
# # Update all invoices and set the number column to its id value.
|
463
503
|
# Invoice.update_all('number = id')
|
504
|
+
#
|
505
|
+
# # Update all books with 'Rails' in their title
|
506
|
+
# Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
|
464
507
|
def update_all(updates)
|
465
508
|
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
466
509
|
|
510
|
+
return 0 if @none
|
511
|
+
|
467
512
|
if updates.is_a?(Hash)
|
468
513
|
if klass.locking_enabled? &&
|
469
514
|
!updates.key?(klass.locking_column) &&
|
@@ -599,6 +644,8 @@ module ActiveRecord
|
|
599
644
|
# Post.distinct.delete_all
|
600
645
|
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
|
601
646
|
def delete_all
|
647
|
+
return 0 if @none
|
648
|
+
|
602
649
|
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
603
650
|
value = @values[method]
|
604
651
|
method == :distinct ? value : value&.any?
|
@@ -720,7 +767,7 @@ module ActiveRecord
|
|
720
767
|
# Returns sql statement for the relation.
|
721
768
|
#
|
722
769
|
# User.where(name: 'Oscar').to_sql
|
723
|
-
# #
|
770
|
+
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
|
724
771
|
def to_sql
|
725
772
|
@to_sql ||= if eager_loading?
|
726
773
|
apply_join_dependency do |relation, join_dependency|
|
@@ -774,8 +821,13 @@ module ActiveRecord
|
|
774
821
|
end
|
775
822
|
end
|
776
823
|
|
777
|
-
def pretty_print(
|
778
|
-
|
824
|
+
def pretty_print(pp)
|
825
|
+
subject = loaded? ? records : annotate("loading for pp")
|
826
|
+
entries = subject.take([limit_value, 11].compact.min)
|
827
|
+
|
828
|
+
entries[10] = "..." if entries.size == 11
|
829
|
+
|
830
|
+
pp.pp(entries)
|
779
831
|
end
|
780
832
|
|
781
833
|
# Returns true if relation is blank.
|
@@ -837,10 +889,6 @@ module ActiveRecord
|
|
837
889
|
@loaded = true
|
838
890
|
end
|
839
891
|
|
840
|
-
def null_relation? # :nodoc:
|
841
|
-
is_a?(NullRelation)
|
842
|
-
end
|
843
|
-
|
844
892
|
private
|
845
893
|
def already_in_scope?(registry)
|
846
894
|
@delegate_to_klass && registry.current_scope(klass, true)
|
@@ -925,6 +973,14 @@ module ActiveRecord
|
|
925
973
|
end
|
926
974
|
|
927
975
|
def exec_main_query(async: false)
|
976
|
+
if @none
|
977
|
+
if async
|
978
|
+
return FutureResult::Complete.new([])
|
979
|
+
else
|
980
|
+
return []
|
981
|
+
end
|
982
|
+
end
|
983
|
+
|
928
984
|
skip_query_cache_if_necessary do
|
929
985
|
if where_clause.contradiction?
|
930
986
|
[].freeze
|
data/lib/active_record/result.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
###
|
5
|
+
# = Active Record \Result
|
6
|
+
#
|
5
7
|
# This class encapsulates a result returned from calling
|
6
8
|
# {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
|
7
9
|
# on any database connection adapter. For example:
|
@@ -36,8 +38,12 @@ module ActiveRecord
|
|
36
38
|
|
37
39
|
attr_reader :columns, :rows, :column_types
|
38
40
|
|
39
|
-
def self.empty # :nodoc:
|
40
|
-
|
41
|
+
def self.empty(async: false) # :nodoc:
|
42
|
+
if async
|
43
|
+
EMPTY_ASYNC
|
44
|
+
else
|
45
|
+
EMPTY
|
46
|
+
end
|
41
47
|
end
|
42
48
|
|
43
49
|
def initialize(columns, rows, column_types = {})
|
@@ -47,9 +53,6 @@ module ActiveRecord
|
|
47
53
|
@column_types = column_types
|
48
54
|
end
|
49
55
|
|
50
|
-
EMPTY = new([].freeze, [].freeze, {}.freeze)
|
51
|
-
private_constant :EMPTY
|
52
|
-
|
53
56
|
# Returns true if this result set includes the column named +name+
|
54
57
|
def includes_column?(name)
|
55
58
|
@columns.include? name
|
@@ -134,6 +137,11 @@ module ActiveRecord
|
|
134
137
|
@hash_rows = nil
|
135
138
|
end
|
136
139
|
|
140
|
+
def freeze # :nodoc:
|
141
|
+
hash_rows.freeze
|
142
|
+
super
|
143
|
+
end
|
144
|
+
|
137
145
|
private
|
138
146
|
def column_type(name, index, type_overrides)
|
139
147
|
type_overrides.fetch(name) do
|
@@ -183,5 +191,11 @@ module ActiveRecord
|
|
183
191
|
}
|
184
192
|
end
|
185
193
|
end
|
194
|
+
|
195
|
+
EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
|
196
|
+
private_constant :EMPTY
|
197
|
+
|
198
|
+
EMPTY_ASYNC = FutureResult::Complete.new(EMPTY).freeze
|
199
|
+
private_constant :EMPTY_ASYNC
|
186
200
|
end
|
187
201
|
end
|
@@ -10,11 +10,34 @@ module ActiveRecord
|
|
10
10
|
extend self
|
11
11
|
|
12
12
|
def sql_runtime
|
13
|
-
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime]
|
13
|
+
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
|
14
14
|
end
|
15
15
|
|
16
16
|
def sql_runtime=(runtime)
|
17
17
|
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
|
18
18
|
end
|
19
|
+
|
20
|
+
def async_sql_runtime
|
21
|
+
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
|
22
|
+
end
|
23
|
+
|
24
|
+
def async_sql_runtime=(runtime)
|
25
|
+
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
|
26
|
+
end
|
27
|
+
|
28
|
+
def reset
|
29
|
+
rt, self.sql_runtime = sql_runtime, 0.0
|
30
|
+
self.async_sql_runtime = 0.0
|
31
|
+
rt
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
|
37
|
+
runtime = (finish - start) * 1_000.0
|
38
|
+
|
39
|
+
if payload[:async]
|
40
|
+
ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
|
19
41
|
end
|
42
|
+
ActiveRecord::RuntimeRegistry.sql_runtime += runtime
|
20
43
|
end
|
@@ -5,8 +5,8 @@ module ActiveRecord
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
# Accepts an array
|
9
|
-
#
|
8
|
+
# Accepts an array of SQL conditions and sanitizes them into a valid
|
9
|
+
# SQL fragment for a WHERE clause.
|
10
10
|
#
|
11
11
|
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
|
12
12
|
# # => "name='foo''bar' and group_id=4"
|
@@ -17,8 +17,19 @@ module ActiveRecord
|
|
17
17
|
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
|
18
18
|
# # => "name='foo''bar' and group_id='4'"
|
19
19
|
#
|
20
|
+
# This method will NOT sanitize a SQL string since it won't contain
|
21
|
+
# any conditions in it and will return the string as is.
|
22
|
+
#
|
20
23
|
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
|
21
24
|
# # => "name='foo''bar' and group_id='4'"
|
25
|
+
#
|
26
|
+
# Note that this sanitization method is not schema-aware, hence won't do any type casting
|
27
|
+
# and will directly use the database adapter's +quote+ method.
|
28
|
+
# For MySQL specifically this means that numeric parameters will be quoted as strings
|
29
|
+
# to prevent query manipulation attacks.
|
30
|
+
#
|
31
|
+
# sanitize_sql_for_conditions(["role = ?", 0])
|
32
|
+
# # => "role = '0'"
|
22
33
|
def sanitize_sql_for_conditions(condition)
|
23
34
|
return nil if condition.blank?
|
24
35
|
|
@@ -29,8 +40,8 @@ module ActiveRecord
|
|
29
40
|
end
|
30
41
|
alias :sanitize_sql :sanitize_sql_for_conditions
|
31
42
|
|
32
|
-
# Accepts an array
|
33
|
-
#
|
43
|
+
# Accepts an array or hash of SQL conditions and sanitizes them into
|
44
|
+
# a valid SQL fragment for a SET clause.
|
34
45
|
#
|
35
46
|
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
|
36
47
|
# # => "name=NULL and group_id=4"
|
@@ -41,8 +52,19 @@ module ActiveRecord
|
|
41
52
|
# Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
|
42
53
|
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
|
43
54
|
#
|
55
|
+
# This method will NOT sanitize a SQL string since it won't contain
|
56
|
+
# any conditions in it and will return the string as is.
|
57
|
+
#
|
44
58
|
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
|
45
59
|
# # => "name=NULL and group_id='4'"
|
60
|
+
#
|
61
|
+
# Note that this sanitization method is not schema-aware, hence won't do any type casting
|
62
|
+
# and will directly use the database adapter's +quote+ method.
|
63
|
+
# For MySQL specifically this means that numeric parameters will be quoted as strings
|
64
|
+
# to prevent query manipulation attacks.
|
65
|
+
#
|
66
|
+
# sanitize_sql_for_assignment(["role = ?", 0])
|
67
|
+
# # => "role = '0'"
|
46
68
|
def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
|
47
69
|
case assignments
|
48
70
|
when Array; sanitize_sql_array(assignments)
|
@@ -107,12 +129,17 @@ module ActiveRecord
|
|
107
129
|
# sanitize_sql_like("snake_cased_string", "!")
|
108
130
|
# # => "snake!_cased!_string"
|
109
131
|
def sanitize_sql_like(string, escape_character = "\\")
|
110
|
-
|
111
|
-
|
132
|
+
if string.include?(escape_character) && escape_character != "%" && escape_character != "_"
|
133
|
+
string = string.gsub(escape_character, '\0\0')
|
134
|
+
end
|
135
|
+
|
136
|
+
string.gsub(/(?=[%_])/, escape_character)
|
112
137
|
end
|
113
138
|
|
114
139
|
# Accepts an array of conditions. The array has each value
|
115
|
-
# sanitized and interpolated into the SQL statement.
|
140
|
+
# sanitized and interpolated into the SQL statement. If using named bind
|
141
|
+
# variables in SQL statements where a colon is required verbatim use a
|
142
|
+
# backslash to escape.
|
116
143
|
#
|
117
144
|
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
118
145
|
# # => "name='foo''bar' and group_id=4"
|
@@ -120,8 +147,19 @@ module ActiveRecord
|
|
120
147
|
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
121
148
|
# # => "name='foo''bar' and group_id=4"
|
122
149
|
#
|
150
|
+
# sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
|
151
|
+
# # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"
|
152
|
+
#
|
123
153
|
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
124
154
|
# # => "name='foo''bar' and group_id='4'"
|
155
|
+
#
|
156
|
+
# Note that this sanitization method is not schema-aware, hence won't do any type casting
|
157
|
+
# and will directly use the database adapter's +quote+ method.
|
158
|
+
# For MySQL specifically this means that numeric parameters will be quoted as strings
|
159
|
+
# to prevent query manipulation attacks.
|
160
|
+
#
|
161
|
+
# sanitize_sql_array(["role = ?", 0])
|
162
|
+
# # => "role = '0'"
|
125
163
|
def sanitize_sql_array(ary)
|
126
164
|
statement, *values = ary
|
127
165
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
@@ -173,9 +211,11 @@ module ActiveRecord
|
|
173
211
|
end
|
174
212
|
|
175
213
|
def replace_named_bind_variables(statement, bind_vars)
|
176
|
-
statement.gsub(/(
|
214
|
+
statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
|
177
215
|
if $1 == ":" # skip postgresql casts
|
178
216
|
match # return the whole match
|
217
|
+
elsif $1 == "\\" # escaped literal colon
|
218
|
+
match[1..-1] # return match with escaping backlash char removed
|
179
219
|
elsif bind_vars.include?(match = $2.to_sym)
|
180
220
|
replace_bind_variable(bind_vars[match])
|
181
221
|
else
|
@@ -188,13 +228,13 @@ module ActiveRecord
|
|
188
228
|
if value.respond_to?(:map) && !value.acts_like?(:string)
|
189
229
|
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
190
230
|
if values.empty?
|
191
|
-
c.
|
231
|
+
c.quote(c.cast_bound_value(nil))
|
192
232
|
else
|
193
|
-
values.map! { |v| c.
|
233
|
+
values.map! { |v| c.quote(c.cast_bound_value(v)) }.join(",")
|
194
234
|
end
|
195
235
|
else
|
196
236
|
value = value.id_for_database if value.respond_to?(:id_for_database)
|
197
|
-
c.
|
237
|
+
c.quote(c.cast_bound_value(value))
|
198
238
|
end
|
199
239
|
end
|
200
240
|
|
data/lib/active_record/schema.rb
CHANGED
@@ -54,13 +54,12 @@ module ActiveRecord
|
|
54
54
|
def define(info, &block) # :nodoc:
|
55
55
|
instance_eval(&block)
|
56
56
|
|
57
|
+
connection.schema_migration.create_table
|
57
58
|
if info[:version].present?
|
58
|
-
connection.schema_migration.create_table
|
59
59
|
connection.assume_migrated_upto_version(info[:version])
|
60
60
|
end
|
61
61
|
|
62
|
-
|
63
|
-
ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
|
62
|
+
connection.internal_metadata.create_table_and_set_flags(connection.migration_context.current_environment)
|
64
63
|
end
|
65
64
|
end
|
66
65
|
|