activerecord 5.0.7.2 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (363) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +829 -2015
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +11 -9
  5. data/examples/performance.rb +31 -29
  6. data/examples/simple.rb +5 -3
  7. data/lib/active_record.rb +37 -29
  8. data/lib/active_record/aggregations.rb +249 -247
  9. data/lib/active_record/association_relation.rb +30 -18
  10. data/lib/active_record/associations.rb +1714 -1596
  11. data/lib/active_record/associations/alias_tracker.rb +36 -42
  12. data/lib/active_record/associations/association.rb +143 -68
  13. data/lib/active_record/associations/association_scope.rb +98 -94
  14. data/lib/active_record/associations/belongs_to_association.rb +76 -46
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -28
  17. data/lib/active_record/associations/builder/belongs_to.rb +52 -60
  18. data/lib/active_record/associations/builder/collection_association.rb +12 -22
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
  20. data/lib/active_record/associations/builder/has_many.rb +10 -2
  21. data/lib/active_record/associations/builder/has_one.rb +35 -2
  22. data/lib/active_record/associations/builder/singular_association.rb +5 -1
  23. data/lib/active_record/associations/collection_association.rb +104 -259
  24. data/lib/active_record/associations/collection_proxy.rb +169 -125
  25. data/lib/active_record/associations/foreign_association.rb +22 -0
  26. data/lib/active_record/associations/has_many_association.rb +46 -31
  27. data/lib/active_record/associations/has_many_through_association.rb +66 -46
  28. data/lib/active_record/associations/has_one_association.rb +71 -52
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +169 -180
  31. data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +97 -104
  35. data/lib/active_record/associations/preloader/association.rb +109 -97
  36. data/lib/active_record/associations/preloader/through_association.rb +77 -76
  37. data/lib/active_record/associations/singular_association.rb +12 -45
  38. data/lib/active_record/associations/through_association.rb +27 -15
  39. data/lib/active_record/attribute_assignment.rb +55 -60
  40. data/lib/active_record/attribute_methods.rb +111 -141
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
  42. data/lib/active_record/attribute_methods/dirty.rb +172 -112
  43. data/lib/active_record/attribute_methods/primary_key.rb +88 -91
  44. data/lib/active_record/attribute_methods/query.rb +6 -8
  45. data/lib/active_record/attribute_methods/read.rb +18 -50
  46. data/lib/active_record/attribute_methods/serialization.rb +38 -10
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
  48. data/lib/active_record/attribute_methods/write.rb +25 -32
  49. data/lib/active_record/attributes.rb +69 -31
  50. data/lib/active_record/autosave_association.rb +102 -66
  51. data/lib/active_record/base.rb +16 -25
  52. data/lib/active_record/callbacks.rb +202 -43
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +11 -12
  55. data/lib/active_record/connection_adapters.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
  69. data/lib/active_record/connection_adapters/column.rb +55 -13
  70. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  71. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
  82. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  83. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
  85. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
  86. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
  93. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
  95. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
  98. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
  106. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
  107. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  109. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  110. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
  121. data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
  123. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  124. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
  131. data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
  132. data/lib/active_record/connection_handling.rb +287 -45
  133. data/lib/active_record/core.rb +385 -181
  134. data/lib/active_record/counter_cache.rb +60 -28
  135. data/lib/active_record/database_configurations.rb +272 -0
  136. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  137. data/lib/active_record/database_configurations/database_config.rb +80 -0
  138. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  139. data/lib/active_record/database_configurations/url_config.rb +53 -0
  140. data/lib/active_record/delegated_type.rb +209 -0
  141. data/lib/active_record/destroy_association_async_job.rb +36 -0
  142. data/lib/active_record/dynamic_matchers.rb +87 -87
  143. data/lib/active_record/enum.rb +122 -47
  144. data/lib/active_record/errors.rb +153 -22
  145. data/lib/active_record/explain.rb +13 -8
  146. data/lib/active_record/explain_registry.rb +3 -1
  147. data/lib/active_record/explain_subscriber.rb +9 -4
  148. data/lib/active_record/fixture_set/file.rb +20 -22
  149. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  150. data/lib/active_record/fixture_set/render_context.rb +17 -0
  151. data/lib/active_record/fixture_set/table_row.rb +152 -0
  152. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  153. data/lib/active_record/fixtures.rb +246 -507
  154. data/lib/active_record/gem_version.rb +6 -4
  155. data/lib/active_record/inheritance.rb +168 -95
  156. data/lib/active_record/insert_all.rb +208 -0
  157. data/lib/active_record/integration.rb +114 -25
  158. data/lib/active_record/internal_metadata.rb +30 -24
  159. data/lib/active_record/legacy_yaml_adapter.rb +11 -5
  160. data/lib/active_record/locking/optimistic.rb +81 -85
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +68 -31
  163. data/lib/active_record/middleware/database_selector.rb +77 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  166. data/lib/active_record/migration.rb +439 -342
  167. data/lib/active_record/migration/command_recorder.rb +152 -98
  168. data/lib/active_record/migration/compatibility.rb +229 -60
  169. data/lib/active_record/migration/join_table.rb +8 -7
  170. data/lib/active_record/model_schema.rb +230 -122
  171. data/lib/active_record/nested_attributes.rb +213 -203
  172. data/lib/active_record/no_touching.rb +11 -2
  173. data/lib/active_record/null_relation.rb +12 -34
  174. data/lib/active_record/persistence.rb +471 -97
  175. data/lib/active_record/query_cache.rb +23 -12
  176. data/lib/active_record/querying.rb +43 -25
  177. data/lib/active_record/railtie.rb +155 -43
  178. data/lib/active_record/railties/console_sandbox.rb +2 -0
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +507 -195
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +245 -269
  183. data/lib/active_record/relation.rb +475 -324
  184. data/lib/active_record/relation/batches.rb +125 -72
  185. data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
  186. data/lib/active_record/relation/calculations.rb +267 -171
  187. data/lib/active_record/relation/delegation.rb +73 -69
  188. data/lib/active_record/relation/finder_methods.rb +238 -248
  189. data/lib/active_record/relation/from_clause.rb +7 -9
  190. data/lib/active_record/relation/merger.rb +95 -77
  191. data/lib/active_record/relation/predicate_builder.rb +109 -110
  192. data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
  193. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  194. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
  195. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
  196. data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
  197. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  198. data/lib/active_record/relation/query_attribute.rb +33 -2
  199. data/lib/active_record/relation/query_methods.rb +654 -374
  200. data/lib/active_record/relation/record_fetch_warning.rb +8 -6
  201. data/lib/active_record/relation/spawn_methods.rb +15 -14
  202. data/lib/active_record/relation/where_clause.rb +171 -109
  203. data/lib/active_record/result.rb +88 -51
  204. data/lib/active_record/runtime_registry.rb +5 -3
  205. data/lib/active_record/sanitization.rb +73 -100
  206. data/lib/active_record/schema.rb +7 -14
  207. data/lib/active_record/schema_dumper.rb +101 -69
  208. data/lib/active_record/schema_migration.rb +16 -12
  209. data/lib/active_record/scoping.rb +20 -20
  210. data/lib/active_record/scoping/default.rb +92 -95
  211. data/lib/active_record/scoping/named.rb +39 -30
  212. data/lib/active_record/secure_token.rb +19 -9
  213. data/lib/active_record/serialization.rb +7 -3
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +80 -29
  216. data/lib/active_record/store.rb +122 -42
  217. data/lib/active_record/suppressor.rb +6 -3
  218. data/lib/active_record/table_metadata.rb +51 -39
  219. data/lib/active_record/tasks/database_tasks.rb +332 -115
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +246 -0
  225. data/lib/active_record/timestamp.rb +70 -38
  226. data/lib/active_record/touch_later.rb +26 -24
  227. data/lib/active_record/transactions.rb +121 -184
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type.rb +29 -17
  230. data/lib/active_record/type/adapter_specific_registry.rb +44 -48
  231. data/lib/active_record/type/date.rb +2 -0
  232. data/lib/active_record/type/date_time.rb +2 -0
  233. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  234. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  235. data/lib/active_record/type/internal/timezone.rb +2 -0
  236. data/lib/active_record/type/json.rb +30 -0
  237. data/lib/active_record/type/serialized.rb +20 -9
  238. data/lib/active_record/type/text.rb +11 -0
  239. data/lib/active_record/type/time.rb +12 -1
  240. data/lib/active_record/type/type_map.rb +14 -17
  241. data/lib/active_record/type/unsigned_integer.rb +16 -0
  242. data/lib/active_record/type_caster.rb +4 -2
  243. data/lib/active_record/type_caster/connection.rb +17 -13
  244. data/lib/active_record/type_caster/map.rb +10 -6
  245. data/lib/active_record/validations.rb +8 -5
  246. data/lib/active_record/validations/absence.rb +2 -0
  247. data/lib/active_record/validations/associated.rb +4 -3
  248. data/lib/active_record/validations/length.rb +2 -0
  249. data/lib/active_record/validations/numericality.rb +35 -0
  250. data/lib/active_record/validations/presence.rb +4 -2
  251. data/lib/active_record/validations/uniqueness.rb +52 -45
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/arel.rb +54 -0
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes.rb +70 -0
  269. data/lib/arel/nodes/and.rb +32 -0
  270. data/lib/arel/nodes/ascending.rb +23 -0
  271. data/lib/arel/nodes/binary.rb +126 -0
  272. data/lib/arel/nodes/bind_param.rb +44 -0
  273. data/lib/arel/nodes/case.rb +55 -0
  274. data/lib/arel/nodes/casted.rb +62 -0
  275. data/lib/arel/nodes/comment.rb +29 -0
  276. data/lib/arel/nodes/count.rb +12 -0
  277. data/lib/arel/nodes/delete_statement.rb +45 -0
  278. data/lib/arel/nodes/descending.rb +23 -0
  279. data/lib/arel/nodes/equality.rb +15 -0
  280. data/lib/arel/nodes/extract.rb +24 -0
  281. data/lib/arel/nodes/false.rb +16 -0
  282. data/lib/arel/nodes/full_outer_join.rb +8 -0
  283. data/lib/arel/nodes/function.rb +44 -0
  284. data/lib/arel/nodes/grouping.rb +11 -0
  285. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  286. data/lib/arel/nodes/in.rb +15 -0
  287. data/lib/arel/nodes/infix_operation.rb +92 -0
  288. data/lib/arel/nodes/inner_join.rb +8 -0
  289. data/lib/arel/nodes/insert_statement.rb +37 -0
  290. data/lib/arel/nodes/join_source.rb +20 -0
  291. data/lib/arel/nodes/matches.rb +18 -0
  292. data/lib/arel/nodes/named_function.rb +23 -0
  293. data/lib/arel/nodes/node.rb +51 -0
  294. data/lib/arel/nodes/node_expression.rb +13 -0
  295. data/lib/arel/nodes/ordering.rb +27 -0
  296. data/lib/arel/nodes/outer_join.rb +8 -0
  297. data/lib/arel/nodes/over.rb +15 -0
  298. data/lib/arel/nodes/regexp.rb +16 -0
  299. data/lib/arel/nodes/right_outer_join.rb +8 -0
  300. data/lib/arel/nodes/select_core.rb +67 -0
  301. data/lib/arel/nodes/select_statement.rb +41 -0
  302. data/lib/arel/nodes/sql_literal.rb +19 -0
  303. data/lib/arel/nodes/string_join.rb +11 -0
  304. data/lib/arel/nodes/table_alias.rb +31 -0
  305. data/lib/arel/nodes/terminal.rb +16 -0
  306. data/lib/arel/nodes/true.rb +16 -0
  307. data/lib/arel/nodes/unary.rb +44 -0
  308. data/lib/arel/nodes/unary_operation.rb +20 -0
  309. data/lib/arel/nodes/unqualified_column.rb +22 -0
  310. data/lib/arel/nodes/update_statement.rb +41 -0
  311. data/lib/arel/nodes/values_list.rb +9 -0
  312. data/lib/arel/nodes/window.rb +126 -0
  313. data/lib/arel/nodes/with.rb +11 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors.rb +13 -0
  321. data/lib/arel/visitors/dot.rb +308 -0
  322. data/lib/arel/visitors/mysql.rb +93 -0
  323. data/lib/arel/visitors/postgresql.rb +120 -0
  324. data/lib/arel/visitors/sqlite.rb +38 -0
  325. data/lib/arel/visitors/to_sql.rb +899 -0
  326. data/lib/arel/visitors/visitor.rb +45 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/rails/generators/active_record.rb +7 -5
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  331. data/lib/rails/generators/active_record/migration.rb +22 -3
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
  333. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
  335. data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +10 -1
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. metadata +141 -57
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -20
  347. data/lib/active_record/attribute.rb +0 -213
  348. data/lib/active_record/attribute/user_provided_default.rb +0 -28
  349. data/lib/active_record/attribute_decorators.rb +0 -67
  350. data/lib/active_record/attribute_mutation_tracker.rb +0 -70
  351. data/lib/active_record/attribute_set.rb +0 -110
  352. data/lib/active_record/attribute_set/builder.rb +0 -132
  353. data/lib/active_record/collection_cache_key.rb +0 -50
  354. data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
  355. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
  356. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  359. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
  360. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
  361. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
  362. data/lib/active_record/relation/where_clause_factory.rb +0 -38
  363. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/relation/batches/batch_enumerator"
2
4
 
3
5
  module ActiveRecord
4
6
  module Batches
5
- ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
7
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
6
8
 
7
9
  # Looping through a collection of records from the database
8
10
  # (using the Scoping::Named::ClassMethods.all method, for example)
@@ -34,34 +36,44 @@ module ActiveRecord
34
36
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
35
37
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
36
38
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
37
- # the order and limit have to be ignored due to batching.
39
+ # an order is present in the relation.
40
+ # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
41
+ #
42
+ # Limits are honored, and if present there is no requirement for the batch
43
+ # size: it can be less than, equal to, or greater than the limit.
38
44
  #
39
- # This is especially useful if you want multiple workers dealing with
40
- # the same processing queue. You can make worker 1 handle all the records
41
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
42
- # (by setting the +:start+ and +:finish+ option on each worker).
45
+ # The options +start+ and +finish+ are especially useful if you want
46
+ # multiple workers dealing with the same processing queue. You can make
47
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
48
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
49
+ # option on each worker.
43
50
  #
44
- # # Let's process for a batch of 2000 records, skipping the first 2000 rows
45
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
51
+ # # In worker 1, let's process until 9999 records.
52
+ # Person.find_each(finish: 9_999) do |person|
46
53
  # person.party_all_night!
47
54
  # end
48
55
  #
49
- # NOTE: It's not possible to set the order. That is automatically set to
50
- # ascending on the primary key ("id ASC") to make the batch ordering
51
- # work. This also means that this method only works when the primary key is
56
+ # # In worker 2, let's process from record 10_000 and onwards.
57
+ # Person.find_each(start: 10_000) do |person|
58
+ # person.party_all_night!
59
+ # end
60
+ #
61
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
62
+ # ascending on the primary key ("id ASC").
63
+ # This also means that this method only works when the primary key is
52
64
  # orderable (e.g. an integer or string).
53
65
  #
54
- # NOTE: You can't set the limit either, that's used to control
55
- # the batch sizes.
56
- def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
66
+ # NOTE: By its nature, batch processing is subject to race conditions if
67
+ # other processes are modifying the database.
68
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
57
69
  if block_given?
58
- find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
70
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
59
71
  records.each { |record| yield record }
60
72
  end
61
73
  else
62
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
74
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
63
75
  relation = self
64
- apply_limits(relation, start, finish).size
76
+ apply_limits(relation, start, finish, order).size
65
77
  end
66
78
  end
67
79
  end
@@ -89,35 +101,40 @@ module ActiveRecord
89
101
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
90
102
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
91
103
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
92
- # the order and limit have to be ignored due to batching.
104
+ # an order is present in the relation.
105
+ # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
106
+ #
107
+ # Limits are honored, and if present there is no requirement for the batch
108
+ # size: it can be less than, equal to, or greater than the limit.
93
109
  #
94
- # This is especially useful if you want multiple workers dealing with
95
- # the same processing queue. You can make worker 1 handle all the records
96
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
97
- # (by setting the +:start+ and +:finish+ option on each worker).
110
+ # The options +start+ and +finish+ are especially useful if you want
111
+ # multiple workers dealing with the same processing queue. You can make
112
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
113
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
114
+ # option on each worker.
98
115
  #
99
- # # Let's process the next 2000 records
100
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
116
+ # # Let's process from record 10_000 on.
117
+ # Person.find_in_batches(start: 10_000) do |group|
101
118
  # group.each { |person| person.party_all_night! }
102
119
  # end
103
120
  #
104
- # NOTE: It's not possible to set the order. That is automatically set to
105
- # ascending on the primary key ("id ASC") to make the batch ordering
106
- # work. This also means that this method only works when the primary key is
121
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
122
+ # ascending on the primary key ("id ASC").
123
+ # This also means that this method only works when the primary key is
107
124
  # orderable (e.g. an integer or string).
108
125
  #
109
- # NOTE: You can't set the limit either, that's used to control
110
- # the batch sizes.
111
- def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
126
+ # NOTE: By its nature, batch processing is subject to race conditions if
127
+ # other processes are modifying the database.
128
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
112
129
  relation = self
113
130
  unless block_given?
114
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
115
- total = apply_limits(relation, start, finish).size
131
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
132
+ total = apply_limits(relation, start, finish, order).size
116
133
  (total - 1).div(batch_size) + 1
117
134
  end
118
135
  end
119
136
 
120
- in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
137
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
121
138
  yield batch.to_a
122
139
  end
123
140
  end
@@ -149,17 +166,20 @@ module ActiveRecord
149
166
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
150
167
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
151
168
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
152
- # the order and limit have to be ignored due to batching.
169
+ # an order is present in the relation.
170
+ # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
171
+ #
172
+ # Limits are honored, and if present there is no requirement for the batch
173
+ # size, it can be less than, equal, or greater than the limit.
153
174
  #
154
- # This is especially useful if you want to work with the
155
- # ActiveRecord::Relation object instead of the array of records, or if
156
- # you want multiple workers dealing with the same processing queue. You can
157
- # make worker 1 handle all the records between id 0 and 10,000 and worker 2
158
- # handle from 10,000 and beyond (by setting the +:start+ and +:finish+
159
- # option on each worker).
175
+ # The options +start+ and +finish+ are especially useful if you want
176
+ # multiple workers dealing with the same processing queue. You can make
177
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
178
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
179
+ # option on each worker.
160
180
  #
161
- # # Let's process the next 2000 records
162
- # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
181
+ # # Let's process from record 10_000 on.
182
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
163
183
  #
164
184
  # An example of calling where query method on the relation:
165
185
  #
@@ -174,36 +194,47 @@ module ActiveRecord
174
194
  #
175
195
  # Person.in_batches.each_record(&:party_all_night!)
176
196
  #
177
- # NOTE: It's not possible to set the order. That is automatically set to
178
- # ascending on the primary key ("id ASC") to make the batch ordering
179
- # consistent. Therefore the primary key must be orderable, e.g. an integer
180
- # or a string.
197
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
198
+ # ascending on the primary key ("id ASC").
199
+ # This also means that this method only works when the primary key is
200
+ # orderable (e.g. an integer or string).
181
201
  #
182
- # NOTE: You can't set the limit either, that's used to control the batch
183
- # sizes.
184
- def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
202
+ # NOTE: By its nature, batch processing is subject to race conditions if
203
+ # other processes are modifying the database.
204
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
185
205
  relation = self
186
206
  unless block_given?
187
207
  return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
188
208
  end
189
209
 
190
- if arel.orders.present? || arel.taken.present?
191
- act_on_order_or_limit_ignored(error_on_ignore)
210
+ unless [:asc, :desc].include?(order)
211
+ raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
212
+ end
213
+
214
+ if arel.orders.present?
215
+ act_on_ignored_order(error_on_ignore)
192
216
  end
193
217
 
194
- relation = relation.reorder(batch_order).limit(of)
195
- relation = apply_limits(relation, start, finish)
218
+ batch_limit = of
219
+ if limit_value
220
+ remaining = limit_value
221
+ batch_limit = remaining if remaining < batch_limit
222
+ end
223
+
224
+ relation = relation.reorder(batch_order(order)).limit(batch_limit)
225
+ relation = apply_limits(relation, start, finish, order)
226
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
196
227
  batch_relation = relation
197
228
 
198
229
  loop do
199
230
  if load
200
231
  records = batch_relation.records
201
232
  ids = records.map(&:id)
202
- yielded_relation = self.where(primary_key => ids)
233
+ yielded_relation = where(primary_key => ids)
203
234
  yielded_relation.load_records(records)
204
235
  else
205
236
  ids = batch_relation.pluck(primary_key)
206
- yielded_relation = self.where(primary_key => ids)
237
+ yielded_relation = where(primary_key => ids)
207
238
  end
208
239
 
209
240
  break if ids.empty?
@@ -213,31 +244,53 @@ module ActiveRecord
213
244
 
214
245
  yield yielded_relation
215
246
 
216
- break if ids.length < of
217
- batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
247
+ break if ids.length < batch_limit
248
+
249
+ if limit_value
250
+ remaining -= ids.length
251
+
252
+ if remaining == 0
253
+ # Saves a useless iteration when the limit is a multiple of the
254
+ # batch size.
255
+ break
256
+ elsif remaining < batch_limit
257
+ relation = relation.limit(remaining)
258
+ end
259
+ end
260
+
261
+ batch_relation = relation.where(
262
+ predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
263
+ )
218
264
  end
219
265
  end
220
266
 
221
267
  private
268
+ def apply_limits(relation, start, finish, order)
269
+ relation = apply_start_limit(relation, start, order) if start
270
+ relation = apply_finish_limit(relation, finish, order) if finish
271
+ relation
272
+ end
222
273
 
223
- def apply_limits(relation, start, finish)
224
- relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
225
- relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
226
- relation
227
- end
274
+ def apply_start_limit(relation, start, order)
275
+ relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq])
276
+ end
228
277
 
229
- def batch_order
230
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
231
- end
278
+ def apply_finish_limit(relation, finish, order)
279
+ relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq])
280
+ end
281
+
282
+ def batch_order(order)
283
+ table[primary_key].public_send(order)
284
+ end
232
285
 
233
- def act_on_order_or_limit_ignored(error_on_ignore)
234
- raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
286
+ def act_on_ignored_order(error_on_ignore)
287
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
235
288
 
236
- if raise_error
237
- raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
238
- elsif logger
239
- logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
289
+ if raise_error
290
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
291
+ elsif logger
292
+ logger.warn(ORDER_IGNORE_MESSAGE)
293
+ end
240
294
  end
241
- end
242
295
  end
243
296
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Batches
3
5
  class BatchEnumerator
@@ -7,7 +9,7 @@ module ActiveRecord
7
9
  @of = of
8
10
  @relation = relation
9
11
  @start = start
10
- @finish = finish
12
+ @finish = finish
11
13
  end
12
14
 
13
15
  # Looping through a collection of records from the database (using the
@@ -39,19 +41,35 @@ module ActiveRecord
39
41
  end
40
42
  end
41
43
 
42
- # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
44
+ # Deletes records in batches. Returns the total number of rows affected.
45
+ #
46
+ # Person.in_batches.delete_all
47
+ #
48
+ # See Relation#delete_all for details of how each batch is deleted.
49
+ def delete_all
50
+ sum(&:delete_all)
51
+ end
52
+
53
+ # Updates records in batches. Returns the total number of rows affected.
43
54
  #
44
- # People.in_batches.delete_all
45
- # People.where('age < 10').in_batches.destroy_all
46
- # People.in_batches.update_all('age = age + 1')
47
- [:delete_all, :update_all, :destroy_all].each do |method|
48
- define_method(method) do |*args, &block|
49
- @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
50
- relation.send(method, *args, &block)
51
- end
55
+ # Person.in_batches.update_all("age = age + 1")
56
+ #
57
+ # See Relation#update_all for details of how each batch is updated.
58
+ def update_all(updates)
59
+ sum do |relation|
60
+ relation.update_all(updates)
52
61
  end
53
62
  end
54
63
 
64
+ # Destroys records in batches.
65
+ #
66
+ # Person.where("age < 10").in_batches.destroy_all
67
+ #
68
+ # See Relation#destroy_all for details of how each batch is destroyed.
69
+ def destroy_all
70
+ each(&:destroy_all)
71
+ end
72
+
55
73
  # Yields an ActiveRecord::Relation object for each batch of records.
56
74
  #
57
75
  # Person.in_batches.each do |relation|
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+
1
5
  module ActiveRecord
2
6
  module Calculations
3
7
  # Count the records.
@@ -37,7 +41,15 @@ module ActiveRecord
37
41
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
38
42
  # between databases. In invalid cases, an error from the database is thrown.
39
43
  def count(column_name = nil)
40
- calculate(:count, column_name)
44
+ if block_given?
45
+ unless column_name.nil?
46
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
47
+ end
48
+
49
+ super()
50
+ else
51
+ calculate(:count, column_name)
52
+ end
41
53
  end
42
54
 
43
55
  # Calculates the average value on a given column. Returns +nil+ if there's
@@ -71,9 +83,16 @@ module ActiveRecord
71
83
  # #calculate for examples with options.
72
84
  #
73
85
  # Person.sum(:age) # => 4562
74
- def sum(column_name = nil, &block)
75
- return super(&block) if block_given?
76
- calculate(:sum, column_name)
86
+ def sum(column_name = nil)
87
+ if block_given?
88
+ unless column_name.nil?
89
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
90
+ end
91
+
92
+ super()
93
+ else
94
+ calculate(:sum, column_name)
95
+ end
77
96
  end
78
97
 
79
98
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
@@ -108,13 +127,17 @@ module ActiveRecord
108
127
  # ...
109
128
  # end
110
129
  def calculate(operation, column_name)
111
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
112
- column_name = attribute_alias(column_name)
113
- end
114
-
115
130
  if has_include?(column_name)
116
- relation = construct_relation_for_association_calculations
117
- relation = relation.distinct if operation.to_s.downcase == "count"
131
+ relation = apply_join_dependency
132
+
133
+ if operation.to_s.downcase == "count"
134
+ unless distinct_value || distinct_select?(column_name || select_for_count)
135
+ relation.distinct!
136
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
137
+ end
138
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
139
+ relation.order_values = [] if group_values.empty?
140
+ end
118
141
 
119
142
  relation.calculate(operation, column_name)
120
143
  else
@@ -151,29 +174,58 @@ module ActiveRecord
151
174
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
152
175
  # # => [2, 3]
153
176
  #
154
- # Person.pluck('DATEDIFF(updated_at, created_at)')
177
+ # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
155
178
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
156
179
  # # => ['0', '27761', '173']
157
180
  #
158
181
  # See also #ids.
159
182
  #
160
183
  def pluck(*column_names)
161
- if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
184
+ if loaded? && all_attributes?(column_names)
162
185
  return records.pluck(*column_names)
163
186
  end
164
187
 
165
188
  if has_include?(column_names.first)
166
- construct_relation_for_association_calculations.pluck(*column_names)
189
+ relation = apply_join_dependency
190
+ relation.pluck(*column_names)
167
191
  else
192
+ klass.disallow_raw_sql!(column_names)
193
+ columns = arel_columns(column_names)
168
194
  relation = spawn
169
- relation.select_values = column_names.map { |cn|
170
- @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
171
- }
172
- result = klass.connection.select_all(relation.arel, nil, bound_attributes)
173
- result.cast_values(klass.attribute_types)
195
+ relation.select_values = columns
196
+ result = skip_query_cache_if_necessary do
197
+ if where_clause.contradiction?
198
+ ActiveRecord::Result.new([], [])
199
+ else
200
+ klass.connection.select_all(relation.arel, nil)
201
+ end
202
+ end
203
+ type_cast_pluck_values(result, columns)
174
204
  end
175
205
  end
176
206
 
207
+ # Pick the value(s) from the named column(s) in the current relation.
208
+ # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
209
+ # when you have a relation that's already narrowed down to a single row.
210
+ #
211
+ # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
212
+ # more efficient. The value is, again like with pluck, typecast by the column type.
213
+ #
214
+ # Person.where(id: 1).pick(:name)
215
+ # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
216
+ # # => 'David'
217
+ #
218
+ # Person.where(id: 1).pick(:name, :email_address)
219
+ # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
220
+ # # => [ 'David', 'david@loudthinking.com' ]
221
+ def pick(*column_names)
222
+ if loaded? && all_attributes?(column_names)
223
+ return records.pick(*column_names)
224
+ end
225
+
226
+ limit(1).pluck(*column_names).first
227
+ end
228
+
177
229
  # Pluck all the ID's for the relation using the table's primary key
178
230
  #
179
231
  # Person.ids # SELECT people.id FROM people
@@ -183,203 +235,247 @@ module ActiveRecord
183
235
  end
184
236
 
185
237
  private
238
+ def all_attributes?(column_names)
239
+ (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
240
+ end
186
241
 
187
- def has_include?(column_name)
188
- eager_loading? || (includes_values.present? && column_name && column_name != :all)
189
- end
242
+ def has_include?(column_name)
243
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
244
+ end
245
+
246
+ def perform_calculation(operation, column_name)
247
+ operation = operation.to_s.downcase
190
248
 
191
- def perform_calculation(operation, column_name)
192
- operation = operation.to_s.downcase
249
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
250
+ # considered distinct.
251
+ distinct = distinct_value
193
252
 
194
- # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
195
- # considered distinct.
196
- distinct = self.distinct_value
253
+ if operation == "count"
254
+ column_name ||= select_for_count
255
+ if column_name == :all
256
+ if !distinct
257
+ distinct = distinct_select?(select_for_count) if group_values.empty?
258
+ elsif group_values.any? || select_values.empty? && order_values.empty?
259
+ column_name = primary_key
260
+ end
261
+ elsif distinct_select?(column_name)
262
+ distinct = nil
263
+ end
264
+ end
197
265
 
198
- if operation == "count"
199
- column_name ||= select_for_count
200
- column_name = primary_key if column_name == :all && distinct
201
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
266
+ if group_values.any?
267
+ execute_grouped_calculation(operation, column_name, distinct)
268
+ else
269
+ execute_simple_calculation(operation, column_name, distinct)
270
+ end
202
271
  end
203
272
 
204
- if group_values.any?
205
- execute_grouped_calculation(operation, column_name, distinct)
206
- else
207
- execute_simple_calculation(operation, column_name, distinct)
273
+ def distinct_select?(column_name)
274
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
208
275
  end
209
- end
210
276
 
211
- def aggregate_column(column_name)
212
- return column_name if Arel::Expressions === column_name
277
+ def aggregate_column(column_name)
278
+ return column_name if Arel::Expressions === column_name
213
279
 
214
- if @klass.column_names.include?(column_name.to_s)
215
- Arel::Attribute.new(@klass.unscoped.table, column_name)
216
- else
217
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
280
+ arel_column(column_name.to_s) do |name|
281
+ Arel.sql(column_name == :all ? "*" : name)
282
+ end
218
283
  end
219
- end
220
284
 
221
- def operation_over_aggregate_column(column, operation, distinct)
222
- operation == 'count' ? column.count(distinct) : column.send(operation)
223
- end
224
-
225
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
226
- # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
227
- relation = unscope(:order)
285
+ def operation_over_aggregate_column(column, operation, distinct)
286
+ operation == "count" ? column.count(distinct) : column.public_send(operation)
287
+ end
228
288
 
229
- column_alias = column_name
289
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
290
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
291
+ # Shortcut when limit is zero.
292
+ return 0 if limit_value == 0
230
293
 
231
- if operation == "count" && (relation.limit_value || relation.offset_value)
232
- # Shortcut when limit is zero.
233
- return 0 if relation.limit_value == 0
294
+ query_builder = build_count_subquery(spawn, column_name, distinct)
295
+ else
296
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
297
+ relation = unscope(:order).distinct!(false)
234
298
 
235
- query_builder = build_count_subquery(relation, column_name, distinct)
236
- else
237
- column = aggregate_column(column_name)
299
+ column = aggregate_column(column_name)
300
+ select_value = operation_over_aggregate_column(column, operation, distinct)
301
+ select_value.distinct = true if operation == "sum" && distinct
238
302
 
239
- select_value = operation_over_aggregate_column(column, operation, distinct)
303
+ relation.select_values = [select_value]
240
304
 
241
- if operation == "sum" && distinct
242
- select_value.distinct = true
305
+ query_builder = relation.arel
243
306
  end
244
307
 
245
- column_alias = select_value.alias
246
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
247
- relation.select_values = [select_value]
308
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
248
309
 
249
- query_builder = relation.arel
310
+ type_cast_calculated_value(result.cast_values.first, operation) do |value|
311
+ type = column.try(:type_caster) ||
312
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
313
+ type.deserialize(value)
314
+ end
250
315
  end
251
316
 
252
- result = @klass.connection.select_all(query_builder, nil, bound_attributes)
253
- row = result.first
254
- value = row && row.values.first
255
- column = result.column_types.fetch(column_alias) do
256
- type_for(column_name)
257
- end
317
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
318
+ group_fields = group_values
319
+ group_fields = group_fields.uniq if group_fields.size > 1
320
+
321
+ unless group_fields == group_values
322
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
323
+ `#{operation}` with group by duplicated fields does no longer affect to result in Rails 6.2.
324
+ To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields
325
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
326
+ MSG
327
+ group_fields = group_values
328
+ end
258
329
 
259
- type_cast_calculated_value(value, column, operation)
260
- end
330
+ if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
331
+ association = klass._reflect_on_association(group_fields.first)
332
+ associated = association && association.belongs_to? # only count belongs_to associations
333
+ group_fields = Array(association.foreign_key) if associated
334
+ end
335
+ group_fields = arel_columns(group_fields)
261
336
 
262
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
263
- group_attrs = group_values
337
+ group_aliases = group_fields.map { |field|
338
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
339
+ column_alias_for(field.to_s.downcase)
340
+ }
341
+ group_columns = group_aliases.zip(group_fields)
264
342
 
265
- if group_attrs.first.respond_to?(:to_sym)
266
- association = @klass._reflect_on_association(group_attrs.first)
267
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
268
- group_fields = Array(associated ? association.foreign_key : group_attrs)
269
- else
270
- group_fields = group_attrs
271
- end
272
- group_fields = arel_columns(group_fields)
343
+ column = aggregate_column(column_name)
344
+ column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
345
+ select_value = operation_over_aggregate_column(column, operation, distinct)
346
+ select_value.as(column_alias)
273
347
 
274
- group_aliases = group_fields.map { |field| column_alias_for(field) }
275
- group_columns = group_aliases.zip(group_fields)
348
+ select_values = [select_value]
349
+ select_values += self.select_values unless having_clause.empty?
276
350
 
277
- if operation == 'count' && column_name == :all
278
- aggregate_alias = 'count_all'
279
- else
280
- aggregate_alias = column_alias_for([operation, column_name].join(' '))
281
- end
351
+ select_values.concat group_columns.map { |aliaz, field|
352
+ if field.respond_to?(:as)
353
+ field.as(aliaz)
354
+ else
355
+ "#{field} AS #{aliaz}"
356
+ end
357
+ }
282
358
 
283
- select_values = [
284
- operation_over_aggregate_column(
285
- aggregate_column(column_name),
286
- operation,
287
- distinct).as(aggregate_alias)
288
- ]
289
- select_values += self.select_values unless having_clause.empty?
290
-
291
- select_values.concat group_columns.map { |aliaz, field|
292
- if field.respond_to?(:as)
293
- field.as(aliaz)
294
- else
295
- "#{field} AS #{aliaz}"
296
- end
297
- }
359
+ relation = except(:group).distinct!(false)
360
+ relation.group_values = group_fields
361
+ relation.select_values = select_values
298
362
 
299
- relation = except(:group)
300
- relation.group_values = group_fields
301
- relation.select_values = select_values
363
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
302
364
 
303
- calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
365
+ if association
366
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
367
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
368
+ key_records = key_records.index_by(&:id)
369
+ end
304
370
 
305
- if association
306
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
307
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
308
- key_records = Hash[key_records.map { |r| [r.id, r] }]
309
- end
371
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
372
+ types[aliaz] = type_for(col_name) do
373
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
374
+ end
375
+ end
310
376
 
311
- Hash[calculated_data.map do |row|
312
- key = group_columns.map { |aliaz, col_name|
313
- column = type_for(col_name) do
314
- calculated_data.column_types.fetch(aliaz) do
315
- Type::Value.new
316
- end
377
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
378
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
379
+ hash[col_name] = row[i]
317
380
  end
318
- type_cast_calculated_value(row[aliaz], column)
319
- }
320
- key = key.first if key.size == 1
321
- key = key_records[key] if associated
381
+ end
322
382
 
323
- column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
324
- [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
325
- end]
326
- end
383
+ type = nil
384
+ hash_rows.each_with_object({}) do |row, result|
385
+ key = group_aliases.map { |aliaz| row[aliaz] }
386
+ key = key.first if key.size == 1
387
+ key = key_records[key] if associated
327
388
 
328
- # Converts the given keys to the value that the database adapter returns as
329
- # a usable column name:
330
- #
331
- # column_alias_for("users.id") # => "users_id"
332
- # column_alias_for("sum(id)") # => "sum_id"
333
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
334
- # column_alias_for("count(*)") # => "count_all"
335
- def column_alias_for(keys)
336
- if keys.respond_to? :name
337
- keys = "#{keys.relation.name}.#{keys.name}"
389
+ result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
390
+ type ||= column.try(:type_caster) ||
391
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
392
+ type.deserialize(value)
393
+ end
394
+ end
338
395
  end
339
396
 
340
- table_name = keys.to_s.downcase
341
- table_name.gsub!(/\*/, 'all')
342
- table_name.gsub!(/\W+/, ' ')
343
- table_name.strip!
344
- table_name.gsub!(/ +/, '_')
397
+ # Converts the given field to the value that the database adapter returns as
398
+ # a usable column name:
399
+ #
400
+ # column_alias_for("users.id") # => "users_id"
401
+ # column_alias_for("sum(id)") # => "sum_id"
402
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
403
+ # column_alias_for("count(*)") # => "count_all"
404
+ def column_alias_for(field)
405
+ column_alias = +field
406
+ column_alias.gsub!(/\*/, "all")
407
+ column_alias.gsub!(/\W+/, " ")
408
+ column_alias.strip!
409
+ column_alias.gsub!(/ +/, "_")
410
+
411
+ connection.table_alias_for(column_alias)
412
+ end
345
413
 
346
- @klass.connection.table_alias_for(table_name)
347
- end
414
+ def type_for(field, &block)
415
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
416
+ @klass.type_for_attribute(field_name, &block)
417
+ end
348
418
 
349
- def type_for(field, &block)
350
- field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
351
- @klass.type_for_attribute(field_name, &block)
352
- end
419
+ def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
420
+ each_join_dependencies(join_dependencies) do |join|
421
+ type = join.base_klass.attribute_types.fetch(name, nil)
422
+ return type if type
423
+ end
424
+ nil
425
+ end
353
426
 
354
- def type_cast_calculated_value(value, type, operation = nil)
355
- case operation
356
- when 'count' then value.to_i
357
- when 'sum' then type.deserialize(value || 0)
358
- when 'average' then value.respond_to?(:to_d) ? value.to_d : value
359
- else type.deserialize(value)
427
+ def type_cast_pluck_values(result, columns)
428
+ cast_types = if result.columns.size != columns.size
429
+ klass.attribute_types
430
+ else
431
+ join_dependencies = nil
432
+ columns.map.with_index do |column, i|
433
+ column.try(:type_caster) ||
434
+ klass.attribute_types.fetch(name = result.columns[i]) do
435
+ join_dependencies ||= build_join_dependencies
436
+ lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
437
+ result.column_types[name] || Type.default_value
438
+ end
439
+ end
440
+ end
441
+ result.cast_values(cast_types)
360
442
  end
361
- end
362
443
 
363
- def select_for_count
364
- if select_values.present?
365
- return select_values.first if select_values.one?
366
- select_values.join(", ")
367
- else
368
- :all
444
+ def type_cast_calculated_value(value, operation)
445
+ case operation
446
+ when "count"
447
+ value.to_i
448
+ when "sum"
449
+ yield value || 0
450
+ when "average"
451
+ value&.respond_to?(:to_d) ? value.to_d : value
452
+ else # "minimum", "maximum"
453
+ yield value
454
+ end
369
455
  end
370
- end
371
456
 
372
- def build_count_subquery(relation, column_name, distinct)
373
- column_alias = Arel.sql('count_column')
374
- subquery_alias = Arel.sql('subquery_for_count')
457
+ def select_for_count
458
+ if select_values.present?
459
+ return select_values.first if select_values.one?
460
+ select_values.join(", ")
461
+ else
462
+ :all
463
+ end
464
+ end
375
465
 
376
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
377
- relation.select_values = [aliased_column]
378
- subquery = relation.arel.as(subquery_alias)
466
+ def build_count_subquery(relation, column_name, distinct)
467
+ if column_name == :all
468
+ column_alias = Arel.star
469
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
470
+ else
471
+ column_alias = Arel.sql("count_column")
472
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
473
+ end
379
474
 
380
- sm = Arel::SelectManager.new relation.engine
381
- select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
382
- sm.project(select_value).from(subquery)
383
- end
475
+ subquery_alias = Arel.sql("subquery_for_count")
476
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
477
+
478
+ relation.build_subquery(subquery_alias, select_value)
479
+ end
384
480
  end
385
481
  end