activerecord 5.0.7.2 → 6.0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (359) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +708 -2040
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +9 -7
  5. data/examples/performance.rb +31 -29
  6. data/examples/simple.rb +5 -3
  7. data/lib/active_record.rb +37 -22
  8. data/lib/active_record/advisory_lock_base.rb +18 -0
  9. data/lib/active_record/aggregations.rb +249 -247
  10. data/lib/active_record/association_relation.rb +18 -14
  11. data/lib/active_record/associations.rb +1603 -1592
  12. data/lib/active_record/associations/alias_tracker.rb +24 -34
  13. data/lib/active_record/associations/association.rb +114 -55
  14. data/lib/active_record/associations/association_scope.rb +94 -94
  15. data/lib/active_record/associations/belongs_to_association.rb +58 -42
  16. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  17. data/lib/active_record/associations/builder/association.rb +18 -25
  18. data/lib/active_record/associations/builder/belongs_to.rb +43 -54
  19. data/lib/active_record/associations/builder/collection_association.rb +7 -18
  20. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +41 -62
  21. data/lib/active_record/associations/builder/has_many.rb +4 -0
  22. data/lib/active_record/associations/builder/has_one.rb +37 -1
  23. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  24. data/lib/active_record/associations/collection_association.rb +86 -254
  25. data/lib/active_record/associations/collection_proxy.rb +158 -122
  26. data/lib/active_record/associations/foreign_association.rb +9 -0
  27. data/lib/active_record/associations/has_many_association.rb +23 -30
  28. data/lib/active_record/associations/has_many_through_association.rb +58 -44
  29. data/lib/active_record/associations/has_one_association.rb +59 -54
  30. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  31. data/lib/active_record/associations/join_dependency.rb +143 -176
  32. data/lib/active_record/associations/join_dependency/join_association.rb +38 -87
  33. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  34. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  35. data/lib/active_record/associations/preloader.rb +90 -103
  36. data/lib/active_record/associations/preloader/association.rb +86 -100
  37. data/lib/active_record/associations/preloader/through_association.rb +77 -76
  38. data/lib/active_record/associations/singular_association.rb +12 -45
  39. data/lib/active_record/associations/through_association.rb +26 -14
  40. data/lib/active_record/attribute_assignment.rb +54 -61
  41. data/lib/active_record/attribute_decorators.rb +38 -17
  42. data/lib/active_record/attribute_methods.rb +66 -106
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -8
  44. data/lib/active_record/attribute_methods/dirty.rb +179 -109
  45. data/lib/active_record/attribute_methods/primary_key.rb +85 -92
  46. data/lib/active_record/attribute_methods/query.rb +4 -3
  47. data/lib/active_record/attribute_methods/read.rb +20 -49
  48. data/lib/active_record/attribute_methods/serialization.rb +29 -7
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -66
  50. data/lib/active_record/attribute_methods/write.rb +34 -33
  51. data/lib/active_record/attributes.rb +38 -25
  52. data/lib/active_record/autosave_association.rb +54 -35
  53. data/lib/active_record/base.rb +27 -24
  54. data/lib/active_record/callbacks.rb +64 -35
  55. data/lib/active_record/coders/json.rb +2 -0
  56. data/lib/active_record/coders/yaml_column.rb +11 -12
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +552 -323
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +23 -5
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +215 -94
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +59 -35
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -75
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +33 -28
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +228 -147
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +400 -213
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -79
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -202
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +396 -562
  70. data/lib/active_record/connection_adapters/column.rb +41 -13
  71. data/lib/active_record/connection_adapters/connection_specification.rb +172 -139
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -4
  73. data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +137 -49
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +58 -56
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +12 -13
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +48 -30
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -31
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -54
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +24 -21
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +4 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
  101. data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +34 -31
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +8 -4
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +95 -35
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +20 -26
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +378 -308
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +26 -25
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +383 -275
  118. data/lib/active_record/connection_adapters/schema_cache.rb +46 -12
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +13 -8
  120. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
  121. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
  122. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
  123. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +3 -8
  124. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  126. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  127. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +259 -266
  128. data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
  129. data/lib/active_record/connection_handling.rb +143 -40
  130. data/lib/active_record/core.rb +201 -163
  131. data/lib/active_record/counter_cache.rb +60 -28
  132. data/lib/active_record/database_configurations.rb +233 -0
  133. data/lib/active_record/database_configurations/database_config.rb +37 -0
  134. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  135. data/lib/active_record/database_configurations/url_config.rb +78 -0
  136. data/lib/active_record/define_callbacks.rb +22 -0
  137. data/lib/active_record/dynamic_matchers.rb +87 -87
  138. data/lib/active_record/enum.rb +60 -23
  139. data/lib/active_record/errors.rb +114 -18
  140. data/lib/active_record/explain.rb +4 -4
  141. data/lib/active_record/explain_registry.rb +3 -1
  142. data/lib/active_record/explain_subscriber.rb +9 -4
  143. data/lib/active_record/fixture_set/file.rb +13 -8
  144. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  145. data/lib/active_record/fixture_set/render_context.rb +17 -0
  146. data/lib/active_record/fixture_set/table_row.rb +152 -0
  147. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  148. data/lib/active_record/fixtures.rb +194 -504
  149. data/lib/active_record/gem_version.rb +5 -3
  150. data/lib/active_record/inheritance.rb +150 -99
  151. data/lib/active_record/insert_all.rb +179 -0
  152. data/lib/active_record/integration.rb +116 -25
  153. data/lib/active_record/internal_metadata.rb +16 -19
  154. data/lib/active_record/legacy_yaml_adapter.rb +4 -2
  155. data/lib/active_record/locking/optimistic.rb +77 -87
  156. data/lib/active_record/locking/pessimistic.rb +18 -6
  157. data/lib/active_record/log_subscriber.rb +48 -29
  158. data/lib/active_record/middleware/database_selector.rb +74 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +369 -302
  162. data/lib/active_record/migration/command_recorder.rb +134 -100
  163. data/lib/active_record/migration/compatibility.rb +174 -56
  164. data/lib/active_record/migration/join_table.rb +8 -7
  165. data/lib/active_record/model_schema.rb +131 -127
  166. data/lib/active_record/nested_attributes.rb +213 -202
  167. data/lib/active_record/no_touching.rb +12 -3
  168. data/lib/active_record/null_relation.rb +12 -34
  169. data/lib/active_record/persistence.rb +446 -77
  170. data/lib/active_record/query_cache.rb +13 -12
  171. data/lib/active_record/querying.rb +37 -24
  172. data/lib/active_record/railtie.rb +128 -36
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +2 -0
  175. data/lib/active_record/railties/controller_runtime.rb +34 -33
  176. data/lib/active_record/railties/databases.rake +312 -177
  177. data/lib/active_record/readonly_attributes.rb +5 -4
  178. data/lib/active_record/reflection.rb +214 -252
  179. data/lib/active_record/relation.rb +440 -318
  180. data/lib/active_record/relation/batches.rb +98 -52
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +3 -1
  182. data/lib/active_record/relation/calculations.rb +212 -173
  183. data/lib/active_record/relation/delegation.rb +72 -69
  184. data/lib/active_record/relation/finder_methods.rb +207 -247
  185. data/lib/active_record/relation/from_clause.rb +6 -8
  186. data/lib/active_record/relation/merger.rb +78 -62
  187. data/lib/active_record/relation/predicate_builder.rb +83 -105
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +20 -14
  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 +4 -3
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
  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 +7 -18
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  195. data/lib/active_record/relation/query_attribute.rb +33 -2
  196. data/lib/active_record/relation/query_methods.rb +476 -334
  197. data/lib/active_record/relation/record_fetch_warning.rb +5 -3
  198. data/lib/active_record/relation/spawn_methods.rb +8 -8
  199. data/lib/active_record/relation/where_clause.rb +111 -96
  200. data/lib/active_record/relation/where_clause_factory.rb +6 -11
  201. data/lib/active_record/result.rb +69 -40
  202. data/lib/active_record/runtime_registry.rb +5 -3
  203. data/lib/active_record/sanitization.rb +83 -99
  204. data/lib/active_record/schema.rb +7 -14
  205. data/lib/active_record/schema_dumper.rb +71 -69
  206. data/lib/active_record/schema_migration.rb +16 -6
  207. data/lib/active_record/scoping.rb +20 -20
  208. data/lib/active_record/scoping/default.rb +92 -95
  209. data/lib/active_record/scoping/named.rb +47 -27
  210. data/lib/active_record/secure_token.rb +4 -2
  211. data/lib/active_record/serialization.rb +2 -0
  212. data/lib/active_record/statement_cache.rb +63 -28
  213. data/lib/active_record/store.rb +121 -41
  214. data/lib/active_record/suppressor.rb +6 -3
  215. data/lib/active_record/table_metadata.rb +39 -18
  216. data/lib/active_record/tasks/database_tasks.rb +271 -81
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +54 -91
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +77 -47
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +33 -16
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +225 -0
  222. data/lib/active_record/timestamp.rb +70 -36
  223. data/lib/active_record/touch_later.rb +8 -6
  224. data/lib/active_record/transactions.rb +141 -157
  225. data/lib/active_record/translation.rb +3 -1
  226. data/lib/active_record/type.rb +23 -18
  227. data/lib/active_record/type/adapter_specific_registry.rb +44 -48
  228. data/lib/active_record/type/date.rb +2 -0
  229. data/lib/active_record/type/date_time.rb +2 -0
  230. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  231. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  232. data/lib/active_record/type/internal/timezone.rb +2 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +16 -9
  235. data/lib/active_record/type/text.rb +11 -0
  236. data/lib/active_record/type/time.rb +2 -1
  237. data/lib/active_record/type/type_map.rb +14 -17
  238. data/lib/active_record/type/unsigned_integer.rb +16 -0
  239. data/lib/active_record/type_caster.rb +4 -2
  240. data/lib/active_record/type_caster/connection.rb +17 -12
  241. data/lib/active_record/type_caster/map.rb +5 -4
  242. data/lib/active_record/validations.rb +7 -5
  243. data/lib/active_record/validations/absence.rb +2 -0
  244. data/lib/active_record/validations/associated.rb +4 -3
  245. data/lib/active_record/validations/length.rb +2 -0
  246. data/lib/active_record/validations/presence.rb +4 -2
  247. data/lib/active_record/validations/uniqueness.rb +29 -42
  248. data/lib/active_record/version.rb +3 -1
  249. data/lib/arel.rb +62 -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 +256 -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 +203 -0
  317. data/lib/arel/visitors/dot.rb +296 -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 +156 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +158 -0
  323. data/lib/arel/visitors/oracle12.rb +65 -0
  324. data/lib/arel/visitors/postgresql.rb +109 -0
  325. data/lib/arel/visitors/sqlite.rb +38 -0
  326. data/lib/arel/visitors/to_sql.rb +888 -0
  327. data/lib/arel/visitors/visitor.rb +45 -0
  328. data/lib/arel/visitors/where_sql.rb +22 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +7 -5
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  332. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  333. data/lib/rails/generators/active_record/migration.rb +17 -3
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -35
  335. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +1 -1
  336. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +4 -2
  337. data/lib/rails/generators/active_record/model/model_generator.rb +9 -30
  338. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +10 -1
  339. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  340. metadata +137 -52
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -20
  348. data/lib/active_record/attribute.rb +0 -213
  349. data/lib/active_record/attribute/user_provided_default.rb +0 -28
  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/postgresql/oid/rails_5_1_point.rb +0 -50
  355. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  356. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  357. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
  358. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
  359. 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,15 +36,24 @@ 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
+ #
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.
38
49
  #
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).
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
43
54
  #
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|
55
+ # # In worker 2, let's process from record 10_000 and onwards.
56
+ # Person.find_each(start: 10_000) do |person|
46
57
  # person.party_all_night!
47
58
  # end
48
59
  #
@@ -51,8 +62,8 @@ module ActiveRecord
51
62
  # work. This also means that this method only works when the primary key is
52
63
  # orderable (e.g. an integer or string).
53
64
  #
54
- # NOTE: You can't set the limit either, that's used to control
55
- # the batch sizes.
65
+ # NOTE: By its nature, batch processing is subject to race conditions if
66
+ # other processes are modifying the database.
56
67
  def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
57
68
  if block_given?
58
69
  find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
@@ -89,15 +100,19 @@ module ActiveRecord
89
100
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
90
101
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
91
102
  # * <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.
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.
93
107
  #
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).
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.
98
113
  #
99
- # # Let's process the next 2000 records
100
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
114
+ # # Let's process from record 10_000 on.
115
+ # Person.find_in_batches(start: 10_000) do |group|
101
116
  # group.each { |person| person.party_all_night! }
102
117
  # end
103
118
  #
@@ -106,8 +121,8 @@ module ActiveRecord
106
121
  # work. This also means that this method only works when the primary key is
107
122
  # orderable (e.g. an integer or string).
108
123
  #
109
- # NOTE: You can't set the limit either, that's used to control
110
- # the batch sizes.
124
+ # NOTE: By its nature, batch processing is subject to race conditions if
125
+ # other processes are modifying the database.
111
126
  def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
112
127
  relation = self
113
128
  unless block_given?
@@ -149,17 +164,19 @@ module ActiveRecord
149
164
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
150
165
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
151
166
  # * <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.
167
+ # an order is present in the relation.
153
168
  #
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).
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.
160
171
  #
161
- # # Let's process the next 2000 records
162
- # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
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)
163
180
  #
164
181
  # An example of calling where query method on the relation:
165
182
  #
@@ -179,31 +196,38 @@ module ActiveRecord
179
196
  # consistent. Therefore the primary key must be orderable, e.g. an integer
180
197
  # or a string.
181
198
  #
182
- # NOTE: You can't set the limit either, that's used to control the batch
183
- # sizes.
199
+ # NOTE: By its nature, batch processing is subject to race conditions if
200
+ # other processes are modifying the database.
184
201
  def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
185
202
  relation = self
186
203
  unless block_given?
187
204
  return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
188
205
  end
189
206
 
190
- if arel.orders.present? || arel.taken.present?
191
- act_on_order_or_limit_ignored(error_on_ignore)
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
192
215
  end
193
216
 
194
- relation = relation.reorder(batch_order).limit(of)
217
+ relation = relation.reorder(batch_order).limit(batch_limit)
195
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
196
220
  batch_relation = relation
197
221
 
198
222
  loop do
199
223
  if load
200
224
  records = batch_relation.records
201
225
  ids = records.map(&:id)
202
- yielded_relation = self.where(primary_key => ids)
226
+ yielded_relation = where(primary_key => ids)
203
227
  yielded_relation.load_records(records)
204
228
  else
205
229
  ids = batch_relation.pluck(primary_key)
206
- yielded_relation = self.where(primary_key => ids)
230
+ yielded_relation = where(primary_key => ids)
207
231
  end
208
232
 
209
233
  break if ids.empty?
@@ -213,31 +237,53 @@ module ActiveRecord
213
237
 
214
238
  yield yielded_relation
215
239
 
216
- break if ids.length < of
217
- batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
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
+ )
218
257
  end
219
258
  end
220
259
 
221
260
  private
261
+ def apply_limits(relation, start, finish)
262
+ relation = apply_start_limit(relation, start) if start
263
+ relation = apply_finish_limit(relation, finish) if finish
264
+ relation
265
+ end
222
266
 
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
267
+ def apply_start_limit(relation, start)
268
+ relation.where(bind_attribute(primary_key, start) { |attr, bind| attr.gteq(bind) })
269
+ end
228
270
 
229
- def batch_order
230
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
231
- end
271
+ def apply_finish_limit(relation, finish)
272
+ relation.where(bind_attribute(primary_key, finish) { |attr, bind| attr.lteq(bind) })
273
+ end
274
+
275
+ def batch_order
276
+ arel_attribute(primary_key).asc
277
+ end
232
278
 
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)
279
+ def act_on_ignored_order(error_on_ignore)
280
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
235
281
 
236
- if raise_error
237
- raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
238
- elsif logger
239
- logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
282
+ if raise_error
283
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
284
+ elsif logger
285
+ logger.warn(ORDER_IGNORE_MESSAGE)
286
+ end
240
287
  end
241
- end
242
288
  end
243
289
  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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Calculations
3
5
  # Count the records.
@@ -37,7 +39,15 @@ module ActiveRecord
37
39
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
38
40
  # between databases. In invalid cases, an error from the database is thrown.
39
41
  def count(column_name = nil)
40
- calculate(:count, column_name)
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
41
51
  end
42
52
 
43
53
  # Calculates the average value on a given column. Returns +nil+ if there's
@@ -71,9 +81,16 @@ module ActiveRecord
71
81
  # #calculate for examples with options.
72
82
  #
73
83
  # Person.sum(:age) # => 4562
74
- def sum(column_name = nil, &block)
75
- return super(&block) if block_given?
76
- calculate(:sum, column_name)
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
77
94
  end
78
95
 
79
96
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
@@ -108,13 +125,17 @@ module ActiveRecord
108
125
  # ...
109
126
  # end
110
127
  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
128
  if has_include?(column_name)
116
- relation = construct_relation_for_association_calculations
117
- relation = relation.distinct if operation.to_s.downcase == "count"
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
118
139
 
119
140
  relation.calculate(operation, column_name)
120
141
  else
@@ -151,7 +172,7 @@ module ActiveRecord
151
172
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
152
173
  # # => [2, 3]
153
174
  #
154
- # Person.pluck('DATEDIFF(updated_at, created_at)')
175
+ # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
155
176
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
156
177
  # # => ['0', '27761', '173']
157
178
  #
@@ -163,17 +184,35 @@ module ActiveRecord
163
184
  end
164
185
 
165
186
  if has_include?(column_names.first)
166
- construct_relation_for_association_calculations.pluck(*column_names)
187
+ relation = apply_join_dependency
188
+ relation.pluck(*column_names)
167
189
  else
190
+ klass.disallow_raw_sql!(column_names)
168
191
  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)
192
+ relation.select_values = column_names
193
+ result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
173
194
  result.cast_values(klass.attribute_types)
174
195
  end
175
196
  end
176
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
+
177
216
  # Pluck all the ID's for the relation using the table's primary key
178
217
  #
179
218
  # Person.ids # SELECT people.id FROM people
@@ -183,203 +222,203 @@ module ActiveRecord
183
222
  end
184
223
 
185
224
  private
225
+ def has_include?(column_name)
226
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
227
+ end
186
228
 
187
- def has_include?(column_name)
188
- eager_loading? || (includes_values.present? && column_name && column_name != :all)
189
- end
229
+ def perform_calculation(operation, column_name)
230
+ operation = operation.to_s.downcase
190
231
 
191
- def perform_calculation(operation, column_name)
192
- operation = operation.to_s.downcase
232
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
233
+ # considered distinct.
234
+ distinct = distinct_value
193
235
 
194
- # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
195
- # considered distinct.
196
- distinct = self.distinct_value
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
197
248
 
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
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
202
254
  end
203
255
 
204
- if group_values.any?
205
- execute_grouped_calculation(operation, column_name, distinct)
206
- else
207
- execute_simple_calculation(operation, column_name, distinct)
256
+ def distinct_select?(column_name)
257
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
208
258
  end
209
- end
210
259
 
211
- def aggregate_column(column_name)
212
- return column_name if Arel::Expressions === column_name
260
+ def aggregate_column(column_name)
261
+ return column_name if Arel::Expressions === column_name
213
262
 
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)
263
+ arel_column(column_name.to_s) do |name|
264
+ Arel.sql(column_name == :all ? "*" : name)
265
+ end
218
266
  end
219
- end
220
267
 
221
- def operation_over_aggregate_column(column, operation, distinct)
222
- operation == 'count' ? column.count(distinct) : column.send(operation)
223
- end
268
+ def operation_over_aggregate_column(column, operation, distinct)
269
+ operation == "count" ? column.count(distinct) : column.send(operation)
270
+ end
224
271
 
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)
272
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
273
+ column_alias = column_name
228
274
 
229
- column_alias = column_name
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
230
278
 
231
- if operation == "count" && (relation.limit_value || relation.offset_value)
232
- # Shortcut when limit is zero.
233
- return 0 if relation.limit_value == 0
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)
234
283
 
235
- query_builder = build_count_subquery(relation, column_name, distinct)
236
- else
237
- column = aggregate_column(column_name)
284
+ column = aggregate_column(column_name)
238
285
 
239
- select_value = operation_over_aggregate_column(column, operation, distinct)
286
+ select_value = operation_over_aggregate_column(column, operation, distinct)
287
+ if operation == "sum" && distinct
288
+ select_value.distinct = true
289
+ end
240
290
 
241
- if operation == "sum" && distinct
242
- select_value.distinct = true
243
- end
291
+ column_alias = select_value.alias
292
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
293
+ relation.select_values = [select_value]
244
294
 
245
- column_alias = select_value.alias
246
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
247
- relation.select_values = [select_value]
295
+ query_builder = relation.arel
296
+ end
248
297
 
249
- query_builder = relation.arel
250
- end
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
251
304
 
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)
305
+ type_cast_calculated_value(value, type, operation)
257
306
  end
258
307
 
259
- type_cast_calculated_value(value, column, operation)
260
- end
308
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
309
+ group_fields = group_values
261
310
 
262
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
263
- group_attrs = group_values
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)
264
317
 
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)
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
+ }
273
341
 
274
- group_aliases = group_fields.map { |field| column_alias_for(field) }
275
- group_columns = group_aliases.zip(group_fields)
342
+ relation = except(:group).distinct!(false)
343
+ relation.group_values = group_fields
344
+ relation.select_values = select_values
276
345
 
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
346
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
282
347
 
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}"
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] }]
296
352
  end
297
- }
298
-
299
- relation = except(:group)
300
- relation.group_values = group_fields
301
- relation.select_values = select_values
302
-
303
- calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
304
353
 
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
310
-
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
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)
316
358
  end
317
- 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
322
-
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
327
-
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}"
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]
338
367
  end
339
368
 
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!(/ +/, '_')
345
-
346
- @klass.connection.table_alias_for(table_name)
347
- end
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
348
385
 
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
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
353
390
 
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
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
359
396
  else type.deserialize(value)
397
+ end
360
398
  end
361
- end
362
399
 
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
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
369
407
  end
370
- end
371
408
 
372
- def build_count_subquery(relation, column_name, distinct)
373
- column_alias = Arel.sql('count_column')
374
- subquery_alias = Arel.sql('subquery_for_count')
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
375
417
 
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)
418
+ subquery_alias = Arel.sql("subquery_for_count")
419
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
379
420
 
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
421
+ relation.build_subquery(subquery_alias, select_value)
422
+ end
384
423
  end
385
424
  end