提交 ef7a48df 编写于 作者: X Xavier Noria

let EXPLAIN use a thread locals registry [John J. Wang & Xavier Noria]

Closes #10198.
上级 0513f6ca
require 'active_support/lazy_load_hooks'
require 'active_record/explain_registry'
module ActiveRecord
module Explain
# Relation#explain needs to be able to collect the queries.
# Executes the block with the collect flag enabled. Queries are collected
# asynchronously by the subscriber and returned.
def collecting_queries_for_explain # :nodoc:
current = Thread.current
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
ExplainRegistry.collect = true
yield
return current[:available_queries_for_explain]
ExplainRegistry.queries
ensure
# Note that the return value above does not depend on this assignment.
current[:available_queries_for_explain] = original
ExplainRegistry.reset
end
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
str = queries && queries.map do |sql, bind|
str = queries.map do |sql, bind|
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
unless bind.empty?
......@@ -31,6 +31,7 @@ def exec_explain(queries) # :nodoc:
def str.inspect
self
end
str
end
end
......
require 'active_support/per_thread_registry'
module ActiveRecord
# This is a thread locals registry for EXPLAIN. For example
#
# ActiveRecord::ExplainRegistry.queries
#
# returns the collected queries local to the current thread.
#
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
# for further details.
class ExplainRegistry
extend ActiveSupport::PerThreadRegistry
attr_accessor :queries, :collect
def initialize
reset
end
def collect?
@collect
end
def reset
@collect = false
@queries = []
end
end
end
\ No newline at end of file
require 'active_support/notifications'
require 'active_record/explain_registry'
module ActiveRecord
class ExplainSubscriber # :nodoc:
......@@ -7,8 +8,8 @@ def start(name, id, payload)
end
def finish(name, id, payload)
if queries = Thread.current[:available_queries_for_explain]
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
if ExplainRegistry.collect? && !ignore_payload?(payload)
ExplainRegistry.queries << payload.values_at(:sql, :binds)
end
end
......
......@@ -22,13 +22,6 @@ def test_explain_with_eager_loading
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match %(Seq Scan on audit_logs), explain
end
def test_dont_explain_for_set_search_path
queries = Thread.current[:available_queries_for_explain] = []
ActiveRecord::Base.connection.schema_search_path = "public"
assert queries.empty?
end
end
end
end
......
require 'cases/helper'
require 'active_record/explain_subscriber'
require 'active_record/explain_registry'
if ActiveRecord::Base.connection.supports_explain?
class ExplainSubscriberTest < ActiveRecord::TestCase
SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
def test_collects_nothing_if_available_queries_for_explain_is_nil
with_queries(nil) do
SUBSCRIBER.finish(nil, nil, {})
assert_nil Thread.current[:available_queries_for_explain]
end
def setup
ActiveRecord::ExplainRegistry.reset
ActiveRecord::ExplainRegistry.collect = true
end
def test_collects_nothing_if_the_payload_has_an_exception
with_queries([]) do |queries|
SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
assert queries.empty?
end
SUBSCRIBER.finish(nil, nil, exception: Exception.new)
assert queries.empty?
end
def test_collects_nothing_for_ignored_payloads
with_queries([]) do |queries|
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
SUBSCRIBER.finish(nil, nil, :name => ip)
end
assert queries.empty?
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
SUBSCRIBER.finish(nil, nil, name: ip)
end
assert queries.empty?
end
def test_collects_nothing_if_collect_is_false
ActiveRecord::ExplainRegistry.collect = false
SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2])
assert queries.empty?
end
def test_collects_pairs_of_queries_and_binds
sql = 'select 1 from users'
binds = [1, 2]
with_queries([]) do |queries|
SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
assert_equal 1, queries.size
assert_equal sql, queries[0][0]
assert_equal binds, queries[0][1]
end
SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds)
assert_equal 1, queries.size
assert_equal sql, queries[0][0]
assert_equal binds, queries[0][1]
end
def test_collects_nothing_if_unexplained_sqls
with_queries([]) do |queries|
SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length')
assert queries.empty?
end
def test_collects_nothing_if_the_statement_is_not_whitelisted
SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length')
assert queries.empty?
end
def teardown
ActiveRecord::ExplainRegistry.reset
end
def with_queries(queries)
Thread.current[:available_queries_for_explain] = queries
yield queries
ensure
Thread.current[:available_queries_for_explain] = nil
def queries
ActiveRecord::ExplainRegistry.queries
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册