activerecord 7.1.3.4 → 7.2.2.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +652 -2032
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +11 -5
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +3 -3
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/has_one_association.rb +2 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  21. data/lib/active_record/associations/join_dependency.rb +10 -12
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +2 -1
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +6 -0
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +62 -289
  29. data/lib/active_record/attribute_assignment.rb +0 -2
  30. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  32. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  33. data/lib/active_record/attribute_methods/read.rb +4 -16
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  36. data/lib/active_record/attribute_methods/write.rb +3 -3
  37. data/lib/active_record/attribute_methods.rb +89 -58
  38. data/lib/active_record/attributes.rb +61 -47
  39. data/lib/active_record/autosave_association.rb +17 -31
  40. data/lib/active_record/base.rb +2 -3
  41. data/lib/active_record/callbacks.rb +1 -1
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  48. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  60. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  63. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  67. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  73. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  74. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  78. data/lib/active_record/connection_adapters.rb +121 -0
  79. data/lib/active_record/connection_handling.rb +56 -41
  80. data/lib/active_record/core.rb +93 -40
  81. data/lib/active_record/counter_cache.rb +23 -10
  82. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  83. data/lib/active_record/database_configurations/database_config.rb +19 -4
  84. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  85. data/lib/active_record/database_configurations/url_config.rb +20 -1
  86. data/lib/active_record/database_configurations.rb +1 -1
  87. data/lib/active_record/delegated_type.rb +30 -6
  88. data/lib/active_record/destroy_association_async_job.rb +1 -1
  89. data/lib/active_record/dynamic_matchers.rb +2 -2
  90. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  91. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  92. data/lib/active_record/encryption/encryptor.rb +18 -3
  93. data/lib/active_record/encryption/key_provider.rb +1 -1
  94. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  95. data/lib/active_record/encryption/message_serializer.rb +4 -0
  96. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  97. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  98. data/lib/active_record/encryption/scheme.rb +8 -4
  99. data/lib/active_record/encryption.rb +2 -0
  100. data/lib/active_record/enum.rb +19 -2
  101. data/lib/active_record/errors.rb +46 -20
  102. data/lib/active_record/explain.rb +13 -24
  103. data/lib/active_record/fixtures.rb +37 -31
  104. data/lib/active_record/future_result.rb +17 -4
  105. data/lib/active_record/gem_version.rb +3 -3
  106. data/lib/active_record/inheritance.rb +4 -2
  107. data/lib/active_record/insert_all.rb +18 -15
  108. data/lib/active_record/integration.rb +4 -1
  109. data/lib/active_record/internal_metadata.rb +48 -34
  110. data/lib/active_record/locking/optimistic.rb +8 -7
  111. data/lib/active_record/log_subscriber.rb +0 -21
  112. data/lib/active_record/marshalling.rb +4 -1
  113. data/lib/active_record/message_pack.rb +2 -2
  114. data/lib/active_record/migration/command_recorder.rb +2 -3
  115. data/lib/active_record/migration/compatibility.rb +11 -3
  116. data/lib/active_record/migration/default_strategy.rb +4 -5
  117. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  118. data/lib/active_record/migration.rb +85 -76
  119. data/lib/active_record/model_schema.rb +38 -70
  120. data/lib/active_record/nested_attributes.rb +24 -5
  121. data/lib/active_record/normalization.rb +3 -7
  122. data/lib/active_record/persistence.rb +32 -354
  123. data/lib/active_record/query_cache.rb +19 -8
  124. data/lib/active_record/query_logs.rb +15 -0
  125. data/lib/active_record/query_logs_formatter.rb +1 -1
  126. data/lib/active_record/querying.rb +21 -9
  127. data/lib/active_record/railtie.rb +50 -68
  128. data/lib/active_record/railties/controller_runtime.rb +13 -4
  129. data/lib/active_record/railties/databases.rake +42 -45
  130. data/lib/active_record/reflection.rb +106 -38
  131. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  132. data/lib/active_record/relation/batches.rb +14 -8
  133. data/lib/active_record/relation/calculations.rb +96 -63
  134. data/lib/active_record/relation/delegation.rb +8 -11
  135. data/lib/active_record/relation/finder_methods.rb +16 -2
  136. data/lib/active_record/relation/merger.rb +4 -6
  137. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  138. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  139. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  140. data/lib/active_record/relation/predicate_builder.rb +3 -3
  141. data/lib/active_record/relation/query_methods.rb +245 -65
  142. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  143. data/lib/active_record/relation/spawn_methods.rb +2 -18
  144. data/lib/active_record/relation/where_clause.rb +7 -19
  145. data/lib/active_record/relation.rb +500 -66
  146. data/lib/active_record/result.rb +32 -45
  147. data/lib/active_record/runtime_registry.rb +39 -0
  148. data/lib/active_record/sanitization.rb +24 -19
  149. data/lib/active_record/schema.rb +8 -6
  150. data/lib/active_record/schema_dumper.rb +19 -9
  151. data/lib/active_record/schema_migration.rb +30 -14
  152. data/lib/active_record/scoping/named.rb +1 -0
  153. data/lib/active_record/signed_id.rb +20 -1
  154. data/lib/active_record/statement_cache.rb +7 -7
  155. data/lib/active_record/table_metadata.rb +1 -10
  156. data/lib/active_record/tasks/database_tasks.rb +98 -48
  157. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  158. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  159. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  160. data/lib/active_record/test_fixtures.rb +87 -89
  161. data/lib/active_record/testing/query_assertions.rb +121 -0
  162. data/lib/active_record/timestamp.rb +5 -3
  163. data/lib/active_record/token_for.rb +22 -12
  164. data/lib/active_record/touch_later.rb +1 -1
  165. data/lib/active_record/transaction.rb +132 -0
  166. data/lib/active_record/transactions.rb +70 -14
  167. data/lib/active_record/translation.rb +0 -2
  168. data/lib/active_record/type/serialized.rb +1 -3
  169. data/lib/active_record/type_caster/connection.rb +4 -4
  170. data/lib/active_record/validations/associated.rb +9 -3
  171. data/lib/active_record/validations/uniqueness.rb +15 -10
  172. data/lib/active_record/validations.rb +4 -1
  173. data/lib/active_record.rb +150 -41
  174. data/lib/arel/alias_predication.rb +1 -1
  175. data/lib/arel/collectors/bind.rb +2 -0
  176. data/lib/arel/collectors/composite.rb +7 -0
  177. data/lib/arel/collectors/sql_string.rb +1 -1
  178. data/lib/arel/collectors/substitute_binds.rb +1 -1
  179. data/lib/arel/nodes/binary.rb +0 -6
  180. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  181. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  182. data/lib/arel/nodes/node.rb +4 -3
  183. data/lib/arel/nodes/sql_literal.rb +7 -0
  184. data/lib/arel/nodes.rb +2 -2
  185. data/lib/arel/predications.rb +1 -1
  186. data/lib/arel/select_manager.rb +1 -1
  187. data/lib/arel/tree_manager.rb +8 -3
  188. data/lib/arel/update_manager.rb +2 -1
  189. data/lib/arel/visitors/dot.rb +1 -0
  190. data/lib/arel/visitors/mysql.rb +9 -4
  191. data/lib/arel/visitors/postgresql.rb +1 -12
  192. data/lib/arel/visitors/sqlite.rb +25 -0
  193. data/lib/arel/visitors/to_sql.rb +31 -17
  194. data/lib/arel.rb +7 -3
  195. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  196. metadata +21 -15
@@ -72,10 +72,26 @@ module ActiveRecord
72
72
  # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
73
73
  # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
74
74
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
75
+ #
76
+ # You can define join type in the scope and +associated+ will not use `JOIN` by default.
77
+ #
78
+ # Post.left_joins(:author).where.associated(:author)
79
+ # # SELECT "posts".* FROM "posts"
80
+ # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
81
+ # # WHERE "authors"."id" IS NOT NULL
82
+ #
83
+ # Post.left_joins(:comments).where.associated(:author)
84
+ # # SELECT "posts".* FROM "posts"
85
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
86
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
87
+ # # WHERE "author"."id" IS NOT NULL
75
88
  def associated(*associations)
76
89
  associations.each do |association|
77
90
  reflection = scope_association_reflection(association)
78
- @scope.joins!(association)
91
+ unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
92
+ @scope.joins!(association)
93
+ end
94
+
79
95
  if reflection.options[:class_name]
80
96
  self.not(association => { reflection.association_primary_key => nil })
81
97
  else
@@ -157,7 +173,7 @@ module ActiveRecord
157
173
  end # end
158
174
 
159
175
  def #{method_name}=(value) # def includes_values=(value)
160
- assert_mutability! # assert_mutability!
176
+ assert_modifiable! # assert_modifiable!
161
177
  @values[:#{name}] = value # @values[:includes] = value
162
178
  end # end
163
179
  CODE
@@ -419,6 +435,17 @@ module ActiveRecord
419
435
  # # )
420
436
  # # SELECT * FROM posts
421
437
  #
438
+ # You can also pass an array of sub-queries to be joined in a +UNION ALL+.
439
+ #
440
+ # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
441
+ # # => ActiveRecord::Relation
442
+ # # WITH posts_with_tags_or_comments AS (
443
+ # # (SELECT * FROM posts WHERE (tags_count > 0))
444
+ # # UNION ALL
445
+ # # (SELECT * FROM posts WHERE (comments_count > 0))
446
+ # # )
447
+ # # SELECT * FROM posts
448
+ #
422
449
  # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
423
450
  #
424
451
  # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
@@ -457,6 +484,7 @@ module ActiveRecord
457
484
  # .with(posts_with_comments: Post.where("comments_count > ?", 0))
458
485
  # .with(posts_with_tags: Post.where("tags_count > ?", 0))
459
486
  def with(*args)
487
+ raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
460
488
  check_if_method_has_arguments!(__callee__, args)
461
489
  spawn.with!(*args)
462
490
  end
@@ -467,6 +495,30 @@ module ActiveRecord
467
495
  self
468
496
  end
469
497
 
498
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
499
+ #
500
+ # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
501
+ # # => ActiveRecord::Relation
502
+ # # WITH post_and_replies AS (
503
+ # # (SELECT * FROM posts WHERE id = 42)
504
+ # # UNION ALL
505
+ # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
506
+ # # )
507
+ # # SELECT * FROM posts
508
+ #
509
+ # See `#with` for more information.
510
+ def with_recursive(*args)
511
+ check_if_method_has_arguments!(__callee__, args)
512
+ spawn.with_recursive!(*args)
513
+ end
514
+
515
+ # Like #with_recursive but modifies the relation in place.
516
+ def with_recursive!(*args) # :nodoc:
517
+ self.with_values += args
518
+ @with_is_recursive = true
519
+ self
520
+ end
521
+
470
522
  # Allows you to change a previously set select statement.
471
523
  #
472
524
  # Post.select(:title, :body)
@@ -606,7 +658,8 @@ module ActiveRecord
606
658
  self
607
659
  end
608
660
 
609
- # Allows to specify an order by a specific set of values.
661
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
662
+ # ordered and filtered by a specific set of +values+.
610
663
  #
611
664
  # User.in_order_of(:id, [1, 5, 3])
612
665
  # # SELECT "users".* FROM "users"
@@ -617,8 +670,34 @@ module ActiveRecord
617
670
  # # WHEN "users"."id" = 3 THEN 3
618
671
  # # END ASC
619
672
  #
673
+ # +column+ can point to an enum column; the actual query generated may be different depending
674
+ # on the database adapter and the column definition.
675
+ #
676
+ # class Conversation < ActiveRecord::Base
677
+ # enum :status, [ :active, :archived ]
678
+ # end
679
+ #
680
+ # Conversation.in_order_of(:status, [:archived, :active])
681
+ # # SELECT "conversations".* FROM "conversations"
682
+ # # WHERE "conversations"."status" IN (1, 0)
683
+ # # ORDER BY CASE
684
+ # # WHEN "conversations"."status" = 1 THEN 1
685
+ # # WHEN "conversations"."status" = 0 THEN 2
686
+ # # END ASC
687
+ #
688
+ # +values+ can also include +nil+.
689
+ #
690
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
691
+ # # SELECT "conversations".* FROM "conversations"
692
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
693
+ # # ORDER BY CASE
694
+ # # WHEN "conversations"."status" IS NULL THEN 1
695
+ # # WHEN "conversations"."status" = 1 THEN 2
696
+ # # WHEN "conversations"."status" = 0 THEN 3
697
+ # # END ASC
698
+ #
620
699
  def in_order_of(column, values)
621
- klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
700
+ klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
622
701
  return spawn.none! if values.empty?
623
702
 
624
703
  references = column_references([column])
@@ -667,7 +746,7 @@ module ActiveRecord
667
746
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
668
747
  :limit, :offset, :joins, :left_outer_joins, :annotate,
669
748
  :includes, :eager_load, :preload, :from, :readonly,
670
- :having, :optimizer_hints])
749
+ :having, :optimizer_hints, :with])
671
750
 
672
751
  # Removes an unwanted relation that is already defined on a chain of relations.
673
752
  # This is useful when passing around chains of relations and would like to
@@ -717,7 +796,7 @@ module ActiveRecord
717
796
  if !VALID_UNSCOPING_VALUES.include?(scope)
718
797
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
719
798
  end
720
- assert_mutability!
799
+ assert_modifiable!
721
800
  @values.delete(scope)
722
801
  when Hash
723
802
  scope.each do |key, target_value|
@@ -1082,7 +1161,7 @@ module ActiveRecord
1082
1161
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
1083
1162
  end
1084
1163
 
1085
- self.where_clause = self.where_clause.or(other.where_clause)
1164
+ self.where_clause = where_clause.or(other.where_clause)
1086
1165
  self.having_clause = having_clause.or(other.having_clause)
1087
1166
  self.references_values |= other.references_values
1088
1167
 
@@ -1453,6 +1532,9 @@ module ActiveRecord
1453
1532
  # Post.excluding(post_one, post_two)
1454
1533
  # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1455
1534
  #
1535
+ # Post.excluding(Post.drafts)
1536
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1537
+ #
1456
1538
  # This can also be called on associations. As with the above example, either
1457
1539
  # a single record of collection thereof may be specified:
1458
1540
  #
@@ -1468,14 +1550,15 @@ module ActiveRecord
1468
1550
  # is passed in) are not instances of the same model that the relation is
1469
1551
  # scoping.
1470
1552
  def excluding(*records)
1553
+ relations = records.extract! { |element| element.is_a?(Relation) }
1471
1554
  records.flatten!(1)
1472
1555
  records.compact!
1473
1556
 
1474
- unless records.all?(klass)
1557
+ unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1475
1558
  raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1476
1559
  end
1477
1560
 
1478
- spawn.excluding!(records)
1561
+ spawn.excluding!(records + relations.flat_map(&:ids))
1479
1562
  end
1480
1563
  alias :without :excluding
1481
1564
 
@@ -1487,7 +1570,7 @@ module ActiveRecord
1487
1570
 
1488
1571
  # Returns the Arel object associated with the relation.
1489
1572
  def arel(aliases = nil) # :nodoc:
1490
- @arel ||= build_arel(aliases)
1573
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1491
1574
  end
1492
1575
 
1493
1576
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1508,9 +1591,21 @@ module ActiveRecord
1508
1591
  def build_where_clause(opts, rest = []) # :nodoc:
1509
1592
  opts = sanitize_forbidden_attributes(opts)
1510
1593
 
1594
+ if opts.is_a?(Array)
1595
+ opts, *rest = opts
1596
+ end
1597
+
1511
1598
  case opts
1512
- when String, Array
1513
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1599
+ when String
1600
+ if rest.empty?
1601
+ parts = [Arel.sql(opts)]
1602
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1603
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1604
+ elsif opts.include?("?")
1605
+ parts = [build_bound_sql_literal(opts, rest)]
1606
+ else
1607
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1608
+ end
1514
1609
  when Hash
1515
1610
  opts = opts.transform_keys do |key|
1516
1611
  if key.is_a?(Array)
@@ -1541,11 +1636,71 @@ module ActiveRecord
1541
1636
  self
1542
1637
  end
1543
1638
 
1639
+ protected
1640
+ def arel_columns(columns)
1641
+ columns.flat_map do |field|
1642
+ case field
1643
+ when Symbol
1644
+ arel_column(field.to_s) do |attr_name|
1645
+ adapter_class.quote_table_name(attr_name)
1646
+ end
1647
+ when String
1648
+ arel_column(field, &:itself)
1649
+ when Proc
1650
+ field.call
1651
+ when Hash
1652
+ arel_columns_from_hash(field)
1653
+ else
1654
+ field
1655
+ end
1656
+ end
1657
+ end
1658
+
1544
1659
  private
1545
1660
  def async
1546
1661
  spawn.async!
1547
1662
  end
1548
1663
 
1664
+ def build_named_bound_sql_literal(statement, values)
1665
+ bound_values = values.transform_values do |value|
1666
+ if ActiveRecord::Relation === value
1667
+ Arel.sql(value.to_sql)
1668
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1669
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1670
+ values.empty? ? nil : values
1671
+ else
1672
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1673
+ value
1674
+ end
1675
+ end
1676
+
1677
+ begin
1678
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1679
+ rescue Arel::BindError => error
1680
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1681
+ end
1682
+ end
1683
+
1684
+ def build_bound_sql_literal(statement, values)
1685
+ bound_values = values.map do |value|
1686
+ if ActiveRecord::Relation === value
1687
+ Arel.sql(value.to_sql)
1688
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1689
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1690
+ values.empty? ? nil : values
1691
+ else
1692
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1693
+ value
1694
+ end
1695
+ end
1696
+
1697
+ begin
1698
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1699
+ rescue Arel::BindError => error
1700
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1701
+ end
1702
+ end
1703
+
1549
1704
  def lookup_table_klass_from_join_dependencies(table_name)
1550
1705
  each_join_dependencies do |join|
1551
1706
  return join.base_klass if table_name == join.table_name
@@ -1570,12 +1725,11 @@ module ActiveRecord
1570
1725
  )
1571
1726
  end
1572
1727
 
1573
- def assert_mutability!
1574
- raise ImmutableRelation if @loaded
1575
- raise ImmutableRelation if defined?(@arel) && @arel
1728
+ def assert_modifiable!
1729
+ raise UnmodifiableRelation if @loaded || @arel
1576
1730
  end
1577
1731
 
1578
- def build_arel(aliases = nil)
1732
+ def build_arel(connection, aliases = nil)
1579
1733
  arel = Arel::SelectManager.new(table)
1580
1734
 
1581
1735
  build_joins(arel.join_sources, aliases)
@@ -1747,20 +1901,37 @@ module ActiveRecord
1747
1901
  build_with_value_from_hash(with_value)
1748
1902
  end
1749
1903
 
1750
- arel.with(with_statements)
1904
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1751
1905
  end
1752
1906
 
1753
1907
  def build_with_value_from_hash(hash)
1754
1908
  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)
1909
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1910
+ end
1911
+ end
1912
+
1913
+ def build_with_expression_from_value(value, nested = false)
1914
+ case value
1915
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1916
+ when ActiveRecord::Relation
1917
+ if nested
1918
+ value.arel.ast
1919
+ else
1920
+ value.arel
1921
+ end
1922
+ when Arel::SelectManager then value
1923
+ when Array
1924
+ return build_with_expression_from_value(value.first, false) if value.size == 1
1925
+
1926
+ parts = value.map do |query|
1927
+ build_with_expression_from_value(query, true)
1928
+ end
1929
+
1930
+ parts.reduce do |result, value|
1931
+ Arel::Nodes::UnionAll.new(result, value)
1932
+ end
1933
+ else
1934
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1764
1935
  end
1765
1936
  end
1766
1937
 
@@ -1772,31 +1943,14 @@ module ActiveRecord
1772
1943
  ).join_sources.first
1773
1944
  end
1774
1945
 
1775
- def arel_columns(columns)
1776
- columns.flat_map do |field|
1777
- case field
1778
- when Symbol
1779
- arel_column(field.to_s) do |attr_name|
1780
- connection.quote_table_name(attr_name)
1781
- end
1782
- when String
1783
- arel_column(field, &:itself)
1784
- when Proc
1785
- field.call
1786
- else
1787
- field
1788
- end
1789
- end
1790
- end
1791
-
1792
1946
  def arel_column(field)
1793
1947
  field = klass.attribute_aliases[field] || field
1794
1948
  from = from_clause.name || from_clause.value
1795
1949
 
1796
1950
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1797
1951
  table[field]
1798
- elsif field.match?(/\A\w+\.\w+\z/)
1799
- table, column = field.split(".")
1952
+ elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1953
+ self.references_values |= [Arel.sql(table, retryable: true)]
1800
1954
  predicate_builder.resolve_arel_attribute(table, column) do
1801
1955
  lookup_table_klass_from_join_dependencies(table)
1802
1956
  end
@@ -1807,7 +1961,7 @@ module ActiveRecord
1807
1961
 
1808
1962
  def table_name_matches?(from)
1809
1963
  table_name = Regexp.escape(table.name)
1810
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1964
+ quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
1811
1965
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1812
1966
  end
1813
1967
 
@@ -1863,7 +2017,9 @@ module ActiveRecord
1863
2017
  args.each do |arg|
1864
2018
  next unless arg.is_a?(Hash)
1865
2019
  arg.each do |_key, value|
1866
- unless VALID_DIRECTIONS.include?(value)
2020
+ if value.is_a?(Hash)
2021
+ validate_order_args([value])
2022
+ elsif VALID_DIRECTIONS.exclude?(value)
1867
2023
  raise ArgumentError,
1868
2024
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1869
2025
  end
@@ -1871,10 +2027,14 @@ module ActiveRecord
1871
2027
  end
1872
2028
  end
1873
2029
 
2030
+ def flattened_args(args)
2031
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2032
+ end
2033
+
1874
2034
  def preprocess_order_args(order_args)
1875
2035
  @klass.disallow_raw_sql!(
1876
- order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1877
- permit: connection.column_name_with_order_matcher
2036
+ flattened_args(order_args),
2037
+ permit: model.adapter_class.column_name_with_order_matcher
1878
2038
  )
1879
2039
 
1880
2040
  validate_order_args(order_args)
@@ -1888,14 +2048,20 @@ module ActiveRecord
1888
2048
  when Symbol
1889
2049
  order_column(arg.to_s).asc
1890
2050
  when Hash
1891
- arg.map { |field, dir|
1892
- case field
1893
- when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1894
- field.public_send(dir.downcase)
2051
+ arg.map do |key, value|
2052
+ if value.is_a?(Hash)
2053
+ value.map do |field, dir|
2054
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2055
+ end
1895
2056
  else
1896
- order_column(field.to_s).public_send(dir.downcase)
2057
+ case key
2058
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2059
+ key.public_send(value.downcase)
2060
+ else
2061
+ order_column(key.to_s).public_send(value.downcase)
2062
+ end
1897
2063
  end
1898
- }
2064
+ end
1899
2065
  else
1900
2066
  arg
1901
2067
  end
@@ -1909,18 +2075,32 @@ module ActiveRecord
1909
2075
  end
1910
2076
 
1911
2077
  def column_references(order_args)
1912
- references = order_args.flat_map do |arg|
2078
+ order_args.flat_map do |arg|
1913
2079
  case arg
1914
2080
  when String, Symbol
1915
- arg
2081
+ extract_table_name_from(arg)
1916
2082
  when Hash
1917
- arg.keys.map do |key|
1918
- key if key.is_a?(String) || key.is_a?(Symbol)
2083
+ arg
2084
+ .map do |key, value|
2085
+ case value
2086
+ when Hash
2087
+ key.to_s
2088
+ else
2089
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
2090
+ end
2091
+ end
2092
+ when Arel::Attribute
2093
+ arg.relation.name
2094
+ when Arel::Nodes::Ordering
2095
+ if arg.expr.is_a?(Arel::Attribute)
2096
+ arg.expr.relation.name
1919
2097
  end
1920
2098
  end
1921
- end
1922
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1923
- references
2099
+ end.compact
2100
+ end
2101
+
2102
+ def extract_table_name_from(string)
2103
+ string.match(/^\W?(\w+)\W?\./) && $1
1924
2104
  end
1925
2105
 
1926
2106
  def order_column(field)
@@ -1928,7 +2108,7 @@ module ActiveRecord
1928
2108
  if attr_name == "count" && !group_values.empty?
1929
2109
  table[attr_name]
1930
2110
  else
1931
- Arel.sql(connection.quote_table_name(attr_name))
2111
+ Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
1932
2112
  end
1933
2113
  end
1934
2114
  end
@@ -1996,14 +2176,14 @@ module ActiveRecord
1996
2176
  def process_select_args(fields)
1997
2177
  fields.flat_map do |field|
1998
2178
  if field.is_a?(Hash)
1999
- transform_select_hash_values(field)
2179
+ arel_columns_from_hash(field)
2000
2180
  else
2001
2181
  field
2002
2182
  end
2003
2183
  end
2004
2184
  end
2005
2185
 
2006
- def transform_select_hash_values(fields)
2186
+ def arel_columns_from_hash(fields)
2007
2187
  fields.flat_map do |key, columns_aliases|
2008
2188
  case columns_aliases
2009
2189
  when Hash
@@ -3,6 +3,9 @@
3
3
  module ActiveRecord
4
4
  class Relation
5
5
  module RecordFetchWarning
6
+ # Deprecated: subscribe to sql.active_record notifications and
7
+ # access the row count field to detect large result set sizes.
8
+ #
6
9
  # When this module is prepended to ActiveRecord::Relation and
7
10
  # +config.active_record.warn_on_records_fetched_greater_than+ is
8
11
  # set to an integer, if the number of records a query returns is
@@ -41,26 +41,10 @@ module ActiveRecord
41
41
  end
42
42
 
43
43
  def merge!(other, *rest) # :nodoc:
44
- options = rest.extract_options!
45
-
46
- if options.key?(:rewhere)
47
- if options[:rewhere]
48
- ActiveRecord.deprecator.warn(<<-MSG.squish)
49
- Specifying `Relation#merge(rewhere: true)` is deprecated, as that has now been
50
- the default since Rails 7.0. Setting the rewhere option will error in Rails 7.2
51
- MSG
52
- else
53
- ActiveRecord.deprecator.warn(<<-MSG.squish)
54
- `Relation#merge(rewhere: false)` is deprecated without replacement,
55
- and will be removed in Rails 7.2
56
- MSG
57
- end
58
- end
59
-
60
44
  if other.is_a?(Hash)
61
- Relation::HashMerger.new(self, other, options[:rewhere]).merge
45
+ Relation::HashMerger.new(self, other).merge
62
46
  elsif other.is_a?(Relation)
63
- Relation::Merger.new(self, other, options[:rewhere]).merge
47
+ Relation::Merger.new(self, other).merge
64
48
  elsif other.respond_to?(:to_proc)
65
49
  instance_exec(&other)
66
50
  else
@@ -23,12 +23,8 @@ module ActiveRecord
23
23
  WhereClause.new(predicates | other.predicates)
24
24
  end
25
25
 
26
- def merge(other, rewhere = nil)
27
- predicates = if rewhere
28
- except_predicates(other.extract_attributes)
29
- else
30
- predicates_unreferenced_by(other)
31
- end
26
+ def merge(other)
27
+ predicates = except_predicates(other.extract_attributes)
32
28
 
33
29
  WhereClause.new(predicates | other.predicates)
34
30
  end
@@ -51,7 +47,11 @@ module ActiveRecord
51
47
  right = right.ast
52
48
  right = right.expr if right.is_a?(Arel::Nodes::Grouping)
53
49
 
54
- or_clause = Arel::Nodes::Or.new(left, right)
50
+ or_clause = if left.is_a?(Arel::Nodes::Or)
51
+ Arel::Nodes::Or.new(left.children + [right])
52
+ else
53
+ Arel::Nodes::Or.new([left, right])
54
+ end
55
55
 
56
56
  common.predicates << Arel::Nodes::Grouping.new(or_clause)
57
57
  common
@@ -156,18 +156,6 @@ module ActiveRecord
156
156
  equalities
157
157
  end
158
158
 
159
- def predicates_unreferenced_by(other)
160
- referenced_columns = other.referenced_columns
161
-
162
- predicates.reject do |node|
163
- attr = extract_attribute(node) || begin
164
- node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
165
- end
166
-
167
- attr && referenced_columns[attr]
168
- end
169
- end
170
-
171
159
  def equality_node?(node)
172
160
  !node.is_a?(String) && node.equality?
173
161
  end