activerecord 5.2.3 → 6.1.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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +898 -532
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +5 -4
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +95 -42
  10. data/lib/active_record/associations/association_scope.rb +21 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +50 -46
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
  13. data/lib/active_record/associations/builder/association.rb +23 -21
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +31 -29
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +27 -28
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +54 -12
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +71 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +48 -35
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +133 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +45 -8
  47. data/lib/active_record/autosave_association.rb +76 -47
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +293 -132
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +21 -17
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +203 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  95. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  100. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +222 -112
  108. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  110. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  113. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  114. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +175 -187
  115. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  116. data/lib/active_record/connection_adapters.rb +50 -0
  117. data/lib/active_record/connection_handling.rb +285 -33
  118. data/lib/active_record/core.rb +308 -100
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  121. data/lib/active_record/database_configurations/database_config.rb +80 -0
  122. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  123. data/lib/active_record/database_configurations/url_config.rb +53 -0
  124. data/lib/active_record/database_configurations.rb +272 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +71 -17
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +197 -481
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +26 -22
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +34 -35
  147. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector.rb +77 -0
  150. data/lib/active_record/migration/command_recorder.rb +96 -44
  151. data/lib/active_record/migration/compatibility.rb +141 -64
  152. data/lib/active_record/migration/join_table.rb +0 -1
  153. data/lib/active_record/migration.rb +205 -156
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +115 -58
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +402 -78
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +113 -101
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -93
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +65 -40
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -7
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +58 -40
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +487 -199
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +108 -58
  185. data/lib/active_record/relation.rb +375 -104
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +6 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +51 -8
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +39 -43
  202. data/lib/active_record/tasks/database_tasks.rb +276 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +246 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +59 -117
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +117 -32
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -3,8 +3,8 @@
3
3
  require "active_record/relation/from_clause"
4
4
  require "active_record/relation/query_attribute"
5
5
  require "active_record/relation/where_clause"
6
- require "active_record/relation/where_clause_factory"
7
6
  require "active_model/forbidden_attributes_protection"
7
+ require "active_support/core_ext/array/wrap"
8
8
 
9
9
  module ActiveRecord
10
10
  module QueryMethods
@@ -15,8 +15,6 @@ module ActiveRecord
15
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
16
  # In this case, #where must be chained with #not to return a new relation.
17
17
  class WhereChain
18
- include ActiveModel::ForbiddenAttributesProtection
19
-
20
18
  def initialize(scope)
21
19
  @scope = scope
22
20
  end
@@ -43,14 +41,41 @@ module ActiveRecord
43
41
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
44
42
  #
45
43
  # User.where.not(name: "Jon", role: "admin")
46
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
44
+ # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
47
45
  def not(opts, *rest)
48
- opts = sanitize_forbidden_attributes(opts)
49
-
50
- where_clause = @scope.send(:where_clause_factory).build(opts, rest)
46
+ where_clause = @scope.send(:build_where_clause, opts, rest)
51
47
 
52
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
53
48
  @scope.where_clause += where_clause.invert
49
+
50
+ @scope
51
+ end
52
+
53
+ # Returns a new relation with left outer joins and where clause to identify
54
+ # missing relations.
55
+ #
56
+ # For example, posts that are missing a related author:
57
+ #
58
+ # Post.where.missing(:author)
59
+ # # SELECT "posts".* FROM "posts"
60
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
61
+ # # WHERE "authors"."id" IS NULL
62
+ #
63
+ # Additionally, multiple relations can be combined. This will return posts
64
+ # that are missing both an author and any comments:
65
+ #
66
+ # Post.where.missing(:author, :comments)
67
+ # # SELECT "posts".* FROM "posts"
68
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
+ # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
71
+ def missing(*args)
72
+ args.each do |arg|
73
+ reflection = @scope.klass._reflect_on_association(arg)
74
+ opts = { reflection.table_name => { reflection.association_primary_key => nil } }
75
+ @scope.left_outer_joins!(arg)
76
+ @scope.where!(opts)
77
+ end
78
+
54
79
  @scope
55
80
  end
56
81
  end
@@ -59,20 +84,25 @@ module ActiveRecord
59
84
  FROZEN_EMPTY_HASH = {}.freeze
60
85
 
61
86
  Relation::VALUE_METHODS.each do |name|
62
- method_name = \
87
+ method_name, default =
63
88
  case name
64
- when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
65
- when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
66
- when *Relation::CLAUSE_METHODS then "#{name}_clause"
89
+ when *Relation::MULTI_VALUE_METHODS
90
+ ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
91
+ when *Relation::SINGLE_VALUE_METHODS
92
+ ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
93
+ when *Relation::CLAUSE_METHODS
94
+ ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
67
95
  end
68
- class_eval <<-CODE, __FILE__, __LINE__ + 1
69
- def #{method_name} # def includes_values
70
- get_value(#{name.inspect}) # get_value(:includes)
71
- end # end
72
96
 
73
- def #{method_name}=(value) # def includes_values=(value)
74
- set_value(#{name.inspect}, value) # set_value(:includes, value)
75
- end # end
97
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
98
+ def #{method_name} # def includes_values
99
+ @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
100
+ end # end
101
+
102
+ def #{method_name}=(value) # def includes_values=(value)
103
+ assert_mutability! # assert_mutability!
104
+ @values[:#{name}] = value # @values[:includes] = value
105
+ end # end
76
106
  CODE
77
107
  end
78
108
 
@@ -100,7 +130,7 @@ module ActiveRecord
100
130
  #
101
131
  # === conditions
102
132
  #
103
- # If you want to add conditions to your included models you'll have
133
+ # If you want to add string conditions to your included models, you'll have
104
134
  # to explicitly reference them. For example:
105
135
  #
106
136
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -111,15 +141,18 @@ module ActiveRecord
111
141
  #
112
142
  # Note that #includes works with association names while #references needs
113
143
  # the actual table name.
144
+ #
145
+ # If you pass the conditions via hash, you don't need to call #references
146
+ # explicitly, as #where references the tables for you. For example, this
147
+ # will work correctly:
148
+ #
149
+ # User.includes(:posts).where(posts: { name: 'example' })
114
150
  def includes(*args)
115
151
  check_if_method_has_arguments!(:includes, args)
116
152
  spawn.includes!(*args)
117
153
  end
118
154
 
119
155
  def includes!(*args) # :nodoc:
120
- args.reject!(&:blank?)
121
- args.flatten!
122
-
123
156
  self.includes_values |= args
124
157
  self
125
158
  end
@@ -136,7 +169,7 @@ module ActiveRecord
136
169
  end
137
170
 
138
171
  def eager_load!(*args) # :nodoc:
139
- self.eager_load_values += args
172
+ self.eager_load_values |= args
140
173
  self
141
174
  end
142
175
 
@@ -150,10 +183,23 @@ module ActiveRecord
150
183
  end
151
184
 
152
185
  def preload!(*args) # :nodoc:
153
- self.preload_values += args
186
+ self.preload_values |= args
154
187
  self
155
188
  end
156
189
 
190
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
191
+ # then the individual association records are collected from the relation. Like so:
192
+ #
193
+ # account.memberships.extract_associated(:user)
194
+ # # => Returns collection of User records
195
+ #
196
+ # This is short-hand for:
197
+ #
198
+ # account.memberships.preload(:user).collect(&:user)
199
+ def extract_associated(association)
200
+ preload(association).collect(&association)
201
+ end
202
+
157
203
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
158
204
  # and should therefore be JOINed in any query rather than loaded separately.
159
205
  # This method only works in conjunction with #includes.
@@ -170,9 +216,6 @@ module ActiveRecord
170
216
  end
171
217
 
172
218
  def references!(*table_names) # :nodoc:
173
- table_names.flatten!
174
- table_names.map!(&:to_s)
175
-
176
219
  self.references_values |= table_names
177
220
  self
178
221
  end
@@ -226,16 +269,33 @@ module ActiveRecord
226
269
  return super()
227
270
  end
228
271
 
229
- raise ArgumentError, "Call `select' with at least one field" if fields.empty?
272
+ check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
230
273
  spawn._select!(*fields)
231
274
  end
232
275
 
233
276
  def _select!(*fields) # :nodoc:
234
- fields.flatten!
235
- fields.map! do |field|
236
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
237
- end
238
- self.select_values += fields
277
+ self.select_values |= fields
278
+ self
279
+ end
280
+
281
+ # Allows you to change a previously set select statement.
282
+ #
283
+ # Post.select(:title, :body)
284
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
285
+ #
286
+ # Post.select(:title, :body).reselect(:created_at)
287
+ # # SELECT `posts`.`created_at` FROM `posts`
288
+ #
289
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
+ # Note that we're unscoping the entire select statement.
291
+ def reselect(*args)
292
+ check_if_method_has_arguments!(:reselect, args)
293
+ spawn.reselect!(*args)
294
+ end
295
+
296
+ # Same as #reselect but operates on relation in-place instead of copying.
297
+ def reselect!(*args) # :nodoc:
298
+ self.select_values = args
239
299
  self
240
300
  end
241
301
 
@@ -265,8 +325,6 @@ module ActiveRecord
265
325
  end
266
326
 
267
327
  def group!(*args) # :nodoc:
268
- args.flatten!
269
-
270
328
  self.group_values += args
271
329
  self
272
330
  end
@@ -291,15 +349,16 @@ module ActiveRecord
291
349
  # User.order('name DESC, email')
292
350
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
293
351
  def order(*args)
294
- check_if_method_has_arguments!(:order, args)
352
+ check_if_method_has_arguments!(:order, args) do
353
+ sanitize_order_arguments(args)
354
+ end
295
355
  spawn.order!(*args)
296
356
  end
297
357
 
298
358
  # Same as #order but operates on relation in-place instead of copying.
299
359
  def order!(*args) # :nodoc:
300
- preprocess_order_args(args)
301
-
302
- self.order_values += args
360
+ preprocess_order_args(args) unless args.empty?
361
+ self.order_values |= args
303
362
  self
304
363
  end
305
364
 
@@ -313,22 +372,24 @@ module ActiveRecord
313
372
  #
314
373
  # generates a query with 'ORDER BY id ASC, name ASC'.
315
374
  def reorder(*args)
316
- check_if_method_has_arguments!(:reorder, args)
375
+ check_if_method_has_arguments!(:reorder, args) do
376
+ sanitize_order_arguments(args) unless args.all?(&:blank?)
377
+ end
317
378
  spawn.reorder!(*args)
318
379
  end
319
380
 
320
381
  # Same as #reorder but operates on relation in-place instead of copying.
321
382
  def reorder!(*args) # :nodoc:
322
- preprocess_order_args(args)
323
-
383
+ preprocess_order_args(args) unless args.all?(&:blank?)
384
+ args.uniq!
324
385
  self.reordering_value = true
325
386
  self.order_values = args
326
387
  self
327
388
  end
328
389
 
329
390
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
330
- :limit, :offset, :joins, :left_outer_joins,
331
- :includes, :from, :readonly, :having])
391
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
392
+ :includes, :from, :readonly, :having, :optimizer_hints])
332
393
 
333
394
  # Removes an unwanted relation that is already defined on a chain of relations.
334
395
  # This is useful when passing around chains of relations and would like to
@@ -369,7 +430,6 @@ module ActiveRecord
369
430
  end
370
431
 
371
432
  def unscope!(*args) # :nodoc:
372
- args.flatten!
373
433
  self.unscope_values += args
374
434
 
375
435
  args.each do |scope|
@@ -379,14 +439,15 @@ module ActiveRecord
379
439
  if !VALID_UNSCOPING_VALUES.include?(scope)
380
440
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
381
441
  end
382
- set_value(scope, DEFAULT_VALUES[scope])
442
+ assert_mutability!
443
+ @values.delete(scope)
383
444
  when Hash
384
445
  scope.each do |key, target_value|
385
446
  if key != :where
386
447
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
387
448
  end
388
449
 
389
- target_values = Array(target_value).map(&:to_s)
450
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
390
451
  self.where_clause = where_clause.except(*target_values)
391
452
  end
392
453
  else
@@ -419,8 +480,7 @@ module ActiveRecord
419
480
  # # SELECT "users".*
420
481
  # # FROM "users"
421
482
  # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
422
- # # INNER JOIN "comments" "comments_posts"
423
- # # ON "comments_posts"."post_id" = "posts"."id"
483
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
424
484
  #
425
485
  # You can use strings in order to customize your joins:
426
486
  #
@@ -432,9 +492,7 @@ module ActiveRecord
432
492
  end
433
493
 
434
494
  def joins!(*args) # :nodoc:
435
- args.compact!
436
- args.flatten!
437
- self.joins_values += args
495
+ self.joins_values |= args
438
496
  self
439
497
  end
440
498
 
@@ -450,9 +508,7 @@ module ActiveRecord
450
508
  alias :left_joins :left_outer_joins
451
509
 
452
510
  def left_outer_joins!(*args) # :nodoc:
453
- args.compact!
454
- args.flatten!
455
- self.left_outer_joins_values += args
511
+ self.left_outer_joins_values |= args
456
512
  self
457
513
  end
458
514
 
@@ -575,20 +631,18 @@ module ActiveRecord
575
631
  #
576
632
  # If the condition is any blank-ish object, then #where is a no-op and returns
577
633
  # the current relation.
578
- def where(opts = :chain, *rest)
579
- if :chain == opts
634
+ def where(*args)
635
+ if args.empty?
580
636
  WhereChain.new(spawn)
581
- elsif opts.blank?
637
+ elsif args.length == 1 && args.first.blank?
582
638
  self
583
639
  else
584
- spawn.where!(opts, *rest)
640
+ spawn.where!(*args)
585
641
  end
586
642
  end
587
643
 
588
644
  def where!(opts, *rest) # :nodoc:
589
- opts = sanitize_forbidden_attributes(opts)
590
- references!(PredicateBuilder.references(opts)) if Hash === opts
591
- self.where_clause += where_clause_factory.build(opts, rest)
645
+ self.where_clause += build_where_clause(opts, rest)
592
646
  self
593
647
  end
594
648
 
@@ -606,7 +660,44 @@ module ActiveRecord
606
660
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
607
661
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
608
662
  def rewhere(conditions)
609
- unscope(where: conditions.keys).where(conditions)
663
+ scope = spawn
664
+ where_clause = scope.build_where_clause(conditions)
665
+
666
+ scope.unscope!(where: where_clause.extract_attributes)
667
+ scope.where_clause += where_clause
668
+ scope
669
+ end
670
+
671
+ # Returns a new relation, which is the logical intersection of this relation and the one passed
672
+ # as an argument.
673
+ #
674
+ # The two relations must be structurally compatible: they must be scoping the same model, and
675
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
676
+ # present).
677
+ #
678
+ # Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
679
+ # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
680
+ #
681
+ def and(other)
682
+ if other.is_a?(Relation)
683
+ spawn.and!(other)
684
+ else
685
+ raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
686
+ end
687
+ end
688
+
689
+ def and!(other) # :nodoc:
690
+ incompatible_values = structurally_incompatible_values_for(other)
691
+
692
+ unless incompatible_values.empty?
693
+ raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
694
+ end
695
+
696
+ self.where_clause |= other.where_clause
697
+ self.having_clause |= other.having_clause
698
+ self.references_values |= other.references_values
699
+
700
+ self
610
701
  end
611
702
 
612
703
  # Returns a new relation, which is the logical union of this relation and the one passed as an
@@ -614,21 +705,21 @@ module ActiveRecord
614
705
  #
615
706
  # The two relations must be structurally compatible: they must be scoping the same model, and
616
707
  # they must differ only by #where (if no #group has been defined) or #having (if a #group is
617
- # present). Neither relation may have a #limit, #offset, or #distinct set.
708
+ # present).
618
709
  #
619
710
  # Post.where("id = 1").or(Post.where("author_id = 3"))
620
711
  # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
621
712
  #
622
713
  def or(other)
623
- unless other.is_a? Relation
714
+ if other.is_a?(Relation)
715
+ spawn.or!(other)
716
+ else
624
717
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
625
718
  end
626
-
627
- spawn.or!(other)
628
719
  end
629
720
 
630
721
  def or!(other) # :nodoc:
631
- incompatible_values = structurally_incompatible_values_for_or(other)
722
+ incompatible_values = structurally_incompatible_values_for(other)
632
723
 
633
724
  unless incompatible_values.empty?
634
725
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
@@ -636,7 +727,7 @@ module ActiveRecord
636
727
 
637
728
  self.where_clause = self.where_clause.or(other.where_clause)
638
729
  self.having_clause = having_clause.or(other.having_clause)
639
- self.references_values += other.references_values
730
+ self.references_values |= other.references_values
640
731
 
641
732
  self
642
733
  end
@@ -650,10 +741,7 @@ module ActiveRecord
650
741
  end
651
742
 
652
743
  def having!(opts, *rest) # :nodoc:
653
- opts = sanitize_forbidden_attributes(opts)
654
- references!(PredicateBuilder.references(opts)) if Hash === opts
655
-
656
- self.having_clause += having_clause_factory.build(opts, rest)
744
+ self.having_clause += build_having_clause(opts, rest)
657
745
  self
658
746
  end
659
747
 
@@ -755,6 +843,21 @@ module ActiveRecord
755
843
  self
756
844
  end
757
845
 
846
+ # Sets the returned relation to strict_loading mode. This will raise an error
847
+ # if the record tries to lazily load an association.
848
+ #
849
+ # user = User.strict_loading.first
850
+ # user.comments.to_a
851
+ # => ActiveRecord::StrictLoadingViolationError
852
+ def strict_loading(value = true)
853
+ spawn.strict_loading!(value)
854
+ end
855
+
856
+ def strict_loading!(value = true) # :nodoc:
857
+ self.strict_loading_value = value
858
+ self
859
+ end
860
+
758
861
  # Sets attributes to be used when creating new records from a
759
862
  # relation object.
760
863
  #
@@ -879,6 +982,27 @@ module ActiveRecord
879
982
  self
880
983
  end
881
984
 
985
+ # Specify optimizer hints to be used in the SELECT statement.
986
+ #
987
+ # Example (for MySQL):
988
+ #
989
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
990
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
991
+ #
992
+ # Example (for PostgreSQL with pg_hint_plan):
993
+ #
994
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
+ def optimizer_hints(*args)
997
+ check_if_method_has_arguments!(:optimizer_hints, args)
998
+ spawn.optimizer_hints!(*args)
999
+ end
1000
+
1001
+ def optimizer_hints!(*args) # :nodoc:
1002
+ self.optimizer_hints_values |= args
1003
+ self
1004
+ end
1005
+
882
1006
  # Reverse the existing order clause on the relation.
883
1007
  #
884
1008
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -887,8 +1011,7 @@ module ActiveRecord
887
1011
  end
888
1012
 
889
1013
  def reverse_order! # :nodoc:
890
- orders = order_values.uniq
891
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
892
1015
  self.order_values = reverse_sql_order(orders)
893
1016
  self
894
1017
  end
@@ -898,25 +1021,109 @@ module ActiveRecord
898
1021
  self
899
1022
  end
900
1023
 
1024
+ def skip_preloading! # :nodoc:
1025
+ self.skip_preloading_value = true
1026
+ self
1027
+ end
1028
+
1029
+ # Adds an SQL comment to queries generated from this relation. For example:
1030
+ #
1031
+ # User.annotate("selecting user names").select(:name)
1032
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
1033
+ #
1034
+ # User.annotate("selecting", "user", "names").select(:name)
1035
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1036
+ #
1037
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1038
+ def annotate(*args)
1039
+ check_if_method_has_arguments!(:annotate, args)
1040
+ spawn.annotate!(*args)
1041
+ end
1042
+
1043
+ # Like #annotate, but modifies relation in place.
1044
+ def annotate!(*args) # :nodoc:
1045
+ self.annotate_values += args
1046
+ self
1047
+ end
1048
+
1049
+ # Deduplicate multiple values.
1050
+ def uniq!(name)
1051
+ if values = @values[name]
1052
+ values.uniq! if values.is_a?(Array) && !values.empty?
1053
+ end
1054
+ self
1055
+ end
1056
+
901
1057
  # Returns the Arel object associated with the relation.
902
1058
  def arel(aliases = nil) # :nodoc:
903
1059
  @arel ||= build_arel(aliases)
904
1060
  end
905
1061
 
906
- # Returns a relation value with a given name
907
- def get_value(name) # :nodoc:
908
- @values.fetch(name, DEFAULT_VALUES[name])
1062
+ def construct_join_dependency(associations, join_type) # :nodoc:
1063
+ ActiveRecord::Associations::JoinDependency.new(
1064
+ klass, table, associations, join_type
1065
+ )
909
1066
  end
910
1067
 
911
1068
  protected
1069
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1070
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
1071
+
1072
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1073
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1074
+ end
1075
+ end
1076
+
1077
+ def build_where_clause(opts, rest = []) # :nodoc:
1078
+ opts = sanitize_forbidden_attributes(opts)
1079
+
1080
+ case opts
1081
+ when String, Array
1082
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1083
+ when Hash
1084
+ opts = opts.stringify_keys
1085
+ references = PredicateBuilder.references(opts)
1086
+ self.references_values |= references unless references.empty?
1087
+
1088
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1089
+ lookup_reflection_from_join_dependencies(table_name)
1090
+ end
1091
+ when Arel::Nodes::Node
1092
+ parts = [opts]
1093
+ else
1094
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1095
+ end
912
1096
 
913
- # Sets the relation value with the given name
914
- def set_value(name, value) # :nodoc:
915
- assert_mutability!
916
- @values[name] = value
1097
+ Relation::WhereClause.new(parts)
917
1098
  end
1099
+ alias :build_having_clause :build_where_clause
918
1100
 
919
1101
  private
1102
+ def lookup_reflection_from_join_dependencies(table_name)
1103
+ each_join_dependencies do |join|
1104
+ return join.reflection if table_name == join.table_name
1105
+ end
1106
+ nil
1107
+ end
1108
+
1109
+ def each_join_dependencies(join_dependencies = build_join_dependencies)
1110
+ join_dependencies.each do |join_dependency|
1111
+ join_dependency.each do |join|
1112
+ yield join
1113
+ end
1114
+ end
1115
+ end
1116
+
1117
+ def build_join_dependencies
1118
+ associations = joins_values | left_outer_joins_values
1119
+ associations |= eager_load_values unless eager_load_values.empty?
1120
+ associations |= includes_values unless includes_values.empty?
1121
+
1122
+ join_dependencies = []
1123
+ join_dependencies.unshift construct_join_dependency(
1124
+ select_association_list(associations, join_dependencies), nil
1125
+ )
1126
+ end
920
1127
 
921
1128
  def assert_mutability!
922
1129
  raise ImmutableRelation if @loaded
@@ -926,40 +1133,44 @@ module ActiveRecord
926
1133
  def build_arel(aliases)
927
1134
  arel = Arel::SelectManager.new(table)
928
1135
 
929
- aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
930
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
1136
+ build_joins(arel.join_sources, aliases)
931
1137
 
932
1138
  arel.where(where_clause.ast) unless where_clause.empty?
933
1139
  arel.having(having_clause.ast) unless having_clause.empty?
934
- if limit_value
935
- limit_attribute = ActiveModel::Attribute.with_cast_value(
936
- "LIMIT".freeze,
937
- connection.sanitize_limit(limit_value),
938
- Type.default_value,
939
- )
940
- arel.take(Arel::Nodes::BindParam.new(limit_attribute))
941
- end
942
- if offset_value
943
- offset_attribute = ActiveModel::Attribute.with_cast_value(
944
- "OFFSET".freeze,
945
- offset_value.to_i,
946
- Type.default_value,
947
- )
948
- arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
949
- end
950
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1140
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1141
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1142
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
951
1143
 
952
1144
  build_order(arel)
953
-
954
1145
  build_select(arel)
955
1146
 
1147
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
956
1148
  arel.distinct(distinct_value)
957
1149
  arel.from(build_from) unless from_clause.empty?
958
1150
  arel.lock(lock_value) if lock_value
959
1151
 
1152
+ unless annotate_values.empty?
1153
+ annotates = annotate_values
1154
+ annotates = annotates.uniq if annotates.size > 1
1155
+ unless annotates == annotate_values
1156
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
1157
+ Duplicated query annotations are no longer shown in queries in Rails 6.2.
1158
+ To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1159
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1160
+ MSG
1161
+ annotates = annotate_values
1162
+ end
1163
+ arel.comment(*annotates)
1164
+ end
1165
+
960
1166
  arel
961
1167
  end
962
1168
 
1169
+ def build_cast_value(name, value)
1170
+ cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1171
+ Arel::Nodes::BindParam.new(cast_value)
1172
+ end
1173
+
963
1174
  def build_from
964
1175
  opts = from_clause.value
965
1176
  name = from_clause.name
@@ -975,75 +1186,101 @@ module ActiveRecord
975
1186
  end
976
1187
  end
977
1188
 
978
- def build_left_outer_joins(manager, outer_joins, aliases)
979
- buckets = outer_joins.group_by do |join|
980
- case join
1189
+ def select_association_list(associations, stashed_joins = nil)
1190
+ result = []
1191
+ associations.each do |association|
1192
+ case association
981
1193
  when Hash, Symbol, Array
982
- :association_join
1194
+ result << association
983
1195
  when ActiveRecord::Associations::JoinDependency
984
- :stashed_join
1196
+ stashed_joins&.<< association
985
1197
  else
986
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1198
+ yield association if block_given?
987
1199
  end
988
1200
  end
1201
+ result
1202
+ end
989
1203
 
990
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1204
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
991
1205
  end
992
1206
 
993
- def build_joins(manager, joins, aliases)
994
- buckets = joins.group_by do |join|
995
- case join
996
- when String
997
- :string_join
998
- when Hash, Symbol, Array
999
- :association_join
1000
- when ActiveRecord::Associations::JoinDependency
1001
- :stashed_join
1002
- when Arel::Nodes::Join
1003
- :join_node
1207
+ def build_join_buckets
1208
+ buckets = Hash.new { |h, k| h[k] = [] }
1209
+
1210
+ unless left_outer_joins_values.empty?
1211
+ stashed_left_joins = []
1212
+ left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1213
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1214
+ end
1215
+
1216
+ if joins_values.empty?
1217
+ buckets[:association_join] = left_joins
1218
+ buckets[:stashed_join] = stashed_left_joins
1219
+ return buckets, Arel::Nodes::OuterJoin
1220
+ else
1221
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1222
+ end
1223
+ end
1224
+
1225
+ joins = joins_values.dup
1226
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1227
+ stashed_eager_load = joins.pop if joins.last.base_klass == klass
1228
+ end
1229
+
1230
+ joins.each_with_index do |join, i|
1231
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
1232
+ end
1233
+
1234
+ while joins.first.is_a?(Arel::Nodes::Join)
1235
+ join_node = joins.shift
1236
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1237
+ buckets[:join_node] << join_node
1238
+ else
1239
+ buckets[:leading_join] << join_node
1240
+ end
1241
+ end
1242
+
1243
+ buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1244
+ if join.is_a?(Arel::Nodes::Join)
1245
+ buckets[:join_node] << join
1004
1246
  else
1005
1247
  raise "unknown class: %s" % join.class.name
1006
1248
  end
1007
1249
  end
1008
1250
 
1009
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1251
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1252
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1253
+
1254
+ return buckets, Arel::Nodes::InnerJoin
1010
1255
  end
1011
1256
 
1012
- def build_join_query(manager, buckets, join_type, aliases)
1013
- buckets.default = []
1257
+ def build_joins(join_sources, aliases = nil)
1258
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1259
+
1260
+ buckets, join_type = build_join_buckets
1014
1261
 
1015
1262
  association_joins = buckets[:association_join]
1016
1263
  stashed_joins = buckets[:stashed_join]
1017
- join_nodes = buckets[:join_node].uniq
1018
- string_joins = buckets[:string_join].map(&:strip).uniq
1019
-
1020
- join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1021
- alias_tracker = alias_tracker(join_list, aliases)
1264
+ leading_joins = buckets[:leading_join]
1265
+ join_nodes = buckets[:join_node]
1022
1266
 
1023
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1024
- klass, table, association_joins
1025
- )
1026
-
1027
- joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1028
- joins.each { |join| manager.from(join) }
1029
-
1030
- manager.join_sources.concat(join_list)
1267
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1031
1268
 
1032
- alias_tracker.aliases
1033
- end
1269
+ unless association_joins.empty? && stashed_joins.empty?
1270
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1271
+ join_dependency = construct_join_dependency(association_joins, join_type)
1272
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1273
+ end
1034
1274
 
1035
- def convert_join_strings_to_ast(joins)
1036
- joins
1037
- .flatten
1038
- .reject(&:blank?)
1039
- .map { |join| table.create_string_join(Arel.sql(join)) }
1275
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1276
+ join_sources
1040
1277
  end
1041
1278
 
1042
1279
  def build_select(arel)
1043
1280
  if select_values.any?
1044
- arel.project(*arel_columns(select_values.uniq))
1281
+ arel.project(*arel_columns(select_values))
1045
1282
  elsif klass.ignored_columns.any?
1046
- arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1283
+ arel.project(*klass.column_names.map { |field| table[field] })
1047
1284
  else
1048
1285
  arel.project(table[Arel.star])
1049
1286
  end
@@ -1053,10 +1290,11 @@ module ActiveRecord
1053
1290
  columns.flat_map do |field|
1054
1291
  case field
1055
1292
  when Symbol
1056
- field = field.to_s
1057
- arel_column(field) { connection.quote_table_name(field) }
1293
+ arel_column(field.to_s) do |attr_name|
1294
+ connection.quote_table_name(attr_name)
1295
+ end
1058
1296
  when String
1059
- arel_column(field) { field }
1297
+ arel_column(field, &:itself)
1060
1298
  when Proc
1061
1299
  field.call
1062
1300
  else
@@ -1066,23 +1304,30 @@ module ActiveRecord
1066
1304
  end
1067
1305
 
1068
1306
  def arel_column(field)
1069
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1307
+ field = klass.attribute_aliases[field] || field
1070
1308
  from = from_clause.name || from_clause.value
1071
1309
 
1072
1310
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1073
- arel_attribute(field)
1311
+ table[field]
1312
+ elsif field.match?(/\A\w+\.\w+\z/)
1313
+ table, column = field.split(".")
1314
+ predicate_builder.resolve_arel_attribute(table, column) do
1315
+ lookup_reflection_from_join_dependencies(table)
1316
+ end
1074
1317
  else
1075
- yield
1318
+ yield field
1076
1319
  end
1077
1320
  end
1078
1321
 
1079
1322
  def table_name_matches?(from)
1080
- /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1323
+ table_name = Regexp.escape(table.name)
1324
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1325
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1081
1326
  end
1082
1327
 
1083
1328
  def reverse_sql_order(order_query)
1084
1329
  if order_query.empty?
1085
- return [arel_attribute(primary_key).desc] if primary_key
1330
+ return [table[primary_key].desc] if primary_key
1086
1331
  raise IrreversibleOrderError,
1087
1332
  "Relation has no current order and table has no primary key to be used as default order"
1088
1333
  end
@@ -1093,9 +1338,11 @@ module ActiveRecord
1093
1338
  o.desc
1094
1339
  when Arel::Nodes::Ordering
1095
1340
  o.reverse
1341
+ when Arel::Nodes::NodeExpression
1342
+ o.desc
1096
1343
  when String
1097
1344
  if does_not_support_reverse?(o)
1098
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1345
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1099
1346
  end
1100
1347
  o.split(",").map! do |s|
1101
1348
  s.strip!
@@ -1115,13 +1362,11 @@ module ActiveRecord
1115
1362
  # Uses SQL function with multiple arguments.
1116
1363
  (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1117
1364
  # Uses "nulls first" like construction.
1118
- /nulls (first|last)\Z/i.match?(order)
1365
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1119
1366
  end
1120
1367
 
1121
1368
  def build_order(arel)
1122
- orders = order_values.uniq
1123
- orders.reject!(&:blank?)
1124
-
1369
+ orders = order_values.compact_blank
1125
1370
  arel.order(*orders) unless orders.empty?
1126
1371
  end
1127
1372
 
@@ -1141,40 +1386,28 @@ module ActiveRecord
1141
1386
  end
1142
1387
 
1143
1388
  def preprocess_order_args(order_args)
1144
- order_args.map! do |arg|
1145
- klass.sanitize_sql_for_order(arg)
1146
- end
1147
- order_args.flatten!
1148
-
1149
- @klass.enforce_raw_sql_whitelist(
1389
+ @klass.disallow_raw_sql!(
1150
1390
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1151
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
1391
+ permit: connection.column_name_with_order_matcher
1152
1392
  )
1153
1393
 
1154
1394
  validate_order_args(order_args)
1155
1395
 
1156
- references = order_args.grep(String)
1157
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1158
- references!(references) if references.any?
1396
+ references = column_references(order_args)
1397
+ self.references_values |= references unless references.empty?
1159
1398
 
1160
1399
  # if a symbol is given we prepend the quoted table name
1161
1400
  order_args.map! do |arg|
1162
1401
  case arg
1163
1402
  when Symbol
1164
- arg = arg.to_s
1165
- arel_column(arg) {
1166
- Arel.sql(connection.quote_table_name(arg))
1167
- }.asc
1403
+ order_column(arg.to_s).asc
1168
1404
  when Hash
1169
1405
  arg.map { |field, dir|
1170
1406
  case field
1171
1407
  when Arel::Nodes::SqlLiteral
1172
- field.send(dir.downcase)
1408
+ field.public_send(dir.downcase)
1173
1409
  else
1174
- field = field.to_s
1175
- arel_column(field) {
1176
- Arel.sql(connection.quote_table_name(field))
1177
- }.send(dir.downcase)
1410
+ order_column(field.to_s).public_send(dir.downcase)
1178
1411
  end
1179
1412
  }
1180
1413
  else
@@ -1183,6 +1416,54 @@ module ActiveRecord
1183
1416
  end.flatten!
1184
1417
  end
1185
1418
 
1419
+ def sanitize_order_arguments(order_args)
1420
+ order_args.map! do |arg|
1421
+ klass.sanitize_sql_for_order(arg)
1422
+ end
1423
+ order_args.flatten!
1424
+ order_args.compact_blank!
1425
+ end
1426
+
1427
+ def column_references(order_args)
1428
+ references = order_args.grep(String)
1429
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1430
+ references
1431
+ end
1432
+
1433
+ def order_column(field)
1434
+ arel_column(field) do |attr_name|
1435
+ if attr_name == "count" && !group_values.empty?
1436
+ table[attr_name]
1437
+ else
1438
+ Arel.sql(connection.quote_table_name(attr_name))
1439
+ end
1440
+ end
1441
+ end
1442
+
1443
+ def resolve_arel_attributes(attrs)
1444
+ attrs.flat_map do |attr|
1445
+ case attr
1446
+ when Arel::Predications
1447
+ attr
1448
+ when Hash
1449
+ attr.flat_map do |table, columns|
1450
+ table = table.to_s
1451
+ Array(columns).map do |column|
1452
+ predicate_builder.resolve_arel_attribute(table, column)
1453
+ end
1454
+ end
1455
+ else
1456
+ attr = attr.to_s
1457
+ if attr.include?(".")
1458
+ table, column = attr.split(".", 2)
1459
+ predicate_builder.resolve_arel_attribute(table, column)
1460
+ else
1461
+ attr
1462
+ end
1463
+ end
1464
+ end
1465
+ end
1466
+
1186
1467
  # Checks to make sure that the arguments are not blank. Note that if some
1187
1468
  # blank-like object were initially passed into the query method, then this
1188
1469
  # method will not raise an error.
@@ -1199,33 +1480,40 @@ module ActiveRecord
1199
1480
  # check_if_method_has_arguments!("references", args)
1200
1481
  # ...
1201
1482
  # end
1202
- def check_if_method_has_arguments!(method_name, args)
1483
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1203
1484
  if args.blank?
1204
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1485
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1486
+ elsif block_given?
1487
+ yield args
1488
+ else
1489
+ args.flatten!
1490
+ args.compact_blank!
1205
1491
  end
1206
1492
  end
1207
1493
 
1208
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1209
- def structurally_incompatible_values_for_or(other)
1210
- STRUCTURAL_OR_METHODS.reject do |method|
1211
- get_value(method) == other.get_value(method)
1494
+ STRUCTURAL_VALUE_METHODS = (
1495
+ Relation::VALUE_METHODS -
1496
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1497
+ ).freeze # :nodoc:
1498
+
1499
+ def structurally_incompatible_values_for(other)
1500
+ values = other.values
1501
+ STRUCTURAL_VALUE_METHODS.reject do |method|
1502
+ v1, v2 = @values[method], values[method]
1503
+ if v1.is_a?(Array)
1504
+ next true unless v2.is_a?(Array)
1505
+ v1 = v1.uniq
1506
+ v2 = v2.uniq
1507
+ end
1508
+ v1 == v2
1212
1509
  end
1213
1510
  end
1511
+ end
1214
1512
 
1215
- def where_clause_factory
1216
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1217
- end
1218
- alias having_clause_factory where_clause_factory
1219
-
1220
- DEFAULT_VALUES = {
1221
- create_with: FROZEN_EMPTY_HASH,
1222
- where: Relation::WhereClause.empty,
1223
- having: Relation::WhereClause.empty,
1224
- from: Relation::FromClause.empty
1225
- }
1226
-
1227
- Relation::MULTI_VALUE_METHODS.each do |value|
1228
- DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1229
- end
1513
+ class Relation # :nodoc:
1514
+ # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1515
+ # TODO: Remove the class once Rails 6.1 has released.
1516
+ class WhereClauseFactory # :nodoc:
1517
+ end
1230
1518
  end
1231
1519
  end