mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
f41825809c
This patch has two main portions: 1. Add SQL comment support to Arel via Arel::Nodes::Comment. 2. Implement a Relation#annotate method on top of that. == Adding SQL comment support Adds a new Arel::Nodes::Comment node that represents an optional SQL comment and teachers the relevant visitors how to handle it. Comment nodes may be added to the basic CRUD statement nodes and set through any of the four (Select|Insert|Update|Delete)Manager objects. For example: manager = Arel::UpdateManager.new manager.table table manager.comment("annotation") manager.to_sql # UPDATE "users" /* annotation */ This new node type will be used by ActiveRecord::Relation to enable query annotation via SQL comments. == Implementing the Relation#annotate method Implements `ActiveRecord::Relation#annotate`, which accepts a comment string that will be appeneded to any queries generated by the relation. Some examples: relation = Post.where(id: 123).annotate("metadata string") relation.first # SELECT "posts".* FROM "posts" WHERE "posts"."id" = 123 # LIMIT 1 /* metadata string */ class Tag < ActiveRecord::Base scope :foo_annotated, -> { annotate("foo") } end Tag.foo_annotated.annotate("bar").first # SELECT "tags".* FROM "tags" LIMIT 1 /* foo */ /* bar */ Also wires up the plumbing so this works with `#update_all` and `#delete_all` as well. This feature is useful for instrumentation and general analysis of queries generated at runtime.
193 lines
6.6 KiB
Ruby
193 lines
6.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "cases/helper"
|
|
require "models/author"
|
|
require "models/comment"
|
|
require "models/developer"
|
|
require "models/computer"
|
|
require "models/post"
|
|
require "models/project"
|
|
require "models/rating"
|
|
|
|
class RelationMergingTest < ActiveRecord::TestCase
|
|
fixtures :developers, :comments, :authors, :author_addresses, :posts
|
|
|
|
def test_relation_merging
|
|
devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3"))
|
|
assert_equal [developers(:david), developers(:jamis)], devs.to_a
|
|
|
|
dev_with_count = Developer.limit(1).merge(Developer.order("id DESC")).merge(Developer.select("developers.*"))
|
|
assert_equal [developers(:poor_jamis)], dev_with_count.to_a
|
|
end
|
|
|
|
def test_relation_to_sql
|
|
post = Post.first
|
|
sql = post.comments.to_sql
|
|
assert_match(/.?post_id.? = #{post.id}\z/i, sql)
|
|
end
|
|
|
|
def test_relation_merging_with_arel_equalities_keeps_last_equality
|
|
devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge(
|
|
Developer.where(Developer.arel_table[:salary].eq(9000))
|
|
)
|
|
assert_equal [developers(:poor_jamis)], devs.to_a
|
|
end
|
|
|
|
def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand
|
|
salary_attr = Developer.arel_table[:salary]
|
|
devs = Developer.where(
|
|
Arel::Nodes::NamedFunction.new("abs", [salary_attr]).eq(80000)
|
|
).merge(
|
|
Developer.where(
|
|
Arel::Nodes::NamedFunction.new("abs", [salary_attr]).eq(9000)
|
|
)
|
|
)
|
|
assert_equal [developers(:poor_jamis)], devs.to_a
|
|
end
|
|
|
|
def test_relation_merging_with_eager_load
|
|
relations = []
|
|
relations << Post.order("comments.id DESC").merge(Post.eager_load(:last_comment)).merge(Post.all)
|
|
relations << Post.eager_load(:last_comment).merge(Post.order("comments.id DESC")).merge(Post.all)
|
|
|
|
relations.each do |posts|
|
|
post = posts.find { |p| p.id == 1 }
|
|
assert_equal Post.find(1).last_comment, post.last_comment
|
|
end
|
|
end
|
|
|
|
def test_relation_merging_with_locks
|
|
devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2))
|
|
assert_predicate devs, :locked?
|
|
end
|
|
|
|
def test_relation_merging_with_preload
|
|
[Post.all.merge(Post.preload(:author)), Post.preload(:author).merge(Post.all)].each do |posts|
|
|
assert_queries(2) { assert posts.first.author }
|
|
end
|
|
end
|
|
|
|
def test_relation_merging_with_joins
|
|
comments = Comment.joins(:post).where(body: "Thank you for the welcome").merge(Post.where(body: "Such a lovely day"))
|
|
assert_equal 1, comments.count
|
|
end
|
|
|
|
def test_relation_merging_with_left_outer_joins
|
|
comments = Comment.joins(:post).where(body: "Thank you for the welcome").merge(Post.left_outer_joins(:author).where(body: "Such a lovely day"))
|
|
|
|
assert_equal 1, comments.count
|
|
end
|
|
|
|
def test_relation_merging_with_skip_query_cache
|
|
assert_equal Post.all.merge(Post.all.skip_query_cache!).skip_query_cache_value, true
|
|
end
|
|
|
|
def test_relation_merging_with_association
|
|
assert_queries(2) do # one for loading post, and another one merged query
|
|
post = Post.where(body: "Such a lovely day").first
|
|
comments = Comment.where(body: "Thank you for the welcome").merge(post.comments)
|
|
assert_equal 1, comments.count
|
|
end
|
|
end
|
|
|
|
test "merge collapses wheres from the LHS only" do
|
|
left = Post.where(title: "omg").where(comments_count: 1)
|
|
right = Post.where(title: "wtf").where(title: "bbq")
|
|
|
|
merged = left.merge(right)
|
|
|
|
assert_not_includes merged.to_sql, "omg"
|
|
assert_includes merged.to_sql, "wtf"
|
|
assert_includes merged.to_sql, "bbq"
|
|
end
|
|
|
|
def test_merging_reorders_bind_params
|
|
post = Post.first
|
|
right = Post.where(id: 1)
|
|
left = Post.where(title: post.title)
|
|
|
|
merged = left.merge(right)
|
|
assert_equal post, merged.first
|
|
end
|
|
|
|
def test_merging_compares_symbols_and_strings_as_equal
|
|
post = PostThatLoadsCommentsInAnAfterSaveHook.create!(title: "First Post", body: "Blah blah blah.")
|
|
assert_equal "First comment!", post.comments.where(body: "First comment!").first_or_create.body
|
|
end
|
|
|
|
def test_merging_with_from_clause
|
|
relation = Post.all
|
|
assert_empty relation.from_clause
|
|
relation = relation.merge(Post.from("posts"))
|
|
assert_not_empty relation.from_clause
|
|
end
|
|
|
|
def test_merging_with_from_clause_on_different_class
|
|
assert Comment.joins(:post).merge(Post.from("posts")).first
|
|
end
|
|
|
|
def test_merging_with_order_with_binds
|
|
relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
|
|
assert_equal ["title LIKE '%suffix'"], relation.order_values
|
|
end
|
|
|
|
def test_merging_with_order_without_binds
|
|
relation = Post.all.merge(Post.order(Arel.sql("title LIKE '%?'")))
|
|
assert_equal ["title LIKE '%?'"], relation.order_values
|
|
end
|
|
|
|
def test_merging_annotations_respects_merge_order
|
|
assert_sql(%r{/\* foo \*/ /\* bar \*/}) do
|
|
Post.annotate("foo").merge(Post.annotate("bar")).first
|
|
end
|
|
assert_sql(%r{/\* bar \*/ /\* foo \*/}) do
|
|
Post.annotate("bar").merge(Post.annotate("foo")).first
|
|
end
|
|
assert_sql(%r{/\* foo \*/ /\* bar \*/ /\* baz \*/ /\* qux \*/}) do
|
|
Post.annotate("foo").annotate("bar").merge(Post.annotate("baz").annotate("qux")).first
|
|
end
|
|
end
|
|
end
|
|
|
|
class MergingDifferentRelationsTest < ActiveRecord::TestCase
|
|
fixtures :posts, :authors, :author_addresses, :developers
|
|
|
|
test "merging where relations" do
|
|
hello_by_bob = Post.where(body: "hello").joins(:author).
|
|
merge(Author.where(name: "Bob")).order("posts.id").pluck("posts.id")
|
|
|
|
assert_equal [posts(:misc_by_bob).id,
|
|
posts(:other_by_bob).id], hello_by_bob
|
|
end
|
|
|
|
test "merging order relations" do
|
|
posts_by_author_name = Post.limit(3).joins(:author).
|
|
merge(Author.order(:name)).pluck("authors.name")
|
|
|
|
assert_equal ["Bob", "Bob", "David"], posts_by_author_name
|
|
|
|
posts_by_author_name = Post.limit(3).joins(:author).
|
|
merge(Author.order("name")).pluck("authors.name")
|
|
|
|
assert_equal ["Bob", "Bob", "David"], posts_by_author_name
|
|
end
|
|
|
|
test "merging order relations (using a hash argument)" do
|
|
posts_by_author_name = Post.limit(4).joins(:author).
|
|
merge(Author.order(name: :desc)).pluck("authors.name")
|
|
|
|
assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name
|
|
end
|
|
|
|
test "relation merging (using a proc argument)" do
|
|
dev = Developer.where(name: "Jamis").first
|
|
|
|
comment_1 = dev.comments.create!(body: "I'm Jamis", post: Post.first)
|
|
rating_1 = comment_1.ratings.create!
|
|
|
|
comment_2 = dev.comments.create!(body: "I'm John", post: Post.first)
|
|
comment_2.ratings.create!
|
|
|
|
assert_equal dev.ratings, [rating_1]
|
|
end
|
|
end
|