Browse files

initial revision

  • Loading branch information...
1 parent 8a7fb69 commit 9485a8f98951f0be7641c9c8a6b756aecba496d9 @dsboulder dsboulder committed Dec 9, 2007
View
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 [name of plugin creator]
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
13 README
@@ -0,0 +1,13 @@
+QueryReviewer
+=============
+
+Introduction goes here.
+
+
+Example
+=======
+
+Example goes here.
+
+
+Copyright (c) 2007 [name of plugin creator], released under the MIT license
View
22 Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the query_reviewer plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the query_reviewer plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'QueryReviewer'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
View
8 init.rb
@@ -0,0 +1,8 @@
+# Include hook code here
+
+require "active_record"
+require "action_controller"
+require 'query_reviewer'
+ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, QueryReviewer::MysqlAdapterExtensions)
+ActionController::Base.send(:include, QueryReviewer::ControllerExtensions)
+Array.send(:include, QueryReviewer::ArrayExtensions)
View
1 install.rb
@@ -0,0 +1 @@
+# Install hook code here
View
40 lib/query_reviewer.rb
@@ -0,0 +1,40 @@
+# QueryReviewer
+require "ostruct"
+require "query_reviewer/array_extensions"
+require "query_reviewer/sql_query"
+require "query_reviewer/sql_sub_query"
+require "query_reviewer/mysql_adapter_extensions"
+require "query_reviewer/controller_extensions"
+
+
+module QueryReviewer
+ CONFIGURATION = YAML.load(File.read(File.join(File.dirname(__FILE__), "..", "query_reviewer.yml")))["all"] || {}
+ CONFIGURATION.merge!(YAML.load(File.read(File.join(File.dirname(__FILE__), "..", "query_reviewer.yml")))[RAILS_ENV || "development"])
+
+ if CONFIGURATION["enabled"]
+ begin
+ CONFIGURATION["uv"] ||= !Gem.searcher.find("uv").nil?
+ if CONFIGURATION["uv"]
+ require "uv"
+ end
+ rescue
+ CONFIGURATION["uv"] ||= false
+ end
+ end
+
+ class QueryWarning
+ attr_reader :query, :severity, :problem, :desc, :table, :id
+
+ cattr_accessor :next_id
+ self.next_id = 1
+
+ def initialize(options)
+ @query = options[:query]
+ @severity = options[:severity]
+ @problem = options[:problem]
+ @desc = options[:desc]
+ @table = options[:table]
+ @id = (self.class.next_id += 1)
+ end
+ end
+end
View
29 lib/query_reviewer/array_extensions.rb
@@ -0,0 +1,29 @@
+module QueryReviewer
+ module ArrayExtensions #taken from query_analyser plugin
+ protected
+ def qa_columnized_row(fields, sized)
+ row = []
+ fields.each_with_index do |f, i|
+ row << sprintf("%0-#{sized[i]}s", f.to_s)
+ end
+ row.join(' | ')
+ end
+
+ public
+
+ def qa_columnized
+ sized = {}
+ self.each do |row|
+ row.values.each_with_index do |value, i|
+ sized[i] = [sized[i].to_i, row.keys[i].length, value.to_s.length].max
+ end
+ end
+
+ table = []
+ table << qa_columnized_row(self.first.keys, sized)
+ table << '-' * table.first.length
+ self.each { |row| table << qa_columnized_row(row.values, sized) }
+ table.join("\n ") # Spaces added to work with format_log_entry
+ end
+ end
+end
View
37 lib/query_reviewer/controller_extensions.rb
@@ -0,0 +1,37 @@
+require File.join(File.dirname(__FILE__), "views", "query_review_box_helper")
+
+module QueryReviewer
+ module ControllerExtensions
+ class QueryViewBase < ActionView::Base
+ include QueryReviewer::Views::QueryReviewBoxHelper
+ end
+
+ def self.included(base)
+ base.alias_method_chain :perform_action, :query_review
+ base.alias_method_chain :process, :query_review
+ end
+
+ def add_query_output_to_view
+ if response.body.match(/<\/body>/i) && Thread.current["queries"]
+ idx = (response.body =~ /<\/body>/i)
+ faux_view = QueryViewBase.new([File.join(File.dirname(__FILE__), "views")], {}, self)
+ queries = SqlQueryCollection.new(Thread.current["queries"])
+ queries.analyze!
+ faux_view.instance_variable_set("@queries", queries)
+ html = faux_view.render(:partial => "/box.rhtml")
+ response.body.insert(idx, html)
+ end
+ end
+
+ def perform_action_with_query_review
+ r = perform_action_without_query_review
+ add_query_output_to_view if response.content_type.blank? || response.content_type == "text/html"
+ r
+ end
+
+ def process_with_query_review(request, response, method = :perform_action, *arguments) #:nodoc:
+ Thread.current["queries"] = []
+ process_without_query_review(request, response, method, *arguments)
+ end
+ end
+end
View
21 lib/query_reviewer/mysql_adapter_extensions.rb
@@ -0,0 +1,21 @@
+module QueryReviewer
+ module MysqlAdapterExtensions
+ def self.included(base)
+ base.alias_method_chain :select, :review
+ end
+
+ def select_with_review(sql, name = nil)
+ query_results = select_without_review(sql, name)
+
+ if @logger and @logger.level <= Logger::INFO and sql =~ /^select/i
+ cols = @logger.silence do
+ select_without_review("explain #{sql}", name)
+ end
+ query = SqlQuery.new(sql, cols)
+ Thread.current["queries"] << query if Thread.current["queries"] && Thread.current["queries"].respond_to?(:<<)
+ @logger.debug(format_log_entry("Analyzing #{name}\n", query.to_table))
+ end
+ query_results
+ end
+ end
+end
View
46 lib/query_reviewer/sql_query.rb
@@ -0,0 +1,46 @@
+module QueryReviewer
+ # a single SQL SELECT query
+ class SqlQuery
+ attr_reader :sql, :rows, :subqueries, :trace, :id
+
+ cattr_accessor :next_id
+ self.next_id = 1
+
+ def initialize(sql, rows)
+ @rows = rows
+ @sql = sql
+ @subqueries = rows.collect{|row| SqlSubQuery.new(self, row)}
+ @id = (self.class.next_id += 1)
+ # get_trace
+ end
+
+ def to_table
+ rows.qa_columnized
+ end
+
+ def warnings
+ self.subqueries.collect(&:warnings).flatten
+ end
+
+ def has_warnings?
+ !self.warnings.empty?
+ end
+
+ def max_severity
+ self.warnings.empty? ? 0 : self.warnings.collect(&:severity).max
+ end
+
+ def analyze!
+ self.subqueries.collect(&:analyze!)
+ end
+
+ def get_trace
+ begin
+ raise "not a real exception"
+ rescue
+ @trace = $!.backtrace
+ end
+ puts @trace.inspect
+ end
+ end
+end
View
57 lib/query_reviewer/sql_query_collection.rb
@@ -0,0 +1,57 @@
+module QueryReviewer
+ # a collection of SQL SELECT queries
+ class SqlQueryCollection
+ attr_reader :queries
+ def initialize(queries)
+ @queries = queries
+ end
+
+ def analyze!
+ self.queries.collect(&:analyze!)
+
+ @warnings = []
+
+ if @queries.length > QueryReviewer::CONFIGURATION["critical_query_count"]
+ warn(:severity => QueryReviewer::CONFIGURATION["critical_severity"], :problem => "#{@queries.length} queries on this page", :description => "Too many queries can severely slow down a page")
+ elsif @queries.length > QueryReviewer::CONFIGURATION["warn_query_count"]
+ warn(:severity => QueryReviewer::CONFIGURATION["warn_severity"], :problem => "#{@queries.length} queries on this page", :description => "Too many queries can slow down a page")
+ end
+ end
+
+ def warn(options)
+ @warnings << QueryWarning.new(options)
+ end
+
+ def warnings
+ self.queries.collect(&:warnings).flatten.sort{|a,b| a.severity <=> b.severity}.reverse
+ end
+
+ def collection_warnings
+ @warnings
+ end
+
+ def max_severity
+ warnings.empty? && collection_warnings.empty? ? 0 : [warnings.collect(&:severity).flatten.max, collection_warnings.collect(&:severity).flatten.max].max
+ end
+
+ def total_severity
+ warnings.collect(&:severity).sum
+ end
+
+ def total_with_warnings
+ queries.select(&:has_warnings?).length
+ end
+
+ def total_without_warnings
+ queries.length - total_with_warnings
+ end
+
+ def percent_with_warnings
+ queries.empty? ? 0 : (100.0 * total_with_warnings / queries.length).to_i
+ end
+
+ def percent_without_warnings
+ queries.empty? ? 0 : (100.0 * total_without_warnings / queries.length).to_i
+ end
+ end
+end
View
85 lib/query_reviewer/sql_sub_query.rb
@@ -0,0 +1,85 @@
+module QueryReviewer
+ # a single part of an SQL SELECT query
+ class SqlSubQuery < OpenStruct
+ delegate :sql, :to => :parent
+ attr_reader :cols, :warnings, :parent
+ def initialize(parent, cols)
+ @parent = parent
+ @warnings = []
+ @cols = cols.inject({}) {|memo, obj| memo[obj[0].to_s.downcase] = obj[1].to_s.downcase; memo }
+ @cols["query_type"] = @cols.delete("type")
+ super(@cols)
+ end
+
+ def analyze!
+ @warnings = []
+ analyze_select_type!
+ analyze_query_type!
+ analyze_key!
+ analyze_extras!
+ end
+
+ private
+
+ def warn(options)
+ if (options[:field])
+ field = options.delete(:field)
+ val = self.send(field)
+ options[:problem] = ("#{field.to_s.titleize}: #{val.blank? ? "(blank)" : val}")
+ end
+ options[:query] = self
+ options[:table] = @table[:table]
+ puts("Adding warning: #{options.inspect}")
+ @warnings << QueryWarning.new(options)
+ end
+
+ def praise(options)
+ # no credit, only pain
+ end
+
+ def analyze_select_type!
+ if select_type.match /uncacheable subquery/
+ warn(:severity => 10, :field => "select_type", :desc => "Subquery must be run once for EVERY row in main query")
+ elsif select_type.match /dependent/
+ warn(:severity => 2, :field => "select_type", :desc => "Dependent subqueries can not be executed while the main query is running")
+ end
+ end
+
+ def analyze_query_type!
+ case query_type
+ when "system", "const", "eq_ref":
+ praise("Yay")
+ when "ref", "ref_or_null", "range", "index_merge":
+ praise("Not bad eh...")
+ when "unique_subquery", "index_subquery":
+ #NOT SURE
+ when "index":
+ warn(:severity => 8, :field => "query_type", :desc => "Full index tree scan (slightly faster than a full table scan)") unless extra.include?("using where")
+ when "all":
+ warn(:severity => 9, :field => "query_type", :desc => "Full table scan") unless extra.include?("using where")
+ end
+ end
+
+ def analyze_key!
+ if self.key == "const"
+ praise "Way to go!"
+ elsif self.key.blank?
+ warn :severity => 6, :field => "key", :desc => "No index was used here. In this case, that meant scanning #{self.rows} rows."
+ end
+ end
+
+ def analyze_extras!
+ if self.extra.match(/range checked for each record/)
+ warn :severity => 4, :problem => "Range checked for each record", :desc => "MySQL found no good index to use, but found that some of indexes might be used after column values from preceding tables are known"
+ end
+
+ if self.extra.match(/using filesort/)
+ warn :severity => 2, :problem => "Using filesort", :desc => "MySQL must do an extra pass to find out how to retrieve the rows in sorted order."
+ end
+
+ if self.extra.match(/using temporary/)
+ warn :severity => 10, :problem => "Using temporary table", :desc => "To resolve the query, MySQL needs to create a temporary table to hold the result."
+ end
+ end
+ end
+end
View
203 lib/query_reviewer/views/_box.rhtml
@@ -0,0 +1,203 @@
+<style>
+#query_review {
+ position: absolute;
+ top: 1px;
+ left: 1px;
+ height: 18px;
+ margin: 1px;
+ opacity: 0.8;
+ padding-left: 3px;
+ padding-right: 3px;
+ border: 1px solid black;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+#query_review.sql_ok {
+ background-color: #090;
+ color: #020;
+}
+
+#query_review.sql_warning {
+ background-color: #FAE100;
+ color: #F05000;
+}
+
+#query_review.sql_critical {
+ background-color: #F99;
+ color: #A00;
+}
+
+#query_review a {
+ color: inherit;
+ text-decoration: none;
+}
+
+#query_review_details {
+ position: absolute;
+ top: 21px;
+ left: 1px;
+ width: 700px;
+ height: 500px;
+ opacity: 0.8;
+ margin: 1px;
+ padding: 2px;
+ border: 1px solid black;
+ font-size: 12px;
+ overflow: auto;
+ background-color: #DDD;
+}
+
+#query_review_details p {
+ margin: 2px 0 2px 0;
+ padding: 0px;
+}
+
+#query_review_details a {
+ color: #29ABE2;
+}
+
+#query_review_details ul {
+ list-style-type: circle;
+ padding-left: 15px;
+}
+
+#query_review_details code {
+ white-space: normal;
+ line-height: 120%;
+}
+
+#query_review_details .title {
+ font-weight: bold;
+}
+
+#query_review_details .indent {
+ padding-left: 10px;
+}
+
+#query_review_details .number {
+ font-weight: bold;
+}
+
+#query_review_details .bad {
+ color: #900;
+}
+
+#query_review_details .good {
+ color: #090;
+}
+
+#query_review_details div.divider {
+ width: 504px;
+ position: relative;
+ left: -2px;
+ height: 1px;
+ border-top: 1px dashed black;
+ margin: 2px 0 2px 0;
+}
+
+#query_review_details .small {
+ font-size: 10px;
+}
+
+#query_review_details div.spectrum_container {
+ margin-top: 4px;
+ width: 35px;
+ display: block;
+ float: left;
+ position: relative;
+ height: 14px;
+ margin-right: 5px;
+}
+
+#query_review_details div.spectrum_elem {
+ width: 1px;
+ display: block;
+ float: left;
+ height: 10px;
+}
+#query_review_details div.spectrum_pointer {
+ width: 2px;
+ display: block;
+ float: left;
+ position: absolute;
+ height: 14px;
+ background-color: black;
+ top: -2px;
+}
+</style>
+
+<style>
+.sql {
+ color: black;
+}
+.sql .String {
+ color: #009933;
+}
+.sql .Keyword {
+ color: #0000FF;
+}
+.sql .Constant {
+ color: #6782D3;
+}
+.sql .Number {
+ color: #0066FF;
+}
+.sql .Comment {
+ color: #0066FF;
+ font-style: italic;
+}
+</style>
+
+<script type="text/javascript">
+//Super lame show/hide functions so we don't need any 3rd party JS libs
+function query_review_show(id) {
+ document.getElementById(id).style.display = "block";
+}
+
+function query_review_hide(id) {
+ document.getElementById(id).style.display = "none";
+}
+function query_review_toggle(id) {
+ if(document.getElementById(id).style.display == "none") {
+ document.getElementById(id).style.display = "block";
+ } else {
+ document.getElementById(id).style.display = "none";
+ }
+}
+</script>
+
+<div id="query_review" class="<%= parent_div_class %>">
+ <a href="javascript: query_review_toggle('query_review_details')">SQL <%= parent_div_status %></a>
+</div>
+<div id="query_review_details" style="display: none;">
+ <p>Total queries: <span class="number"><%= @queries.queries.length %></span></p>
+ <p class="indent">With warnings: <span class="number bad"><%= @queries.total_with_warnings %></span> (<%= @queries.percent_with_warnings %>%)</p>
+ <p class="indent">Without warnings: <span class="number good"><%= @queries.total_without_warnings %></span> (<%= @queries.percent_without_warnings %>%)</p>
+ <% if @queries.total_with_warnings > 0 %>
+ <div class="divider"></div>
+ <p class="title">Warnings:</p>
+ <ul>
+ <%= render :partial => "/warning_no_query", :collection => warnings_no_query_sorted_nonignored %>
+ <%= render :partial => "/query_with_warning", :collection => queries_with_warnings_sorted_nonignored %>
+ </ul>
+ <a class="title" href="javascript: query_review_toggle('query_review_ignored_warnings')">Ignored Warnings:</a>
+ <ul style="display: none;" id="query_review_ignored_warnings">
+ <%= render :partial => "/warning_no_query", :collection => warnings_no_query_sorted_ignored %>
+ <%= render :partial => "/query_with_warning", :collection => queries_with_warnings_sorted_ignored %>
+ </ul>
+ <% end %>
+ <div class="divider"></div>
+ <p class="title">Safe queries:</p>
+ <% if @queries.queries.empty? %>
+ No queries to display.
+ <% else %>
+ <ul class="small">
+ <% @queries.queries.reject{|q| q.has_warnings?}.each do |query| %>
+ <li>
+ <%= render :partial => "/query_sql", :object => query %>
+ </li>
+ <% end %>
+ </ul>
+ <% end %>
+</div>
View
24 lib/query_reviewer/views/_explain.rhtml
@@ -0,0 +1,24 @@
+<table cellpadding="1" cellspacing="0" border="1">
+ <tr>
+ <th>table</th>
+ <th>select_type</th>
+ <th>type</th>
+ <th>extra</th>
+ <th>possible_keys</th>
+ <th>key</th>
+ <th>ref</th>
+ <th>rows</th>
+ </tr>
+ <% query.subqueries.each do |subquery| %>
+ <tr>
+ <td><%= h subquery.table %></td>
+ <td><%= h subquery.select_type %></td>
+ <td><%= h subquery.query_type %></td>
+ <td><%= h subquery.extra %></td>
+ <td><%= h subquery.possible_keys %></td>
+ <td><%= h subquery.key %></td>
+ <td><%= h subquery.ref %></td>
+ <td><%= h subquery.rows %></td>
+ <tr>
+ <% end %>
+</table>
View
4 lib/query_reviewer/views/_query_sql.rhtml
@@ -0,0 +1,4 @@
+<%= syntax_highlighted_sql(query_sql.sql) %>
+<% if query_sql.respond_to?(:subqueries) && query_sql.subqueries.length > 1 %>
+ (<%= query_sql.subqueries.length - 1 %> subqueries)
+<% end %>
View
22 lib/query_reviewer/views/_query_with_warning.rhtml
@@ -0,0 +1,22 @@
+<li id="query_<%= query_with_warning.id %>">
+ <div>
+ <%= render :partial => "/spectrum", :locals => {:severity => query_with_warning.max_severity} %>
+ <p>
+ <i>Table <%= query_with_warning.warnings.first.table %>,</i> <%= query_with_warning.warnings.sort{|a,b| a.severity <=> b.severity}.reverse.collect(&:problem).join(", ") %>
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_desc')" title="show/hide warning message">MSG</a>
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_sql')" title="show/hide sql">SQL</a>
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_explain')" title="show/hide sql">EXPLN</a>
+ </p>
+ </div>
+ <p id="warning_<%= query_with_warning.id %>_desc" class="indent">
+ <% query_with_warning.warnings.each do |warn| %>
+ <span style="color: <%= severity_color warn.severity%>"><%= warn.desc %><br/>
+ <% end %>
+ </p>
+ <p style="display: none" id="warning_<%= query_with_warning.id %>_sql" class="indent small">
+ <%= render :partial => "/query_sql", :object => query_with_warning %>
+ </p>
+ <div style="display: none" id="warning_<%= query_with_warning.id %>_explain" class="indent small">
+ <%= render :partial => "/explain", :locals => {:query => query_with_warning} %>
+ </div>
+</li>
View
10 lib/query_reviewer/views/_spectrum.rhtml
@@ -0,0 +1,10 @@
+<div class="spectrum_container">
+ <% 0.upto(15) do |i| %>
+ <div class="spectrum_elem" style="background-color: <%= "##{i.to_s(16)}F0"%>"></div>
+ <% end %>
+ <% 0.upto(15) do |i| %>
+ <div class="spectrum_elem" style="background-color: <%= "#F#{((15-i)).to_s(16)}0"%>"></div>
+ <% end %>
+ <div class="spectrum_elem" style="background-color: #FF0000"></div>
+ <div class="spectrum_pointer" style="left: <%= severity * 3 + 2 %>px;"></div
+</div>
View
12 lib/query_reviewer/views/_warning_no_query.rhtml
@@ -0,0 +1,12 @@
+<li id="warning_<%= warning_no_query.id %>">
+ <div>
+ <%= render :partial => "/spectrum", :locals => {:severity => warning_no_query.severity} %>
+ <p>
+ <%= warning_no_query.problem %>
+ <a href="javascript: query_review_toggle('warning_nq_<%= warning_no_query.id %>_desc')" title="show/hide warning message">MSG</a>
+ </p>
+ </div>
+ <p id="warning_nq_<%= warning_no_query.id %>_desc" class="indent">
+ <span style="color: <%= severity_color warning_no_query.severity%>"><%= warning_no_query.desc %><br/>
+ </p>
+</li>
View
68 lib/query_reviewer/views/query_review_box_helper.rb
@@ -0,0 +1,68 @@
+module QueryReviewer
+ module Views
+ module QueryReviewBoxHelper
+ def parent_div_class
+ "sql_#{parent_div_status.downcase}"
+ end
+
+ def parent_div_status
+ if @queries.max_severity < (QueryReviewer::CONFIGURATION["warn_severity"] || 4)
+ "OK"
+ elsif @queries.max_severity < (QueryReviewer::CONFIGURATION["critical_severity"] || 7)
+ # uh oh
+ "WARNING"
+ else
+ # oh @#&!
+ "CRITICAL"
+ end
+ end
+
+ def syntax_highlighted_sql(sql)
+ if QueryReviewer::CONFIGURATION["uv"]
+ uv_out = Uv.parse(sql, "xhtml", "sql_rails", false, "blackboard")
+ uv_out.gsub("<pre class=\"blackboard\">", "<code class=\"sql\">").gsub("</pre>", "</code>")
+ else
+ sql
+ end
+ end
+
+ def severity_color(severity)
+ red = (severity * 16.0 / 10).to_i
+ green = ((10-severity) * 16.0 / 10).to_i
+ red = 8 if red > 8
+ red = 0 if red < 0
+ green = 8 if green > 8
+ green = 0 if green < 0
+ "##{red.to_s(16)}#{green.to_s(16)}0"
+ end
+
+ def queries_with_warnings
+ @queries.queries.select{|q| q.has_warnings?}
+ end
+
+ def queries_with_warnings_sorted
+ queries_with_warnings.sort{|a,b| a.max_severity <=> b.max_severity}.reverse
+ end
+
+ def queries_with_warnings_sorted_nonignored
+ queries_with_warnings_sorted.select{|q| q.max_severity >= ::QueryReviewer::CONFIGURATION["warn_severity"]}
+ end
+
+ def queries_with_warnings_sorted_ignored
+ queries_with_warnings_sorted.select{|q| q.max_severity < ::QueryReviewer::CONFIGURATION["warn_severity"]}
+ end
+
+ def warnings_no_query_sorted
+ @queries.collection_warnings.sort{|a,b| a.severity <=> b.severity}.reverse
+ end
+
+ def warnings_no_query_sorted_ignored
+ warnings_no_query_sorted.select{|q| q.severity < ::QueryReviewer::CONFIGURATION["warn_severity"]}
+ end
+
+ def warnings_no_query_sorted_nonignored
+ warnings_no_query_sorted.select{|q| q.severity >= ::QueryReviewer::CONFIGURATION["warn_severity"]}
+ end
+ end
+ end
+end
View
11 query_reviewer.yml
@@ -0,0 +1,11 @@
+all:
+ warn_severity: 3
+ critical_severity: 7
+ warn_query_count: 20
+ critical_query_count: 50
+
+development:
+ enabled: true
+
+production:
+
View
4 tasks/query_reviewer_tasks.rake
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :query_reviewer do
+# # Task goes here
+# end
View
8 test/sql_query_test.rb
@@ -0,0 +1,8 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class SqlQueryTest < Test::Unit::TestCase
+ # Replace this with your real tests.
+ def test_this_plugin
+ assert true
+ end
+end
View
17 test/sql_sub_query_test.rb
@@ -0,0 +1,17 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class SqlSubqueryTest < Test::Unit::TestCase
+ def test_analyze_select_type
+ query = get_query(:select_type => "DEPENDENT UNION")
+ query.send :analyze_select_type!
+ query.should_warn("DEPENDENT UNION", 2)
+
+ query = get_query(:select_type => "UNCACHEABLE SUBQUERY")
+ query.send :analyze_select_type!
+ query.should_warn("UNCACHEABLE SUBQUERY", 10)
+ end
+
+ def get_query(options)
+ SqlSubQuery.new(options)
+ end
+end
View
18 test/test_helper.rb
@@ -0,0 +1,18 @@
+require "rubygems"
+require "activesupport"
+require 'test/unit'
+require "query_reviewer"
+
+module QueryReviewer
+ class SqlSubQuery
+ include Test::Unit::Assertions
+ def should_warn(problem, severity = nil)
+ assert self.warnings.detect{|warn| warn.problem.downcase == problem.downcase &&
+ (!severity || warn.severity == severity)}
+ end
+ end
+end
+
+class Test::Unit::TestCase
+ include QueryReviewer
+end
View
1 uninstall.rb
@@ -0,0 +1 @@
+# Uninstall hook code here

0 comments on commit 9485a8f

Please sign in to comment.