activerecord 6.0.0

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 (340) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +195 -0
  8. data/lib/active_record/aggregations.rb +285 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1865 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +332 -0
  13. data/lib/active_record/associations/association_scope.rb +166 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +124 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +36 -0
  16. data/lib/active_record/associations/builder/association.rb +136 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +130 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +72 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +114 -0
  20. data/lib/active_record/associations/builder/has_many.rb +19 -0
  21. data/lib/active_record/associations/builder/has_one.rb +64 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +44 -0
  23. data/lib/active_record/associations/collection_association.rb +498 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1128 -0
  25. data/lib/active_record/associations/foreign_association.rb +20 -0
  26. data/lib/active_record/associations/has_many_association.rb +136 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +220 -0
  28. data/lib/active_record/associations/has_one_association.rb +118 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +258 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +80 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +201 -0
  35. data/lib/active_record/associations/preloader/association.rb +133 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +116 -0
  37. data/lib/active_record/associations/singular_association.rb +59 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +85 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +420 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +81 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +221 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +136 -0
  45. data/lib/active_record/attribute_methods/query.rb +41 -0
  46. data/lib/active_record/attribute_methods/read.rb +47 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +61 -0
  50. data/lib/active_record/attributes.rb +279 -0
  51. data/lib/active_record/autosave_association.rb +508 -0
  52. data/lib/active_record/base.rb +328 -0
  53. data/lib/active_record/callbacks.rb +339 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1165 -0
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +85 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +512 -0
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +154 -0
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +251 -0
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +713 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1475 -0
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +761 -0
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +821 -0
  69. data/lib/active_record/connection_adapters/column.rb +95 -0
  70. data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +146 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +949 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +141 -0
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +557 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +267 -0
  129. data/lib/active_record/core.rb +599 -0
  130. data/lib/active_record/counter_cache.rb +193 -0
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +122 -0
  137. data/lib/active_record/enum.rb +274 -0
  138. data/lib/active_record/errors.rb +388 -0
  139. data/lib/active_record/explain.rb +50 -0
  140. data/lib/active_record/explain_registry.rb +32 -0
  141. data/lib/active_record/explain_subscriber.rb +34 -0
  142. data/lib/active_record/fixture_set/file.rb +82 -0
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +738 -0
  148. data/lib/active_record/gem_version.rb +17 -0
  149. data/lib/active_record/inheritance.rb +293 -0
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +207 -0
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  154. data/lib/active_record/locale/en.yml +48 -0
  155. data/lib/active_record/locking/optimistic.rb +197 -0
  156. data/lib/active_record/locking/pessimistic.rb +89 -0
  157. data/lib/active_record/log_subscriber.rb +118 -0
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +1397 -0
  162. data/lib/active_record/migration/command_recorder.rb +284 -0
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +17 -0
  165. data/lib/active_record/model_schema.rb +542 -0
  166. data/lib/active_record/nested_attributes.rb +600 -0
  167. data/lib/active_record/no_touching.rb +65 -0
  168. data/lib/active_record/null_relation.rb +68 -0
  169. data/lib/active_record/persistence.rb +967 -0
  170. data/lib/active_record/query_cache.rb +52 -0
  171. data/lib/active_record/querying.rb +82 -0
  172. data/lib/active_record/railtie.rb +263 -0
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +7 -0
  175. data/lib/active_record/railties/controller_runtime.rb +51 -0
  176. data/lib/active_record/railties/databases.rake +527 -0
  177. data/lib/active_record/readonly_attributes.rb +24 -0
  178. data/lib/active_record/reflection.rb +1042 -0
  179. data/lib/active_record/relation.rb +859 -0
  180. data/lib/active_record/relation/batches.rb +290 -0
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +424 -0
  183. data/lib/active_record/relation/delegation.rb +130 -0
  184. data/lib/active_record/relation/finder_methods.rb +552 -0
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +184 -0
  187. data/lib/active_record/relation/predicate_builder.rb +150 -0
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +1359 -0
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +77 -0
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +168 -0
  202. data/lib/active_record/runtime_registry.rb +24 -0
  203. data/lib/active_record/sanitization.rb +214 -0
  204. data/lib/active_record/schema.rb +61 -0
  205. data/lib/active_record/schema_dumper.rb +270 -0
  206. data/lib/active_record/schema_migration.rb +60 -0
  207. data/lib/active_record/scoping.rb +106 -0
  208. data/lib/active_record/scoping/default.rb +151 -0
  209. data/lib/active_record/scoping/named.rb +217 -0
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +22 -0
  212. data/lib/active_record/statement_cache.rb +148 -0
  213. data/lib/active_record/store.rb +290 -0
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +506 -0
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +224 -0
  222. data/lib/active_record/timestamp.rb +167 -0
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +493 -0
  225. data/lib/active_record/translation.rb +24 -0
  226. data/lib/active_record/type.rb +78 -0
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +9 -0
  229. data/lib/active_record/type/date_time.rb +9 -0
  230. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  231. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +71 -0
  235. data/lib/active_record/type/text.rb +11 -0
  236. data/lib/active_record/type/time.rb +21 -0
  237. data/lib/active_record/type/type_map.rb +62 -0
  238. data/lib/active_record/type/unsigned_integer.rb +17 -0
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +94 -0
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +60 -0
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +68 -0
  247. data/lib/active_record/validations/uniqueness.rb +226 -0
  248. data/lib/active_record/version.rb +10 -0
  249. data/lib/arel.rb +51 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +19 -0
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +48 -0
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +75 -0
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  337. data/lib/rails/generators/active_record/model/model_generator.rb +49 -0
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  340. metadata +415 -0
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/batches/batch_enumerator"
4
+
5
+ module ActiveRecord
6
+ module Batches
7
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
8
+
9
+ # Looping through a collection of records from the database
10
+ # (using the Scoping::Named::ClassMethods.all method, for example)
11
+ # is very inefficient since it will try to instantiate all the objects at once.
12
+ #
13
+ # In that case, batch processing methods allow you to work
14
+ # with the records in batches, thereby greatly reducing memory consumption.
15
+ #
16
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
17
+ # specified by the +:batch_size+ option).
18
+ #
19
+ # Person.find_each do |person|
20
+ # person.do_awesome_stuff
21
+ # end
22
+ #
23
+ # Person.where("age > 21").find_each do |person|
24
+ # person.party_all_night!
25
+ # end
26
+ #
27
+ # If you do not provide a block to #find_each, it will return an Enumerator
28
+ # for chaining with other methods:
29
+ #
30
+ # Person.find_each.with_index do |person, index|
31
+ # person.award_trophy(index + 1)
32
+ # end
33
+ #
34
+ # ==== Options
35
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
36
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
37
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
38
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
39
+ # an order is present in the relation.
40
+ #
41
+ # Limits are honored, and if present there is no requirement for the batch
42
+ # size: it can be less than, equal to, or greater than the limit.
43
+ #
44
+ # The options +start+ and +finish+ are especially useful if you want
45
+ # multiple workers dealing with the same processing queue. You can make
46
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
47
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
48
+ # option on each worker.
49
+ #
50
+ # # In worker 1, let's process until 9999 records.
51
+ # Person.find_each(finish: 9_999) do |person|
52
+ # person.party_all_night!
53
+ # end
54
+ #
55
+ # # In worker 2, let's process from record 10_000 and onwards.
56
+ # Person.find_each(start: 10_000) do |person|
57
+ # person.party_all_night!
58
+ # end
59
+ #
60
+ # NOTE: It's not possible to set the order. That is automatically set to
61
+ # ascending on the primary key ("id ASC") to make the batch ordering
62
+ # work. This also means that this method only works when the primary key is
63
+ # orderable (e.g. an integer or string).
64
+ #
65
+ # NOTE: By its nature, batch processing is subject to race conditions if
66
+ # other processes are modifying the database.
67
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
68
+ if block_given?
69
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
70
+ records.each { |record| yield record }
71
+ end
72
+ else
73
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
74
+ relation = self
75
+ apply_limits(relation, start, finish).size
76
+ end
77
+ end
78
+ end
79
+
80
+ # Yields each batch of records that was found by the find options as
81
+ # an array.
82
+ #
83
+ # Person.where("age > 21").find_in_batches do |group|
84
+ # sleep(50) # Make sure it doesn't get too crowded in there!
85
+ # group.each { |person| person.party_all_night! }
86
+ # end
87
+ #
88
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
89
+ # for chaining with other methods:
90
+ #
91
+ # Person.find_in_batches.with_index do |group, batch|
92
+ # puts "Processing group ##{batch}"
93
+ # group.each(&:recover_from_last_night!)
94
+ # end
95
+ #
96
+ # To be yielded each record one by one, use #find_each instead.
97
+ #
98
+ # ==== Options
99
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
100
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
101
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
102
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
103
+ # an order is present in the relation.
104
+ #
105
+ # Limits are honored, and if present there is no requirement for the batch
106
+ # size: it can be less than, equal to, or greater than the limit.
107
+ #
108
+ # The options +start+ and +finish+ are especially useful if you want
109
+ # multiple workers dealing with the same processing queue. You can make
110
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
111
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
112
+ # option on each worker.
113
+ #
114
+ # # Let's process from record 10_000 on.
115
+ # Person.find_in_batches(start: 10_000) do |group|
116
+ # group.each { |person| person.party_all_night! }
117
+ # end
118
+ #
119
+ # NOTE: It's not possible to set the order. That is automatically set to
120
+ # ascending on the primary key ("id ASC") to make the batch ordering
121
+ # work. This also means that this method only works when the primary key is
122
+ # orderable (e.g. an integer or string).
123
+ #
124
+ # NOTE: By its nature, batch processing is subject to race conditions if
125
+ # other processes are modifying the database.
126
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
127
+ relation = self
128
+ unless block_given?
129
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
130
+ total = apply_limits(relation, start, finish).size
131
+ (total - 1).div(batch_size) + 1
132
+ end
133
+ end
134
+
135
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
136
+ yield batch.to_a
137
+ end
138
+ end
139
+
140
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
141
+ #
142
+ # Person.where("age > 21").in_batches do |relation|
143
+ # relation.delete_all
144
+ # sleep(10) # Throttle the delete queries
145
+ # end
146
+ #
147
+ # If you do not provide a block to #in_batches, it will return a
148
+ # BatchEnumerator which is enumerable.
149
+ #
150
+ # Person.in_batches.each_with_index do |relation, batch_index|
151
+ # puts "Processing relation ##{batch_index}"
152
+ # relation.delete_all
153
+ # end
154
+ #
155
+ # Examples of calling methods on the returned BatchEnumerator object:
156
+ #
157
+ # Person.in_batches.delete_all
158
+ # Person.in_batches.update_all(awesome: true)
159
+ # Person.in_batches.each_record(&:party_all_night!)
160
+ #
161
+ # ==== Options
162
+ # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
163
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
164
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
165
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
166
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
167
+ # an order is present in the relation.
168
+ #
169
+ # Limits are honored, and if present there is no requirement for the batch
170
+ # size, it can be less than, equal, or greater than the limit.
171
+ #
172
+ # The options +start+ and +finish+ are especially useful if you want
173
+ # multiple workers dealing with the same processing queue. You can make
174
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
175
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
176
+ # option on each worker.
177
+ #
178
+ # # Let's process from record 10_000 on.
179
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
180
+ #
181
+ # An example of calling where query method on the relation:
182
+ #
183
+ # Person.in_batches.each do |relation|
184
+ # relation.update_all('age = age + 1')
185
+ # relation.where('age > 21').update_all(should_party: true)
186
+ # relation.where('age <= 21').delete_all
187
+ # end
188
+ #
189
+ # NOTE: If you are going to iterate through each record, you should call
190
+ # #each_record on the yielded BatchEnumerator:
191
+ #
192
+ # Person.in_batches.each_record(&:party_all_night!)
193
+ #
194
+ # NOTE: It's not possible to set the order. That is automatically set to
195
+ # ascending on the primary key ("id ASC") to make the batch ordering
196
+ # consistent. Therefore the primary key must be orderable, e.g. an integer
197
+ # or a string.
198
+ #
199
+ # NOTE: By its nature, batch processing is subject to race conditions if
200
+ # other processes are modifying the database.
201
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
202
+ relation = self
203
+ unless block_given?
204
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
205
+ end
206
+
207
+ if arel.orders.present?
208
+ act_on_ignored_order(error_on_ignore)
209
+ end
210
+
211
+ batch_limit = of
212
+ if limit_value
213
+ remaining = limit_value
214
+ batch_limit = remaining if remaining < batch_limit
215
+ end
216
+
217
+ relation = relation.reorder(batch_order).limit(batch_limit)
218
+ relation = apply_limits(relation, start, finish)
219
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
220
+ batch_relation = relation
221
+
222
+ loop do
223
+ if load
224
+ records = batch_relation.records
225
+ ids = records.map(&:id)
226
+ yielded_relation = where(primary_key => ids)
227
+ yielded_relation.load_records(records)
228
+ else
229
+ ids = batch_relation.pluck(primary_key)
230
+ yielded_relation = where(primary_key => ids)
231
+ end
232
+
233
+ break if ids.empty?
234
+
235
+ primary_key_offset = ids.last
236
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
237
+
238
+ yield yielded_relation
239
+
240
+ break if ids.length < batch_limit
241
+
242
+ if limit_value
243
+ remaining -= ids.length
244
+
245
+ if remaining == 0
246
+ # Saves a useless iteration when the limit is a multiple of the
247
+ # batch size.
248
+ break
249
+ elsif remaining < batch_limit
250
+ relation = relation.limit(remaining)
251
+ end
252
+ end
253
+
254
+ batch_relation = relation.where(
255
+ bind_attribute(primary_key, primary_key_offset) { |attr, bind| attr.gt(bind) }
256
+ )
257
+ end
258
+ end
259
+
260
+ private
261
+
262
+ def apply_limits(relation, start, finish)
263
+ relation = apply_start_limit(relation, start) if start
264
+ relation = apply_finish_limit(relation, finish) if finish
265
+ relation
266
+ end
267
+
268
+ def apply_start_limit(relation, start)
269
+ relation.where(bind_attribute(primary_key, start) { |attr, bind| attr.gteq(bind) })
270
+ end
271
+
272
+ def apply_finish_limit(relation, finish)
273
+ relation.where(bind_attribute(primary_key, finish) { |attr, bind| attr.lteq(bind) })
274
+ end
275
+
276
+ def batch_order
277
+ arel_attribute(primary_key).asc
278
+ end
279
+
280
+ def act_on_ignored_order(error_on_ignore)
281
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
282
+
283
+ if raise_error
284
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
285
+ elsif logger
286
+ logger.warn(ORDER_IGNORE_MESSAGE)
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Batches
5
+ class BatchEnumerator
6
+ include Enumerable
7
+
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
9
+ @of = of
10
+ @relation = relation
11
+ @start = start
12
+ @finish = finish
13
+ end
14
+
15
+ # Looping through a collection of records from the database (using the
16
+ # +all+ method, for example) is very inefficient since it will try to
17
+ # instantiate all the objects at once.
18
+ #
19
+ # In that case, batch processing methods allow you to work with the
20
+ # records in batches, thereby greatly reducing memory consumption.
21
+ #
22
+ # Person.in_batches.each_record do |person|
23
+ # person.do_awesome_stuff
24
+ # end
25
+ #
26
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
27
+ # person.party_all_night!
28
+ # end
29
+ #
30
+ # If you do not provide a block to #each_record, it will return an Enumerator
31
+ # for chaining with other methods:
32
+ #
33
+ # Person.in_batches.each_record.with_index do |person, index|
34
+ # person.award_trophy(index + 1)
35
+ # end
36
+ def each_record
37
+ return to_enum(:each_record) unless block_given?
38
+
39
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
40
+ relation.records.each { |record| yield record }
41
+ end
42
+ end
43
+
44
+ # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
45
+ #
46
+ # People.in_batches.delete_all
47
+ # People.where('age < 10').in_batches.destroy_all
48
+ # People.in_batches.update_all('age = age + 1')
49
+ [:delete_all, :update_all, :destroy_all].each do |method|
50
+ define_method(method) do |*args, &block|
51
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
52
+ relation.send(method, *args, &block)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Yields an ActiveRecord::Relation object for each batch of records.
58
+ #
59
+ # Person.in_batches.each do |relation|
60
+ # relation.update_all(awesome: true)
61
+ # end
62
+ def each
63
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
64
+ return enum.each { |relation| yield relation } if block_given?
65
+ enum
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,424 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Calculations
5
+ # Count the records.
6
+ #
7
+ # Person.count
8
+ # # => the total count of all people
9
+ #
10
+ # Person.count(:age)
11
+ # # => returns the total count of all people whose age is present in database
12
+ #
13
+ # Person.count(:all)
14
+ # # => performs a COUNT(*) (:all is an alias for '*')
15
+ #
16
+ # Person.distinct.count(:age)
17
+ # # => counts the number of different age values
18
+ #
19
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
20
+ # it returns a Hash whose keys represent the aggregated column,
21
+ # and the values are the respective amounts:
22
+ #
23
+ # Person.group(:city).count
24
+ # # => { 'Rome' => 5, 'Paris' => 3 }
25
+ #
26
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
27
+ # keys are an array containing the individual values of each column and the value
28
+ # of each key would be the #count.
29
+ #
30
+ # Article.group(:status, :category).count
31
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
32
+ # ["published", "business"]=>0, ["published", "technology"]=>2}
33
+ #
34
+ # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
35
+ #
36
+ # Person.select(:age).count
37
+ # # => counts the number of different age values
38
+ #
39
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
40
+ # between databases. In invalid cases, an error from the database is thrown.
41
+ def count(column_name = nil)
42
+ if block_given?
43
+ unless column_name.nil?
44
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
45
+ end
46
+
47
+ super()
48
+ else
49
+ calculate(:count, column_name)
50
+ end
51
+ end
52
+
53
+ # Calculates the average value on a given column. Returns +nil+ if there's
54
+ # no row. See #calculate for examples with options.
55
+ #
56
+ # Person.average(:age) # => 35.8
57
+ def average(column_name)
58
+ calculate(:average, column_name)
59
+ end
60
+
61
+ # Calculates the minimum value on a given column. The value is returned
62
+ # with the same data type of the column, or +nil+ if there's no row. See
63
+ # #calculate for examples with options.
64
+ #
65
+ # Person.minimum(:age) # => 7
66
+ def minimum(column_name)
67
+ calculate(:minimum, column_name)
68
+ end
69
+
70
+ # Calculates the maximum value on a given column. The value is returned
71
+ # with the same data type of the column, or +nil+ if there's no row. See
72
+ # #calculate for examples with options.
73
+ #
74
+ # Person.maximum(:age) # => 93
75
+ def maximum(column_name)
76
+ calculate(:maximum, column_name)
77
+ end
78
+
79
+ # Calculates the sum of values on a given column. The value is returned
80
+ # with the same data type of the column, +0+ if there's no row. See
81
+ # #calculate for examples with options.
82
+ #
83
+ # Person.sum(:age) # => 4562
84
+ def sum(column_name = nil)
85
+ 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()
91
+ else
92
+ calculate(:sum, column_name)
93
+ end
94
+ end
95
+
96
+ # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
97
+ # #minimum, and #maximum have been added as shortcuts.
98
+ #
99
+ # Person.calculate(:count, :all) # The same as Person.count
100
+ # Person.average(:age) # SELECT AVG(age) FROM people...
101
+ #
102
+ # # Selects the minimum age for any family without any minors
103
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
104
+ #
105
+ # Person.sum("2 * age")
106
+ #
107
+ # There are two basic forms of output:
108
+ #
109
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
110
+ # for AVG, and the given column's type for everything else.
111
+ #
112
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
113
+ # takes either a column name, or the name of a belongs_to association.
114
+ #
115
+ # values = Person.group('last_name').maximum(:age)
116
+ # puts values["Drake"]
117
+ # # => 43
118
+ #
119
+ # drake = Family.find_by(last_name: 'Drake')
120
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
121
+ # puts values[drake]
122
+ # # => 43
123
+ #
124
+ # values.each do |family, max_age|
125
+ # ...
126
+ # end
127
+ def calculate(operation, column_name)
128
+ if has_include?(column_name)
129
+ relation = apply_join_dependency
130
+
131
+ if operation.to_s.downcase == "count"
132
+ unless distinct_value || distinct_select?(column_name || select_for_count)
133
+ relation.distinct!
134
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
135
+ end
136
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
137
+ relation.order_values = []
138
+ end
139
+
140
+ relation.calculate(operation, column_name)
141
+ else
142
+ perform_calculation(operation, column_name)
143
+ end
144
+ end
145
+
146
+ # 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.
148
+ #
149
+ # Person.pluck(:name)
150
+ #
151
+ # instead of
152
+ #
153
+ # Person.all.map(&:name)
154
+ #
155
+ # Pluck returns an Array of attribute values type-casted to match
156
+ # the plucked column names, if they can be deduced. Plucking an SQL fragment
157
+ # returns String values by default.
158
+ #
159
+ # Person.pluck(:name)
160
+ # # SELECT people.name FROM people
161
+ # # => ['David', 'Jeremy', 'Jose']
162
+ #
163
+ # Person.pluck(:id, :name)
164
+ # # SELECT people.id, people.name FROM people
165
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
166
+ #
167
+ # Person.distinct.pluck(:role)
168
+ # # SELECT DISTINCT role FROM people
169
+ # # => ['admin', 'member', 'guest']
170
+ #
171
+ # Person.where(age: 21).limit(5).pluck(:id)
172
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
173
+ # # => [2, 3]
174
+ #
175
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
176
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
177
+ # # => ['0', '27761', '173']
178
+ #
179
+ # See also #ids.
180
+ #
181
+ 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)
184
+ end
185
+
186
+ if has_include?(column_names.first)
187
+ relation = apply_join_dependency
188
+ relation.pluck(*column_names)
189
+ else
190
+ klass.disallow_raw_sql!(column_names)
191
+ 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)
195
+ end
196
+ end
197
+
198
+ # Pick the value(s) from the named column(s) in the current relation.
199
+ # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
200
+ # when you have a relation that's already narrowed down to a single row.
201
+ #
202
+ # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
203
+ # more efficient. The value is, again like with pluck, typecast by the column type.
204
+ #
205
+ # Person.where(id: 1).pick(:name)
206
+ # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
207
+ # # => 'David'
208
+ #
209
+ # Person.where(id: 1).pick(:name, :email_address)
210
+ # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
211
+ # # => [ 'David', 'david@loudthinking.com' ]
212
+ def pick(*column_names)
213
+ limit(1).pluck(*column_names).first
214
+ end
215
+
216
+ # Pluck all the ID's for the relation using the table's primary key
217
+ #
218
+ # 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
220
+ def ids
221
+ pluck primary_key
222
+ end
223
+
224
+ private
225
+ def has_include?(column_name)
226
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
227
+ end
228
+
229
+ def perform_calculation(operation, column_name)
230
+ operation = operation.to_s.downcase
231
+
232
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
233
+ # considered distinct.
234
+ distinct = distinct_value
235
+
236
+ if operation == "count"
237
+ column_name ||= select_for_count
238
+ if column_name == :all
239
+ if !distinct
240
+ distinct = distinct_select?(select_for_count) if group_values.empty?
241
+ elsif group_values.any? || select_values.empty? && order_values.empty?
242
+ column_name = primary_key
243
+ end
244
+ elsif distinct_select?(column_name)
245
+ distinct = nil
246
+ end
247
+ end
248
+
249
+ if group_values.any?
250
+ execute_grouped_calculation(operation, column_name, distinct)
251
+ else
252
+ execute_simple_calculation(operation, column_name, distinct)
253
+ end
254
+ end
255
+
256
+ def distinct_select?(column_name)
257
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
258
+ end
259
+
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
+ def operation_over_aggregate_column(column, operation, distinct)
269
+ operation == "count" ? column.count(distinct) : column.send(operation)
270
+ end
271
+
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?)
276
+ # Shortcut when limit is zero.
277
+ return 0 if limit_value == 0
278
+
279
+ query_builder = build_count_subquery(spawn, column_name, distinct)
280
+ else
281
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
282
+ relation = unscope(:order).distinct!(false)
283
+
284
+ column = aggregate_column(column_name)
285
+
286
+ select_value = operation_over_aggregate_column(column, operation, distinct)
287
+ if operation == "sum" && distinct
288
+ select_value.distinct = true
289
+ end
290
+
291
+ column_alias = select_value.alias
292
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
293
+ relation.select_values = [select_value]
294
+
295
+ query_builder = relation.arel
296
+ end
297
+
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)
303
+ end
304
+
305
+ type_cast_calculated_value(value, type, operation)
306
+ end
307
+
308
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
309
+ group_fields = group_values
310
+
311
+ if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
312
+ association = klass._reflect_on_association(group_fields.first)
313
+ associated = association && association.belongs_to? # only count belongs_to associations
314
+ group_fields = Array(association.foreign_key) if associated
315
+ 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
+
342
+ relation = except(:group).distinct!(false)
343
+ relation.group_values = group_fields
344
+ relation.select_values = select_values
345
+
346
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
347
+
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
353
+
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)
358
+ end
359
+ type_cast_calculated_value(row[aliaz], type)
360
+ }
361
+ key = key.first if key.size == 1
362
+ key = key_records[key] if associated
363
+
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
368
+
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)
384
+ end
385
+
386
+ def type_for(field, &block)
387
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
388
+ @klass.type_for_attribute(field_name, &block)
389
+ end
390
+
391
+ def type_cast_calculated_value(value, type, operation = nil)
392
+ 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)
397
+ end
398
+ end
399
+
400
+ def select_for_count
401
+ if select_values.present?
402
+ return select_values.first if select_values.one?
403
+ select_values.join(", ")
404
+ else
405
+ :all
406
+ end
407
+ end
408
+
409
+ def build_count_subquery(relation, column_name, distinct)
410
+ if column_name == :all
411
+ column_alias = Arel.star
412
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
413
+ else
414
+ column_alias = Arel.sql("count_column")
415
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
416
+ end
417
+
418
+ subquery_alias = Arel.sql("subquery_for_count")
419
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
420
+
421
+ relation.build_subquery(subquery_alias, select_value)
422
+ end
423
+ end
424
+ end