explain.rb 3.2 KB
Newer Older
1
require 'active_support/core_ext/class/attribute'
2

3
module ActiveRecord
4
  module Explain
5 6 7 8 9
    def self.extended(base)
      # If a query takes longer than these many seconds we log its query plan
      # automatically. nil disables this feature.
      base.config_attribute :auto_explain_threshold_in_seconds, :global => true
    end
10

11 12 13 14 15 16 17 18 19 20 21
    # If auto explain is enabled, this method triggers EXPLAIN logging for the
    # queries triggered by the block if it takes more than the threshold as a
    # whole. That is, the threshold is not checked against each individual
    # query, but against the duration of the entire block. This approach is
    # convenient for relations.
    #
    # The available_queries_for_explain thread variable collects the queries
    # to be explained. If the value is nil, it means queries are not being
    # currently collected. A false value indicates collecting is turned
    # off. Otherwise it is an array of queries.
    def logging_query_plan # :nodoc:
22 23
      return yield unless logger

24 25 26 27 28 29 30 31 32 33 34
      threshold = auto_explain_threshold_in_seconds
      current   = Thread.current
      if threshold && current[:available_queries_for_explain].nil?
        begin
          queries = current[:available_queries_for_explain] = []
          start = Time.now
          result = yield
          logger.warn(exec_explain(queries)) if Time.now - start > threshold
          result
        ensure
          current[:available_queries_for_explain] = nil
35
        end
36 37
      else
        yield
38
      end
39
    end
40

41 42 43 44 45 46 47 48 49 50
    # Relation#explain needs to be able to collect the queries regardless of
    # whether auto explain is enabled. This method serves that purpose.
    def collecting_queries_for_explain # :nodoc:
      current = Thread.current
      original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
      return yield, current[:available_queries_for_explain]
    ensure
      # Note that the return value above does not depend on this assigment.
      current[:available_queries_for_explain] = original
    end
51

52 53 54
    # 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:
55
      str = queries && queries.map do |sql, bind|
56 57 58 59 60 61 62
        [].tap do |msg|
          msg << "EXPLAIN for: #{sql}"
          unless bind.empty?
            bind_msg = bind.map {|col, val| [col.name, val]}.inspect
            msg.last << " #{bind_msg}"
          end
          msg << connection.explain(sql, bind)
63
        end.join("\n")
64
      end.join("\n")
65 66 67 68 69 70

      # overriding inspect to be more human readable
      def str.inspect
        self
      end
      str
71
    end
72

73 74 75 76 77 78
    # Silences automatic EXPLAIN logging for the duration of the block.
    #
    # This has high priority, no EXPLAINs will be run even if downwards
    # the threshold is set to 0.
    #
    # As the name of the method suggests this only applies to automatic
79
    # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run.
80 81 82 83 84 85
    def silence_auto_explain
      current = Thread.current
      original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
      yield
    ensure
      current[:available_queries_for_explain] = original
86 87 88
    end
  end
end