Fix cases where CTE's are not supported

CTE's are not supported for MySQL versions below 8.0, so these tests
were failing locally for me on MySQL 5.7. While 5.7 is quite old, it's
still supported and used by many major Rails applications and we need a
check for support in our tests.

In addition to this PR I'll need to get a buildkite build running for
5.7. This wasn't caught earlier because our buildkite upgraded to 8.0.
This commit is contained in:
eileencodes 2022-07-13 10:46:10 -04:00
parent 05ae3faaf3
commit 3c7e190ee8
No known key found for this signature in database
GPG Key ID: BA5C575120BBE8DF
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