sql_safety_net 1.1.11 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +32 -11
- data/Rakefile +3 -5
- data/lib/sql_safety_net/cache_store.rb +17 -7
- data/lib/sql_safety_net/configuration.rb +35 -46
- data/lib/sql_safety_net/connection_adapter.rb +49 -80
- data/lib/sql_safety_net/explain_plan/mysql.rb +33 -0
- data/lib/sql_safety_net/explain_plan/postgresql.rb +30 -0
- data/lib/sql_safety_net/explain_plan.rb +24 -0
- data/lib/sql_safety_net/formatter.rb +178 -0
- data/lib/sql_safety_net/middleware.rb +41 -0
- data/lib/sql_safety_net/query_analysis.rb +52 -49
- data/lib/sql_safety_net/query_info.rb +33 -0
- data/lib/sql_safety_net.rb +68 -17
- data/spec/cache_store_spec.rb +8 -17
- data/spec/configuration_spec.rb +63 -58
- data/spec/connection_adapter_spec.rb +109 -114
- data/spec/explain_plan/mysql_spec.rb +113 -0
- data/spec/explain_plan/postgresql_spec.rb +44 -0
- data/spec/formatter_spec.rb +46 -0
- data/spec/middleware_spec.rb +90 -0
- data/spec/query_analysis_spec.rb +92 -50
- data/spec/query_info_spec.rb +46 -0
- data/spec/spec_helper.rb +7 -26
- data/spec/sql_safety_net_spec.rb +21 -0
- metadata +112 -89
- data/lib/sql_safety_net/connection_adapter/mysql_adapter.rb +0 -43
- data/lib/sql_safety_net/connection_adapter/postgresql_adapter.rb +0 -48
- data/lib/sql_safety_net/rack_handler.rb +0 -196
- data/spec/example_database.yml +0 -14
- data/spec/mysql_connection_adapter_spec.rb +0 -32
- data/spec/postgres_connection_adapter_spec.rb +0 -43
- data/spec/rack_handler_spec.rb +0 -193
@@ -1,196 +0,0 @@
|
|
1
|
-
require 'rack'
|
2
|
-
|
3
|
-
module SqlSafetyNet
|
4
|
-
# This Rack handler must be added to the middleware stack in order for query analysis to be output.
|
5
|
-
# If the configuration option for debug is set to true, it will add response headers indicating information
|
6
|
-
# about the queries executed in the course of the request.
|
7
|
-
class RackHandler
|
8
|
-
|
9
|
-
X_SQL_SAFETY_NET_HEADER = "X-SqlSafetyNet".freeze
|
10
|
-
HTML_CONTENT_TYPE_PATTERN = /text\/(x?)html/i
|
11
|
-
XML_CONTENT_TYPE_PATTERN = /application\/xml/i
|
12
|
-
|
13
|
-
def initialize(app, logger = Rails.logger)
|
14
|
-
@app = app
|
15
|
-
@logger = logger
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(env)
|
19
|
-
response = nil
|
20
|
-
analysis = QueryAnalysis.analyze do
|
21
|
-
response = @app.call(env)
|
22
|
-
end
|
23
|
-
|
24
|
-
if @logger && (analysis.too_many_selects? || analysis.too_many_rows?)
|
25
|
-
request = Rack::Request.new(env)
|
26
|
-
@logger.warn("Excess database usage: request generated #{analysis.selects} queries and returned #{analysis.rows} rows [#{request.request_method} #{request.url}]")
|
27
|
-
end
|
28
|
-
|
29
|
-
# Add a response header that contains a summary of the debug info
|
30
|
-
if SqlSafetyNet.config.header? || SqlSafetyNet.config.debug?
|
31
|
-
headers = response[1]
|
32
|
-
headers[X_SQL_SAFETY_NET_HEADER] = "selects=#{analysis.selects}; rows=#{analysis.rows}; elapsed_time=#{(analysis.elapsed_time * 1000).round}; flagged_queries=#{analysis.flagged_queries.size}" if headers
|
33
|
-
end
|
34
|
-
|
35
|
-
if SqlSafetyNet.config.debug?
|
36
|
-
wrapped_response = Rack::Response.new(response[2], response[0], response[1])
|
37
|
-
if analysis.flagged? || SqlSafetyNet.config.always_show?
|
38
|
-
request = Rack::Request.new(env)
|
39
|
-
# Ignore Ajax calls
|
40
|
-
unless request.xhr?
|
41
|
-
# Only if content type is text/html
|
42
|
-
type = wrapped_response.content_type
|
43
|
-
if type.nil? || type.to_s.match(HTML_CONTENT_TYPE_PATTERN)
|
44
|
-
wrapped_response.write(flagged_sql_html(analysis))
|
45
|
-
elsif type.to_s.match(XML_CONTENT_TYPE_PATTERN)
|
46
|
-
wrapped_response.write(xml_comment(flagged_sql_text(analysis)))
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
response = wrapped_response.finish
|
51
|
-
end
|
52
|
-
|
53
|
-
response
|
54
|
-
end
|
55
|
-
|
56
|
-
def flagged_sql_html(analysis)
|
57
|
-
flagged_html = ''
|
58
|
-
cached_selects = 0
|
59
|
-
cached_rows = 0
|
60
|
-
cached_elapsed_time = 0.0
|
61
|
-
|
62
|
-
if analysis.flagged_queries?
|
63
|
-
flagged_html << '<div style="color:#C00;">'
|
64
|
-
flagged_html << "<div style=\"font-weight:bold; margin-bottom:10px;\">#{analysis.flagged_queries.size == 1 ? 'This query has' : "These #{analysis.flagged_queries.size} queries have"} flagged query plans:</div>"
|
65
|
-
analysis.flagged_queries.each do |query|
|
66
|
-
if query[:cached]
|
67
|
-
cached_selects += 1
|
68
|
-
cached_rows += query[:rows]
|
69
|
-
cached_elapsed_time += query[:elapsed_time]
|
70
|
-
end
|
71
|
-
flagged_html << '<div style="margin-bottom:10px;">'
|
72
|
-
flagged_html << "<div style=\"font-weight:bold; margin-bottom: 5px;\">#{query[:rows]} rows returned, #{(query[:elapsed_time] * 1000).round} ms#{ " <span style='color:teal;'>(CACHED)</span>" if query[:cached]}</div>"
|
73
|
-
flagged_html << "<div style=\"font-weight:bold; margin-bottom: 5px;\">#{query[:flags].join(', ')}</div>"
|
74
|
-
flagged_html << "<div style=\"margin-bottom: 5px;\">#{Rack::Utils.escape_html(query[:sql])}</div>"
|
75
|
-
flagged_html << "<div style=\"margin-bottom: 5px;\">Query Plan: #{Rack::Utils.escape_html(query[:query_plan].inspect)}</div>" if query[:query_plan]
|
76
|
-
flagged_html << '</div>'
|
77
|
-
end
|
78
|
-
flagged_html << '</div>'
|
79
|
-
end
|
80
|
-
|
81
|
-
if analysis.too_many_selects? || analysis.too_many_rows? || SqlSafetyNet.config.always_show?
|
82
|
-
flagged_html << "<div style=\"font-weight:bold; margin-bottom:10px;\">#{analysis.non_flagged_queries.size == 1 ? 'This query' : "These #{analysis.non_flagged_queries.size} queries"} did not have flagged query plans:</div>"
|
83
|
-
analysis.non_flagged_queries.each do |query|
|
84
|
-
if query[:cached]
|
85
|
-
cached_selects += 1
|
86
|
-
cached_rows += query[:rows]
|
87
|
-
cached_elapsed_time += query[:elapsed_time]
|
88
|
-
end
|
89
|
-
flagged_html << '<div style="margin-bottom:10px;">'
|
90
|
-
flagged_html << "<div style=\"font-weight:bold; margin-bottom: 5px;\">#{query[:rows]} rows returned, #{(query[:elapsed_time] * 1000).round} ms#{ " <span style='color:teal;'>(CACHED)</span>" if query[:cached]}</div>"
|
91
|
-
flagged_html << "<div style=\"margin-bottom: 5px;\">#{Rack::Utils.escape_html(query[:sql])}</div>"
|
92
|
-
flagged_html << '</div>'
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
color_scheme = '#060'
|
97
|
-
if analysis.flagged_queries?
|
98
|
-
color_scheme = '#C00'
|
99
|
-
elsif analysis.flagged?
|
100
|
-
color_scheme = '#C60'
|
101
|
-
end
|
102
|
-
label = (analysis.flagged?) ? 'SQL WARNING' : 'SQL INFO'
|
103
|
-
|
104
|
-
cache_html = nil
|
105
|
-
if cached_selects > 0
|
106
|
-
cache_html = <<-EOS
|
107
|
-
<div style="margin-bottom:10px; font-weight:bold;">
|
108
|
-
Some of the queries will be cached.
|
109
|
-
<div style="color:#C00;">
|
110
|
-
Uncached: #{analysis.selects - cached_selects} selects, #{analysis.rows - cached_rows} rows, #{((analysis.elapsed_time - cached_elapsed_time) * 1000).round} ms
|
111
|
-
</div>
|
112
|
-
<div style="color:teal;">
|
113
|
-
Cached: #{cached_selects} selects, #{cached_rows} rows, #{(cached_elapsed_time * 1000).round} ms
|
114
|
-
</div>
|
115
|
-
</div>
|
116
|
-
EOS
|
117
|
-
end
|
118
|
-
|
119
|
-
<<-EOS
|
120
|
-
<div id="sql_safety_net_warning" style="font-family:sans-serif; font-size:10px; position:fixed; z-index:999999999; text-align:left; #{SqlSafetyNet.config.position}">
|
121
|
-
<div style="background-color:#{color_scheme}; color:#FFF; padding:4px; width:160px; float:right;">
|
122
|
-
<a href="javascript:void(document.getElementById('sql_safety_net_warning').style.display = 'none')" style="text-decoration:none; float:right; display:block; font-size:9px;"><span style="color:#FFF; text-decoration:none; font-weight:bold;">×</span></a>
|
123
|
-
<a href="javascript:void(document.getElementById('sql_safety_net_flagged_queries').style.display = (document.getElementById('sql_safety_net_flagged_queries').style.display == 'block' ? 'none' : 'block'))" style="text-decoration:none;">
|
124
|
-
<span style="color:#FFF; text-decoration:none; font-weight:bold;">#{label} »</span>
|
125
|
-
</a>
|
126
|
-
<div>#{analysis.selects} selects, #{analysis.rows} rows, #{(analysis.elapsed_time * 1000).round} ms</div>
|
127
|
-
</div>
|
128
|
-
<div id="sql_safety_net_flagged_queries" style="clear:right; display:none; width:500px; padding:2px; border:1px solid #{color_scheme}; background-color:#FFF; color:#000; overflow:auto; max-height:500px;">
|
129
|
-
<div style="margin-bottom:10px; font-weight:bold;">
|
130
|
-
There are #{analysis.selects} queries on this page that return #{analysis.rows} rows and took #{(analysis.elapsed_time * 1000).round} ms to execute.
|
131
|
-
</div>
|
132
|
-
#{cache_html}
|
133
|
-
#{flagged_html}
|
134
|
-
</div>
|
135
|
-
</div>
|
136
|
-
EOS
|
137
|
-
end
|
138
|
-
|
139
|
-
def flagged_sql_text(analysis)
|
140
|
-
flagged_text = ''
|
141
|
-
cached_selects = 0
|
142
|
-
cached_rows = 0
|
143
|
-
cached_elapsed_time = 0.0
|
144
|
-
|
145
|
-
if analysis.flagged_queries?
|
146
|
-
flagged_text << "#{analysis.flagged_queries.size == 1 ? 'This query has' : "These #{analysis.flagged_queries.size} queries have"} flagged query plans:\n\n"
|
147
|
-
analysis.flagged_queries.each do |query|
|
148
|
-
if query[:cached]
|
149
|
-
cached_selects += 1
|
150
|
-
cached_rows += query[:rows]
|
151
|
-
cached_elapsed_time += query[:elapsed_time]
|
152
|
-
end
|
153
|
-
flagged_text << "#{query[:rows]} rows returned, #{(query[:elapsed_time] * 1000).round} ms#{ " (CACHED)" if query[:cached]}\n"
|
154
|
-
flagged_text << "#{query[:flags].join(', ')}\n\n"
|
155
|
-
flagged_text << "#{query[:sql]}\n\n"
|
156
|
-
flagged_text << "Query Plan: #{Rack::Utils.escape_html(query[:query_plan].inspect)}\n" if query[:query_plan]
|
157
|
-
flagged_text << "\n"
|
158
|
-
end
|
159
|
-
flagged_text << "\n"
|
160
|
-
end
|
161
|
-
|
162
|
-
if analysis.too_many_selects? || analysis.too_many_rows? || SqlSafetyNet.config.always_show?
|
163
|
-
flagged_text << "#{analysis.non_flagged_queries.size == 1 ? 'This query' : "These #{analysis.non_flagged_queries.size} queries"} did not have flagged query plans:\n\n"
|
164
|
-
analysis.non_flagged_queries.each do |query|
|
165
|
-
if query[:cached]
|
166
|
-
cached_selects += 1
|
167
|
-
cached_rows += query[:rows]
|
168
|
-
cached_elapsed_time += query[:elapsed_time]
|
169
|
-
end
|
170
|
-
flagged_text << "#{query[:rows]} rows returned, #{(query[:elapsed_time] * 1000).round} ms#{ " (CACHED)" if query[:cached]}\n\n"
|
171
|
-
flagged_text << "#{query[:sql]}\n\n"
|
172
|
-
flagged_text << "\n"
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
label = (analysis.flagged?) ? 'SQL WARNING' : 'SQL INFO'
|
177
|
-
|
178
|
-
cache_text = ""
|
179
|
-
if cached_selects > 0
|
180
|
-
cache_text << "Some of the queries will be cached.\n\n"
|
181
|
-
cache_text << "Uncached: #{analysis.selects - cached_selects} selects, #{analysis.rows - cached_rows} rows, #{((analysis.elapsed_time - cached_elapsed_time) * 1000).round} ms\n"
|
182
|
-
cache_text << "Cached: #{cached_selects} selects, #{cached_rows} rows, #{(cached_elapsed_time * 1000).round} ms\n\n"
|
183
|
-
end
|
184
|
-
|
185
|
-
text = "SqlSafetyNet\n\n"
|
186
|
-
text << "There are #{analysis.selects} queries on this page that return #{analysis.rows} rows and took #{(analysis.elapsed_time * 1000).round} ms to execute.\n\n"
|
187
|
-
text << cache_text
|
188
|
-
text << flagged_text
|
189
|
-
text
|
190
|
-
end
|
191
|
-
|
192
|
-
def xml_comment(text)
|
193
|
-
"<!-- #{text.gsub('-->', '\\-\\->')} -->"
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
data/spec/example_database.yml
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# Copy this file to database.yml to run the test with connection settings for you environment.
|
2
|
-
# You must create the database in both mysql and postgres.
|
3
|
-
|
4
|
-
mysql:
|
5
|
-
adapter: mysql
|
6
|
-
username: root
|
7
|
-
password: ""
|
8
|
-
database: sql_safety_net_test
|
9
|
-
|
10
|
-
postgresql:
|
11
|
-
adapter: postgresql
|
12
|
-
username: postgres
|
13
|
-
password: postgres
|
14
|
-
database: sql_safety_net_test
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe SqlSafetyNet::ConnectionAdapter::MysqlAdapter do
|
4
|
-
|
5
|
-
before(:each) do
|
6
|
-
@connection = SqlSafetyNet::MysqlTestModel.connection
|
7
|
-
end
|
8
|
-
|
9
|
-
it "should use query plan analysis to flag queries" do
|
10
|
-
query_plan = [{'type' => 'ALL', 'rows' => 200}]
|
11
|
-
@connection.should_receive(:select_without_sql_safety_net).with('EXPLAIN Select sql', 'EXPLAIN', []).and_return(query_plan)
|
12
|
-
@connection.should_receive(:analyze_query_plan).with(query_plan).and_return(["bad query"])
|
13
|
-
@connection.analyze_query('Select sql', 'name', []).should == {:query_plan => query_plan, :flags => ["bad query"]}
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should only analyze query plans for select statements" do
|
17
|
-
@connection.should_not_receive(:select_without_sql_safety_net)
|
18
|
-
@connection.should_not_receive(:analyze_query_plan)
|
19
|
-
@connection.analyze_query('Execute sql', 'name', []).should == nil
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should translate query plans into flags" do
|
23
|
-
@connection.send(:analyze_query_plan, [{'type' => 'ALL', 'rows' => 500}]).should == ["table scan", "no index used", "no indexes possible"]
|
24
|
-
@connection.send(:analyze_query_plan, [{'Extra' => 'using temporary table; using filesort', 'rows' => 200, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["uses temporary table for 200 rows", "uses filesort for 200 rows"]
|
25
|
-
@connection.send(:analyze_query_plan, [{'select_type' => 'dependent subquery', 'rows' => 20, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["dependent subquery"]
|
26
|
-
@connection.send(:analyze_query_plan, [{'select_type' => 'uncacheable subquery', 'rows' => 20, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["uncacheable subquery"]
|
27
|
-
@connection.send(:analyze_query_plan, [{'rows' => 20000, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["examines 20000 rows"]
|
28
|
-
@connection.send(:analyze_query_plan, [{'select_type' => 'SIMPLE', 'rows' => 1, 'key' => 'index', 'possible_keys' => 'index'}]).should == []
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
@@ -1,43 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe SqlSafetyNet::ConnectionAdapter::PostgreSQLAdapter do
|
4
|
-
|
5
|
-
before(:each) do
|
6
|
-
@connection = SqlSafetyNet::PostgresqlTestModel.connection
|
7
|
-
end
|
8
|
-
|
9
|
-
it "should use query plan analysis to flag queries" do
|
10
|
-
query_plan = [{"QUERY PLAN"=>"Limit (cost=0.00..0.06 rows=1 width=335)"}, {"QUERY PLAN"=>" -> Seq Scan on records (cost=0.00..12.20 rows=220 width=335)"}]
|
11
|
-
@connection.should_receive(:select_without_sql_safety_net).with('EXPLAIN Select sql', 'EXPLAIN', []).and_return(query_plan)
|
12
|
-
@connection.should_receive(:analyze_query_plan).with(query_plan).and_return(["bad query"])
|
13
|
-
@connection.analyze_query('Select sql', 'name', []).should == {:query_plan => query_plan, :flags => ["bad query"]}
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should only analyze query plans for select statements" do
|
17
|
-
@connection.should_not_receive(:select_without_sql_safety_net)
|
18
|
-
@connection.should_not_receive(:analyze_query_plan)
|
19
|
-
@connection.analyze_query('Execute sql', 'name', []).should == nil
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should not flag a small table scan" do
|
23
|
-
query_plan = [{"QUERY PLAN"=>"Seq Scan on records (cost=0.00..12.20 rows=10 width=335)"}]
|
24
|
-
@connection.send(:analyze_query_plan, query_plan).should == []
|
25
|
-
end
|
26
|
-
|
27
|
-
it "should flag a table scan" do
|
28
|
-
query_plan = [{"QUERY PLAN"=>"Seq Scan on records (cost=0.00..12.20 rows=1000000 width=335)"}]
|
29
|
-
@connection.send(:analyze_query_plan, query_plan).should == ["table scan"]
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should flag too many rows returned" do
|
33
|
-
query_plan = [{"QUERY PLAN"=>"Index Scan using records_pkey on records (cost=0.00..8.27 rows=1000000 width=336)"}]
|
34
|
-
@connection.send(:analyze_query_plan, query_plan).should == ["examines 1000000 rows"]
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should apply a limit to the rows returned" do
|
38
|
-
query_plan = [{"QUERY PLAN"=>"Limit (cost=0.00..0.06 rows=1 width=335)"}, {"QUERY PLAN"=>" -> Seq Scan on records (cost=0.00..12.20 rows=1000000 width=335)"}]
|
39
|
-
@connection.send(:analyze_query_plan, query_plan).should == []
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
data/spec/rack_handler_spec.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe SqlSafetyNet::RackHandler do
|
4
|
-
|
5
|
-
class SqlSafetyNet::TestApp
|
6
|
-
attr_accessor :response, :block
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@response = Rack::Response.new
|
10
|
-
end
|
11
|
-
|
12
|
-
def call (env)
|
13
|
-
@block.call(env) if @block
|
14
|
-
@response["Content-Type"] = env["response_content_type"] if env["response_content_type"]
|
15
|
-
@response.finish
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
before(:each) do
|
20
|
-
SqlSafetyNet.config.debug = true
|
21
|
-
SqlSafetyNet::QueryAnalysis.clear
|
22
|
-
end
|
23
|
-
|
24
|
-
let(:logger) do
|
25
|
-
logger = Object.new
|
26
|
-
def logger.warn (message)
|
27
|
-
# noop
|
28
|
-
end
|
29
|
-
logger
|
30
|
-
end
|
31
|
-
|
32
|
-
let(:app){ SqlSafetyNet::TestApp.new }
|
33
|
-
|
34
|
-
let(:handler){ SqlSafetyNet::RackHandler.new(app, logger) }
|
35
|
-
|
36
|
-
let(:env) do
|
37
|
-
{
|
38
|
-
"rack.url_scheme" => "http",
|
39
|
-
"PATH_INFO" => "/test",
|
40
|
-
"SERVER_PORT" => 80,
|
41
|
-
"HTTP_HOST" => "example.com",
|
42
|
-
"REQUEST_METHOD" => "GET"
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should append the bad queries to the HTML when debug is enabled and always_show is false" do
|
47
|
-
SqlSafetyNet.config.always_show = false
|
48
|
-
|
49
|
-
app.block = lambda do |env|
|
50
|
-
SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
|
51
|
-
end
|
52
|
-
app.response.write("Monkeys are neat.")
|
53
|
-
|
54
|
-
r = handler.call(env)
|
55
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
56
|
-
|
57
|
-
response.body.join("").should include('<div id="sql_safety_net_warning"')
|
58
|
-
response.body.join("").should include("Monkeys are neat.")
|
59
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should not append the bad queries to the HTML when there are none and always_show is false" do
|
63
|
-
SqlSafetyNet.config.always_show = false
|
64
|
-
|
65
|
-
app.response.write("Monkeys are neat.")
|
66
|
-
|
67
|
-
r = handler.call(env)
|
68
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
69
|
-
|
70
|
-
response.body.join("").should == "Monkeys are neat."
|
71
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=0"
|
72
|
-
end
|
73
|
-
|
74
|
-
it "should append the queries to the HTML when there are none and always_show is true" do
|
75
|
-
SqlSafetyNet.config.always_show = true
|
76
|
-
|
77
|
-
app.response.write("Monkeys are neat.")
|
78
|
-
|
79
|
-
r = handler.call(env)
|
80
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
81
|
-
|
82
|
-
response.body.join("").should include('<div id="sql_safety_net_warning"')
|
83
|
-
response.body.join("").should include("Monkeys are neat.")
|
84
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=0"
|
85
|
-
end
|
86
|
-
|
87
|
-
it "should append the bad queries to XML when debug is enabled" do
|
88
|
-
SqlSafetyNet.config.always_show = false
|
89
|
-
|
90
|
-
app.block = lambda do |env|
|
91
|
-
SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
|
92
|
-
end
|
93
|
-
app.response.write("<woot>Monkeys are neat.</woot>")
|
94
|
-
|
95
|
-
r = handler.call(env.merge("response_content_type" => "application/xml"))
|
96
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
97
|
-
|
98
|
-
response.body.join("").should_not include('<div id="sql_safety_net_warning"')
|
99
|
-
response.body.join("").should include("<!-- SqlSafetyNet")
|
100
|
-
response.body.join("").should include("<woot>Monkeys are neat.</woot>")
|
101
|
-
Hash.from_xml(response.body.join('')).should == {"woot" => "Monkeys are neat."}
|
102
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
|
103
|
-
end
|
104
|
-
|
105
|
-
it "should not append the bad queries to the HTML or add a response header if debug is not enabled" do
|
106
|
-
SqlSafetyNet.config.debug = false
|
107
|
-
|
108
|
-
app.block = lambda do |env|
|
109
|
-
SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
|
110
|
-
end
|
111
|
-
app.response.write("Monkeys are neat.")
|
112
|
-
|
113
|
-
r = handler.call(env)
|
114
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
115
|
-
|
116
|
-
response.body.join("").should == "Monkeys are neat."
|
117
|
-
response["X-SqlSafetyNet"].should == nil
|
118
|
-
end
|
119
|
-
|
120
|
-
it "should not append the bad queries to the HTML if not text/html" do
|
121
|
-
app.block = lambda do |env|
|
122
|
-
SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
|
123
|
-
end
|
124
|
-
app.response.write("Monkeys are neat.")
|
125
|
-
app.response["Content-Type"] = "text/plain"
|
126
|
-
|
127
|
-
r = handler.call(env)
|
128
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
129
|
-
|
130
|
-
response.body.join("").should == "Monkeys are neat."
|
131
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
|
132
|
-
end
|
133
|
-
|
134
|
-
it "should not append the bad queries to the HTML if Ajax request" do
|
135
|
-
app.block = lambda do |env|
|
136
|
-
SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
|
137
|
-
end
|
138
|
-
app.response.write("Monkeys are neat.")
|
139
|
-
app.response["Content-Type"] = "text/plain"
|
140
|
-
|
141
|
-
r = handler.call(env.merge("HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"))
|
142
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
143
|
-
|
144
|
-
response.body.join("").should == "Monkeys are neat."
|
145
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
|
146
|
-
end
|
147
|
-
|
148
|
-
it "should log too many selects even if debug is not enabled" do
|
149
|
-
SqlSafetyNet.config.debug = false
|
150
|
-
app.block = lambda do |env|
|
151
|
-
SqlSafetyNet::QueryAnalysis.current.selects = 1000
|
152
|
-
end
|
153
|
-
logger.should_receive(:warn).with("Excess database usage: request generated 1000 queries and returned 0 rows [GET http://example.com/test]")
|
154
|
-
r = handler.call(env)
|
155
|
-
end
|
156
|
-
|
157
|
-
it "should log too many rows even if debug is not enabled" do
|
158
|
-
SqlSafetyNet.config.debug = false
|
159
|
-
SqlSafetyNet.config.header = false
|
160
|
-
app.block = lambda do |env|
|
161
|
-
SqlSafetyNet::QueryAnalysis.current.rows = 10000
|
162
|
-
end
|
163
|
-
app.response.write("Monkeys are neat.")
|
164
|
-
|
165
|
-
logger.should_receive(:warn).with("Excess database usage: request generated 0 queries and returned 10000 rows [GET http://example.com/test]")
|
166
|
-
r = handler.call(env)
|
167
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
168
|
-
|
169
|
-
response.body.join("").should == "Monkeys are neat."
|
170
|
-
response["X-SqlSafetyNet"].should == nil
|
171
|
-
end
|
172
|
-
|
173
|
-
it "should log too many rows even if debug is not enabled" do
|
174
|
-
SqlSafetyNet.config.debug = false
|
175
|
-
SqlSafetyNet.config.header = true
|
176
|
-
app.block = lambda do |env|
|
177
|
-
SqlSafetyNet::QueryAnalysis.current.rows = 10
|
178
|
-
end
|
179
|
-
app.response.write("Monkeys are neat.")
|
180
|
-
r = handler.call(env)
|
181
|
-
response = Rack::Response.new(r[2], r[0], r[1])
|
182
|
-
|
183
|
-
response.body.join("").should == "Monkeys are neat."
|
184
|
-
response["X-SqlSafetyNet"].should == "selects=0; rows=10; elapsed_time=0; flagged_queries=0"
|
185
|
-
end
|
186
|
-
|
187
|
-
it "should not log warnings if there are none" do
|
188
|
-
SqlSafetyNet.config.debug = false
|
189
|
-
logger.should_not_receive(:warn)
|
190
|
-
r = handler.call(env)
|
191
|
-
end
|
192
|
-
|
193
|
-
end
|