sql_safety_net 1.1.11 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|