diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 906cb644b42..3da5c2850d1 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -326,6 +326,9 @@ module ActiveRecord # Add a Common Table Expression (CTE) that you can then reference within another SELECT statement. # + # Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to + # use CTE's with MySQL 5.7. + # # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)) # # => ActiveRecord::Relation # # WITH posts_with_tags AS ( diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 84f357278af..bd5e2001496 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -394,30 +394,32 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase assert_equal dev.ratings, [rating_1] end - test "merging relation with common table expression" do - posts_with_tags = Post.with(posts_with_tags: Post.where("tags_count > 0")).from("posts_with_tags AS posts") - posts_with_comments = Post.where("legacy_comments_count > 0") - relation = posts_with_comments.merge(posts_with_tags).order("posts.id") + if ActiveRecord::Base.connection.supports_common_table_expressions? + test "merging relation with common table expression" do + posts_with_tags = Post.with(posts_with_tags: Post.where("tags_count > 0")).from("posts_with_tags AS posts") + posts_with_comments = Post.where("legacy_comments_count > 0") + relation = posts_with_comments.merge(posts_with_tags).order("posts.id") - assert_equal [1, 2, 7], relation.pluck(:id) - end + assert_equal [1, 2, 7], relation.pluck(:id) + end - test "merging multiple relations with common table expression" do - posts_with_tags = Post.with(posts_with_tags: Post.where("tags_count > 0")) - posts_with_comments = Post.with(posts_with_comments: Post.where("legacy_comments_count > 0")) - relation = posts_with_comments.merge(posts_with_tags) - .joins("JOIN posts_with_tags pwt ON pwt.id = posts.id JOIN posts_with_comments pwc ON pwc.id = posts.id").order("posts.id") + test "merging multiple relations with common table expression" do + posts_with_tags = Post.with(posts_with_tags: Post.where("tags_count > 0")) + posts_with_comments = Post.with(posts_with_comments: Post.where("legacy_comments_count > 0")) + relation = posts_with_comments.merge(posts_with_tags) + .joins("JOIN posts_with_tags pwt ON pwt.id = posts.id JOIN posts_with_comments pwc ON pwc.id = posts.id").order("posts.id") - assert_equal [1, 2, 7], relation.pluck(:id) - end + assert_equal [1, 2, 7], relation.pluck(:id) + end - test "relation merger leaves to database to decide what to do when multiple CTEs with same alias are passed" do - posts_with_tags = Post.with(popular_posts: Post.where("tags_count > 0")) - posts_with_comments = Post.with(popular_posts: Post.where("legacy_comments_count > 0")) - relation = posts_with_tags.merge(posts_with_comments).joins("JOIN popular_posts pp ON pp.id = posts.id") + test "relation merger leaves to database to decide what to do when multiple CTEs with same alias are passed" do + posts_with_tags = Post.with(popular_posts: Post.where("tags_count > 0")) + posts_with_comments = Post.with(popular_posts: Post.where("legacy_comments_count > 0")) + relation = posts_with_tags.merge(posts_with_comments).joins("JOIN popular_posts pp ON pp.id = posts.id") - assert_raises ActiveRecord::StatementInvalid do - relation.load + assert_raises ActiveRecord::StatementInvalid do + relation.load + end end end end diff --git a/activerecord/test/cases/relation/with_test.rb b/activerecord/test/cases/relation/with_test.rb index b71918b555e..c1a068436f3 100644 --- a/activerecord/test/cases/relation/with_test.rb +++ b/activerecord/test/cases/relation/with_test.rb @@ -15,50 +15,58 @@ module ActiveRecord POSTS_WITH_TAGS_AND_COMMENTS = (POSTS_WITH_COMMENTS & POSTS_WITH_TAGS).sort.freeze POSTS_WITH_TAGS_AND_MULTIPLE_COMMENTS = (POSTS_WITH_MULTIPLE_COMMENTS & POSTS_WITH_TAGS).sort.freeze - def test_with_when_hash_is_passed_as_an_argument - relation = Post - .with(posts_with_comments: Post.where("legacy_comments_count > 0")) - .from("posts_with_comments AS posts") + if ActiveRecord::Base.connection.supports_common_table_expressions? + def test_with_when_hash_is_passed_as_an_argument + relation = Post + .with(posts_with_comments: Post.where("legacy_comments_count > 0")) + .from("posts_with_comments AS posts") - assert_equal POSTS_WITH_COMMENTS, relation.order(:id).pluck(:id) - end + assert_equal POSTS_WITH_COMMENTS, relation.order(:id).pluck(:id) + end - def test_with_when_hash_with_multiple_elements_of_different_type_is_passed_as_an_argument - cte_options = { - posts_with_tags: Post.arel_table.project(Arel.star).where(Post.arel_table[:tags_count].gt(0)), - posts_with_tags_and_comments: Arel.sql("SELECT * FROM posts_with_tags WHERE legacy_comments_count > 0"), - "posts_with_tags_and_multiple_comments" => Post.where("legacy_comments_count > 1").from("posts_with_tags_and_comments AS posts") - } - relation = Post.with(cte_options).from("posts_with_tags_and_multiple_comments AS posts") + def test_with_when_hash_with_multiple_elements_of_different_type_is_passed_as_an_argument + cte_options = { + posts_with_tags: Post.arel_table.project(Arel.star).where(Post.arel_table[:tags_count].gt(0)), + posts_with_tags_and_comments: Arel.sql("SELECT * FROM posts_with_tags WHERE legacy_comments_count > 0"), + "posts_with_tags_and_multiple_comments" => Post.where("legacy_comments_count > 1").from("posts_with_tags_and_comments AS posts") + } + relation = Post.with(cte_options).from("posts_with_tags_and_multiple_comments AS posts") - assert_equal POSTS_WITH_TAGS_AND_MULTIPLE_COMMENTS, relation.order(:id).pluck(:id) - end + assert_equal POSTS_WITH_TAGS_AND_MULTIPLE_COMMENTS, relation.order(:id).pluck(:id) + end - def test_multiple_with_calls - relation = Post - .with(posts_with_tags: Post.where("tags_count > 0")) - .from("posts_with_tags_and_comments AS posts") - .with(posts_with_tags_and_comments: Arel.sql("SELECT * FROM posts_with_tags WHERE legacy_comments_count > 0")) + def test_multiple_with_calls + relation = Post + .with(posts_with_tags: Post.where("tags_count > 0")) + .from("posts_with_tags_and_comments AS posts") + .with(posts_with_tags_and_comments: Arel.sql("SELECT * FROM posts_with_tags WHERE legacy_comments_count > 0")) - assert_equal POSTS_WITH_TAGS_AND_COMMENTS, relation.order(:id).pluck(:id) - end + assert_equal POSTS_WITH_TAGS_AND_COMMENTS, relation.order(:id).pluck(:id) + end - def test_count_after_with_call - relation = Post.with(posts_with_comments: Post.where("legacy_comments_count > 0")) + def test_count_after_with_call + relation = Post.with(posts_with_comments: Post.where("legacy_comments_count > 0")) - assert_equal Post.count, relation.count - assert_equal POSTS_WITH_COMMENTS.size, relation.from("posts_with_comments AS posts").count - assert_equal POSTS_WITH_COMMENTS.size, relation.joins("JOIN posts_with_comments ON posts_with_comments.id = posts.id").count - end + assert_equal Post.count, relation.count + assert_equal POSTS_WITH_COMMENTS.size, relation.from("posts_with_comments AS posts").count + assert_equal POSTS_WITH_COMMENTS.size, relation.joins("JOIN posts_with_comments ON posts_with_comments.id = posts.id").count + end - def test_with_when_called_from_active_record_scope - assert_equal POSTS_WITH_TAGS, Post.with_tags_cte.order(:id).pluck(:id) - end + def test_with_when_called_from_active_record_scope + assert_equal POSTS_WITH_TAGS, Post.with_tags_cte.order(:id).pluck(:id) + end - def test_with_when_invalid_params_are_passed - assert_raise(ArgumentError) { Post.with } - assert_raise(ArgumentError) { Post.with(posts_with_tags: nil).load } - assert_raise(ArgumentError) { Post.with(posts_with_tags: [Post.where("tags_count > 0")]).load } + def test_with_when_invalid_params_are_passed + assert_raise(ArgumentError) { Post.with } + assert_raise(ArgumentError) { Post.with(posts_with_tags: nil).load } + assert_raise(ArgumentError) { Post.with(posts_with_tags: [Post.where("tags_count > 0")]).load } + end + else + def test_common_table_expressions_are_unsupported + assert_raises ActiveRecord::StatementInvalid do + Post.with_tags_cte.order(:id).pluck(:id) + end + end end end end