activerecord 6.0.0 → 7.2.3

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