activerecord 7.1.5.2 → 8.1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "concurrent/map"
|
|
4
|
+
require "concurrent/atomic/atomic_fixnum"
|
|
4
5
|
|
|
5
6
|
module ActiveRecord
|
|
6
7
|
module ConnectionAdapters # :nodoc:
|
|
@@ -12,16 +13,15 @@ module ActiveRecord
|
|
|
12
13
|
dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
|
|
13
14
|
:truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
|
|
14
15
|
:exec_insert_all
|
|
15
|
-
|
|
16
|
-
base.set_callback :checkout, :after, :configure_query_cache!
|
|
17
|
-
base.set_callback :checkin, :after, :disable_query_cache!
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
def dirties_query_cache(base, *method_names)
|
|
21
19
|
method_names.each do |method_name|
|
|
22
20
|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
|
23
21
|
def #{method_name}(...)
|
|
24
|
-
|
|
22
|
+
if pool.dirties_query_cache
|
|
23
|
+
ActiveRecord::Base.clear_query_caches_for_current_thread
|
|
24
|
+
end
|
|
25
25
|
super
|
|
26
26
|
end
|
|
27
27
|
end_code
|
|
@@ -29,60 +29,225 @@ module ActiveRecord
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# This is the actual query cache store.
|
|
33
|
+
#
|
|
34
|
+
# It has an internal hash whose keys are either SQL strings, or arrays of
|
|
35
|
+
# two elements [SQL string, binds], if there are binds. The hash values
|
|
36
|
+
# are their corresponding ActiveRecord::Result objects.
|
|
37
|
+
#
|
|
38
|
+
# Keeping the hash size under max size is achieved with LRU eviction.
|
|
39
|
+
#
|
|
40
|
+
# The store gets passed a version object, which is shared among the query
|
|
41
|
+
# cache stores of a given connection pool (see ConnectionPoolConfiguration
|
|
42
|
+
# down below). The version value may be externally changed as a way to
|
|
43
|
+
# signal cache invalidation, that is why all methods have a guard for it.
|
|
44
|
+
class Store # :nodoc:
|
|
45
|
+
attr_accessor :enabled, :dirties
|
|
46
|
+
alias_method :enabled?, :enabled
|
|
47
|
+
alias_method :dirties?, :dirties
|
|
48
|
+
|
|
49
|
+
def initialize(version, max_size)
|
|
50
|
+
@version = version
|
|
51
|
+
@current_version = version.value
|
|
52
|
+
@map = {}
|
|
53
|
+
@max_size = max_size
|
|
54
|
+
@enabled = false
|
|
55
|
+
@dirties = true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def size
|
|
59
|
+
check_version
|
|
60
|
+
@map.size
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def empty?
|
|
64
|
+
check_version
|
|
65
|
+
@map.empty?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def [](key)
|
|
69
|
+
check_version
|
|
70
|
+
return unless @enabled
|
|
71
|
+
|
|
72
|
+
if entry = @map.delete(key)
|
|
73
|
+
@map[key] = entry
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def compute_if_absent(key)
|
|
78
|
+
check_version
|
|
79
|
+
|
|
80
|
+
return yield unless @enabled
|
|
81
|
+
|
|
82
|
+
if entry = @map.delete(key)
|
|
83
|
+
return @map[key] = entry
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if @max_size && @map.size >= @max_size
|
|
87
|
+
@map.shift # evict the oldest entry
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
@map[key] ||= yield
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def clear
|
|
94
|
+
@map.clear
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
def check_version
|
|
100
|
+
if @current_version != @version.value
|
|
101
|
+
@map.clear
|
|
102
|
+
@current_version = @version.value
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Each connection pool has one of these registries. They map execution
|
|
108
|
+
# contexts to query cache stores.
|
|
109
|
+
#
|
|
110
|
+
# The keys of the internal map are threads or fibers (whatever
|
|
111
|
+
# ActiveSupport::IsolatedExecutionState.context returns), and their
|
|
112
|
+
# associated values are their respective query cache stores.
|
|
113
|
+
class QueryCacheRegistry # :nodoc:
|
|
114
|
+
def initialize
|
|
115
|
+
@mutex = Mutex.new
|
|
116
|
+
@map = ConnectionPool::WeakThreadKeyMap.new
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def compute_if_absent(context)
|
|
120
|
+
@map[context] || @mutex.synchronize do
|
|
121
|
+
@map[context] ||= yield
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def clear
|
|
126
|
+
@map.synchronize do
|
|
127
|
+
@map.clear
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
module ConnectionPoolConfiguration # :nodoc:
|
|
133
|
+
def initialize(...)
|
|
34
134
|
super
|
|
35
|
-
@
|
|
135
|
+
@query_cache_version = Concurrent::AtomicFixnum.new
|
|
136
|
+
@thread_query_caches = QueryCacheRegistry.new
|
|
137
|
+
@query_cache_max_size = \
|
|
138
|
+
case query_cache = db_config&.query_cache
|
|
139
|
+
when 0, false
|
|
140
|
+
nil
|
|
141
|
+
when Integer
|
|
142
|
+
query_cache
|
|
143
|
+
when nil
|
|
144
|
+
DEFAULT_SIZE
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def checkout_and_verify(connection)
|
|
149
|
+
super
|
|
150
|
+
connection.query_cache ||= query_cache
|
|
151
|
+
connection
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Disable the query cache within the block.
|
|
155
|
+
def disable_query_cache(dirties: true)
|
|
156
|
+
cache = query_cache
|
|
157
|
+
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
|
|
158
|
+
begin
|
|
159
|
+
yield
|
|
160
|
+
ensure
|
|
161
|
+
cache.enabled, cache.dirties = old_enabled, old_dirties
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def enable_query_cache
|
|
166
|
+
cache = query_cache
|
|
167
|
+
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
|
|
168
|
+
begin
|
|
169
|
+
yield
|
|
170
|
+
ensure
|
|
171
|
+
cache.enabled, cache.dirties = old_enabled, old_dirties
|
|
172
|
+
end
|
|
36
173
|
end
|
|
37
174
|
|
|
38
175
|
def enable_query_cache!
|
|
39
|
-
|
|
40
|
-
|
|
176
|
+
query_cache.enabled = true
|
|
177
|
+
query_cache.dirties = true
|
|
41
178
|
end
|
|
42
179
|
|
|
43
180
|
def disable_query_cache!
|
|
44
|
-
|
|
45
|
-
|
|
181
|
+
query_cache.enabled = false
|
|
182
|
+
query_cache.dirties = true
|
|
46
183
|
end
|
|
47
184
|
|
|
48
185
|
def query_cache_enabled
|
|
49
|
-
|
|
186
|
+
query_cache.enabled
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def dirties_query_cache
|
|
190
|
+
query_cache.dirties
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def clear_query_cache
|
|
194
|
+
if @pinned_connection
|
|
195
|
+
# With transactional fixtures, and especially systems test
|
|
196
|
+
# another thread may use the same connection, but with a different
|
|
197
|
+
# query cache. So we must clear them all.
|
|
198
|
+
@query_cache_version.increment
|
|
199
|
+
end
|
|
200
|
+
query_cache.clear
|
|
50
201
|
end
|
|
51
|
-
end
|
|
52
202
|
|
|
53
|
-
|
|
203
|
+
def query_cache
|
|
204
|
+
@thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
|
|
205
|
+
Store.new(@query_cache_version, @query_cache_max_size)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
54
209
|
|
|
55
210
|
def initialize(*)
|
|
56
211
|
super
|
|
57
|
-
@query_cache
|
|
58
|
-
|
|
59
|
-
|
|
212
|
+
@query_cache = nil
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
attr_writer :query_cache
|
|
216
|
+
|
|
217
|
+
def query_cache
|
|
218
|
+
if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
|
|
219
|
+
# With transactional tests, if the connection is pinned, any thread
|
|
220
|
+
# other than the one that pinned the connection need to go through the
|
|
221
|
+
# query cache pool, so each thread get a different cache.
|
|
222
|
+
pool.query_cache
|
|
223
|
+
else
|
|
224
|
+
@query_cache
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def query_cache_enabled
|
|
229
|
+
query_cache&.enabled?
|
|
60
230
|
end
|
|
61
231
|
|
|
62
232
|
# Enable the query cache within the block.
|
|
63
|
-
def cache
|
|
64
|
-
|
|
65
|
-
yield
|
|
66
|
-
ensure
|
|
67
|
-
@query_cache_enabled = old
|
|
68
|
-
clear_query_cache unless @query_cache_enabled
|
|
233
|
+
def cache(&block)
|
|
234
|
+
pool.enable_query_cache(&block)
|
|
69
235
|
end
|
|
70
236
|
|
|
71
237
|
def enable_query_cache!
|
|
72
|
-
|
|
238
|
+
pool.enable_query_cache!
|
|
73
239
|
end
|
|
74
240
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
241
|
+
# Disable the query cache within the block.
|
|
242
|
+
#
|
|
243
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
|
244
|
+
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
|
245
|
+
def uncached(dirties: true, &block)
|
|
246
|
+
pool.disable_query_cache(dirties: dirties, &block)
|
|
78
247
|
end
|
|
79
248
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
old, @query_cache_enabled = @query_cache_enabled, false
|
|
83
|
-
yield
|
|
84
|
-
ensure
|
|
85
|
-
@query_cache_enabled = old
|
|
249
|
+
def disable_query_cache!
|
|
250
|
+
pool.disable_query_cache!
|
|
86
251
|
end
|
|
87
252
|
|
|
88
253
|
# Clears the query cache.
|
|
@@ -92,24 +257,22 @@ module ActiveRecord
|
|
|
92
257
|
# the same SQL query and repeatedly return the same result each time, silently
|
|
93
258
|
# undermining the randomness you were expecting.
|
|
94
259
|
def clear_query_cache
|
|
95
|
-
|
|
96
|
-
@query_cache.clear
|
|
97
|
-
end
|
|
260
|
+
pool.clear_query_cache
|
|
98
261
|
end
|
|
99
262
|
|
|
100
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
|
|
263
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
|
|
101
264
|
arel = arel_from_relation(arel)
|
|
102
265
|
|
|
103
266
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
|
104
267
|
# Such queries should not be cached.
|
|
105
|
-
if
|
|
106
|
-
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
|
268
|
+
if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
|
|
269
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
|
|
107
270
|
|
|
108
271
|
if async
|
|
109
|
-
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
|
|
272
|
+
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
|
110
273
|
FutureResult.wrap(result)
|
|
111
274
|
else
|
|
112
|
-
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
|
|
275
|
+
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
|
|
113
276
|
end
|
|
114
277
|
else
|
|
115
278
|
super
|
|
@@ -117,55 +280,56 @@ module ActiveRecord
|
|
|
117
280
|
end
|
|
118
281
|
|
|
119
282
|
private
|
|
283
|
+
def unset_query_cache!
|
|
284
|
+
@query_cache = nil
|
|
285
|
+
end
|
|
286
|
+
|
|
120
287
|
def lookup_sql_cache(sql, name, binds)
|
|
121
288
|
key = binds.empty? ? sql : [sql, binds]
|
|
122
|
-
hit = false
|
|
123
|
-
result = nil
|
|
124
289
|
|
|
290
|
+
result = nil
|
|
125
291
|
@lock.synchronize do
|
|
126
|
-
|
|
127
|
-
hit = true
|
|
128
|
-
@query_cache[key] = result
|
|
129
|
-
end
|
|
292
|
+
result = query_cache[key]
|
|
130
293
|
end
|
|
131
294
|
|
|
132
|
-
if
|
|
295
|
+
if result
|
|
133
296
|
ActiveSupport::Notifications.instrument(
|
|
134
297
|
"sql.active_record",
|
|
135
|
-
|
|
298
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
136
299
|
)
|
|
137
|
-
|
|
138
|
-
result
|
|
139
300
|
end
|
|
301
|
+
|
|
302
|
+
result
|
|
140
303
|
end
|
|
141
304
|
|
|
142
305
|
def cache_sql(sql, name, binds)
|
|
143
306
|
key = binds.empty? ? sql : [sql, binds]
|
|
144
307
|
result = nil
|
|
145
|
-
hit =
|
|
308
|
+
hit = true
|
|
146
309
|
|
|
147
310
|
@lock.synchronize do
|
|
148
|
-
|
|
149
|
-
hit =
|
|
150
|
-
|
|
151
|
-
else
|
|
152
|
-
result = @query_cache[key] = yield
|
|
153
|
-
if @query_cache_max_size && @query_cache.size > @query_cache_max_size
|
|
154
|
-
@query_cache.shift
|
|
155
|
-
end
|
|
311
|
+
result = query_cache.compute_if_absent(key) do
|
|
312
|
+
hit = false
|
|
313
|
+
yield
|
|
156
314
|
end
|
|
157
315
|
end
|
|
158
316
|
|
|
159
317
|
if hit
|
|
160
318
|
ActiveSupport::Notifications.instrument(
|
|
161
319
|
"sql.active_record",
|
|
162
|
-
|
|
320
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
163
321
|
)
|
|
164
322
|
end
|
|
165
323
|
|
|
166
324
|
result.dup
|
|
167
325
|
end
|
|
168
326
|
|
|
327
|
+
def cache_notification_info_result(sql, name, binds, result)
|
|
328
|
+
payload = cache_notification_info(sql, name, binds)
|
|
329
|
+
payload[:row_count] = result.length
|
|
330
|
+
payload
|
|
331
|
+
end
|
|
332
|
+
|
|
169
333
|
# Database adapters can override this method to
|
|
170
334
|
# provide custom cache information.
|
|
171
335
|
def cache_notification_info(sql, name, binds)
|
|
@@ -175,26 +339,10 @@ module ActiveRecord
|
|
|
175
339
|
type_casted_binds: -> { type_casted_binds(binds) },
|
|
176
340
|
name: name,
|
|
177
341
|
connection: self,
|
|
342
|
+
transaction: current_transaction.user_transaction.presence,
|
|
178
343
|
cached: true
|
|
179
344
|
}
|
|
180
345
|
end
|
|
181
|
-
|
|
182
|
-
def configure_query_cache!
|
|
183
|
-
case query_cache = pool.db_config.query_cache
|
|
184
|
-
when 0, false
|
|
185
|
-
return
|
|
186
|
-
when Integer
|
|
187
|
-
@query_cache_max_size = query_cache
|
|
188
|
-
when nil
|
|
189
|
-
@query_cache_max_size = DEFAULT_SIZE
|
|
190
|
-
else
|
|
191
|
-
@query_cache_max_size = nil # no limit
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
if pool.query_cache_enabled
|
|
195
|
-
enable_query_cache!
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
346
|
end
|
|
199
347
|
end
|
|
200
348
|
end
|
|
@@ -1,12 +1,72 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/big_decimal/conversions"
|
|
4
|
-
require "active_support/multibyte/chars"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
7
6
|
module ConnectionAdapters # :nodoc:
|
|
8
7
|
# = Active Record Connection Adapters \Quoting
|
|
9
8
|
module Quoting
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
module ClassMethods # :nodoc:
|
|
12
|
+
# Regexp for column names (with or without a table name prefix).
|
|
13
|
+
# Matches the following:
|
|
14
|
+
#
|
|
15
|
+
# "#{table_name}.#{column_name}"
|
|
16
|
+
# "#{column_name}"
|
|
17
|
+
def column_name_matcher
|
|
18
|
+
/
|
|
19
|
+
\A
|
|
20
|
+
(
|
|
21
|
+
(?:
|
|
22
|
+
# table_name.column_name | function(one or no argument)
|
|
23
|
+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
|
24
|
+
)
|
|
25
|
+
(?:(?:\s+AS)?\s+\w+)?
|
|
26
|
+
)
|
|
27
|
+
(?:\s*,\s*\g<1>)*
|
|
28
|
+
\z
|
|
29
|
+
/ix
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Regexp for column names with order (with or without a table name prefix,
|
|
33
|
+
# with or without various order modifiers). Matches the following:
|
|
34
|
+
#
|
|
35
|
+
# "#{table_name}.#{column_name}"
|
|
36
|
+
# "#{table_name}.#{column_name} #{direction}"
|
|
37
|
+
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
|
38
|
+
# "#{table_name}.#{column_name} NULLS LAST"
|
|
39
|
+
# "#{column_name}"
|
|
40
|
+
# "#{column_name} #{direction}"
|
|
41
|
+
# "#{column_name} #{direction} NULLS FIRST"
|
|
42
|
+
# "#{column_name} NULLS LAST"
|
|
43
|
+
def column_name_with_order_matcher
|
|
44
|
+
/
|
|
45
|
+
\A
|
|
46
|
+
(
|
|
47
|
+
(?:
|
|
48
|
+
# table_name.column_name | function(one or no argument)
|
|
49
|
+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
|
50
|
+
)
|
|
51
|
+
(?:\s+ASC|\s+DESC)?
|
|
52
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
53
|
+
)
|
|
54
|
+
(?:\s*,\s*\g<1>)*
|
|
55
|
+
\z
|
|
56
|
+
/ix
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Quotes the column name. Must be implemented by subclasses
|
|
60
|
+
def quote_column_name(column_name)
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Quotes the table name. Defaults to column name quoting.
|
|
65
|
+
def quote_table_name(table_name)
|
|
66
|
+
quote_column_name(table_name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
10
70
|
# Quotes the column value to help prevent
|
|
11
71
|
# {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
|
|
12
72
|
def quote(value)
|
|
@@ -23,10 +83,8 @@ module ActiveRecord
|
|
|
23
83
|
when Type::Time::Value then "'#{quoted_time(value)}'"
|
|
24
84
|
when Date, Time then "'#{quoted_date(value)}'"
|
|
25
85
|
when Class then "'#{value}'"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
value.to_s
|
|
29
|
-
else raise TypeError, "can't quote #{value.class.name}"
|
|
86
|
+
else
|
|
87
|
+
raise TypeError, "can't quote #{value.class.name}"
|
|
30
88
|
end
|
|
31
89
|
end
|
|
32
90
|
|
|
@@ -35,7 +93,7 @@ module ActiveRecord
|
|
|
35
93
|
# to a String.
|
|
36
94
|
def type_cast(value)
|
|
37
95
|
case value
|
|
38
|
-
when Symbol,
|
|
96
|
+
when Symbol, Type::Binary::Data, ActiveSupport::Multibyte::Chars
|
|
39
97
|
value.to_s
|
|
40
98
|
when true then unquoted_true
|
|
41
99
|
when false then unquoted_false
|
|
@@ -44,25 +102,11 @@ module ActiveRecord
|
|
|
44
102
|
when nil, Numeric, String then value
|
|
45
103
|
when Type::Time::Value then quoted_time(value)
|
|
46
104
|
when Date, Time then quoted_date(value)
|
|
47
|
-
else
|
|
105
|
+
else
|
|
106
|
+
raise TypeError, "can't cast #{value.class.name}"
|
|
48
107
|
end
|
|
49
108
|
end
|
|
50
109
|
|
|
51
|
-
# Quote a value to be used as a bound parameter of unknown type. For example,
|
|
52
|
-
# MySQL might perform dangerous castings when comparing a string to a number,
|
|
53
|
-
# so this method will cast numbers to string.
|
|
54
|
-
#
|
|
55
|
-
# Deprecated: Consider `Arel.sql("... ? ...", value)` or
|
|
56
|
-
# +sanitize_sql+ instead.
|
|
57
|
-
def quote_bound_value(value)
|
|
58
|
-
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
|
59
|
-
#quote_bound_value is deprecated and will be removed in Rails 7.2.
|
|
60
|
-
Consider Arel.sql(".. ? ..", value) or #sanitize_sql instead.
|
|
61
|
-
MSG
|
|
62
|
-
|
|
63
|
-
quote(cast_bound_value(value))
|
|
64
|
-
end
|
|
65
|
-
|
|
66
110
|
# Cast a value to be used as a bound parameter of unknown type. For example,
|
|
67
111
|
# MySQL might perform dangerous castings when comparing a string to a number,
|
|
68
112
|
# so this method will cast numbers to string.
|
|
@@ -70,33 +114,20 @@ module ActiveRecord
|
|
|
70
114
|
value
|
|
71
115
|
end
|
|
72
116
|
|
|
73
|
-
# If you are having to call this function, you are likely doing something
|
|
74
|
-
# wrong. The column does not have sufficient type information if the user
|
|
75
|
-
# provided a custom type on the class level either explicitly (via
|
|
76
|
-
# Attributes::ClassMethods#attribute) or implicitly (via
|
|
77
|
-
# AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
|
|
78
|
-
# In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
|
|
79
|
-
# represent the type doesn't sufficiently reflect the differences
|
|
80
|
-
# (varchar vs binary) for example. The type used to get this primitive
|
|
81
|
-
# should have been provided before reaching the connection adapter.
|
|
82
|
-
def lookup_cast_type_from_column(column) # :nodoc:
|
|
83
|
-
lookup_cast_type(column.sql_type)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
117
|
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
|
87
118
|
# characters.
|
|
88
119
|
def quote_string(s)
|
|
89
120
|
s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
|
|
90
121
|
end
|
|
91
122
|
|
|
92
|
-
# Quotes the column name.
|
|
123
|
+
# Quotes the column name.
|
|
93
124
|
def quote_column_name(column_name)
|
|
94
|
-
column_name
|
|
125
|
+
self.class.quote_column_name(column_name)
|
|
95
126
|
end
|
|
96
127
|
|
|
97
|
-
# Quotes the table name.
|
|
128
|
+
# Quotes the table name.
|
|
98
129
|
def quote_table_name(table_name)
|
|
99
|
-
|
|
130
|
+
self.class.quote_table_name(table_name)
|
|
100
131
|
end
|
|
101
132
|
|
|
102
133
|
# Override to return the quoted table name for assignment. Defaults to
|
|
@@ -115,7 +146,9 @@ module ActiveRecord
|
|
|
115
146
|
if value.is_a?(Proc)
|
|
116
147
|
value.call
|
|
117
148
|
else
|
|
118
|
-
|
|
149
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
150
|
+
cast_type = column.fetch_cast_type(self)
|
|
151
|
+
value = cast_type.serialize(value)
|
|
119
152
|
quote(value)
|
|
120
153
|
end
|
|
121
154
|
end
|
|
@@ -165,10 +198,10 @@ module ActiveRecord
|
|
|
165
198
|
end
|
|
166
199
|
|
|
167
200
|
def sanitize_as_sql_comment(value) # :nodoc:
|
|
168
|
-
# Sanitize a string to appear within
|
|
201
|
+
# Sanitize a string to appear within an SQL comment
|
|
169
202
|
# For compatibility, this also surrounding "/*+", "/*", and "*/"
|
|
170
203
|
# charcacters, possibly with single surrounding space.
|
|
171
|
-
# Then follows that by replacing any internal "*/" or "
|
|
204
|
+
# Then follows that by replacing any internal "*/" or "/*" with
|
|
172
205
|
# "* /" or "/ *"
|
|
173
206
|
comment = value.to_s.dup
|
|
174
207
|
comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
|
|
@@ -177,62 +210,14 @@ module ActiveRecord
|
|
|
177
210
|
comment
|
|
178
211
|
end
|
|
179
212
|
|
|
180
|
-
def
|
|
181
|
-
|
|
213
|
+
def lookup_cast_type(sql_type) # :nodoc:
|
|
214
|
+
# TODO: Make this method private after we release 8.1.
|
|
215
|
+
type_map.lookup(sql_type)
|
|
182
216
|
end
|
|
183
217
|
|
|
184
|
-
def column_name_with_order_matcher # :nodoc:
|
|
185
|
-
COLUMN_NAME_WITH_ORDER
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Regexp for column names (with or without a table name prefix).
|
|
189
|
-
# Matches the following:
|
|
190
|
-
#
|
|
191
|
-
# "#{table_name}.#{column_name}"
|
|
192
|
-
# "#{column_name}"
|
|
193
|
-
COLUMN_NAME = /
|
|
194
|
-
\A
|
|
195
|
-
(
|
|
196
|
-
(?:
|
|
197
|
-
# table_name.column_name | function(one or no argument)
|
|
198
|
-
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
|
199
|
-
)
|
|
200
|
-
(?:(?:\s+AS)?\s+\w+)?
|
|
201
|
-
)
|
|
202
|
-
(?:\s*,\s*\g<1>)*
|
|
203
|
-
\z
|
|
204
|
-
/ix
|
|
205
|
-
|
|
206
|
-
# Regexp for column names with order (with or without a table name prefix,
|
|
207
|
-
# with or without various order modifiers). Matches the following:
|
|
208
|
-
#
|
|
209
|
-
# "#{table_name}.#{column_name}"
|
|
210
|
-
# "#{table_name}.#{column_name} #{direction}"
|
|
211
|
-
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
|
212
|
-
# "#{table_name}.#{column_name} NULLS LAST"
|
|
213
|
-
# "#{column_name}"
|
|
214
|
-
# "#{column_name} #{direction}"
|
|
215
|
-
# "#{column_name} #{direction} NULLS FIRST"
|
|
216
|
-
# "#{column_name} NULLS LAST"
|
|
217
|
-
COLUMN_NAME_WITH_ORDER = /
|
|
218
|
-
\A
|
|
219
|
-
(
|
|
220
|
-
(?:
|
|
221
|
-
# table_name.column_name | function(one or no argument)
|
|
222
|
-
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
|
223
|
-
)
|
|
224
|
-
(?:\s+ASC|\s+DESC)?
|
|
225
|
-
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
226
|
-
)
|
|
227
|
-
(?:\s*,\s*\g<1>)*
|
|
228
|
-
\z
|
|
229
|
-
/ix
|
|
230
|
-
|
|
231
|
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
|
232
|
-
|
|
233
218
|
private
|
|
234
219
|
def type_casted_binds(binds)
|
|
235
|
-
binds
|
|
220
|
+
binds&.map do |value|
|
|
236
221
|
if ActiveModel::Attribute === value
|
|
237
222
|
type_cast(value.value_for_database)
|
|
238
223
|
else
|
|
@@ -240,26 +225,6 @@ module ActiveRecord
|
|
|
240
225
|
end
|
|
241
226
|
end
|
|
242
227
|
end
|
|
243
|
-
|
|
244
|
-
def lookup_cast_type(sql_type)
|
|
245
|
-
type_map.lookup(sql_type)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def warn_quote_duration_deprecated
|
|
249
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
|
250
|
-
Using ActiveSupport::Duration as an interpolated bind parameter in a SQL
|
|
251
|
-
string template is deprecated. To avoid this warning, you should explicitly
|
|
252
|
-
convert the duration to a more specific database type. For example, if you
|
|
253
|
-
want to use a duration as an integer number of seconds:
|
|
254
|
-
```
|
|
255
|
-
Record.where("duration = ?", 1.hour.to_i)
|
|
256
|
-
```
|
|
257
|
-
If you want to use a duration as an ISO 8601 string:
|
|
258
|
-
```
|
|
259
|
-
Record.where("duration = ?", 1.hour.iso8601)
|
|
260
|
-
```
|
|
261
|
-
MSG
|
|
262
|
-
end
|
|
263
228
|
end
|
|
264
229
|
end
|
|
265
230
|
end
|