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.

Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +286 -6
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/associations.rb +3 -2
  6. data/lib/active_record/associations/association.rb +1 -1
  7. data/lib/active_record/associations/builder/association.rb +14 -18
  8. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  10. data/lib/active_record/associations/builder/has_many.rb +2 -0
  11. data/lib/active_record/associations/builder/has_one.rb +35 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  13. data/lib/active_record/associations/collection_proxy.rb +1 -1
  14. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  15. data/lib/active_record/associations/preloader.rb +11 -6
  16. data/lib/active_record/associations/preloader/association.rb +32 -30
  17. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  18. data/lib/active_record/attribute_methods.rb +4 -3
  19. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  20. data/lib/active_record/attribute_methods/dirty.rb +42 -14
  21. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  22. data/lib/active_record/attribute_methods/query.rb +2 -3
  23. data/lib/active_record/attribute_methods/read.rb +3 -9
  24. data/lib/active_record/attribute_methods/write.rb +6 -12
  25. data/lib/active_record/attributes.rb +13 -0
  26. data/lib/active_record/autosave_association.rb +13 -3
  27. data/lib/active_record/base.rb +0 -1
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
  29. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  30. data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
  31. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  32. data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
  33. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
  35. data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
  36. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
  37. data/lib/active_record/connection_adapters/column.rb +17 -13
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  39. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
  41. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  42. data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
  43. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  44. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  45. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  46. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  47. data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
  48. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  49. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  50. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
  53. data/lib/active_record/connection_handling.rb +17 -10
  54. data/lib/active_record/core.rb +15 -20
  55. data/lib/active_record/database_configurations.rb +14 -14
  56. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  57. data/lib/active_record/database_configurations/url_config.rb +12 -12
  58. data/lib/active_record/dynamic_matchers.rb +1 -1
  59. data/lib/active_record/enum.rb +6 -0
  60. data/lib/active_record/errors.rb +1 -1
  61. data/lib/active_record/gem_version.rb +1 -1
  62. data/lib/active_record/insert_all.rb +180 -0
  63. data/lib/active_record/integration.rb +13 -1
  64. data/lib/active_record/internal_metadata.rb +5 -1
  65. data/lib/active_record/locking/optimistic.rb +3 -4
  66. data/lib/active_record/log_subscriber.rb +1 -1
  67. data/lib/active_record/migration.rb +25 -18
  68. data/lib/active_record/migration/command_recorder.rb +28 -14
  69. data/lib/active_record/migration/compatibility.rb +10 -0
  70. data/lib/active_record/persistence.rb +206 -13
  71. data/lib/active_record/querying.rb +17 -12
  72. data/lib/active_record/railties/databases.rake +68 -6
  73. data/lib/active_record/reflection.rb +2 -2
  74. data/lib/active_record/relation.rb +98 -20
  75. data/lib/active_record/relation/calculations.rb +39 -39
  76. data/lib/active_record/relation/delegation.rb +22 -30
  77. data/lib/active_record/relation/finder_methods.rb +3 -9
  78. data/lib/active_record/relation/merger.rb +7 -16
  79. data/lib/active_record/relation/query_methods.rb +153 -38
  80. data/lib/active_record/relation/where_clause.rb +9 -5
  81. data/lib/active_record/sanitization.rb +3 -2
  82. data/lib/active_record/schema_dumper.rb +5 -0
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/scoping/default.rb +6 -7
  85. data/lib/active_record/scoping/named.rb +1 -1
  86. data/lib/active_record/statement_cache.rb +2 -2
  87. data/lib/active_record/store.rb +48 -0
  88. data/lib/active_record/table_metadata.rb +3 -3
  89. data/lib/active_record/tasks/database_tasks.rb +36 -1
  90. data/lib/active_record/touch_later.rb +2 -2
  91. data/lib/active_record/transactions.rb +52 -41
  92. data/lib/active_record/validations/uniqueness.rb +3 -5
  93. data/lib/arel/insert_manager.rb +3 -3
  94. data/lib/arel/nodes.rb +2 -1
  95. data/lib/arel/nodes/comment.rb +29 -0
  96. data/lib/arel/nodes/select_core.rb +16 -12
  97. data/lib/arel/nodes/unary.rb +1 -0
  98. data/lib/arel/nodes/values_list.rb +2 -17
  99. data/lib/arel/select_manager.rb +10 -10
  100. data/lib/arel/visitors/depth_first.rb +6 -1
  101. data/lib/arel/visitors/dot.rb +7 -2
  102. data/lib/arel/visitors/ibm_db.rb +13 -0
  103. data/lib/arel/visitors/informix.rb +6 -0
  104. data/lib/arel/visitors/mssql.rb +15 -1
  105. data/lib/arel/visitors/oracle12.rb +4 -5
  106. data/lib/arel/visitors/postgresql.rb +4 -10
  107. data/lib/arel/visitors/to_sql.rb +87 -108
  108. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  109. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  110. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  111. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  112. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  113. metadata +12 -11
  114. data/lib/active_record/collection_cache_key.rb +0 -53
  115. data/lib/arel/nodes/values.rb +0 -16
@@ -2,18 +2,23 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Querying
5
- delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all
6
- delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
7
- delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
8
- delegate :find_or_create_by, :find_or_create_by!, :create_or_find_by, :create_or_find_by!, :find_or_initialize_by, to: :all
9
- delegate :find_by, :find_by!, to: :all
10
- delegate :destroy_all, :delete_all, :update_all, :destroy_by, :delete_by, to: :all
11
- delegate :find_each, :find_in_batches, :in_batches, to: :all
12
- delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
13
- :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending,
14
- :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all
15
- delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
16
- delegate :pluck, :pick, :ids, to: :all
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: Rails.env).each do |db_config|
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: Rails.env).each do |db_config|
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: Rails.env).each do |db_config|
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: Rails.env).each do |db_config|
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: Rails.env).each do |db_config|
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: Rails.env).each do |db_config|
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 the records fetched by
291
- # this query. The cache key is built with a fingerprint of the sql query,
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
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
297
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
297
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
298
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
298
299
  #
299
- # If the collection is loaded, the method will iterate through the records
300
- # to generate the timestamp, otherwise it will trigger one SQL query like:
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
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
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] ||= @klass.collection_cache_key(self, 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(&timestamp_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/on attributes
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/on attributes.
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 = get_value(method)
483
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
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 ActiveRecord::NullRelation === relation
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
- relation.distinct!
133
- # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
134
- if (column_name == :all || column_name.nil?) && select_values.empty?
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 && (group_values.any? || select_values.empty? && order_values.empty?)
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 column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
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
- if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
258
- @klass.arel_attribute(column_name)
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
- group_attrs = group_values
309
+ group_fields = group_values
306
310
 
307
- if group_attrs.first.respond_to?(:to_sym)
308
- association = @klass._reflect_on_association(group_attrs.first)
309
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
310
- group_fields = Array(associated ? association.foreign_key : group_attrs)
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| column_alias_for(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
- if operation == "count" && column_name == :all
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 = group_fields
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 keys to the value that the database adapter returns as
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(keys)
376
- if keys.respond_to? :name
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
- table_name = keys.to_s.downcase
381
- table_name.gsub!(/\*/, "all")
382
- table_name.gsub!(/\W+/, " ")
383
- table_name.strip!
384
- table_name.gsub!(/ +/, "_")
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
- @klass.connection.table_alias_for(table_name)
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
- subquery = relation.arel.as(Arel.sql("subquery_for_count"))
421
- select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
420
+ subquery_alias = Arel.sql("subquery_for_count")
421
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
422
422
 
423
- Arel::SelectManager.new(subquery).project(select_value)
423
+ relation.build_subquery(subquery_alias, select_value)
424
424
  end
425
425
  end
426
426
  end