activerecord 7.0.0 → 7.1.0
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 +1607 -1040
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- 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 +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- 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 +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- 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 +345 -219
- 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 +40 -26
- 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 +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- 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 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- 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 +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- 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 +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- 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 +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -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 +3 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -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 +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- 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 +358 -57
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
- 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 +45 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +136 -148
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- 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 +87 -34
- 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/disable_joins_association_relation.rb +1 -1
- 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 +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
- 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.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +114 -27
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- 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 +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- 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 +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +104 -9
- data/lib/active_record/migration/compatibility.rb +158 -64
- 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.rb +271 -117
- data/lib/active_record/model_schema.rb +82 -50
- data/lib/active_record/nested_attributes.rb +23 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +127 -61
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +142 -143
- 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 +177 -45
- 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 +200 -83
- 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 +31 -3
- 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 +25 -1
- data/lib/active_record/relation/query_methods.rb +429 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- 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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- 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/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- 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 +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.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/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- 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 +50 -15
- 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 find no record because a
|
168
|
+
# 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,34 @@ module ActiveRecord
|
|
275
291
|
end
|
276
292
|
|
277
293
|
# Returns true if there are no records.
|
278
|
-
def none?
|
279
|
-
return
|
294
|
+
def none?(*args)
|
295
|
+
return true if @none
|
296
|
+
|
297
|
+
return super if args.present? || block_given?
|
280
298
|
empty?
|
281
299
|
end
|
282
300
|
|
283
301
|
# Returns true if there are any records.
|
284
|
-
def any?
|
285
|
-
return
|
302
|
+
def any?(*args)
|
303
|
+
return false if @none
|
304
|
+
|
305
|
+
return super if args.present? || block_given?
|
286
306
|
!empty?
|
287
307
|
end
|
288
308
|
|
289
309
|
# Returns true if there is exactly one record.
|
290
|
-
def one?
|
291
|
-
return
|
310
|
+
def one?(*args)
|
311
|
+
return false if @none
|
312
|
+
|
313
|
+
return super if args.present? || block_given?
|
292
314
|
return records.one? if loaded?
|
293
315
|
limited_count == 1
|
294
316
|
end
|
295
317
|
|
296
318
|
# Returns true if there is more than one record.
|
297
319
|
def many?
|
320
|
+
return false if @none
|
321
|
+
|
298
322
|
return super if block_given?
|
299
323
|
return records.many? if loaded?
|
300
324
|
limited_count > 1
|
@@ -307,7 +331,7 @@ module ActiveRecord
|
|
307
331
|
# # => "products/query-1850ab3d302391b85b8693e941286659"
|
308
332
|
#
|
309
333
|
# 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.
|
334
|
+
# in \Rails 6.0 and earlier, the cache key will also include a version.
|
311
335
|
#
|
312
336
|
# ActiveRecord::Base.collection_cache_versioning = false
|
313
337
|
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
@@ -388,7 +412,7 @@ module ActiveRecord
|
|
388
412
|
end
|
389
413
|
|
390
414
|
if timestamp
|
391
|
-
"#{size}-#{timestamp.utc.
|
415
|
+
"#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
|
392
416
|
else
|
393
417
|
"#{size}"
|
394
418
|
end
|
@@ -409,7 +433,7 @@ module ActiveRecord
|
|
409
433
|
# Comment.where(post_id: 1).scoping do
|
410
434
|
# Comment.first
|
411
435
|
# end
|
412
|
-
# #
|
436
|
+
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
|
413
437
|
#
|
414
438
|
# If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
|
415
439
|
# for the relation including +update+ and +delete+ on instances.
|
@@ -429,10 +453,10 @@ module ActiveRecord
|
|
429
453
|
end
|
430
454
|
end
|
431
455
|
|
432
|
-
def _exec_scope(
|
456
|
+
def _exec_scope(...) # :nodoc:
|
433
457
|
@delegate_to_klass = true
|
434
458
|
registry = klass.scope_registry
|
435
|
-
_scoping(nil, registry) { instance_exec(
|
459
|
+
_scoping(nil, registry) { instance_exec(...) || self }
|
436
460
|
ensure
|
437
461
|
@delegate_to_klass = false
|
438
462
|
end
|
@@ -446,7 +470,8 @@ module ActiveRecord
|
|
446
470
|
#
|
447
471
|
# ==== Parameters
|
448
472
|
#
|
449
|
-
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
473
|
+
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement. Any strings provided will
|
474
|
+
# be type cast, unless you use +Arel.sql+. (Don't pass user-provided values to +Arel.sql+.)
|
450
475
|
#
|
451
476
|
# ==== Examples
|
452
477
|
#
|
@@ -461,9 +486,14 @@ module ActiveRecord
|
|
461
486
|
#
|
462
487
|
# # Update all invoices and set the number column to its id value.
|
463
488
|
# Invoice.update_all('number = id')
|
489
|
+
#
|
490
|
+
# # Update all books with 'Rails' in their title
|
491
|
+
# Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
|
464
492
|
def update_all(updates)
|
465
493
|
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
466
494
|
|
495
|
+
return 0 if @none
|
496
|
+
|
467
497
|
if updates.is_a?(Hash)
|
468
498
|
if klass.locking_enabled? &&
|
469
499
|
!updates.key?(klass.locking_column) &&
|
@@ -599,6 +629,8 @@ module ActiveRecord
|
|
599
629
|
# Post.distinct.delete_all
|
600
630
|
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
|
601
631
|
def delete_all
|
632
|
+
return 0 if @none
|
633
|
+
|
602
634
|
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
603
635
|
value = @values[method]
|
604
636
|
method == :distinct ? value : value&.any?
|
@@ -646,6 +678,21 @@ module ActiveRecord
|
|
646
678
|
# Schedule the query to be performed from a background thread pool.
|
647
679
|
#
|
648
680
|
# Post.where(published: true).load_async # => #<ActiveRecord::Relation>
|
681
|
+
#
|
682
|
+
# When the +Relation+ is iterated, if the background query wasn't executed yet,
|
683
|
+
# it will be performed by the foreground thread.
|
684
|
+
#
|
685
|
+
# Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured
|
686
|
+
# for queries to actually be executed concurrently. Otherwise it defaults to
|
687
|
+
# executing them in the foreground.
|
688
|
+
#
|
689
|
+
# +load_async+ will also fall back to executing in the foreground in the test environment when transactional
|
690
|
+
# fixtures are enabled.
|
691
|
+
#
|
692
|
+
# If the query was actually executed in the background, the Active Record logs will show
|
693
|
+
# it by prefixing the log line with <tt>ASYNC</tt>:
|
694
|
+
#
|
695
|
+
# ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
|
649
696
|
def load_async
|
650
697
|
return load if !connection.async_enabled?
|
651
698
|
|
@@ -697,6 +744,7 @@ module ActiveRecord
|
|
697
744
|
@to_sql = @arel = @loaded = @should_eager_load = nil
|
698
745
|
@offsets = @take = nil
|
699
746
|
@cache_keys = nil
|
747
|
+
@cache_versions = nil
|
700
748
|
@records = nil
|
701
749
|
self
|
702
750
|
end
|
@@ -704,7 +752,7 @@ module ActiveRecord
|
|
704
752
|
# Returns sql statement for the relation.
|
705
753
|
#
|
706
754
|
# User.where(name: 'Oscar').to_sql
|
707
|
-
# #
|
755
|
+
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
|
708
756
|
def to_sql
|
709
757
|
@to_sql ||= if eager_loading?
|
710
758
|
apply_join_dependency do |relation, join_dependency|
|
@@ -721,7 +769,7 @@ module ActiveRecord
|
|
721
769
|
#
|
722
770
|
# User.where(name: 'Oscar').where_values_hash
|
723
771
|
# # => {name: "Oscar"}
|
724
|
-
def where_values_hash(relation_table_name = klass.table_name)
|
772
|
+
def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
|
725
773
|
where_clause.to_h(relation_table_name)
|
726
774
|
end
|
727
775
|
|
@@ -741,7 +789,7 @@ module ActiveRecord
|
|
741
789
|
# Joins that are also marked for preloading. In which case we should just eager load them.
|
742
790
|
# Note that this is a naive implementation because we could have strings and symbols which
|
743
791
|
# represent the same association, but that aren't matched by this. Also, we could have
|
744
|
-
# nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
|
792
|
+
# nested hashes which partially match, e.g. <tt>{ a: :b } & { a: [:b, :c] }</tt>
|
745
793
|
def joined_includes_values
|
746
794
|
includes_values & joins_values
|
747
795
|
end
|
@@ -758,8 +806,13 @@ module ActiveRecord
|
|
758
806
|
end
|
759
807
|
end
|
760
808
|
|
761
|
-
def pretty_print(
|
762
|
-
|
809
|
+
def pretty_print(pp)
|
810
|
+
subject = loaded? ? records : annotate("loading for pp")
|
811
|
+
entries = subject.take([limit_value, 11].compact.min)
|
812
|
+
|
813
|
+
entries[10] = "..." if entries.size == 11
|
814
|
+
|
815
|
+
pp.pp(entries)
|
763
816
|
end
|
764
817
|
|
765
818
|
# Returns true if relation is blank.
|
@@ -821,10 +874,6 @@ module ActiveRecord
|
|
821
874
|
@loaded = true
|
822
875
|
end
|
823
876
|
|
824
|
-
def null_relation? # :nodoc:
|
825
|
-
is_a?(NullRelation)
|
826
|
-
end
|
827
|
-
|
828
877
|
private
|
829
878
|
def already_in_scope?(registry)
|
830
879
|
@delegate_to_klass && registry.current_scope(klass, true)
|
@@ -902,13 +951,21 @@ module ActiveRecord
|
|
902
951
|
preload_associations(records) unless skip_preloading_value
|
903
952
|
|
904
953
|
records.each(&:readonly!) if readonly_value
|
905
|
-
records.each
|
954
|
+
records.each { |record| record.strict_loading!(strict_loading_value) } unless strict_loading_value.nil?
|
906
955
|
|
907
956
|
records
|
908
957
|
end
|
909
958
|
end
|
910
959
|
|
911
960
|
def exec_main_query(async: false)
|
961
|
+
if @none
|
962
|
+
if async
|
963
|
+
return Promise::Complete.new([])
|
964
|
+
else
|
965
|
+
return []
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
912
969
|
skip_query_cache_if_necessary do
|
913
970
|
if where_clause.contradiction?
|
914
971
|
[].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
|
@@ -108,7 +111,7 @@ module ActiveRecord
|
|
108
111
|
type = if type_overrides.is_a?(Array)
|
109
112
|
type_overrides.first
|
110
113
|
else
|
111
|
-
column_type(columns.first, type_overrides)
|
114
|
+
column_type(columns.first, 0, type_overrides)
|
112
115
|
end
|
113
116
|
|
114
117
|
rows.map do |(value)|
|
@@ -118,7 +121,7 @@ module ActiveRecord
|
|
118
121
|
types = if type_overrides.is_a?(Array)
|
119
122
|
type_overrides
|
120
123
|
else
|
121
|
-
columns.map { |name| column_type(name, type_overrides) }
|
124
|
+
columns.map.with_index { |name, i| column_type(name, i, type_overrides) }
|
122
125
|
end
|
123
126
|
|
124
127
|
rows.map do |values|
|
@@ -134,10 +137,17 @@ 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
|
-
def column_type(name, type_overrides
|
146
|
+
def column_type(name, index, type_overrides)
|
139
147
|
type_overrides.fetch(name) do
|
140
|
-
column_types.fetch(
|
148
|
+
column_types.fetch(index) do
|
149
|
+
column_types.fetch(name, Type.default_value)
|
150
|
+
end
|
141
151
|
end
|
142
152
|
end
|
143
153
|
|
@@ -181,5 +191,11 @@ module ActiveRecord
|
|
181
191
|
}
|
182
192
|
end
|
183
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
|
184
200
|
end
|
185
201
|
end
|
@@ -10,11 +10,20 @@ 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 reset
|
21
|
+
rt, self.sql_runtime = sql_runtime, 0.0
|
22
|
+
rt
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
26
|
+
|
27
|
+
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
|
28
|
+
ActiveRecord::RuntimeRegistry.sql_runtime += (finish - start) * 1_000.0
|
29
|
+
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)
|
@@ -92,26 +114,32 @@ module ActiveRecord
|
|
92
114
|
end
|
93
115
|
|
94
116
|
# Sanitizes a +string+ so that it is safe to use within an SQL
|
95
|
-
# LIKE statement. This method uses +escape_character+ to escape all
|
117
|
+
# LIKE statement. This method uses +escape_character+ to escape all
|
118
|
+
# occurrences of itself, "_" and "%".
|
96
119
|
#
|
97
|
-
# sanitize_sql_like("100%")
|
98
|
-
# # => "100\\%"
|
120
|
+
# sanitize_sql_like("100% true!")
|
121
|
+
# # => "100\\% true!"
|
99
122
|
#
|
100
123
|
# sanitize_sql_like("snake_cased_string")
|
101
124
|
# # => "snake\\_cased\\_string"
|
102
125
|
#
|
103
|
-
# sanitize_sql_like("100%", "!")
|
104
|
-
# # => "100!%"
|
126
|
+
# sanitize_sql_like("100% true!", "!")
|
127
|
+
# # => "100!% true!!"
|
105
128
|
#
|
106
129
|
# sanitize_sql_like("snake_cased_string", "!")
|
107
130
|
# # => "snake!_cased!_string"
|
108
131
|
def sanitize_sql_like(string, escape_character = "\\")
|
109
|
-
|
110
|
-
|
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)
|
111
137
|
end
|
112
138
|
|
113
139
|
# Accepts an array of conditions. The array has each value
|
114
|
-
# 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.
|
115
143
|
#
|
116
144
|
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
117
145
|
# # => "name='foo''bar' and group_id=4"
|
@@ -119,8 +147,19 @@ module ActiveRecord
|
|
119
147
|
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
120
148
|
# # => "name='foo''bar' and group_id=4"
|
121
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
|
+
#
|
122
153
|
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
123
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'"
|
124
163
|
def sanitize_sql_array(ary)
|
125
164
|
statement, *values = ary
|
126
165
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
@@ -172,9 +211,11 @@ module ActiveRecord
|
|
172
211
|
end
|
173
212
|
|
174
213
|
def replace_named_bind_variables(statement, bind_vars)
|
175
|
-
statement.gsub(/(
|
214
|
+
statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
|
176
215
|
if $1 == ":" # skip postgresql casts
|
177
216
|
match # return the whole match
|
217
|
+
elsif $1 == "\\" # escaped literal colon
|
218
|
+
match[1..-1] # return match with escaping backlash char removed
|
178
219
|
elsif bind_vars.include?(match = $2.to_sym)
|
179
220
|
replace_bind_variable(bind_vars[match])
|
180
221
|
else
|
@@ -187,13 +228,13 @@ module ActiveRecord
|
|
187
228
|
if value.respond_to?(:map) && !value.acts_like?(:string)
|
188
229
|
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
189
230
|
if values.empty?
|
190
|
-
c.
|
231
|
+
c.quote(c.cast_bound_value(nil))
|
191
232
|
else
|
192
|
-
values.map! { |v| c.
|
233
|
+
values.map! { |v| c.quote(c.cast_bound_value(v)) }.join(",")
|
193
234
|
end
|
194
235
|
else
|
195
236
|
value = value.id_for_database if value.respond_to?(:id_for_database)
|
196
|
-
c.
|
237
|
+
c.quote(c.cast_bound_value(value))
|
197
238
|
end
|
198
239
|
end
|
199
240
|
|