require "#{File.dirname(__FILE__)}/abstract_note"
module Footnotes
module Notes
class QueriesNote < AbstractNote
@@sql = []
cattr_accessor :sql
def self.start!(controller)
@@sql = []
end
def self.to_sym
:queries
end
def title
"Queries (#{@@sql.length})"
end
def stylesheet
<<-STYLESHEET
#queries_debug_info table td, #queries_debug_info table th{border:1px solid #A00; padding:0 3px; text-align:center;}
#queries_debug_info table thead, #queries_debug_info table tbody {color:#A00;}
#queries_debug_info p {background-color:#F3F3FF; border:1px solid #CCC; margin:12px; padding:4px 6px;}
#queries_debug_info a:hover {text-decoration:underline;}
STYLESHEET
end
def content
html = ''
@@sql.each_with_index do |item, i|
sql_links = []
sql_links << "explain" if item.explain
sql_links << "trace" if item.trace
html << <<-HTML
#{escape(item.type.to_s.upcase)} (#{sql_links.join(' | ')})
#{print_name_and_time(item.name, item.time)}
#{print_query(item.query)}
#{print_explain(i, item.explain) if item.explain}
#{parse_trace(item.trace) if item.trace}
HTML
end
return html
end
protected
def parse_explain(results)
table = []
table << results.fetch_fields.map(&:name)
results.each{|row| table << row}
table
end
def parse_trace(trace)
trace.map do |t|
s = t.split(':')
%[#{escape(t)}
]
end.join
end
def print_name_and_time(name, time)
"#{escape(name || 'SQL')} (#{sprintf('%f', time)}s)"
end
def print_query(query)
escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
end
def print_explain(i, explain)
mount_table(parse_explain(explain), :id => "qtable_#{i}", :style => 'margin:10px;display:none;')
end
end
end
module Extensions
class Sql
attr_accessor :type, :name, :time, :query, :explain, :trace
def initialize(type, name, time, query, explain)
@type = type
@name = name
@time = time
@query = query
@explain = explain
# Strip, select those ones from app and reject first two, because they are from the plugin
@trace = Kernel.caller.collect(&:strip).select{|i| i.gsub!(/^#{RAILS_ROOT}\//im, '') }[2..-1]
end
end
module QueryAnalyzer
def self.included(base)
base.class_eval do
alias_method_chain :execute, :analyzer
end
end
def execute_with_analyzer(query, name = nil)
query_results = nil
time = Benchmark.realtime { query_results = execute_without_analyzer(query, name) }
if query =~ /^(select|create|update|delete)\b/i
type = $&.downcase.to_sym
explain = nil
if adapter_name == 'MySQL' && type == :select
log_silence do
explain = execute_without_analyzer("EXPLAIN #{query}", name)
end
end
Footnotes::Notes::QueriesNote.sql << Footnotes::Extensions::Sql.new(type, name, time, query, explain)
end
query_results
end
end
module AbstractAdapter
def log_silence
result = nil
if @logger
@logger.silence do
result = yield
end
else
result = yield
end
result
end
end
end
end
if Footnotes::Notes::QueriesNote.included?
ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Footnotes::Extensions::AbstractAdapter
ActiveRecord::ConnectionAdapters.local_constants.each do |adapter|
next unless adapter =~ /.*[^Abstract]Adapter$/
next if adapter =~ /SQLite.Adapter$/
eval("ActiveRecord::ConnectionAdapters::#{adapter}").send :include, Footnotes::Extensions::QueryAnalyzer
end
end