activerecord 7.0.0 → 7.1.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  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 +18 -3
  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 +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  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.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -3,17 +3,16 @@
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.
14
- # In this case, #where must be chained with #not to return a new relation.
12
+ # +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
13
+ # In this case, +where+ can be chained to return a new relation.
15
14
  class WhereChain
16
- def initialize(scope)
15
+ def initialize(scope) # :nodoc:
17
16
  @scope = scope
18
17
  end
19
18
 
@@ -39,7 +38,14 @@ 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')
42
+ #
43
+ # If there is a non-nil condition on a nullable column in the hash condition, the records that have
44
+ # nil values on the nullable column won't be returned.
45
+ # User.create!(nullable_country: nil)
46
+ # User.where.not(nullable_country: "UK")
47
+ # # SELECT * FROM users WHERE NOT (nullable_country = 'UK')
48
+ # # => []
43
49
  def not(opts, *rest)
44
50
  where_clause = @scope.send(:build_where_clause, opts, rest)
45
51
 
@@ -68,9 +74,13 @@ module ActiveRecord
68
74
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
69
75
  def associated(*associations)
70
76
  associations.each do |association|
71
- reflection = @scope.klass._reflect_on_association(association)
77
+ reflection = scope_association_reflection(association)
72
78
  @scope.joins!(association)
73
- 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
74
84
  end
75
85
 
76
86
  @scope
@@ -96,13 +106,35 @@ module ActiveRecord
96
106
  # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
97
107
  def missing(*associations)
98
108
  associations.each do |association|
99
- reflection = @scope.klass._reflect_on_association(association)
109
+ reflection = scope_association_reflection(association)
100
110
  @scope.left_outer_joins!(association)
101
- @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
102
116
  end
103
117
 
104
118
  @scope
105
119
  end
120
+
121
+ private
122
+ def scope_association_reflection(association)
123
+ reflection = @scope.klass._reflect_on_association(association)
124
+ unless reflection
125
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
126
+ end
127
+ reflection
128
+ end
129
+ end
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
106
138
  end
107
139
 
108
140
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -133,45 +165,69 @@ module ActiveRecord
133
165
 
134
166
  alias extensions extending_values
135
167
 
136
- # Specify relationships to be included in the result set. For
137
- # 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:
138
173
  #
139
- # users = User.includes(:address)
174
+ # users = User.includes(:address).limit(5)
140
175
  # users.each do |user|
141
176
  # user.address.city
142
177
  # end
143
178
  #
144
- # allows you to access the +address+ attribute of the +User+ model without
145
- # firing an additional query. This will often result in a
146
- # 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.
147
184
  #
148
- # 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.
149
188
  #
150
- # users = User.includes(:address, :friends)
189
+ # You can also specify multiple associations. Each association will result
190
+ # in an additional query:
151
191
  #
152
- # 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)
153
196
  #
154
- # users = User.includes(:address, friends: [:address, :followers])
197
+ # Loading nested associations is possible using a Hash:
155
198
  #
156
- # === conditions
199
+ # User.includes(:address, friends: [:address, :followers])
200
+ #
201
+ # === Conditions
157
202
  #
158
203
  # If you want to add string conditions to your included models, you'll have
159
204
  # to explicitly reference them. For example:
160
205
  #
161
- # User.includes(:posts).where('posts.name = ?', 'example')
206
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
162
207
  #
163
208
  # Will throw an error, but this will work:
164
209
  #
165
- # 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.
166
217
  #
167
218
  # Note that #includes works with association names while #references needs
168
219
  # the actual table name.
169
220
  #
170
- # 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
171
222
  # explicitly, as #where references the tables for you. For example, this
172
223
  # will work correctly:
173
224
  #
174
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.
175
231
  def includes(*args)
176
232
  check_if_method_has_arguments!(__callee__, args)
177
233
  spawn.includes!(*args)
@@ -182,12 +238,32 @@ module ActiveRecord
182
238
  self
183
239
  end
184
240
 
185
- # 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:
243
+ #
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.
186
255
  #
187
- # User.eager_load(:posts)
188
- # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
189
- # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
190
- # # "users"."id"
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.
191
267
  def eager_load(*args)
192
268
  check_if_method_has_arguments!(__callee__, args)
193
269
  spawn.eager_load!(*args)
@@ -198,10 +274,28 @@ module ActiveRecord
198
274
  self
199
275
  end
200
276
 
201
- # 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
284
+ #
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.
202
290
  #
203
- # User.preload(:posts)
204
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
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 ...
205
299
  def preload(*args)
206
300
  check_if_method_has_arguments!(__callee__, args)
207
301
  spawn.preload!(*args)
@@ -226,7 +320,7 @@ module ActiveRecord
226
320
  end
227
321
 
228
322
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
229
- # 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.
230
324
  # This method only works in conjunction with #includes.
231
325
  # See #includes for more details.
232
326
  #
@@ -270,10 +364,18 @@ module ActiveRecord
270
364
  # Model.select(:field, :other_field, :and_one_more)
271
365
  # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
272
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
+ #
273
375
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
274
376
  #
275
377
  # Model.select('field AS field_one', 'other_field AS field_two')
276
- # # => [#<Model id: nil, field: "value", other_field: "value">]
378
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
277
379
  #
278
380
  # If an alias was specified, it will be accessible from the resulting objects:
279
381
  #
@@ -284,7 +386,7 @@ module ActiveRecord
284
386
  # except +id+ will throw ActiveModel::MissingAttributeError:
285
387
  #
286
388
  # Model.select(:field).first.other_field
287
- # # => ActiveModel::MissingAttributeError: missing attribute: other_field
389
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
288
390
  def select(*fields)
289
391
  if block_given?
290
392
  if fields.any?
@@ -295,6 +397,8 @@ module ActiveRecord
295
397
  end
296
398
 
297
399
  check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
400
+
401
+ fields = process_select_args(fields)
298
402
  spawn._select!(*fields)
299
403
  end
300
404
 
@@ -303,6 +407,66 @@ module ActiveRecord
303
407
  self
304
408
  end
305
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
+
306
470
  # Allows you to change a previously set select statement.
307
471
  #
308
472
  # Post.select(:title, :body)
@@ -315,6 +479,7 @@ module ActiveRecord
315
479
  # Note that we're unscoping the entire select statement.
316
480
  def reselect(*args)
317
481
  check_if_method_has_arguments!(__callee__, args)
482
+ args = process_select_args(args)
318
483
  spawn.reselect!(*args)
319
484
  end
320
485
 
@@ -354,6 +519,27 @@ module ActiveRecord
354
519
  self
355
520
  end
356
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
+
357
543
  # Applies an <code>ORDER BY</code> clause to a query.
358
544
  #
359
545
  # #order accepts arguments in one of several formats.
@@ -402,7 +588,7 @@ module ActiveRecord
402
588
  # User.order(Arel.sql('end_date - start_date'))
403
589
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
404
590
  #
405
- # 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.
406
592
  #
407
593
  # User.order(Arel.sql("payload->>'kind'"))
408
594
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
@@ -420,22 +606,37 @@ module ActiveRecord
420
606
  self
421
607
  end
422
608
 
423
- # Allows to specify an order by a specific set of values. Depending on your
424
- # 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.
425
610
  #
426
611
  # User.in_order_of(:id, [1, 5, 3])
427
- # # SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
612
+ # # SELECT "users".* FROM "users"
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
428
619
  #
429
620
  def in_order_of(column, values)
430
621
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
622
+ return spawn.none! if values.empty?
431
623
 
432
624
  references = column_references([column])
433
625
  self.references_values |= references unless references.empty?
434
626
 
435
627
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
436
- column = order_column(column.to_s) if column.is_a?(Symbol)
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
437
636
 
438
- spawn.order!(connection.field_ordered_value(column, values))
637
+ spawn
638
+ .order!(build_case_for_value_position(arel_column, values))
639
+ .where!(where_clause)
439
640
  end
440
641
 
441
642
  # Replaces any existing order defined on the relation with the specified order.
@@ -446,7 +647,7 @@ module ActiveRecord
446
647
  #
447
648
  # User.order('email DESC').reorder('id ASC').order('name ASC')
448
649
  #
449
- # generates a query with 'ORDER BY id ASC, name ASC'.
650
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
450
651
  def reorder(*args)
451
652
  check_if_method_has_arguments!(__callee__, args) do
452
653
  sanitize_order_arguments(args)
@@ -465,7 +666,8 @@ module ActiveRecord
465
666
 
466
667
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
467
668
  :limit, :offset, :joins, :left_outer_joins, :annotate,
468
- :includes, :from, :readonly, :having, :optimizer_hints])
669
+ :includes, :eager_load, :preload, :from, :readonly,
670
+ :having, :optimizer_hints])
469
671
 
470
672
  # Removes an unwanted relation that is already defined on a chain of relations.
471
673
  # This is useful when passing around chains of relations and would like to
@@ -575,7 +777,7 @@ module ActiveRecord
575
777
  # Performs LEFT OUTER JOINs on +args+:
576
778
  #
577
779
  # User.left_outer_joins(:posts)
578
- # => 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"
579
781
  #
580
782
  def left_outer_joins(*args)
581
783
  check_if_method_has_arguments!(__callee__, args)
@@ -595,7 +797,7 @@ module ActiveRecord
595
797
  # SQL is given as an illustration; the actual query generated may be different depending
596
798
  # on the database adapter.
597
799
  #
598
- # === string
800
+ # === \String
599
801
  #
600
802
  # A single string, without additional arguments, is passed to the query
601
803
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -607,7 +809,7 @@ module ActiveRecord
607
809
  # to injection attacks if not done properly. As an alternative, it is recommended
608
810
  # to use one of the following methods.
609
811
  #
610
- # === array
812
+ # === \Array
611
813
  #
612
814
  # If an array is passed, then the first element of the array is treated as a template, and
613
815
  # the remaining elements are inserted into the template to generate the condition.
@@ -647,7 +849,7 @@ module ActiveRecord
647
849
  # dependencies on the underlying database. If your code is intended for general consumption,
648
850
  # test with multiple database backends.
649
851
  #
650
- # === hash
852
+ # === \Hash
651
853
  #
652
854
  # #where will also accept a hash condition, in which the keys are fields and the values
653
855
  # are values to be searched for.
@@ -681,6 +883,12 @@ module ActiveRecord
681
883
  # PriceEstimate.where(estimate_of: treasure)
682
884
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
683
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
+ #
684
892
  # === Joins
685
893
  #
686
894
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -693,17 +901,31 @@ module ActiveRecord
693
901
  # User.joins(:posts).where("posts.published" => true)
694
902
  # User.joins(:posts).where(posts: { published: true })
695
903
  #
696
- # === no argument
904
+ # === No Argument
697
905
  #
698
906
  # If no argument is passed, #where returns a new instance of WhereChain, that
699
- # can be chained with #not to return a new relation that negates the where clause.
907
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
908
+ #
909
+ # Chaining with WhereChain#not:
700
910
  #
701
911
  # User.where.not(name: "Jon")
702
912
  # # SELECT * FROM users WHERE name != 'Jon'
703
913
  #
704
- # See WhereChain for more details on #not.
914
+ # Chaining with WhereChain#associated:
705
915
  #
706
- # === blank condition
916
+ # Post.where.associated(:author)
917
+ # # SELECT "posts".* FROM "posts"
918
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
919
+ # # WHERE "authors"."id" IS NOT NULL
920
+ #
921
+ # Chaining with WhereChain#missing:
922
+ #
923
+ # Post.where.missing(:author)
924
+ # # SELECT "posts".* FROM "posts"
925
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
926
+ # # WHERE "authors"."id" IS NULL
927
+ #
928
+ # === Blank Condition
707
929
  #
708
930
  # If the condition is any blank-ish object, then #where is a no-op and returns
709
931
  # the current relation.
@@ -736,6 +958,8 @@ module ActiveRecord
736
958
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
737
959
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
738
960
  def rewhere(conditions)
961
+ return unscope(:where) if conditions.nil?
962
+
739
963
  scope = spawn
740
964
  where_clause = scope.build_where_clause(conditions)
741
965
 
@@ -841,7 +1065,11 @@ module ActiveRecord
841
1065
  #
842
1066
  def or(other)
843
1067
  if other.is_a?(Relation)
844
- spawn.or!(other)
1068
+ if @none
1069
+ other.spawn
1070
+ else
1071
+ spawn.or!(other)
1072
+ end
845
1073
  else
846
1074
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
847
1075
  end
@@ -954,15 +1182,29 @@ module ActiveRecord
954
1182
  end
955
1183
 
956
1184
  def none! # :nodoc:
957
- where!("1=0").extending!(NullRelation)
1185
+ unless @none
1186
+ where!("1=0")
1187
+ @none = true
1188
+ end
1189
+ self
958
1190
  end
959
1191
 
960
- # Sets readonly attributes for the returned relation. If value is
961
- # 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.
962
1198
  #
963
1199
  # users = User.readonly
964
1200
  # users.first.save
965
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
966
1208
  def readonly(value = true)
967
1209
  spawn.readonly!(value)
968
1210
  end
@@ -1079,7 +1321,7 @@ module ActiveRecord
1079
1321
  #
1080
1322
  # The object returned is a relation, which can be further extended.
1081
1323
  #
1082
- # === Using a module
1324
+ # === Using a \Module
1083
1325
  #
1084
1326
  # module Pagination
1085
1327
  # def page(number)
@@ -1094,7 +1336,7 @@ module ActiveRecord
1094
1336
  #
1095
1337
  # scope = Model.all.extending(Pagination, SomethingElse)
1096
1338
  #
1097
- # === Using a block
1339
+ # === Using a Block
1098
1340
  #
1099
1341
  # scope = Model.all.extending do
1100
1342
  # def page(number)
@@ -1181,6 +1423,8 @@ module ActiveRecord
1181
1423
  # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1182
1424
  #
1183
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.
1184
1428
  def annotate(*args)
1185
1429
  check_if_method_has_arguments!(__callee__, args)
1186
1430
  spawn.annotate!(*args)
@@ -1269,8 +1513,12 @@ module ActiveRecord
1269
1513
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1270
1514
  when Hash
1271
1515
  opts = opts.transform_keys do |key|
1272
- key = key.to_s
1273
- 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
1274
1522
  end
1275
1523
  references = PredicateBuilder.references(opts)
1276
1524
  self.references_values |= references unless references.empty?
@@ -1288,7 +1536,16 @@ module ActiveRecord
1288
1536
  end
1289
1537
  alias :build_having_clause :build_where_clause
1290
1538
 
1539
+ def async!
1540
+ @async = true
1541
+ self
1542
+ end
1543
+
1291
1544
  private
1545
+ def async
1546
+ spawn.async!
1547
+ end
1548
+
1292
1549
  def lookup_table_klass_from_join_dependencies(table_name)
1293
1550
  each_join_dependencies do |join|
1294
1551
  return join.base_klass if table_name == join.table_name
@@ -1303,13 +1560,13 @@ module ActiveRecord
1303
1560
  end
1304
1561
 
1305
1562
  def build_join_dependencies
1306
- associations = joins_values | left_outer_joins_values
1307
- associations |= eager_load_values unless eager_load_values.empty?
1308
- 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?
1309
1566
 
1310
1567
  join_dependencies = []
1311
1568
  join_dependencies.unshift construct_join_dependency(
1312
- select_association_list(associations, join_dependencies), nil
1569
+ select_named_joins(joins, join_dependencies), nil
1313
1570
  )
1314
1571
  end
1315
1572
 
@@ -1330,6 +1587,7 @@ module ActiveRecord
1330
1587
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1331
1588
 
1332
1589
  build_order(arel)
1590
+ build_with(arel)
1333
1591
  build_select(arel)
1334
1592
 
1335
1593
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1365,6 +1623,18 @@ module ActiveRecord
1365
1623
  end
1366
1624
  end
1367
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
+
1368
1638
  def select_association_list(associations, stashed_joins = nil)
1369
1639
  result = []
1370
1640
  associations.each do |association|
@@ -1380,20 +1650,21 @@ module ActiveRecord
1380
1650
  result
1381
1651
  end
1382
1652
 
1383
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1384
- end
1385
-
1386
1653
  def build_join_buckets
1387
1654
  buckets = Hash.new { |h, k| h[k] = [] }
1388
1655
 
1389
1656
  unless left_outer_joins_values.empty?
1390
1657
  stashed_left_joins = []
1391
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1392
- 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
1393
1664
  end
1394
1665
 
1395
1666
  if joins_values.empty?
1396
- buckets[:association_join] = left_joins
1667
+ buckets[:named_join] = left_joins
1397
1668
  buckets[:stashed_join] = stashed_left_joins
1398
1669
  return buckets, Arel::Nodes::OuterJoin
1399
1670
  else
@@ -1419,9 +1690,11 @@ module ActiveRecord
1419
1690
  end
1420
1691
  end
1421
1692
 
1422
- 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|
1423
1694
  if join.is_a?(Arel::Nodes::Join)
1424
1695
  buckets[:join_node] << join
1696
+ elsif join.is_a?(CTEJoin)
1697
+ buckets[:join_node] << build_with_join_node(join.name)
1425
1698
  else
1426
1699
  raise "unknown class: %s" % join.class.name
1427
1700
  end
@@ -1438,16 +1711,16 @@ module ActiveRecord
1438
1711
 
1439
1712
  buckets, join_type = build_join_buckets
1440
1713
 
1441
- association_joins = buckets[:association_join]
1442
- stashed_joins = buckets[:stashed_join]
1443
- leading_joins = buckets[:leading_join]
1444
- 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]
1445
1718
 
1446
1719
  join_sources.concat(leading_joins) unless leading_joins.empty?
1447
1720
 
1448
- unless association_joins.empty? && stashed_joins.empty?
1721
+ unless named_joins.empty? && stashed_joins.empty?
1449
1722
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1450
- join_dependency = construct_join_dependency(association_joins, join_type)
1723
+ join_dependency = construct_join_dependency(named_joins, join_type)
1451
1724
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1452
1725
  end
1453
1726
 
@@ -1465,6 +1738,40 @@ module ActiveRecord
1465
1738
  end
1466
1739
  end
1467
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
+
1468
1775
  def arel_columns(columns)
1469
1776
  columns.flat_map do |field|
1470
1777
  case field
@@ -1583,7 +1890,7 @@ module ActiveRecord
1583
1890
  when Hash
1584
1891
  arg.map { |field, dir|
1585
1892
  case field
1586
- when Arel::Nodes::SqlLiteral
1893
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1587
1894
  field.public_send(dir.downcase)
1588
1895
  else
1589
1896
  order_column(field.to_s).public_send(dir.downcase)
@@ -1607,7 +1914,9 @@ module ActiveRecord
1607
1914
  when String, Symbol
1608
1915
  arg
1609
1916
  when Hash
1610
- arg.keys
1917
+ arg.keys.map do |key|
1918
+ key if key.is_a?(String) || key.is_a?(Symbol)
1919
+ end
1611
1920
  end
1612
1921
  end
1613
1922
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
@@ -1624,6 +1933,15 @@ module ActiveRecord
1624
1933
  end
1625
1934
  end
1626
1935
 
1936
+ def build_case_for_value_position(column, values)
1937
+ node = Arel::Nodes::Case.new
1938
+ values.each.with_index(1) do |value, order|
1939
+ node.when(column.eq(value)).then(order)
1940
+ end
1941
+
1942
+ Arel::Nodes::Ascending.new(node)
1943
+ end
1944
+
1627
1945
  def resolve_arel_attributes(attrs)
1628
1946
  attrs.flat_map do |attr|
1629
1947
  case attr
@@ -1675,6 +1993,41 @@ module ActiveRecord
1675
1993
  end
1676
1994
  end
1677
1995
 
1996
+ def process_select_args(fields)
1997
+ fields.flat_map do |field|
1998
+ if field.is_a?(Hash)
1999
+ transform_select_hash_values(field)
2000
+ else
2001
+ field
2002
+ end
2003
+ end
2004
+ end
2005
+
2006
+ def transform_select_hash_values(fields)
2007
+ fields.flat_map do |key, columns_aliases|
2008
+ case columns_aliases
2009
+ when Hash
2010
+ columns_aliases.map do |column, column_alias|
2011
+ if values[:joins]&.include?(key)
2012
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2013
+ self.references_values |= references unless references.empty?
2014
+ end
2015
+ arel_column("#{key}.#{column}") do
2016
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2017
+ end.as(column_alias.to_s)
2018
+ end
2019
+ when Array
2020
+ columns_aliases.map do |column|
2021
+ arel_column("#{key}.#{column}", &:itself)
2022
+ end
2023
+ when String, Symbol
2024
+ arel_column(key.to_s) do
2025
+ predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2026
+ end.as(columns_aliases.to_s)
2027
+ end
2028
+ end
2029
+ end
2030
+
1678
2031
  STRUCTURAL_VALUE_METHODS = (
1679
2032
  Relation::VALUE_METHODS -
1680
2033
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]