activerecord 7.0.4 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. data/lib/active_record/null_relation.rb +0 -63
@@ -3,14 +3,13 @@
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_model/forbidden_attributes_protection"
7
6
  require "active_support/core_ext/array/wrap"
8
7
 
9
8
  module ActiveRecord
10
9
  module QueryMethods
11
10
  include ActiveModel::ForbiddenAttributesProtection
12
11
 
13
- # WhereChain objects act as placeholder for queries in which +where+ does not have any parameter.
12
+ # +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
14
13
  # In this case, +where+ can be chained to return a new relation.
15
14
  class WhereChain
16
15
  def initialize(scope) # :nodoc:
@@ -39,7 +38,7 @@ module ActiveRecord
39
38
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
40
39
  #
41
40
  # User.where.not(name: "Jon", role: "admin")
42
- # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
41
+ # # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin')
43
42
  #
44
43
  # If there is a non-nil condition on a nullable column in the hash condition, the records that have
45
44
  # nil values on the nullable column won't be returned.
@@ -77,7 +76,11 @@ module ActiveRecord
77
76
  associations.each do |association|
78
77
  reflection = scope_association_reflection(association)
79
78
  @scope.joins!(association)
80
- self.not(reflection.table_name => { reflection.association_primary_key => nil })
79
+ if reflection.options[:class_name]
80
+ self.not(association => { reflection.association_primary_key => nil })
81
+ else
82
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
83
+ end
81
84
  end
82
85
 
83
86
  @scope
@@ -105,7 +108,11 @@ module ActiveRecord
105
108
  associations.each do |association|
106
109
  reflection = scope_association_reflection(association)
107
110
  @scope.left_outer_joins!(association)
108
- @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
111
+ if reflection.options[:class_name]
112
+ @scope.where!(association => { reflection.association_primary_key => nil })
113
+ else
114
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
115
+ end
109
116
  end
110
117
 
111
118
  @scope
@@ -121,6 +128,15 @@ module ActiveRecord
121
128
  end
122
129
  end
123
130
 
131
+ # A wrapper to distinguish CTE joins from other nodes.
132
+ class CTEJoin # :nodoc:
133
+ attr_reader :name
134
+
135
+ def initialize(name)
136
+ @name = name
137
+ end
138
+ end
139
+
124
140
  FROZEN_EMPTY_ARRAY = [].freeze
125
141
  FROZEN_EMPTY_HASH = {}.freeze
126
142
 
@@ -149,45 +165,69 @@ module ActiveRecord
149
165
 
150
166
  alias extensions extending_values
151
167
 
152
- # Specify relationships to be included in the result set. For
153
- # example:
168
+ # Specify associations +args+ to be eager loaded to prevent N + 1 queries.
169
+ # A separate query is performed for each association, unless a join is
170
+ # required by conditions.
171
+ #
172
+ # For example:
154
173
  #
155
- # users = User.includes(:address)
174
+ # users = User.includes(:address).limit(5)
156
175
  # users.each do |user|
157
176
  # user.address.city
158
177
  # end
159
178
  #
160
- # allows you to access the +address+ attribute of the +User+ model without
161
- # firing an additional query. This will often result in a
162
- # performance improvement over a simple join.
179
+ # # SELECT "users".* FROM "users" LIMIT 5
180
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
181
+ #
182
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
183
+ # are loaded with a single query.
163
184
  #
164
- # You can also specify multiple relationships, like this:
185
+ # Loading the associations in a separate query will often result in a
186
+ # performance improvement over a simple join, as a join can result in many
187
+ # rows that contain redundant data and it performs poorly at scale.
165
188
  #
166
- # users = User.includes(:address, :friends)
189
+ # You can also specify multiple associations. Each association will result
190
+ # in an additional query:
167
191
  #
168
- # Loading nested relationships is possible using a Hash:
192
+ # User.includes(:address, :friends).to_a
193
+ # # SELECT "users".* FROM "users"
194
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
195
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
169
196
  #
170
- # users = User.includes(:address, friends: [:address, :followers])
197
+ # Loading nested associations is possible using a Hash:
198
+ #
199
+ # User.includes(:address, friends: [:address, :followers])
171
200
  #
172
201
  # === Conditions
173
202
  #
174
203
  # If you want to add string conditions to your included models, you'll have
175
204
  # to explicitly reference them. For example:
176
205
  #
177
- # User.includes(:posts).where('posts.name = ?', 'example')
206
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
178
207
  #
179
208
  # Will throw an error, but this will work:
180
209
  #
181
- # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
210
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
211
+ # # SELECT "users"."id" AS t0_r0, ... FROM "users"
212
+ # # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
213
+ # # WHERE "posts"."name" = ? [["name", "example"]]
214
+ #
215
+ # As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
216
+ # the posts is no longer performed.
182
217
  #
183
218
  # Note that #includes works with association names while #references needs
184
219
  # the actual table name.
185
220
  #
186
- # If you pass the conditions via hash, you don't need to call #references
221
+ # If you pass the conditions via a Hash, you don't need to call #references
187
222
  # explicitly, as #where references the tables for you. For example, this
188
223
  # will work correctly:
189
224
  #
190
225
  # User.includes(:posts).where(posts: { name: 'example' })
226
+ #
227
+ # NOTE: Conditions affect both sides of an association. For example, the
228
+ # above code will return only users that have a post named "example",
229
+ # <em>and will only include posts named "example"</em>, even when a
230
+ # matching user has other additional posts.
191
231
  def includes(*args)
192
232
  check_if_method_has_arguments!(__callee__, args)
193
233
  spawn.includes!(*args)
@@ -198,12 +238,32 @@ module ActiveRecord
198
238
  self
199
239
  end
200
240
 
201
- # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
241
+ # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
242
+ # Performs a single query joining all specified associations. For example:
202
243
  #
203
- # User.eager_load(:posts)
204
- # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
205
- # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
206
- # # "users"."id"
244
+ # users = User.eager_load(:address).limit(5)
245
+ # users.each do |user|
246
+ # user.address.city
247
+ # end
248
+ #
249
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
250
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
251
+ # # LIMIT 5
252
+ #
253
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
254
+ # are loaded with a single joined query.
255
+ #
256
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
257
+ # similar to #includes:
258
+ #
259
+ # User.eager_load(:address, friends: [:address, :followers])
260
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
261
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
262
+ # # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
263
+ # # ...
264
+ #
265
+ # NOTE: Loading the associations in a join can result in many rows that
266
+ # contain redundant data and it performs poorly at scale.
207
267
  def eager_load(*args)
208
268
  check_if_method_has_arguments!(__callee__, args)
209
269
  spawn.eager_load!(*args)
@@ -214,10 +274,28 @@ module ActiveRecord
214
274
  self
215
275
  end
216
276
 
217
- # Allows preloading of +args+, in the same way that #includes does:
277
+ # Specify associations +args+ to be eager loaded using separate queries.
278
+ # A separate query is performed for each association.
279
+ #
280
+ # users = User.preload(:address).limit(5)
281
+ # users.each do |user|
282
+ # user.address.city
283
+ # end
218
284
  #
219
- # User.preload(:posts)
220
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
285
+ # # SELECT "users".* FROM "users" LIMIT 5
286
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
287
+ #
288
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
289
+ # are loaded with a separate query.
290
+ #
291
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
292
+ # similar to #includes:
293
+ #
294
+ # User.preload(:address, friends: [:address, :followers])
295
+ # # SELECT "users".* FROM "users"
296
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
297
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
298
+ # # SELECT ...
221
299
  def preload(*args)
222
300
  check_if_method_has_arguments!(__callee__, args)
223
301
  spawn.preload!(*args)
@@ -242,7 +320,7 @@ module ActiveRecord
242
320
  end
243
321
 
244
322
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
245
- # and should therefore be JOINed in any query rather than loaded separately.
323
+ # and should therefore be +JOIN+ed in any query rather than loaded separately.
246
324
  # This method only works in conjunction with #includes.
247
325
  # See #includes for more details.
248
326
  #
@@ -286,10 +364,18 @@ module ActiveRecord
286
364
  # Model.select(:field, :other_field, :and_one_more)
287
365
  # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
288
366
  #
367
+ # The argument also can be a hash of fields and aliases.
368
+ #
369
+ # Model.select(models: { field: :alias, other_field: :other_alias })
370
+ # # => [#<Model id: nil, alias: "value", other_alias: "value">]
371
+ #
372
+ # Model.select(models: [:field, :other_field])
373
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
374
+ #
289
375
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
290
376
  #
291
377
  # Model.select('field AS field_one', 'other_field AS field_two')
292
- # # => [#<Model id: nil, field: "value", other_field: "value">]
378
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
293
379
  #
294
380
  # If an alias was specified, it will be accessible from the resulting objects:
295
381
  #
@@ -300,7 +386,7 @@ module ActiveRecord
300
386
  # except +id+ will throw ActiveModel::MissingAttributeError:
301
387
  #
302
388
  # Model.select(:field).first.other_field
303
- # # => ActiveModel::MissingAttributeError: missing attribute: other_field
389
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
304
390
  def select(*fields)
305
391
  if block_given?
306
392
  if fields.any?
@@ -311,6 +397,8 @@ module ActiveRecord
311
397
  end
312
398
 
313
399
  check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
400
+
401
+ fields = process_select_args(fields)
314
402
  spawn._select!(*fields)
315
403
  end
316
404
 
@@ -319,6 +407,66 @@ module ActiveRecord
319
407
  self
320
408
  end
321
409
 
410
+ # Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
411
+ #
412
+ # Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
413
+ # use CTE's with MySQL 5.7.
414
+ #
415
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
416
+ # # => ActiveRecord::Relation
417
+ # # WITH posts_with_tags AS (
418
+ # # SELECT * FROM posts WHERE (tags_count > 0)
419
+ # # )
420
+ # # SELECT * FROM posts
421
+ #
422
+ # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
423
+ #
424
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
425
+ # # => ActiveRecord::Relation
426
+ # # WITH posts_with_tags AS (
427
+ # # SELECT * FROM posts WHERE (tags_count > 0)
428
+ # # )
429
+ # # SELECT * FROM posts_with_tags AS posts
430
+ #
431
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
432
+ # # => ActiveRecord::Relation
433
+ # # WITH posts_with_tags AS (
434
+ # # SELECT * FROM posts WHERE (tags_count > 0)
435
+ # # )
436
+ # # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
437
+ #
438
+ # It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
439
+ # and you have verified it is safe for the database, you can pass it as SQL literal
440
+ # using +Arel+.
441
+ #
442
+ # Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
443
+ #
444
+ # Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
445
+ # be used with unsafe values that include unsanitized input.
446
+ #
447
+ # To add multiple CTEs just pass multiple key-value pairs
448
+ #
449
+ # Post.with(
450
+ # posts_with_comments: Post.where("comments_count > ?", 0),
451
+ # posts_with_tags: Post.where("tags_count > ?", 0)
452
+ # )
453
+ #
454
+ # or chain multiple +.with+ calls
455
+ #
456
+ # Post
457
+ # .with(posts_with_comments: Post.where("comments_count > ?", 0))
458
+ # .with(posts_with_tags: Post.where("tags_count > ?", 0))
459
+ def with(*args)
460
+ check_if_method_has_arguments!(__callee__, args)
461
+ spawn.with!(*args)
462
+ end
463
+
464
+ # Like #with, but modifies relation in place.
465
+ def with!(*args) # :nodoc:
466
+ self.with_values += args
467
+ self
468
+ end
469
+
322
470
  # Allows you to change a previously set select statement.
323
471
  #
324
472
  # Post.select(:title, :body)
@@ -331,6 +479,7 @@ module ActiveRecord
331
479
  # Note that we're unscoping the entire select statement.
332
480
  def reselect(*args)
333
481
  check_if_method_has_arguments!(__callee__, args)
482
+ args = process_select_args(args)
334
483
  spawn.reselect!(*args)
335
484
  end
336
485
 
@@ -370,6 +519,27 @@ module ActiveRecord
370
519
  self
371
520
  end
372
521
 
522
+ # Allows you to change a previously set group statement.
523
+ #
524
+ # Post.group(:title, :body)
525
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
526
+ #
527
+ # Post.group(:title, :body).regroup(:title)
528
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
529
+ #
530
+ # This is short-hand for <tt>unscope(:group).group(fields)</tt>.
531
+ # Note that we're unscoping the entire group statement.
532
+ def regroup(*args)
533
+ check_if_method_has_arguments!(__callee__, args)
534
+ spawn.regroup!(*args)
535
+ end
536
+
537
+ # Same as #regroup but operates on relation in-place instead of copying.
538
+ def regroup!(*args) # :nodoc:
539
+ self.group_values = args
540
+ self
541
+ end
542
+
373
543
  # Applies an <code>ORDER BY</code> clause to a query.
374
544
  #
375
545
  # #order accepts arguments in one of several formats.
@@ -418,7 +588,7 @@ module ActiveRecord
418
588
  # User.order(Arel.sql('end_date - start_date'))
419
589
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
420
590
  #
421
- # Custom query syntax, like JSON columns for Postgres, is supported in this way.
591
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
422
592
  #
423
593
  # User.order(Arel.sql("payload->>'kind'"))
424
594
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
@@ -436,13 +606,16 @@ module ActiveRecord
436
606
  self
437
607
  end
438
608
 
439
- # Allows to specify an order by a specific set of values. Depending on your
440
- # adapter this will either use a CASE statement or a built-in function.
609
+ # Allows to specify an order by a specific set of values.
441
610
  #
442
611
  # User.in_order_of(:id, [1, 5, 3])
443
612
  # # SELECT "users".* FROM "users"
444
- # # ORDER BY FIELD("users"."id", 1, 5, 3)
445
613
  # # WHERE "users"."id" IN (1, 5, 3)
614
+ # # ORDER BY CASE
615
+ # # WHEN "users"."id" = 1 THEN 1
616
+ # # WHEN "users"."id" = 5 THEN 2
617
+ # # WHEN "users"."id" = 3 THEN 3
618
+ # # END ASC
446
619
  #
447
620
  def in_order_of(column, values)
448
621
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
@@ -452,11 +625,18 @@ module ActiveRecord
452
625
  self.references_values |= references unless references.empty?
453
626
 
454
627
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
455
- arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
628
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
629
+
630
+ where_clause =
631
+ if values.include?(nil)
632
+ arel_column.in(values.compact).or(arel_column.eq(nil))
633
+ else
634
+ arel_column.in(values)
635
+ end
456
636
 
457
637
  spawn
458
- .order!(connection.field_ordered_value(arel_column, values))
459
- .where!(arel_column.in(values))
638
+ .order!(build_case_for_value_position(arel_column, values))
639
+ .where!(where_clause)
460
640
  end
461
641
 
462
642
  # Replaces any existing order defined on the relation with the specified order.
@@ -467,7 +647,7 @@ module ActiveRecord
467
647
  #
468
648
  # User.order('email DESC').reorder('id ASC').order('name ASC')
469
649
  #
470
- # generates a query with 'ORDER BY id ASC, name ASC'.
650
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
471
651
  def reorder(*args)
472
652
  check_if_method_has_arguments!(__callee__, args) do
473
653
  sanitize_order_arguments(args)
@@ -486,7 +666,8 @@ module ActiveRecord
486
666
 
487
667
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
488
668
  :limit, :offset, :joins, :left_outer_joins, :annotate,
489
- :includes, :from, :readonly, :having, :optimizer_hints])
669
+ :includes, :eager_load, :preload, :from, :readonly,
670
+ :having, :optimizer_hints])
490
671
 
491
672
  # Removes an unwanted relation that is already defined on a chain of relations.
492
673
  # This is useful when passing around chains of relations and would like to
@@ -596,7 +777,7 @@ module ActiveRecord
596
777
  # Performs LEFT OUTER JOINs on +args+:
597
778
  #
598
779
  # User.left_outer_joins(:posts)
599
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
780
+ # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
600
781
  #
601
782
  def left_outer_joins(*args)
602
783
  check_if_method_has_arguments!(__callee__, args)
@@ -616,7 +797,7 @@ module ActiveRecord
616
797
  # SQL is given as an illustration; the actual query generated may be different depending
617
798
  # on the database adapter.
618
799
  #
619
- # === string
800
+ # === \String
620
801
  #
621
802
  # A single string, without additional arguments, is passed to the query
622
803
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -628,7 +809,7 @@ module ActiveRecord
628
809
  # to injection attacks if not done properly. As an alternative, it is recommended
629
810
  # to use one of the following methods.
630
811
  #
631
- # === array
812
+ # === \Array
632
813
  #
633
814
  # If an array is passed, then the first element of the array is treated as a template, and
634
815
  # the remaining elements are inserted into the template to generate the condition.
@@ -668,7 +849,7 @@ module ActiveRecord
668
849
  # dependencies on the underlying database. If your code is intended for general consumption,
669
850
  # test with multiple database backends.
670
851
  #
671
- # === hash
852
+ # === \Hash
672
853
  #
673
854
  # #where will also accept a hash condition, in which the keys are fields and the values
674
855
  # are values to be searched for.
@@ -702,6 +883,12 @@ module ActiveRecord
702
883
  # PriceEstimate.where(estimate_of: treasure)
703
884
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
704
885
  #
886
+ # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
887
+ # an array of columns with an array of tuples as values.
888
+ #
889
+ # Article.where([:author_id, :id] => [[15, 1], [15, 2]])
890
+ # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
891
+ #
705
892
  # === Joins
706
893
  #
707
894
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -714,7 +901,7 @@ module ActiveRecord
714
901
  # User.joins(:posts).where("posts.published" => true)
715
902
  # User.joins(:posts).where(posts: { published: true })
716
903
  #
717
- # === no argument
904
+ # === No Argument
718
905
  #
719
906
  # If no argument is passed, #where returns a new instance of WhereChain, that
720
907
  # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
@@ -738,7 +925,7 @@ module ActiveRecord
738
925
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
739
926
  # # WHERE "authors"."id" IS NULL
740
927
  #
741
- # === blank condition
928
+ # === Blank Condition
742
929
  #
743
930
  # If the condition is any blank-ish object, then #where is a no-op and returns
744
931
  # the current relation.
@@ -771,6 +958,8 @@ module ActiveRecord
771
958
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
772
959
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
773
960
  def rewhere(conditions)
961
+ return unscope(:where) if conditions.nil?
962
+
774
963
  scope = spawn
775
964
  where_clause = scope.build_where_clause(conditions)
776
965
 
@@ -876,7 +1065,11 @@ module ActiveRecord
876
1065
  #
877
1066
  def or(other)
878
1067
  if other.is_a?(Relation)
879
- spawn.or!(other)
1068
+ if @none
1069
+ other.spawn
1070
+ else
1071
+ spawn.or!(other)
1072
+ end
880
1073
  else
881
1074
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
882
1075
  end
@@ -989,15 +1182,29 @@ module ActiveRecord
989
1182
  end
990
1183
 
991
1184
  def none! # :nodoc:
992
- where!("1=0").extending!(NullRelation)
1185
+ unless @none
1186
+ where!("1=0")
1187
+ @none = true
1188
+ end
1189
+ self
993
1190
  end
994
1191
 
995
- # Sets readonly attributes for the returned relation. If value is
996
- # true (default), attempting to update a record will result in an error.
1192
+ def null_relation? # :nodoc:
1193
+ @none
1194
+ end
1195
+
1196
+ # Mark a relation as readonly. Attempting to update a record will result in
1197
+ # an error.
997
1198
  #
998
1199
  # users = User.readonly
999
1200
  # users.first.save
1000
1201
  # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1202
+ #
1203
+ # To make a readonly relation writable, pass +false+.
1204
+ #
1205
+ # users.readonly(false)
1206
+ # users.first.save
1207
+ # => true
1001
1208
  def readonly(value = true)
1002
1209
  spawn.readonly!(value)
1003
1210
  end
@@ -1114,7 +1321,7 @@ module ActiveRecord
1114
1321
  #
1115
1322
  # The object returned is a relation, which can be further extended.
1116
1323
  #
1117
- # === Using a module
1324
+ # === Using a \Module
1118
1325
  #
1119
1326
  # module Pagination
1120
1327
  # def page(number)
@@ -1129,7 +1336,7 @@ module ActiveRecord
1129
1336
  #
1130
1337
  # scope = Model.all.extending(Pagination, SomethingElse)
1131
1338
  #
1132
- # === Using a block
1339
+ # === Using a Block
1133
1340
  #
1134
1341
  # scope = Model.all.extending do
1135
1342
  # def page(number)
@@ -1216,6 +1423,8 @@ module ActiveRecord
1216
1423
  # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1217
1424
  #
1218
1425
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1426
+ #
1427
+ # Some escaping is performed, however untrusted user input should not be used.
1219
1428
  def annotate(*args)
1220
1429
  check_if_method_has_arguments!(__callee__, args)
1221
1430
  spawn.annotate!(*args)
@@ -1304,8 +1513,12 @@ module ActiveRecord
1304
1513
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1305
1514
  when Hash
1306
1515
  opts = opts.transform_keys do |key|
1307
- key = key.to_s
1308
- klass.attribute_aliases[key] || key
1516
+ if key.is_a?(Array)
1517
+ key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1518
+ else
1519
+ key = key.to_s
1520
+ klass.attribute_aliases[key] || key
1521
+ end
1309
1522
  end
1310
1523
  references = PredicateBuilder.references(opts)
1311
1524
  self.references_values |= references unless references.empty?
@@ -1323,7 +1536,16 @@ module ActiveRecord
1323
1536
  end
1324
1537
  alias :build_having_clause :build_where_clause
1325
1538
 
1539
+ def async!
1540
+ @async = true
1541
+ self
1542
+ end
1543
+
1326
1544
  private
1545
+ def async
1546
+ spawn.async!
1547
+ end
1548
+
1327
1549
  def lookup_table_klass_from_join_dependencies(table_name)
1328
1550
  each_join_dependencies do |join|
1329
1551
  return join.base_klass if table_name == join.table_name
@@ -1338,13 +1560,13 @@ module ActiveRecord
1338
1560
  end
1339
1561
 
1340
1562
  def build_join_dependencies
1341
- associations = joins_values | left_outer_joins_values
1342
- associations |= eager_load_values unless eager_load_values.empty?
1343
- associations |= includes_values unless includes_values.empty?
1563
+ joins = joins_values | left_outer_joins_values
1564
+ joins |= eager_load_values unless eager_load_values.empty?
1565
+ joins |= includes_values unless includes_values.empty?
1344
1566
 
1345
1567
  join_dependencies = []
1346
1568
  join_dependencies.unshift construct_join_dependency(
1347
- select_association_list(associations, join_dependencies), nil
1569
+ select_named_joins(joins, join_dependencies), nil
1348
1570
  )
1349
1571
  end
1350
1572
 
@@ -1365,6 +1587,7 @@ module ActiveRecord
1365
1587
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1366
1588
 
1367
1589
  build_order(arel)
1590
+ build_with(arel)
1368
1591
  build_select(arel)
1369
1592
 
1370
1593
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1400,6 +1623,18 @@ module ActiveRecord
1400
1623
  end
1401
1624
  end
1402
1625
 
1626
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1627
+ cte_joins, associations = join_names.partition do |join_name|
1628
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1629
+ end
1630
+
1631
+ cte_joins.each do |cte_name|
1632
+ block&.call(CTEJoin.new(cte_name))
1633
+ end
1634
+
1635
+ select_association_list(associations, stashed_joins, &block)
1636
+ end
1637
+
1403
1638
  def select_association_list(associations, stashed_joins = nil)
1404
1639
  result = []
1405
1640
  associations.each do |association|
@@ -1415,20 +1650,21 @@ module ActiveRecord
1415
1650
  result
1416
1651
  end
1417
1652
 
1418
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1419
- end
1420
-
1421
1653
  def build_join_buckets
1422
1654
  buckets = Hash.new { |h, k| h[k] = [] }
1423
1655
 
1424
1656
  unless left_outer_joins_values.empty?
1425
1657
  stashed_left_joins = []
1426
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1427
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1658
+ left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
1659
+ if left_join.is_a?(CTEJoin)
1660
+ buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
1661
+ else
1662
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1663
+ end
1428
1664
  end
1429
1665
 
1430
1666
  if joins_values.empty?
1431
- buckets[:association_join] = left_joins
1667
+ buckets[:named_join] = left_joins
1432
1668
  buckets[:stashed_join] = stashed_left_joins
1433
1669
  return buckets, Arel::Nodes::OuterJoin
1434
1670
  else
@@ -1454,9 +1690,11 @@ module ActiveRecord
1454
1690
  end
1455
1691
  end
1456
1692
 
1457
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1693
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1458
1694
  if join.is_a?(Arel::Nodes::Join)
1459
1695
  buckets[:join_node] << join
1696
+ elsif join.is_a?(CTEJoin)
1697
+ buckets[:join_node] << build_with_join_node(join.name)
1460
1698
  else
1461
1699
  raise "unknown class: %s" % join.class.name
1462
1700
  end
@@ -1473,16 +1711,16 @@ module ActiveRecord
1473
1711
 
1474
1712
  buckets, join_type = build_join_buckets
1475
1713
 
1476
- association_joins = buckets[:association_join]
1477
- stashed_joins = buckets[:stashed_join]
1478
- leading_joins = buckets[:leading_join]
1479
- join_nodes = buckets[:join_node]
1714
+ named_joins = buckets[:named_join]
1715
+ stashed_joins = buckets[:stashed_join]
1716
+ leading_joins = buckets[:leading_join]
1717
+ join_nodes = buckets[:join_node]
1480
1718
 
1481
1719
  join_sources.concat(leading_joins) unless leading_joins.empty?
1482
1720
 
1483
- unless association_joins.empty? && stashed_joins.empty?
1721
+ unless named_joins.empty? && stashed_joins.empty?
1484
1722
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1485
- join_dependency = construct_join_dependency(association_joins, join_type)
1723
+ join_dependency = construct_join_dependency(named_joins, join_type)
1486
1724
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1487
1725
  end
1488
1726
 
@@ -1500,6 +1738,40 @@ module ActiveRecord
1500
1738
  end
1501
1739
  end
1502
1740
 
1741
+ def build_with(arel)
1742
+ return if with_values.empty?
1743
+
1744
+ with_statements = with_values.map do |with_value|
1745
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1746
+
1747
+ build_with_value_from_hash(with_value)
1748
+ end
1749
+
1750
+ arel.with(with_statements)
1751
+ end
1752
+
1753
+ def build_with_value_from_hash(hash)
1754
+ hash.map do |name, value|
1755
+ expression =
1756
+ case value
1757
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1758
+ when ActiveRecord::Relation then value.arel
1759
+ when Arel::SelectManager then value
1760
+ else
1761
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1762
+ end
1763
+ Arel::Nodes::TableAlias.new(expression, name)
1764
+ end
1765
+ end
1766
+
1767
+ def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
1768
+ with_table = Arel::Table.new(name)
1769
+
1770
+ table.join(with_table, kind).on(
1771
+ with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1772
+ ).join_sources.first
1773
+ end
1774
+
1503
1775
  def arel_columns(columns)
1504
1776
  columns.flat_map do |field|
1505
1777
  case field
@@ -1618,7 +1890,7 @@ module ActiveRecord
1618
1890
  when Hash
1619
1891
  arg.map { |field, dir|
1620
1892
  case field
1621
- when Arel::Nodes::SqlLiteral
1893
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1622
1894
  field.public_send(dir.downcase)
1623
1895
  else
1624
1896
  order_column(field.to_s).public_send(dir.downcase)
@@ -1637,16 +1909,32 @@ module ActiveRecord
1637
1909
  end
1638
1910
 
1639
1911
  def column_references(order_args)
1640
- references = order_args.flat_map do |arg|
1912
+ order_args.flat_map do |arg|
1641
1913
  case arg
1642
1914
  when String, Symbol
1643
- arg
1915
+ extract_table_name_from(arg)
1644
1916
  when Hash
1645
- arg.keys
1917
+ arg
1918
+ .map do |key, value|
1919
+ case value
1920
+ when Hash
1921
+ key.to_s
1922
+ else
1923
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
1924
+ end
1925
+ end
1926
+ when Arel::Attribute
1927
+ arg.relation.name
1928
+ when Arel::Nodes::Ordering
1929
+ if arg.expr.is_a?(Arel::Attribute)
1930
+ arg.expr.relation.name
1931
+ end
1646
1932
  end
1647
- end
1648
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1649
- references
1933
+ end.compact
1934
+ end
1935
+
1936
+ def extract_table_name_from(string)
1937
+ string.match(/^\W?(\w+)\W?\./) && $1
1650
1938
  end
1651
1939
 
1652
1940
  def order_column(field)
@@ -1659,6 +1947,15 @@ module ActiveRecord
1659
1947
  end
1660
1948
  end
1661
1949
 
1950
+ def build_case_for_value_position(column, values)
1951
+ node = Arel::Nodes::Case.new
1952
+ values.each.with_index(1) do |value, order|
1953
+ node.when(column.eq(value)).then(order)
1954
+ end
1955
+
1956
+ Arel::Nodes::Ascending.new(node)
1957
+ end
1958
+
1662
1959
  def resolve_arel_attributes(attrs)
1663
1960
  attrs.flat_map do |attr|
1664
1961
  case attr
@@ -1710,6 +2007,41 @@ module ActiveRecord
1710
2007
  end
1711
2008
  end
1712
2009
 
2010
+ def process_select_args(fields)
2011
+ fields.flat_map do |field|
2012
+ if field.is_a?(Hash)
2013
+ transform_select_hash_values(field)
2014
+ else
2015
+ field
2016
+ end
2017
+ end
2018
+ end
2019
+
2020
+ def transform_select_hash_values(fields)
2021
+ fields.flat_map do |key, columns_aliases|
2022
+ case columns_aliases
2023
+ when Hash
2024
+ columns_aliases.map do |column, column_alias|
2025
+ if values[:joins]&.include?(key)
2026
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2027
+ self.references_values |= references unless references.empty?
2028
+ end
2029
+ arel_column("#{key}.#{column}") do
2030
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2031
+ end.as(column_alias.to_s)
2032
+ end
2033
+ when Array
2034
+ columns_aliases.map do |column|
2035
+ arel_column("#{key}.#{column}", &:itself)
2036
+ end
2037
+ when String, Symbol
2038
+ arel_column(key.to_s) do
2039
+ predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2040
+ end.as(columns_aliases.to_s)
2041
+ end
2042
+ end
2043
+ end
2044
+
1713
2045
  STRUCTURAL_VALUE_METHODS = (
1714
2046
  Relation::VALUE_METHODS -
1715
2047
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]