activerecord 7.0.0 → 7.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.
Files changed (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +2 -4
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -3,7 +3,49 @@
3
3
  require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Calculations
6
7
  module Calculations
8
+ class ColumnAliasTracker # :nodoc:
9
+ def initialize(connection)
10
+ @connection = connection
11
+ @aliases = Hash.new(0)
12
+ end
13
+
14
+ def alias_for(field)
15
+ aliased_name = column_alias_for(field)
16
+
17
+ if @aliases[aliased_name] == 0
18
+ @aliases[aliased_name] = 1
19
+ aliased_name
20
+ else
21
+ # Update the count
22
+ count = @aliases[aliased_name] += 1
23
+ "#{truncate(aliased_name)}_#{count}"
24
+ end
25
+ end
26
+
27
+ private
28
+ # Converts the given field to the value that the database adapter returns as
29
+ # a usable column name:
30
+ #
31
+ # column_alias_for("users.id") # => "users_id"
32
+ # column_alias_for("sum(id)") # => "sum_id"
33
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
34
+ # column_alias_for("count(*)") # => "count_all"
35
+ def column_alias_for(field)
36
+ column_alias = +field
37
+ column_alias.gsub!(/\*/, "all")
38
+ column_alias.gsub!(/\W+/, " ")
39
+ column_alias.strip!
40
+ column_alias.gsub!(/ +/, "_")
41
+ @connection.table_alias_for(column_alias)
42
+ end
43
+
44
+ def truncate(name)
45
+ name.slice(0, @connection.table_alias_length - 2)
46
+ end
47
+ end
48
+
7
49
  # Count the records.
8
50
  #
9
51
  # Person.count
@@ -30,8 +72,7 @@ module ActiveRecord
30
72
  # of each key would be the #count.
31
73
  #
32
74
  # Article.group(:status, :category).count
33
- # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
34
- # # ["published", "business"]=>0, ["published", "technology"]=>2}
75
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
35
76
  #
36
77
  # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
37
78
  #
@@ -40,6 +81,16 @@ module ActiveRecord
40
81
  #
41
82
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
42
83
  # between databases. In invalid cases, an error from the database is thrown.
84
+ #
85
+ # When given a block, loads all records in the relation, if the relation
86
+ # hasn't been loaded yet. Calls the block with each record in the relation.
87
+ # Returns the number of records for which the block returns a truthy value.
88
+ #
89
+ # Person.count { |person| person.age > 21 }
90
+ # # => counts the number of people older that 21
91
+ #
92
+ # Note: If there are a lot of records in the relation, loading all records
93
+ # could result in performance issues.
43
94
  def count(column_name = nil)
44
95
  if block_given?
45
96
  unless column_name.nil?
@@ -52,6 +103,12 @@ module ActiveRecord
52
103
  end
53
104
  end
54
105
 
106
+ # Same as #count, but performs the query asynchronously and returns an
107
+ # ActiveRecord::Promise.
108
+ def async_count(column_name = nil)
109
+ async.count(column_name)
110
+ end
111
+
55
112
  # Calculates the average value on a given column. Returns +nil+ if there's
56
113
  # no row. See #calculate for examples with options.
57
114
  #
@@ -60,6 +117,12 @@ module ActiveRecord
60
117
  calculate(:average, column_name)
61
118
  end
62
119
 
120
+ # Same as #average, but performs the query asynchronously and returns an
121
+ # ActiveRecord::Promise.
122
+ def async_average(column_name)
123
+ async.average(column_name)
124
+ end
125
+
63
126
  # Calculates the minimum value on a given column. The value is returned
64
127
  # with the same data type of the column, or +nil+ if there's no row. See
65
128
  # #calculate for examples with options.
@@ -69,6 +132,12 @@ module ActiveRecord
69
132
  calculate(:minimum, column_name)
70
133
  end
71
134
 
135
+ # Same as #minimum, but performs the query asynchronously and returns an
136
+ # ActiveRecord::Promise.
137
+ def async_minimum(column_name)
138
+ async.minimum(column_name)
139
+ end
140
+
72
141
  # Calculates the maximum value on a given column. The value is returned
73
142
  # with the same data type of the column, or +nil+ if there's no row. See
74
143
  # #calculate for examples with options.
@@ -78,32 +147,42 @@ module ActiveRecord
78
147
  calculate(:maximum, column_name)
79
148
  end
80
149
 
150
+ # Same as #maximum, but performs the query asynchronously and returns an
151
+ # ActiveRecord::Promise.
152
+ def async_maximum(column_name)
153
+ async.maximum(column_name)
154
+ end
155
+
81
156
  # Calculates the sum of values on a given column. The value is returned
82
157
  # with the same data type of the column, +0+ if there's no row. See
83
158
  # #calculate for examples with options.
84
159
  #
85
160
  # Person.sum(:age) # => 4562
86
- def sum(identity_or_column = nil, &block)
161
+ #
162
+ # When given a block, loads all records in the relation, if the relation
163
+ # hasn't been loaded yet. Calls the block with each record in the relation.
164
+ # Returns the sum of +initial_value_or_column+ and the block return
165
+ # values:
166
+ #
167
+ # Person.sum { |person| person.age } # => 4562
168
+ # Person.sum(1000) { |person| person.age } # => 5562
169
+ #
170
+ # Note: If there are a lot of records in the relation, loading all records
171
+ # could result in performance issues.
172
+ def sum(initial_value_or_column = 0, &block)
87
173
  if block_given?
88
- values = map(&block)
89
- if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [])
90
- identity_or_column = 0
91
- end
92
-
93
- if identity_or_column.nil?
94
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
95
- Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
96
- Sum of non-numeric elements requires an initial argument.
97
- MSG
98
- values.inject(:+) || 0
99
- else
100
- values.sum(identity_or_column)
101
- end
174
+ map(&block).sum(initial_value_or_column)
102
175
  else
103
- calculate(:sum, identity_or_column)
176
+ calculate(:sum, initial_value_or_column)
104
177
  end
105
178
  end
106
179
 
180
+ # Same as #sum, but performs the query asynchronously and returns an
181
+ # ActiveRecord::Promise.
182
+ def async_sum(identity_or_column = nil)
183
+ async.sum(identity_or_column)
184
+ end
185
+
107
186
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
108
187
  # #minimum, and #maximum have been added as shortcuts.
109
188
  #
@@ -136,13 +215,26 @@ module ActiveRecord
136
215
  # ...
137
216
  # end
138
217
  def calculate(operation, column_name)
218
+ operation = operation.to_s.downcase
219
+
220
+ if @none
221
+ case operation
222
+ when "count", "sum"
223
+ result = group_values.any? ? Hash.new : 0
224
+ return @async ? Promise::Complete.new(result) : result
225
+ when "average", "minimum", "maximum"
226
+ result = group_values.any? ? Hash.new : nil
227
+ return @async ? Promise::Complete.new(result) : result
228
+ end
229
+ end
230
+
139
231
  if has_include?(column_name)
140
232
  relation = apply_join_dependency
141
233
 
142
- if operation.to_s.downcase == "count"
234
+ if operation == "count"
143
235
  unless distinct_value || distinct_select?(column_name || select_for_count)
144
236
  relation.distinct!
145
- relation.select_values = [ klass.primary_key || table[Arel.star] ]
237
+ relation.select_values = Array(klass.primary_key || table[Arel.star])
146
238
  end
147
239
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
148
240
  relation.order_values = [] if group_values.empty?
@@ -155,7 +247,7 @@ module ActiveRecord
155
247
  end
156
248
 
157
249
  # Use #pluck as a shortcut to select one or more attributes without
158
- # loading a bunch of records just to grab the attributes you want.
250
+ # loading an entire record object per row.
159
251
  #
160
252
  # Person.pluck(:name)
161
253
  #
@@ -183,36 +275,62 @@ module ActiveRecord
183
275
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
184
276
  # # => [2, 3]
185
277
  #
278
+ # Comment.joins(:person).pluck(:id, person: [:id])
279
+ # # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
280
+ # # => [[1, 2], [2, 2]]
281
+ #
186
282
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
187
283
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
188
284
  # # => ['0', '27761', '173']
189
285
  #
190
286
  # See also #ids.
191
- #
192
287
  def pluck(*column_names)
288
+ if @none
289
+ if @async
290
+ return Promise::Complete.new([])
291
+ else
292
+ return []
293
+ end
294
+ end
295
+
193
296
  if loaded? && all_attributes?(column_names)
194
- return records.pluck(*column_names)
297
+ result = records.pluck(*column_names)
298
+ if @async
299
+ return Promise::Complete.new(result)
300
+ else
301
+ return result
302
+ end
195
303
  end
196
304
 
197
305
  if has_include?(column_names.first)
198
306
  relation = apply_join_dependency
199
307
  relation.pluck(*column_names)
200
308
  else
201
- klass.disallow_raw_sql!(column_names)
309
+ klass.disallow_raw_sql!(flattened_args(column_names))
202
310
  columns = arel_columns(column_names)
203
311
  relation = spawn
204
312
  relation.select_values = columns
205
313
  result = skip_query_cache_if_necessary do
206
314
  if where_clause.contradiction?
207
- ActiveRecord::Result.empty
315
+ ActiveRecord::Result.empty(async: @async)
208
316
  else
209
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
317
+ klass.with_connection do |c|
318
+ c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
319
+ end
210
320
  end
211
321
  end
212
- type_cast_pluck_values(result, columns)
322
+ result.then do |result|
323
+ type_cast_pluck_values(result, columns)
324
+ end
213
325
  end
214
326
  end
215
327
 
328
+ # Same as #pluck, but performs the query asynchronously and returns an
329
+ # ActiveRecord::Promise.
330
+ def async_pluck(*column_names)
331
+ async.pluck(*column_names)
332
+ end
333
+
216
334
  # Pick the value(s) from the named column(s) in the current relation.
217
335
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
218
336
  # when you have a relation that's already narrowed down to a single row.
@@ -229,18 +347,63 @@ module ActiveRecord
229
347
  # # => [ 'David', 'david@loudthinking.com' ]
230
348
  def pick(*column_names)
231
349
  if loaded? && all_attributes?(column_names)
232
- return records.pick(*column_names)
350
+ result = records.pick(*column_names)
351
+ return @async ? Promise::Complete.new(result) : result
233
352
  end
234
353
 
235
- limit(1).pluck(*column_names).first
354
+ limit(1).pluck(*column_names).then(&:first)
355
+ end
356
+
357
+ # Same as #pick, but performs the query asynchronously and returns an
358
+ # ActiveRecord::Promise.
359
+ def async_pick(*column_names)
360
+ async.pick(*column_names)
236
361
  end
237
362
 
238
- # Pluck all the ID's for the relation using the table's primary key
363
+ # Returns the base model's ID's for the relation using the table's primary key
239
364
  #
240
365
  # Person.ids # SELECT people.id FROM people
241
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
366
+ # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
242
367
  def ids
243
- pluck primary_key
368
+ primary_key_array = Array(primary_key)
369
+
370
+ if loaded?
371
+ result = records.map do |record|
372
+ if primary_key_array.one?
373
+ record._read_attribute(primary_key_array.first)
374
+ else
375
+ primary_key_array.map { |column| record._read_attribute(column) }
376
+ end
377
+ end
378
+ return @async ? Promise::Complete.new(result) : result
379
+ end
380
+
381
+ if has_include?(primary_key)
382
+ relation = apply_join_dependency.group(*primary_key_array)
383
+ return relation.ids
384
+ end
385
+
386
+ columns = arel_columns(primary_key_array)
387
+ relation = spawn
388
+ relation.select_values = columns
389
+
390
+ result = if relation.where_clause.contradiction?
391
+ ActiveRecord::Result.empty
392
+ else
393
+ skip_query_cache_if_necessary do
394
+ klass.with_connection do |c|
395
+ c.select_all(relation, "#{klass.name} Ids", async: @async)
396
+ end
397
+ end
398
+ end
399
+
400
+ result.then { |result| type_cast_pluck_values(result, columns) }
401
+ end
402
+
403
+ # Same as #ids, but performs the query asynchronously and returns an
404
+ # ActiveRecord::Promise.
405
+ def async_ids
406
+ async.ids
244
407
  end
245
408
 
246
409
  private
@@ -287,7 +450,7 @@ module ActiveRecord
287
450
  return column_name if Arel::Expressions === column_name
288
451
 
289
452
  arel_column(column_name.to_s) do |name|
290
- Arel.sql(column_name == :all ? "*" : name)
453
+ column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
291
454
  end
292
455
  end
293
456
 
@@ -296,10 +459,11 @@ module ActiveRecord
296
459
  end
297
460
 
298
461
  def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
299
- if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
462
+ if build_count_subquery?(operation, column_name, distinct)
300
463
  # Shortcut when limit is zero.
301
464
  return 0 if limit_value == 0
302
465
 
466
+ relation = self
303
467
  query_builder = build_count_subquery(spawn, column_name, distinct)
304
468
  else
305
469
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -314,15 +478,25 @@ module ActiveRecord
314
478
  query_builder = relation.arel
315
479
  end
316
480
 
317
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}") }
318
-
319
- if operation != "count"
320
- type = column.try(:type_caster) ||
321
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
322
- type = type.subtype if Enum::EnumType === type
481
+ query_result = if relation.where_clause.contradiction?
482
+ ActiveRecord::Result.empty
483
+ else
484
+ skip_query_cache_if_necessary do
485
+ @klass.with_connection do |c|
486
+ c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
487
+ end
488
+ end
323
489
  end
324
490
 
325
- type_cast_calculated_value(result.cast_values.first, operation, type)
491
+ query_result.then do |result|
492
+ if operation != "count"
493
+ type = column.try(:type_caster) ||
494
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
495
+ type = type.subtype if Enum::EnumType === type
496
+ end
497
+
498
+ type_cast_calculated_value(result.cast_values.first, operation, type)
499
+ end
326
500
  end
327
501
 
328
502
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
@@ -336,82 +510,75 @@ module ActiveRecord
336
510
  end
337
511
  group_fields = arel_columns(group_fields)
338
512
 
339
- group_aliases = group_fields.map { |field|
340
- field = connection.visitor.compile(field) if Arel.arel_node?(field)
341
- column_alias_for(field.to_s.downcase)
342
- }
343
- group_columns = group_aliases.zip(group_fields)
513
+ @klass.with_connection do |connection|
514
+ column_alias_tracker = ColumnAliasTracker.new(connection)
344
515
 
345
- column = aggregate_column(column_name)
346
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
347
- select_value = operation_over_aggregate_column(column, operation, distinct)
348
- select_value.as(column_alias)
516
+ group_aliases = group_fields.map { |field|
517
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
518
+ column_alias_tracker.alias_for(field.to_s.downcase)
519
+ }
520
+ group_columns = group_aliases.zip(group_fields)
349
521
 
350
- select_values = [select_value]
351
- select_values += self.select_values unless having_clause.empty?
352
-
353
- select_values.concat group_columns.map { |aliaz, field|
354
- if field.respond_to?(:as)
355
- field.as(aliaz)
356
- else
357
- "#{field} AS #{aliaz}"
358
- end
359
- }
522
+ column = aggregate_column(column_name)
523
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
524
+ select_value = operation_over_aggregate_column(column, operation, distinct)
525
+ select_value.as(adapter_class.quote_column_name(column_alias))
360
526
 
361
- relation = except(:group).distinct!(false)
362
- relation.group_values = group_fields
363
- relation.select_values = select_values
527
+ select_values = [select_value]
528
+ select_values += self.select_values unless having_clause.empty?
364
529
 
365
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}") }
530
+ select_values.concat group_columns.map { |aliaz, field|
531
+ aliaz = adapter_class.quote_column_name(aliaz)
532
+ if field.respond_to?(:as)
533
+ field.as(aliaz)
534
+ else
535
+ "#{field} AS #{aliaz}"
536
+ end
537
+ }
366
538
 
367
- if association
368
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
369
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
370
- key_records = key_records.index_by(&:id)
371
- end
539
+ relation = except(:group).distinct!(false)
540
+ relation.group_values = group_fields
541
+ relation.select_values = select_values
372
542
 
373
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
374
- types[aliaz] = type_for(col_name) do
375
- calculated_data.column_types.fetch(aliaz, Type.default_value)
543
+ result = skip_query_cache_if_necessary do
544
+ connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
376
545
  end
377
- end
378
546
 
379
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
380
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
381
- hash[col_name] = row[i]
382
- end
383
- end
547
+ result.then do |calculated_data|
548
+ if association
549
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
550
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
551
+ key_records = key_records.index_by(&:id)
552
+ end
384
553
 
385
- if operation != "count"
386
- type = column.try(:type_caster) ||
387
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
388
- type = type.subtype if Enum::EnumType === type
389
- end
554
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
555
+ types[aliaz] = col_name.try(:type_caster) ||
556
+ type_for(col_name) do
557
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
558
+ end
559
+ end
390
560
 
391
- hash_rows.each_with_object({}) do |row, result|
392
- key = group_aliases.map { |aliaz| row[aliaz] }
393
- key = key.first if key.size == 1
394
- key = key_records[key] if associated
561
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
562
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
563
+ hash[col_name] = row[i]
564
+ end
565
+ end
395
566
 
396
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
397
- end
398
- end
567
+ if operation != "count"
568
+ type = column.try(:type_caster) ||
569
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
570
+ type = type.subtype if Enum::EnumType === type
571
+ end
572
+
573
+ hash_rows.each_with_object({}) do |row, result|
574
+ key = group_aliases.map { |aliaz| row[aliaz] }
575
+ key = key.first if key.size == 1
576
+ key = key_records[key] if associated
399
577
 
400
- # Converts the given field to the value that the database adapter returns as
401
- # a usable column name:
402
- #
403
- # column_alias_for("users.id") # => "users_id"
404
- # column_alias_for("sum(id)") # => "sum_id"
405
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
406
- # column_alias_for("count(*)") # => "count_all"
407
- def column_alias_for(field)
408
- column_alias = +field
409
- column_alias.gsub!(/\*/, "all")
410
- column_alias.gsub!(/\W+/, " ")
411
- column_alias.strip!
412
- column_alias.gsub!(/ +/, "_")
413
-
414
- connection.table_alias_for(column_alias)
578
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
579
+ end
580
+ end
581
+ end
415
582
  end
416
583
 
417
584
  def type_for(field, &block)
@@ -437,7 +604,7 @@ module ActiveRecord
437
604
  klass.attribute_types.fetch(name = result.columns[i]) do
438
605
  join_dependencies ||= build_join_dependencies
439
606
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
440
- result.column_types[name] || Type.default_value
607
+ result.column_types[i] || Type.default_value
441
608
  end
442
609
  end
443
610
  end
@@ -465,12 +632,30 @@ module ActiveRecord
465
632
  def select_for_count
466
633
  if select_values.present?
467
634
  return select_values.first if select_values.one?
468
- select_values.join(", ")
635
+
636
+ select_values.map do |field|
637
+ column = arel_column(field.to_s) do |attr_name|
638
+ Arel.sql(attr_name)
639
+ end
640
+
641
+ if column.is_a?(Arel::Nodes::SqlLiteral)
642
+ column
643
+ else
644
+ "#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
645
+ end
646
+ end.join(", ")
469
647
  else
470
648
  :all
471
649
  end
472
650
  end
473
651
 
652
+ def build_count_subquery?(operation, column_name, distinct)
653
+ # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
654
+ # multiple columns, so we need to use subquery for this.
655
+ operation == "count" &&
656
+ (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
657
+ end
658
+
474
659
  def build_count_subquery(relation, column_name, distinct)
475
660
  if column_name == :all
476
661
  column_alias = Arel.star
@@ -480,7 +665,7 @@ module ActiveRecord
480
665
  relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
481
666
  end
482
667
 
483
- subquery_alias = Arel.sql("subquery_for_count")
668
+ subquery_alias = Arel.sql("subquery_for_count", retryable: true)
484
669
  select_value = operation_over_aggregate_column(column_alias, "count", false)
485
670
 
486
671
  relation.build_subquery(subquery_alias, select_value)