Merge pull request #45586 from eileencodes/check-for-cte-support

Fix cases where CTE's are not supported
This commit is contained in:
Eileen M. Uchitelle 2022-07-13 12:12:42 -04:00 committed by GitHub
commit e7633661b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 54 deletions

View File

@ -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 (

View File

@ -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

View File

@ -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