activerecord 6.0.0.beta3 → 6.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +286 -6
- data/README.rdoc +3 -1
- data/lib/active_record.rb +0 -1
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +3 -13
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_proxy.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations/preloader/association.rb +32 -30
- data/lib/active_record/associations/preloader/through_association.rb +48 -28
- data/lib/active_record/attribute_methods.rb +4 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +42 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +13 -3
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
- data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
- data/lib/active_record/connection_handling.rb +17 -10
- data/lib/active_record/core.rb +15 -20
- data/lib/active_record/database_configurations.rb +14 -14
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +12 -12
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +6 -0
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +180 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +10 -0
- data/lib/active_record/persistence.rb +206 -13
- data/lib/active_record/querying.rb +17 -12
- data/lib/active_record/railties/databases.rake +68 -6
- data/lib/active_record/reflection.rb +2 -2
- data/lib/active_record/relation.rb +98 -20
- data/lib/active_record/relation/calculations.rb +39 -39
- data/lib/active_record/relation/delegation.rb +22 -30
- data/lib/active_record/relation/finder_methods.rb +3 -9
- data/lib/active_record/relation/merger.rb +7 -16
- data/lib/active_record/relation/query_methods.rb +153 -38
- data/lib/active_record/relation/where_clause.rb +9 -5
- data/lib/active_record/sanitization.rb +3 -2
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +1 -1
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +3 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -1
- data/lib/active_record/touch_later.rb +2 -2
- data/lib/active_record/transactions.rb +52 -41
- data/lib/active_record/validations/uniqueness.rb +3 -5
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +6 -1
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +87 -108
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +12 -11
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -2,18 +2,23 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Querying
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
5
|
+
QUERYING_METHODS = [
|
6
|
+
:find, :find_by, :find_by!, :take, :take!, :first, :first!, :last, :last!,
|
7
|
+
:second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!,
|
8
|
+
:forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
|
9
|
+
:exists?, :any?, :many?, :none?, :one?,
|
10
|
+
:first_or_create, :first_or_create!, :first_or_initialize,
|
11
|
+
:find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
|
12
|
+
:create_or_find_by, :create_or_find_by!,
|
13
|
+
:destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
|
14
|
+
:find_each, :find_in_batches, :in_batches,
|
15
|
+
:select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
|
16
|
+
:where, :rewhere, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly, :extending, :or,
|
17
|
+
:having, :create_with, :distinct, :references, :none, :unscope, :optimizer_hints, :merge, :except, :only,
|
18
|
+
:count, :average, :minimum, :maximum, :sum, :calculate, :annotate,
|
19
|
+
:pluck, :pick, :ids
|
20
|
+
].freeze # :nodoc:
|
21
|
+
delegate(*QUERYING_METHODS, to: :all)
|
17
22
|
|
18
23
|
# Executes a custom SQL query against your database and returns all the results. The results will
|
19
24
|
# be returned as an array, with the requested columns encapsulated as attributes of the model you call
|
@@ -66,6 +66,11 @@ db_namespace = namespace :db do
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
# desc "Truncates tables of each database for current environment"
|
70
|
+
task truncate_all: [:load_config, :check_protected_environments] do
|
71
|
+
ActiveRecord::Tasks::DatabaseTasks.truncate_all
|
72
|
+
end
|
73
|
+
|
69
74
|
# desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
|
70
75
|
task purge: [:load_config, :check_protected_environments] do
|
71
76
|
ActiveRecord::Tasks::DatabaseTasks.purge_current
|
@@ -73,7 +78,7 @@ db_namespace = namespace :db do
|
|
73
78
|
|
74
79
|
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
|
75
80
|
task migrate: :load_config do
|
76
|
-
ActiveRecord::Base.configurations.configs_for(env_name:
|
81
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
77
82
|
ActiveRecord::Base.establish_connection(db_config.config)
|
78
83
|
ActiveRecord::Tasks::DatabaseTasks.migrate
|
79
84
|
end
|
@@ -123,6 +128,8 @@ db_namespace = namespace :db do
|
|
123
128
|
|
124
129
|
# desc 'Runs the "up" for a given migration VERSION.'
|
125
130
|
task up: :load_config do
|
131
|
+
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
|
132
|
+
|
126
133
|
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
|
127
134
|
|
128
135
|
ActiveRecord::Tasks::DatabaseTasks.check_target_version
|
@@ -134,8 +141,29 @@ db_namespace = namespace :db do
|
|
134
141
|
db_namespace["_dump"].invoke
|
135
142
|
end
|
136
143
|
|
144
|
+
namespace :up do
|
145
|
+
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
|
146
|
+
task spec_name => :load_config do
|
147
|
+
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
|
148
|
+
|
149
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
|
150
|
+
|
151
|
+
ActiveRecord::Base.establish_connection(db_config.config)
|
152
|
+
ActiveRecord::Tasks::DatabaseTasks.check_target_version
|
153
|
+
ActiveRecord::Base.connection.migration_context.run(
|
154
|
+
:up,
|
155
|
+
ActiveRecord::Tasks::DatabaseTasks.target_version
|
156
|
+
)
|
157
|
+
|
158
|
+
db_namespace["_dump"].invoke
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
137
163
|
# desc 'Runs the "down" for a given migration VERSION.'
|
138
164
|
task down: :load_config do
|
165
|
+
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:down")
|
166
|
+
|
139
167
|
raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
|
140
168
|
|
141
169
|
ActiveRecord::Tasks::DatabaseTasks.check_target_version
|
@@ -147,9 +175,28 @@ db_namespace = namespace :db do
|
|
147
175
|
db_namespace["_dump"].invoke
|
148
176
|
end
|
149
177
|
|
178
|
+
namespace :down do
|
179
|
+
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
|
180
|
+
task spec_name => :load_config do
|
181
|
+
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
|
182
|
+
|
183
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
|
184
|
+
|
185
|
+
ActiveRecord::Base.establish_connection(db_config.config)
|
186
|
+
ActiveRecord::Tasks::DatabaseTasks.check_target_version
|
187
|
+
ActiveRecord::Base.connection.migration_context.run(
|
188
|
+
:down,
|
189
|
+
ActiveRecord::Tasks::DatabaseTasks.target_version
|
190
|
+
)
|
191
|
+
|
192
|
+
db_namespace["_dump"].invoke
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
150
197
|
desc "Display status of migrations"
|
151
198
|
task status: :load_config do
|
152
|
-
ActiveRecord::Base.configurations.configs_for(env_name:
|
199
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
153
200
|
ActiveRecord::Base.establish_connection(db_config.config)
|
154
201
|
ActiveRecord::Tasks::DatabaseTasks.migrate_status
|
155
202
|
end
|
@@ -217,12 +264,27 @@ db_namespace = namespace :db do
|
|
217
264
|
desc "Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)"
|
218
265
|
task setup: ["db:schema:load_if_ruby", "db:structure:load_if_sql", :seed]
|
219
266
|
|
267
|
+
desc "Runs setup if database does not exist, or runs migrations if it does"
|
268
|
+
task prepare: :load_config do
|
269
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
270
|
+
ActiveRecord::Base.establish_connection(db_config.config)
|
271
|
+
db_namespace["migrate"].invoke
|
272
|
+
rescue ActiveRecord::NoDatabaseError
|
273
|
+
db_namespace["setup"].invoke
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
220
277
|
desc "Loads the seed data from db/seeds.rb"
|
221
278
|
task seed: :load_config do
|
222
279
|
db_namespace["abort_if_pending_migrations"].invoke
|
223
280
|
ActiveRecord::Tasks::DatabaseTasks.load_seed
|
224
281
|
end
|
225
282
|
|
283
|
+
namespace :seed do
|
284
|
+
desc "Truncates tables of each database for current environment and loads the seeds"
|
285
|
+
task replant: [:load_config, :truncate_all, :seed]
|
286
|
+
end
|
287
|
+
|
226
288
|
namespace :fixtures do
|
227
289
|
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
|
228
290
|
task load: :load_config do
|
@@ -275,7 +337,7 @@ db_namespace = namespace :db do
|
|
275
337
|
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
|
276
338
|
task dump: :load_config do
|
277
339
|
require "active_record/schema_dumper"
|
278
|
-
ActiveRecord::Base.configurations.configs_for(env_name:
|
340
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
279
341
|
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
|
280
342
|
File.open(filename, "w:utf-8") do |file|
|
281
343
|
ActiveRecord::Base.establish_connection(db_config.config)
|
@@ -298,7 +360,7 @@ db_namespace = namespace :db do
|
|
298
360
|
namespace :cache do
|
299
361
|
desc "Creates a db/schema_cache.yml file."
|
300
362
|
task dump: :load_config do
|
301
|
-
ActiveRecord::Base.configurations.configs_for(env_name:
|
363
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
302
364
|
ActiveRecord::Base.establish_connection(db_config.config)
|
303
365
|
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.spec_name)
|
304
366
|
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(
|
@@ -310,7 +372,7 @@ db_namespace = namespace :db do
|
|
310
372
|
|
311
373
|
desc "Clears a db/schema_cache.yml file."
|
312
374
|
task clear: :load_config do
|
313
|
-
ActiveRecord::Base.configurations.configs_for(env_name:
|
375
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
314
376
|
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.spec_name)
|
315
377
|
rm_f filename, verbose: false
|
316
378
|
end
|
@@ -321,7 +383,7 @@ db_namespace = namespace :db do
|
|
321
383
|
namespace :structure do
|
322
384
|
desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
|
323
385
|
task dump: :load_config do
|
324
|
-
ActiveRecord::Base.configurations.configs_for(env_name:
|
386
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
325
387
|
ActiveRecord::Base.establish_connection(db_config.config)
|
326
388
|
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
|
327
389
|
ActiveRecord::Tasks::DatabaseTasks.structure_dump(db_config.config, filename)
|
@@ -21,12 +21,12 @@ module ActiveRecord
|
|
21
21
|
|
22
22
|
def add_reflection(ar, name, reflection)
|
23
23
|
ar.clear_reflections_cache
|
24
|
-
name = name.to_s
|
24
|
+
name = -name.to_s
|
25
25
|
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
|
26
26
|
end
|
27
27
|
|
28
28
|
def add_aggregate_reflection(ar, name, reflection)
|
29
|
-
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
|
29
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
@@ -5,7 +5,7 @@ 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]
|
8
|
+
:extending, :unscope, :optimizer_hints, :annotate]
|
9
9
|
|
10
10
|
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
|
11
11
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
@@ -197,6 +197,10 @@ module ActiveRecord
|
|
197
197
|
# if a DELETE between those two statements is run by another client. But for most applications,
|
198
198
|
# that's a significantly less likely condition to hit.
|
199
199
|
# * It relies on exception handling to handle control flow, which may be marginally slower.
|
200
|
+
# * The primary key may auto-increment on each create, even if it fails. This can accelerate
|
201
|
+
# 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
|
203
|
+
# to this problem).
|
200
204
|
#
|
201
205
|
# This method will return a record if all given attributes are covered by unique constraints
|
202
206
|
# (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
|
@@ -287,31 +291,99 @@ module ActiveRecord
|
|
287
291
|
limit_value ? records.many? : size > 1
|
288
292
|
end
|
289
293
|
|
290
|
-
# Returns a cache key that can be used to identify
|
291
|
-
#
|
292
|
-
# the number of records matched by the query and a timestamp of the last
|
293
|
-
# updated record. When a new record comes to match the query, or any of
|
294
|
-
# the existing records is updated or deleted, the cache key changes.
|
294
|
+
# Returns a stable cache key that can be used to identify this query.
|
295
|
+
# The cache key is built with a fingerprint of the SQL query.
|
295
296
|
#
|
296
|
-
#
|
297
|
-
#
|
297
|
+
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
298
|
+
# # => "products/query-1850ab3d302391b85b8693e941286659"
|
298
299
|
#
|
299
|
-
# If
|
300
|
-
#
|
300
|
+
# If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
|
301
|
+
# in Rails 6.0 and earlier, the cache key will also include a version.
|
301
302
|
#
|
302
|
-
#
|
303
|
+
# ActiveRecord::Base.collection_cache_versioning = false
|
304
|
+
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
305
|
+
# # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
|
303
306
|
#
|
304
307
|
# You can also pass a custom timestamp column to fetch the timestamp of the
|
305
308
|
# last updated record.
|
306
309
|
#
|
307
310
|
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
|
308
|
-
#
|
309
|
-
# You can customize the strategy to generate the key on a per model basis
|
310
|
-
# overriding ActiveRecord::Base#collection_cache_key.
|
311
311
|
def cache_key(timestamp_column = :updated_at)
|
312
312
|
@cache_keys ||= {}
|
313
|
-
@cache_keys[timestamp_column] ||=
|
313
|
+
@cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
|
314
|
+
end
|
315
|
+
|
316
|
+
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
|
317
|
+
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
|
318
|
+
key = "#{klass.model_name.cache_key}/query-#{query_signature}"
|
319
|
+
|
320
|
+
if cache_version(timestamp_column)
|
321
|
+
key
|
322
|
+
else
|
323
|
+
"#{key}-#{compute_cache_version(timestamp_column)}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
private :compute_cache_key
|
327
|
+
|
328
|
+
# Returns a cache version that can be used together with the cache key to form
|
329
|
+
# a recyclable caching scheme. The cache version is built with the number of records
|
330
|
+
# matching the query, and the timestamp of the last updated record. When a new record
|
331
|
+
# comes to match the query, or any of the existing records is updated or deleted,
|
332
|
+
# the cache version changes.
|
333
|
+
#
|
334
|
+
# If the collection is loaded, the method will iterate through the records
|
335
|
+
# to generate the timestamp, otherwise it will trigger one SQL query like:
|
336
|
+
#
|
337
|
+
# SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
|
338
|
+
def cache_version(timestamp_column = :updated_at)
|
339
|
+
if collection_cache_versioning
|
340
|
+
@cache_versions ||= {}
|
341
|
+
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def compute_cache_version(timestamp_column) # :nodoc:
|
346
|
+
if loaded? || distinct_value
|
347
|
+
size = records.size
|
348
|
+
if size > 0
|
349
|
+
timestamp = max_by(×tamp_column)._read_attribute(timestamp_column)
|
350
|
+
end
|
351
|
+
else
|
352
|
+
collection = eager_loading? ? apply_join_dependency : self
|
353
|
+
|
354
|
+
column = connection.visitor.compile(arel_attribute(timestamp_column))
|
355
|
+
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
356
|
+
|
357
|
+
if collection.has_limit_or_offset?
|
358
|
+
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
359
|
+
subquery_alias = "subquery_for_cache_key"
|
360
|
+
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
361
|
+
arel = query.build_subquery(subquery_alias, select_values % subquery_column)
|
362
|
+
else
|
363
|
+
query = collection.unscope(:order)
|
364
|
+
query.select_values = [select_values % column]
|
365
|
+
arel = query.arel
|
366
|
+
end
|
367
|
+
|
368
|
+
result = connection.select_one(arel, nil)
|
369
|
+
|
370
|
+
if result
|
371
|
+
column_type = klass.type_for_attribute(timestamp_column)
|
372
|
+
timestamp = column_type.deserialize(result["timestamp"])
|
373
|
+
size = result["size"]
|
374
|
+
else
|
375
|
+
timestamp = nil
|
376
|
+
size = 0
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
if timestamp
|
381
|
+
"#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
|
382
|
+
else
|
383
|
+
"#{size}"
|
384
|
+
end
|
314
385
|
end
|
386
|
+
private :compute_cache_version
|
315
387
|
|
316
388
|
# Scope all queries to the current scope.
|
317
389
|
#
|
@@ -338,6 +410,8 @@ module ActiveRecord
|
|
338
410
|
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
|
339
411
|
# Active Record's normal type casting and serialization.
|
340
412
|
#
|
413
|
+
# Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
|
414
|
+
#
|
341
415
|
# ==== Parameters
|
342
416
|
#
|
343
417
|
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
@@ -412,10 +486,10 @@ module ActiveRecord
|
|
412
486
|
update_all updates
|
413
487
|
end
|
414
488
|
|
415
|
-
# Touches all records in the current relation without instantiating records first with the updated_at
|
489
|
+
# Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
|
416
490
|
# set to the current time or the time specified.
|
417
491
|
# This method can be passed attribute names and an optional time argument.
|
418
|
-
# If attribute names are passed, they are updated along with updated_at
|
492
|
+
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
419
493
|
# If no time argument is passed, the current time is used as default.
|
420
494
|
#
|
421
495
|
# === Examples
|
@@ -479,8 +553,8 @@ module ActiveRecord
|
|
479
553
|
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
|
480
554
|
def delete_all
|
481
555
|
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
482
|
-
value =
|
483
|
-
|
556
|
+
value = @values[method]
|
557
|
+
method == :distinct ? value : value&.any?
|
484
558
|
end
|
485
559
|
if invalid_methods.any?
|
486
560
|
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
@@ -670,6 +744,10 @@ module ActiveRecord
|
|
670
744
|
@loaded = true
|
671
745
|
end
|
672
746
|
|
747
|
+
def null_relation? # :nodoc:
|
748
|
+
is_a?(NullRelation)
|
749
|
+
end
|
750
|
+
|
673
751
|
private
|
674
752
|
def already_in_scope?
|
675
753
|
@delegate_to_klass && begin
|
@@ -719,7 +797,7 @@ module ActiveRecord
|
|
719
797
|
@records =
|
720
798
|
if eager_loading?
|
721
799
|
apply_join_dependency do |relation, join_dependency|
|
722
|
-
if
|
800
|
+
if relation.null_relation?
|
723
801
|
[]
|
724
802
|
else
|
725
803
|
relation = join_dependency.apply_column_aliases(relation)
|
@@ -129,11 +129,12 @@ module ActiveRecord
|
|
129
129
|
relation = apply_join_dependency
|
130
130
|
|
131
131
|
if operation.to_s.downcase == "count"
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
relation.order_values = []
|
132
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
133
|
+
relation.distinct!
|
134
|
+
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
136
135
|
end
|
136
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
137
|
+
relation.order_values = []
|
137
138
|
end
|
138
139
|
|
139
140
|
relation.calculate(operation, column_name)
|
@@ -221,7 +222,6 @@ module ActiveRecord
|
|
221
222
|
end
|
222
223
|
|
223
224
|
private
|
224
|
-
|
225
225
|
def has_include?(column_name)
|
226
226
|
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
227
227
|
end
|
@@ -236,10 +236,12 @@ module ActiveRecord
|
|
236
236
|
if operation == "count"
|
237
237
|
column_name ||= select_for_count
|
238
238
|
if column_name == :all
|
239
|
-
if distinct
|
239
|
+
if !distinct
|
240
|
+
distinct = distinct_select?(select_for_count) if group_values.empty?
|
241
|
+
elsif group_values.any? || select_values.empty? && order_values.empty?
|
240
242
|
column_name = primary_key
|
241
243
|
end
|
242
|
-
elsif
|
244
|
+
elsif distinct_select?(column_name)
|
243
245
|
distinct = nil
|
244
246
|
end
|
245
247
|
end
|
@@ -251,13 +253,15 @@ module ActiveRecord
|
|
251
253
|
end
|
252
254
|
end
|
253
255
|
|
256
|
+
def distinct_select?(column_name)
|
257
|
+
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
258
|
+
end
|
259
|
+
|
254
260
|
def aggregate_column(column_name)
|
255
261
|
return column_name if Arel::Expressions === column_name
|
256
262
|
|
257
|
-
|
258
|
-
|
259
|
-
else
|
260
|
-
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
263
|
+
arel_column(column_name.to_s) do |name|
|
264
|
+
Arel.sql(column_name == :all ? "*" : name)
|
261
265
|
end
|
262
266
|
end
|
263
267
|
|
@@ -302,25 +306,22 @@ module ActiveRecord
|
|
302
306
|
end
|
303
307
|
|
304
308
|
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
305
|
-
|
309
|
+
group_fields = group_values
|
306
310
|
|
307
|
-
if
|
308
|
-
association =
|
309
|
-
associated =
|
310
|
-
group_fields = Array(
|
311
|
-
else
|
312
|
-
group_fields = group_attrs
|
311
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
312
|
+
association = klass._reflect_on_association(group_fields.first)
|
313
|
+
associated = association && association.belongs_to? # only count belongs_to associations
|
314
|
+
group_fields = Array(association.foreign_key) if associated
|
313
315
|
end
|
314
316
|
group_fields = arel_columns(group_fields)
|
315
317
|
|
316
|
-
group_aliases = group_fields.map { |field|
|
318
|
+
group_aliases = group_fields.map { |field|
|
319
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
320
|
+
column_alias_for(field.to_s.downcase)
|
321
|
+
}
|
317
322
|
group_columns = group_aliases.zip(group_fields)
|
318
323
|
|
319
|
-
|
320
|
-
aggregate_alias = "count_all"
|
321
|
-
else
|
322
|
-
aggregate_alias = column_alias_for([operation, column_name].join(" "))
|
323
|
-
end
|
324
|
+
aggregate_alias = column_alias_for("#{operation}_#{column_name.to_s.downcase}")
|
324
325
|
|
325
326
|
select_values = [
|
326
327
|
operation_over_aggregate_column(
|
@@ -339,7 +340,7 @@ module ActiveRecord
|
|
339
340
|
}
|
340
341
|
|
341
342
|
relation = except(:group).distinct!(false)
|
342
|
-
relation.group_values =
|
343
|
+
relation.group_values = group_aliases
|
343
344
|
relation.select_values = select_values
|
344
345
|
|
345
346
|
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
|
@@ -365,25 +366,23 @@ module ActiveRecord
|
|
365
366
|
end]
|
366
367
|
end
|
367
368
|
|
368
|
-
# Converts the given
|
369
|
+
# Converts the given field to the value that the database adapter returns as
|
369
370
|
# a usable column name:
|
370
371
|
#
|
371
372
|
# column_alias_for("users.id") # => "users_id"
|
372
373
|
# column_alias_for("sum(id)") # => "sum_id"
|
373
374
|
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
374
375
|
# column_alias_for("count(*)") # => "count_all"
|
375
|
-
def column_alias_for(
|
376
|
-
if
|
377
|
-
keys = "#{keys.relation.name}.#{keys.name}"
|
378
|
-
end
|
376
|
+
def column_alias_for(field)
|
377
|
+
return field if field.match?(/\A\w{,#{connection.table_alias_length}}\z/)
|
379
378
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
379
|
+
column_alias = +field
|
380
|
+
column_alias.gsub!(/\*/, "all")
|
381
|
+
column_alias.gsub!(/\W+/, " ")
|
382
|
+
column_alias.strip!
|
383
|
+
column_alias.gsub!(/ +/, "_")
|
385
384
|
|
386
|
-
|
385
|
+
connection.table_alias_for(column_alias)
|
387
386
|
end
|
388
387
|
|
389
388
|
def type_for(field, &block)
|
@@ -411,16 +410,17 @@ module ActiveRecord
|
|
411
410
|
|
412
411
|
def build_count_subquery(relation, column_name, distinct)
|
413
412
|
if column_name == :all
|
413
|
+
column_alias = Arel.star
|
414
414
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
415
415
|
else
|
416
416
|
column_alias = Arel.sql("count_column")
|
417
417
|
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
418
418
|
end
|
419
419
|
|
420
|
-
|
421
|
-
select_value = operation_over_aggregate_column(column_alias
|
420
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
421
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
422
422
|
|
423
|
-
|
423
|
+
relation.build_subquery(subquery_alias, select_value)
|
424
424
|
end
|
425
425
|
end
|
426
426
|
end
|