aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/arel
diff options
context:
space:
mode:
authorMatt Yoho <mby@mattyoho.com>2019-03-14 12:57:14 -0700
committerMatt Yoho <mby@mattyoho.com>2019-03-21 20:30:56 -0700
commitf41825809cf3d27f98467bd7bc005e5d6ed94828 (patch)
treec6a7f1bc8670bffc2c20bc71401b0b58d621d4d1 /activerecord/lib/arel
parentefb706daad0e2e1039c6abb4879c837ef8bf4d10 (diff)
downloadrails-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/lib/arel')
-rw-r--r--activerecord/lib/arel/nodes.rb2
-rw-r--r--activerecord/lib/arel/nodes/comment.rb29
-rw-r--r--activerecord/lib/arel/nodes/delete_statement.rb9
-rw-r--r--activerecord/lib/arel/nodes/insert_statement.rb9
-rw-r--r--activerecord/lib/arel/nodes/select_core.rb9
-rw-r--r--activerecord/lib/arel/nodes/update_statement.rb9
-rw-r--r--activerecord/lib/arel/select_manager.rb9
-rw-r--r--activerecord/lib/arel/tree_manager.rb5
-rw-r--r--activerecord/lib/arel/visitors/depth_first.rb4
-rw-r--r--activerecord/lib/arel/visitors/dot.rb4
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb12
11 files changed, 86 insertions, 15 deletions
diff --git a/activerecord/lib/arel/nodes.rb b/activerecord/lib/arel/nodes.rb
index 2f6dd9bc45..f994754620 100644
--- a/activerecord/lib/arel/nodes.rb
+++ b/activerecord/lib/arel/nodes.rb
@@ -61,6 +61,8 @@ require "arel/nodes/outer_join"
require "arel/nodes/right_outer_join"
require "arel/nodes/string_join"
+require "arel/nodes/comment"
+
require "arel/nodes/sql_literal"
require "arel/nodes/casted"
diff --git a/activerecord/lib/arel/nodes/comment.rb b/activerecord/lib/arel/nodes/comment.rb
new file mode 100644
index 0000000000..237ff27e7e
--- /dev/null
+++ b/activerecord/lib/arel/nodes/comment.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Arel # :nodoc: all
+ module Nodes
+ class Comment < Arel::Nodes::Node
+ attr_reader :values
+
+ def initialize(values)
+ super()
+ @values = values
+ end
+
+ def initialize_copy(other)
+ super
+ @values = @values.clone
+ end
+
+ def hash
+ [@values].hash
+ end
+
+ def eql?(other)
+ self.class == other.class &&
+ self.values == other.values
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/delete_statement.rb b/activerecord/lib/arel/nodes/delete_statement.rb
index a419975335..56249b2bad 100644
--- a/activerecord/lib/arel/nodes/delete_statement.rb
+++ b/activerecord/lib/arel/nodes/delete_statement.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class DeleteStatement < Arel::Nodes::Node
- attr_accessor :left, :right, :orders, :limit, :offset, :key
+ attr_accessor :left, :right, :orders, :limit, :offset, :key, :comment
alias :relation :left
alias :relation= :left=
@@ -18,16 +18,18 @@ module Arel # :nodoc: all
@limit = nil
@offset = nil
@key = nil
+ @comment = nil
end
def initialize_copy(other)
super
@left = @left.clone if @left
@right = @right.clone if @right
+ @comment = @comment.clone if @comment
end
def hash
- [self.class, @left, @right, @orders, @limit, @offset, @key].hash
+ [self.class, @left, @right, @orders, @limit, @offset, @key, @comment].hash
end
def eql?(other)
@@ -37,7 +39,8 @@ module Arel # :nodoc: all
self.orders == other.orders &&
self.limit == other.limit &&
self.offset == other.offset &&
- self.key == other.key
+ self.key == other.key &&
+ self.comment == other.comment
end
alias :== :eql?
end
diff --git a/activerecord/lib/arel/nodes/insert_statement.rb b/activerecord/lib/arel/nodes/insert_statement.rb
index d28fd1f6c8..8430dd23da 100644
--- a/activerecord/lib/arel/nodes/insert_statement.rb
+++ b/activerecord/lib/arel/nodes/insert_statement.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class InsertStatement < Arel::Nodes::Node
- attr_accessor :relation, :columns, :values, :select
+ attr_accessor :relation, :columns, :values, :select, :comment
def initialize
super()
@@ -11,6 +11,7 @@ module Arel # :nodoc: all
@columns = []
@values = nil
@select = nil
+ @comment = nil
end
def initialize_copy(other)
@@ -18,10 +19,11 @@ module Arel # :nodoc: all
@columns = @columns.clone
@values = @values.clone if @values
@select = @select.clone if @select
+ @comment = @comment.clone if @comment
end
def hash
- [@relation, @columns, @values, @select].hash
+ [@relation, @columns, @values, @select, @comment].hash
end
def eql?(other)
@@ -29,7 +31,8 @@ module Arel # :nodoc: all
self.relation == other.relation &&
self.columns == other.columns &&
self.select == other.select &&
- self.values == other.values
+ self.values == other.values &&
+ self.comment == other.comment
end
alias :== :eql?
end
diff --git a/activerecord/lib/arel/nodes/select_core.rb b/activerecord/lib/arel/nodes/select_core.rb
index 5df6ac8412..b6154b7ff4 100644
--- a/activerecord/lib/arel/nodes/select_core.rb
+++ b/activerecord/lib/arel/nodes/select_core.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class SelectCore < Arel::Nodes::Node
- attr_accessor :projections, :wheres, :groups, :windows
+ attr_accessor :projections, :wheres, :groups, :windows, :comment
attr_accessor :havings, :source, :set_quantifier, :optimizer_hints
def initialize
@@ -18,6 +18,7 @@ module Arel # :nodoc: all
@groups = []
@havings = []
@windows = []
+ @comment = nil
end
def from
@@ -39,12 +40,13 @@ module Arel # :nodoc: all
@groups = @groups.clone
@havings = @havings.clone
@windows = @windows.clone
+ @comment = @comment.clone if @comment
end
def hash
[
@source, @set_quantifier, @projections, @optimizer_hints,
- @wheres, @groups, @havings, @windows
+ @wheres, @groups, @havings, @windows, @comment
].hash
end
@@ -57,7 +59,8 @@ module Arel # :nodoc: all
self.wheres == other.wheres &&
self.groups == other.groups &&
self.havings == other.havings &&
- self.windows == other.windows
+ self.windows == other.windows &&
+ self.comment == other.comment
end
alias :== :eql?
end
diff --git a/activerecord/lib/arel/nodes/update_statement.rb b/activerecord/lib/arel/nodes/update_statement.rb
index cfaa19e392..015bcd7613 100644
--- a/activerecord/lib/arel/nodes/update_statement.rb
+++ b/activerecord/lib/arel/nodes/update_statement.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class UpdateStatement < Arel::Nodes::Node
- attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key
+ attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key, :comment
def initialize
@relation = nil
@@ -13,16 +13,18 @@ module Arel # :nodoc: all
@limit = nil
@offset = nil
@key = nil
+ @comment = nil
end
def initialize_copy(other)
super
@wheres = @wheres.clone
@values = @values.clone
+ @comment = @comment.clone if @comment
end
def hash
- [@relation, @wheres, @values, @orders, @limit, @offset, @key].hash
+ [@relation, @wheres, @values, @orders, @limit, @offset, @key, @comment].hash
end
def eql?(other)
@@ -33,7 +35,8 @@ module Arel # :nodoc: all
self.orders == other.orders &&
self.limit == other.limit &&
self.offset == other.offset &&
- self.key == other.key
+ self.key == other.key &&
+ self.comment == other.comment
end
alias :== :eql?
end
diff --git a/activerecord/lib/arel/select_manager.rb b/activerecord/lib/arel/select_manager.rb
index 32286b67f4..4e9f527235 100644
--- a/activerecord/lib/arel/select_manager.rb
+++ b/activerecord/lib/arel/select_manager.rb
@@ -244,6 +244,15 @@ module Arel # :nodoc: all
@ctx.source
end
+ def comment(*values)
+ @ctx.comment = Nodes::Comment.new(values)
+ self
+ end
+
+ def comment_node
+ @ctx.comment
+ end
+
private
def collapse(exprs)
exprs = exprs.compact
diff --git a/activerecord/lib/arel/tree_manager.rb b/activerecord/lib/arel/tree_manager.rb
index 0476399618..326c4f995c 100644
--- a/activerecord/lib/arel/tree_manager.rb
+++ b/activerecord/lib/arel/tree_manager.rb
@@ -36,6 +36,11 @@ module Arel # :nodoc: all
@ast.wheres << expr
self
end
+
+ def comment(*values)
+ @ast.comment = Nodes::Comment.new(values)
+ self
+ end
end
attr_reader :ast
diff --git a/activerecord/lib/arel/visitors/depth_first.rb b/activerecord/lib/arel/visitors/depth_first.rb
index 109afb7402..d696edc507 100644
--- a/activerecord/lib/arel/visitors/depth_first.rb
+++ b/activerecord/lib/arel/visitors/depth_first.rb
@@ -181,6 +181,10 @@ module Arel # :nodoc: all
visit o.limit
end
+ def visit_Arel_Nodes_Comment(o)
+ visit o.values
+ end
+
def visit_Array(o)
o.each { |i| visit i }
end
diff --git a/activerecord/lib/arel/visitors/dot.rb b/activerecord/lib/arel/visitors/dot.rb
index 37803ce0c0..ecc386de07 100644
--- a/activerecord/lib/arel/visitors/dot.rb
+++ b/activerecord/lib/arel/visitors/dot.rb
@@ -234,6 +234,10 @@ module Arel # :nodoc: all
end
alias :visit_Set :visit_Array
+ def visit_Arel_Nodes_Comment(o)
+ visit_edge(o, "values")
+ end
+
def visit_edge(o, method)
edge(method) { visit o.send(method) }
end
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 1630226085..4192d9efdc 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -35,6 +35,7 @@ module Arel # :nodoc: all
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
collect_nodes_for o.orders, collector, " ORDER BY "
maybe_visit o.limit, collector
+ maybe_visit o.comment, collector
end
def visit_Arel_Nodes_UpdateStatement(o, collector)
@@ -47,6 +48,7 @@ module Arel # :nodoc: all
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
collect_nodes_for o.orders, collector, " ORDER BY "
maybe_visit o.limit, collector
+ maybe_visit o.comment, collector
end
def visit_Arel_Nodes_InsertStatement(o, collector)
@@ -62,9 +64,9 @@ module Arel # :nodoc: all
maybe_visit o.values, collector
elsif o.select
maybe_visit o.select, collector
- else
- collector
end
+
+ maybe_visit o.comment, collector
end
def visit_Arel_Nodes_Exists(o, collector)
@@ -162,7 +164,7 @@ module Arel # :nodoc: all
collect_nodes_for o.havings, collector, " HAVING ", " AND "
collect_nodes_for o.windows, collector, " WINDOW "
- collector
+ maybe_visit o.comment, collector
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
@@ -170,6 +172,10 @@ module Arel # :nodoc: all
collector << "/*+ #{hints} */"
end
+ def visit_Arel_Nodes_Comment(o, collector)
+ collector << o.values.map { |v| "/* #{sanitize_as_sql_comment(v)} */" }.join(" ")
+ end
+
def collect_nodes_for(nodes, collector, spacer, connector = ", ")
unless nodes.empty?
collector << spacer