Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cases where CTE's are not supported #45586

Merged
merged 1 commit into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions activerecord/lib/active_record/relation/query_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ def _select!(*fields) # :nodoc:

# 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 (
Expand Down
40 changes: 21 additions & 19 deletions activerecord/test/cases/relation/merging_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
78 changes: 43 additions & 35 deletions activerecord/test/cases/relation/with_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,58 @@ class WithTest < ActiveRecord::TestCase
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