mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
let EXPLAIN use a thread locals registry [John J. Wang & Xavier Noria]
Closes #10198.
This commit is contained in:
parent
0513f6ca9c
commit
ef7a48df75
5 changed files with 70 additions and 46 deletions
|
@ -1,22 +1,22 @@
|
||||||
require 'active_support/lazy_load_hooks'
|
require 'active_support/lazy_load_hooks'
|
||||||
|
require 'active_record/explain_registry'
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
module Explain
|
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:
|
def collecting_queries_for_explain # :nodoc:
|
||||||
current = Thread.current
|
ExplainRegistry.collect = true
|
||||||
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
|
|
||||||
yield
|
yield
|
||||||
return current[:available_queries_for_explain]
|
ExplainRegistry.queries
|
||||||
ensure
|
ensure
|
||||||
# Note that the return value above does not depend on this assignment.
|
ExplainRegistry.reset
|
||||||
current[:available_queries_for_explain] = original
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
|
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
|
||||||
# Returns a formatted string ready to be logged.
|
# Returns a formatted string ready to be logged.
|
||||||
def exec_explain(queries) # :nodoc:
|
def exec_explain(queries) # :nodoc:
|
||||||
str = queries && queries.map do |sql, bind|
|
str = queries.map do |sql, bind|
|
||||||
[].tap do |msg|
|
[].tap do |msg|
|
||||||
msg << "EXPLAIN for: #{sql}"
|
msg << "EXPLAIN for: #{sql}"
|
||||||
unless bind.empty?
|
unless bind.empty?
|
||||||
|
@ -31,6 +31,7 @@ module ActiveRecord
|
||||||
def str.inspect
|
def str.inspect
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
str
|
str
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
30
activerecord/lib/active_record/explain_registry.rb
Normal file
30
activerecord/lib/active_record/explain_registry.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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
|
|
@ -1,4 +1,5 @@
|
||||||
require 'active_support/notifications'
|
require 'active_support/notifications'
|
||||||
|
require 'active_record/explain_registry'
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
class ExplainSubscriber # :nodoc:
|
class ExplainSubscriber # :nodoc:
|
||||||
|
@ -7,8 +8,8 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def finish(name, id, payload)
|
def finish(name, id, payload)
|
||||||
if queries = Thread.current[:available_queries_for_explain]
|
if ExplainRegistry.collect? && !ignore_payload?(payload)
|
||||||
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
|
ExplainRegistry.queries << payload.values_at(:sql, :binds)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,6 @@ module ActiveRecord
|
||||||
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
|
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
|
assert_match %(Seq Scan on audit_logs), explain
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,55 +1,54 @@
|
||||||
require 'cases/helper'
|
require 'cases/helper'
|
||||||
|
require 'active_record/explain_subscriber'
|
||||||
|
require 'active_record/explain_registry'
|
||||||
|
|
||||||
if ActiveRecord::Base.connection.supports_explain?
|
if ActiveRecord::Base.connection.supports_explain?
|
||||||
class ExplainSubscriberTest < ActiveRecord::TestCase
|
class ExplainSubscriberTest < ActiveRecord::TestCase
|
||||||
SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
|
SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
|
||||||
|
|
||||||
def test_collects_nothing_if_available_queries_for_explain_is_nil
|
def setup
|
||||||
with_queries(nil) do
|
ActiveRecord::ExplainRegistry.reset
|
||||||
SUBSCRIBER.finish(nil, nil, {})
|
ActiveRecord::ExplainRegistry.collect = true
|
||||||
assert_nil Thread.current[:available_queries_for_explain]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_collects_nothing_if_the_payload_has_an_exception
|
def test_collects_nothing_if_the_payload_has_an_exception
|
||||||
with_queries([]) do |queries|
|
SUBSCRIBER.finish(nil, nil, exception: Exception.new)
|
||||||
SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
|
|
||||||
assert queries.empty?
|
assert queries.empty?
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def test_collects_nothing_for_ignored_payloads
|
def test_collects_nothing_for_ignored_payloads
|
||||||
with_queries([]) do |queries|
|
|
||||||
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
|
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
|
||||||
SUBSCRIBER.finish(nil, nil, :name => ip)
|
SUBSCRIBER.finish(nil, nil, name: ip)
|
||||||
end
|
end
|
||||||
assert queries.empty?
|
assert queries.empty?
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def test_collects_pairs_of_queries_and_binds
|
def test_collects_pairs_of_queries_and_binds
|
||||||
sql = 'select 1 from users'
|
sql = 'select 1 from users'
|
||||||
binds = [1, 2]
|
binds = [1, 2]
|
||||||
with_queries([]) do |queries|
|
SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds)
|
||||||
SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
|
|
||||||
assert_equal 1, queries.size
|
assert_equal 1, queries.size
|
||||||
assert_equal sql, queries[0][0]
|
assert_equal sql, queries[0][0]
|
||||||
assert_equal binds, queries[0][1]
|
assert_equal binds, queries[0][1]
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def test_collects_nothing_if_unexplained_sqls
|
def test_collects_nothing_if_the_statement_is_not_whitelisted
|
||||||
with_queries([]) do |queries|
|
SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length')
|
||||||
SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length')
|
|
||||||
assert queries.empty?
|
assert queries.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveRecord::ExplainRegistry.reset
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_queries(queries)
|
def queries
|
||||||
Thread.current[:available_queries_for_explain] = queries
|
ActiveRecord::ExplainRegistry.queries
|
||||||
yield queries
|
|
||||||
ensure
|
|
||||||
Thread.current[:available_queries_for_explain] = nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue