diff options
author | Matt Yoho <mby@mattyoho.com> | 2019-03-14 12:57:14 -0700 |
---|---|---|
committer | Matt Yoho <mby@mattyoho.com> | 2019-03-21 20:30:56 -0700 |
commit | f41825809cf3d27f98467bd7bc005e5d6ed94828 (patch) | |
tree | c6a7f1bc8670bffc2c20bc71401b0b58d621d4d1 /activerecord/test/cases/relation | |
parent | efb706daad0e2e1039c6abb4879c837ef8bf4d10 (diff) | |
download | rails-f41825809cf3d27f98467bd7bc005e5d6ed94828.tar.gz rails-f41825809cf3d27f98467bd7bc005e5d6ed94828.tar.bz2 rails-f41825809cf3d27f98467bd7bc005e5d6ed94828.zip |
Add Relation#annotate for SQL commenting
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.
Diffstat (limited to 'activerecord/test/cases/relation')
-rw-r--r-- | activerecord/test/cases/relation/delete_all_test.rb | 19 | ||||
-rw-r--r-- | activerecord/test/cases/relation/merging_test.rb | 12 | ||||
-rw-r--r-- | activerecord/test/cases/relation/update_all_test.rb | 27 |
3 files changed, 58 insertions, 0 deletions
diff --git a/activerecord/test/cases/relation/delete_all_test.rb b/activerecord/test/cases/relation/delete_all_test.rb index d1c13fa1b5..9b76936b7e 100644 --- a/activerecord/test/cases/relation/delete_all_test.rb +++ b/activerecord/test/cases/relation/delete_all_test.rb @@ -99,4 +99,23 @@ class DeleteAllTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) } assert posts(:welcome) end + + def test_delete_all_with_annotation_includes_a_query_comment + davids = Author.where(name: "David").annotate("deleting all") + + assert_sql(%r{/\* deleting all \*/}) do + assert_difference("Author.count", -1) { davids.delete_all } + end + end + + def test_delete_all_without_annotation_does_not_include_an_empty_comment + davids = Author.where(name: "David") + + log = capture_sql do + assert_difference("Author.count", -1) { davids.delete_all } + end + + assert_not_predicate log, :empty? + assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty? + end end diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 224e4f39a8..5c5e760e34 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -135,6 +135,18 @@ class RelationMergingTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/cases/relation/update_all_test.rb b/activerecord/test/cases/relation/update_all_test.rb index 0500574f28..526f926841 100644 --- a/activerecord/test/cases/relation/update_all_test.rb +++ b/activerecord/test/cases/relation/update_all_test.rb @@ -241,6 +241,33 @@ class UpdateAllTest < ActiveRecord::TestCase end end + def test_update_all_with_annotation_includes_a_query_comment + tag = Tag.first + + assert_sql(%r{/\* updating all \*/}) do + Post.tagged_with(tag.id).annotate("updating all").update_all(title: "rofl") + end + + posts = Post.tagged_with(tag.id).all.to_a + assert_operator posts.length, :>, 0 + posts.each { |post| assert_equal "rofl", post.title } + end + + def test_update_all_without_annotation_does_not_include_an_empty_comment + tag = Tag.first + + log = capture_sql do + Post.tagged_with(tag.id).update_all(title: "rofl") + end + + assert_not_predicate log, :empty? + assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty? + + posts = Post.tagged_with(tag.id).all.to_a + assert_operator posts.length, :>, 0 + posts.each { |post| assert_equal "rofl", post.title } + end + # Oracle UPDATE does not support ORDER BY unless current_adapter?(:OracleAdapter) def test_update_all_ignores_order_without_limit_from_association |