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
data/spec/query_analysis_spec.rb
CHANGED
@@ -3,69 +3,111 @@ require 'spec_helper'
|
|
3
3
|
describe SqlSafetyNet::QueryAnalysis do
|
4
4
|
|
5
5
|
let(:analysis){ SqlSafetyNet::QueryAnalysis.new }
|
6
|
+
let(:query_info){ SqlSafetyNet::QueryInfo.new("SELECT * FROM *", :elapsed_time => 0.1, :rows => 2, :result_size => 500) }
|
6
7
|
|
7
|
-
it "should
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
it "should instantiate a query analysis inside a block" do
|
9
|
+
SqlSafetyNet::QueryAnalysis.current.should == nil
|
10
|
+
result = SqlSafetyNet::QueryAnalysis.capture do |analysis_1|
|
11
|
+
analysis_1.should be_a(SqlSafetyNet::QueryAnalysis)
|
12
|
+
SqlSafetyNet::QueryAnalysis.capture do |analysis_2|
|
13
|
+
analysis_2.should be_a(SqlSafetyNet::QueryAnalysis)
|
14
|
+
"hello"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
SqlSafetyNet::QueryAnalysis.current.should == nil
|
18
|
+
result.should == "hello"
|
18
19
|
end
|
19
20
|
|
20
|
-
it "should
|
21
|
-
analysis.
|
22
|
-
analysis.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
it "should track the number of queries" do
|
22
|
+
analysis.queries.should == []
|
23
|
+
analysis.total_queries.should == 0
|
24
|
+
analysis << query_info
|
25
|
+
analysis.queries.should == [query_info]
|
26
|
+
analysis.total_queries.should == 1
|
27
|
+
analysis << query_info
|
28
|
+
analysis.queries.should == [query_info, query_info]
|
29
|
+
analysis.total_queries.should == 2
|
29
30
|
end
|
30
31
|
|
31
|
-
it "should
|
32
|
-
analysis.
|
33
|
-
analysis
|
34
|
-
analysis.should
|
35
|
-
analysis
|
32
|
+
it "should track the elasped time of all queries" do
|
33
|
+
analysis.elapsed_time.should == 0.0
|
34
|
+
analysis << query_info
|
35
|
+
analysis.elapsed_time.should == 0.1
|
36
|
+
analysis << query_info
|
37
|
+
analysis.elapsed_time.should == 0.2
|
36
38
|
end
|
37
39
|
|
38
|
-
it "should
|
39
|
-
analysis.rows
|
40
|
-
analysis
|
41
|
-
analysis.
|
40
|
+
it "should track the rows of all queries" do
|
41
|
+
analysis.rows.should == 0
|
42
|
+
analysis << query_info
|
43
|
+
analysis.rows.should == 2
|
44
|
+
analysis << query_info
|
45
|
+
analysis.rows.should == 4
|
42
46
|
end
|
43
47
|
|
44
|
-
it "should
|
45
|
-
analysis.
|
46
|
-
analysis
|
47
|
-
analysis.
|
48
|
+
it "should track the result size of all queries" do
|
49
|
+
analysis.result_size.should == 0
|
50
|
+
analysis << query_info
|
51
|
+
analysis.result_size.should == 500
|
52
|
+
analysis << query_info
|
53
|
+
analysis.result_size.should == 1000
|
48
54
|
end
|
49
55
|
|
50
|
-
|
51
|
-
|
52
|
-
|
56
|
+
describe "flags" do
|
57
|
+
it "should determine the number of queries that have alerts" do
|
58
|
+
analysis.alerted_queries.should == 0
|
59
|
+
analysis << query_info
|
60
|
+
analysis.alerted_queries.should == 0
|
61
|
+
analysis << SqlSafetyNet::QueryInfo.new("SELECT *", :alerts => ["boom"])
|
62
|
+
analysis.alerted_queries.should == 1
|
53
63
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
|
65
|
+
it "should determine if any queries have alerts" do
|
66
|
+
analysis.alerts?.should == false
|
67
|
+
analysis << query_info
|
68
|
+
analysis.alerts?.should == false
|
69
|
+
analysis << SqlSafetyNet::QueryInfo.new("SELECT *", :alerts => ["boom"])
|
70
|
+
analysis.alerts?.should == true
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should determine if too many queries have been made" do
|
74
|
+
SqlSafetyNet.override_config do |config|
|
75
|
+
config.query_limit = 1
|
76
|
+
analysis << query_info
|
77
|
+
analysis.too_many_queries?.should == false
|
78
|
+
analysis << query_info
|
79
|
+
analysis.too_many_queries?.should == true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should determine if too many rows have been returned" do
|
84
|
+
SqlSafetyNet.override_config do |config|
|
85
|
+
config.returned_rows_limit = 3
|
86
|
+
analysis << query_info
|
87
|
+
analysis.too_many_rows?.should == false
|
88
|
+
analysis << query_info
|
89
|
+
analysis.too_many_rows?.should == true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should determine if the results are too big" do
|
94
|
+
SqlSafetyNet.override_config do |config|
|
95
|
+
config.result_size_limit = 600
|
96
|
+
analysis << query_info
|
97
|
+
analysis.results_too_big?.should == false
|
98
|
+
analysis << query_info
|
99
|
+
analysis.results_too_big?.should == true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should determine if the queries take too much time" do
|
104
|
+
SqlSafetyNet.override_config do |config|
|
105
|
+
config.elapsed_time_limit = 0.15
|
106
|
+
analysis << query_info
|
107
|
+
analysis.too_much_time?.should == false
|
108
|
+
analysis << query_info
|
109
|
+
analysis.too_much_time?.should == true
|
65
110
|
end
|
66
|
-
val.should == "woot"
|
67
111
|
end
|
68
|
-
query = analysis.non_flagged_queries.first
|
69
|
-
query[:cached].should == true
|
70
112
|
end
|
71
113
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SqlSafetyNet::QueryInfo do
|
4
|
+
|
5
|
+
describe "constructor" do
|
6
|
+
it "should keep the sql passed in to the constructor" do
|
7
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *")
|
8
|
+
query_info.sql.should == "SELECT * FROM *"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should keep the elapsed time passed in :elapsed_time option" do
|
12
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *", :elapsed_time => 0.1)
|
13
|
+
query_info.elapsed_time.should == 0.1
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should keep the rows passed in :rows option" do
|
17
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *", :rows => 10)
|
18
|
+
query_info.rows.should == 10
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should keep the result_size passed in :result_size option" do
|
22
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *", :result_size => 100)
|
23
|
+
query_info.result_size.should == 100
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should keep the cached value passed in :cached option" do
|
27
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *", :cached => true)
|
28
|
+
query_info.cached?.should == true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "alerts" do
|
33
|
+
it "should not have any alerts by default" do
|
34
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *")
|
35
|
+
query_info.alerts.should == []
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should indicate if it has any alerts" do
|
39
|
+
query_info = SqlSafetyNet::QueryInfo.new("SELECT * FROM *")
|
40
|
+
query_info.alerts?.should == false
|
41
|
+
query_info.alerts << "problem"
|
42
|
+
query_info.alerts?.should == true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
active_record_version = ENV['ACTIVE_RECORD_VERSION'] || ">=
|
3
|
+
active_record_version = ENV['ACTIVE_RECORD_VERSION'] || ">=3.2.0"
|
4
4
|
gem 'rails', active_record_version
|
5
5
|
gem 'activerecord', active_record_version
|
6
6
|
gem 'activesupport', active_record_version
|
7
7
|
require 'active_support/all'
|
8
8
|
require 'active_record'
|
9
|
-
puts "Testing against #{ActiveRecord::VERSION::STRING}"
|
10
|
-
|
11
|
-
require 'mysql'
|
12
|
-
require 'pg'
|
9
|
+
puts "Testing against activerecord #{ActiveRecord::VERSION::STRING}"
|
13
10
|
|
14
11
|
begin
|
15
12
|
require 'simplecov'
|
@@ -24,33 +21,17 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'sql_saf
|
|
24
21
|
|
25
22
|
module SqlSafetyNet
|
26
23
|
class TestModel < ActiveRecord::Base
|
27
|
-
def self.
|
24
|
+
def self.create_table
|
28
25
|
connection.create_table(table_name) do |t|
|
29
26
|
t.string :name
|
27
|
+
t.integer :value
|
30
28
|
end unless table_exists?
|
31
29
|
end
|
32
|
-
|
33
|
-
def self.drop_tables
|
34
|
-
connection.drop_table(table_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.database_config
|
38
|
-
database_yml = File.expand_path(File.join(File.dirname(__FILE__), 'database.yml'))
|
39
|
-
raise "You must create a database.yml file in the spec directory (see example_database.yml)" unless File.exist?(database_yml)
|
40
|
-
YAML.load_file(database_yml)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class MysqlTestModel < TestModel
|
45
|
-
establish_connection(database_config['mysql'])
|
46
|
-
end
|
47
|
-
|
48
|
-
class PostgresqlTestModel < TestModel
|
49
|
-
establish_connection(database_config['postgresql'])
|
50
30
|
end
|
51
31
|
|
52
|
-
|
53
|
-
|
32
|
+
TestModel.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
33
|
+
TestModel.create_table
|
54
34
|
end
|
55
35
|
|
56
36
|
ActiveSupport::Cache::Store.send(:include, SqlSafetyNet::CacheStore)
|
37
|
+
SqlSafetyNet.enable_on_connection_adapter!(SqlSafetyNet::TestModel.connection.class)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SqlSafetyNet do
|
4
|
+
|
5
|
+
it "should have a singleton config" do
|
6
|
+
config = SqlSafetyNet.config
|
7
|
+
config.should be_a(SqlSafetyNet::Configuration)
|
8
|
+
config.object_id.should == SqlSafetyNet.config.object_id
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be able to override the config in a block" do
|
12
|
+
original_config = SqlSafetyNet.config
|
13
|
+
original_style = original_config.style.dup
|
14
|
+
SqlSafetyNet.override_config do |config|
|
15
|
+
config.object_id.should_not == original_config.object_id
|
16
|
+
config.style = {"top" => "5px"}
|
17
|
+
end
|
18
|
+
original_config.style.should == original_style
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
metadata
CHANGED
@@ -1,102 +1,111 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: sql_safety_net
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 2.0.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Brian Durand
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2012-06-14 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: activesupport
|
16
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
17
24
|
none: false
|
18
|
-
requirements:
|
19
|
-
- -
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 7
|
29
|
+
segments:
|
30
|
+
- 3
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
version: 3.0.0
|
22
34
|
type: :runtime
|
23
|
-
|
24
|
-
|
25
|
-
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
26
37
|
name: activerecord
|
27
|
-
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
40
|
none: false
|
29
|
-
requirements:
|
30
|
-
- -
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 7
|
45
|
+
segments:
|
46
|
+
- 3
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
version: 3.0.0
|
33
50
|
type: :runtime
|
34
|
-
|
35
|
-
|
36
|
-
- !ruby/object:Gem::Dependency
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
37
53
|
name: actionpack
|
38
|
-
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
39
56
|
none: false
|
40
|
-
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 7
|
61
|
+
segments:
|
62
|
+
- 3
|
63
|
+
- 0
|
64
|
+
- 0
|
65
|
+
version: 3.0.0
|
44
66
|
type: :runtime
|
45
|
-
|
46
|
-
|
47
|
-
- !ruby/object:Gem::Dependency
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
48
69
|
name: rspec
|
49
|
-
requirement: &70157641408520 !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
|
-
requirements:
|
52
|
-
- - ! '>='
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 2.0.0
|
55
|
-
type: :development
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: *70157641408520
|
58
|
-
- !ruby/object:Gem::Dependency
|
59
|
-
name: mysql
|
60
|
-
requirement: &70157641408000 !ruby/object:Gem::Requirement
|
61
|
-
none: false
|
62
|
-
requirements:
|
63
|
-
- - ! '>='
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
version: '0'
|
66
|
-
type: :development
|
67
70
|
prerelease: false
|
68
|
-
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: pg
|
71
|
-
requirement: &70157641407460 !ruby/object:Gem::Requirement
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
|
-
requirements:
|
74
|
-
- -
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 15
|
77
|
+
segments:
|
78
|
+
- 2
|
79
|
+
- 0
|
80
|
+
- 0
|
81
|
+
version: 2.0.0
|
77
82
|
type: :development
|
78
|
-
|
79
|
-
|
80
|
-
- !ruby/object:Gem::Dependency
|
83
|
+
version_requirements: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
81
85
|
name: sqlite3-ruby
|
82
|
-
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
88
|
none: false
|
84
|
-
requirements:
|
85
|
-
- -
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
88
96
|
type: :development
|
89
|
-
|
90
|
-
version_requirements: *70157641406900
|
97
|
+
version_requirements: *id005
|
91
98
|
description: Debug SQL statements in ActiveRecord by displaying warnings on bad queries.
|
92
|
-
email:
|
99
|
+
email:
|
93
100
|
- mdobrota@tribune.com
|
94
101
|
- ddpr@tribune.com
|
95
102
|
executables: []
|
103
|
+
|
96
104
|
extensions: []
|
97
|
-
|
105
|
+
|
106
|
+
extra_rdoc_files:
|
98
107
|
- README.rdoc
|
99
|
-
files:
|
108
|
+
files:
|
100
109
|
- License.txt
|
101
110
|
- README.rdoc
|
102
111
|
- Rakefile
|
@@ -104,45 +113,59 @@ files:
|
|
104
113
|
- lib/sql_safety_net/cache_store.rb
|
105
114
|
- lib/sql_safety_net/configuration.rb
|
106
115
|
- lib/sql_safety_net/connection_adapter.rb
|
107
|
-
- lib/sql_safety_net/
|
108
|
-
- lib/sql_safety_net/
|
116
|
+
- lib/sql_safety_net/explain_plan.rb
|
117
|
+
- lib/sql_safety_net/explain_plan/mysql.rb
|
118
|
+
- lib/sql_safety_net/explain_plan/postgresql.rb
|
119
|
+
- lib/sql_safety_net/formatter.rb
|
120
|
+
- lib/sql_safety_net/middleware.rb
|
109
121
|
- lib/sql_safety_net/query_analysis.rb
|
110
|
-
- lib/sql_safety_net/
|
122
|
+
- lib/sql_safety_net/query_info.rb
|
111
123
|
- spec/cache_store_spec.rb
|
112
124
|
- spec/configuration_spec.rb
|
113
125
|
- spec/connection_adapter_spec.rb
|
114
|
-
- spec/
|
115
|
-
- spec/
|
116
|
-
- spec/
|
126
|
+
- spec/explain_plan/mysql_spec.rb
|
127
|
+
- spec/explain_plan/postgresql_spec.rb
|
128
|
+
- spec/formatter_spec.rb
|
129
|
+
- spec/middleware_spec.rb
|
117
130
|
- spec/query_analysis_spec.rb
|
118
|
-
- spec/
|
131
|
+
- spec/query_info_spec.rb
|
119
132
|
- spec/spec_helper.rb
|
133
|
+
- spec/sql_safety_net_spec.rb
|
120
134
|
homepage:
|
121
135
|
licenses: []
|
136
|
+
|
122
137
|
post_install_message:
|
123
|
-
rdoc_options:
|
138
|
+
rdoc_options:
|
124
139
|
- --line-numbers
|
125
140
|
- --inline-source
|
126
141
|
- --main
|
127
142
|
- README.rdoc
|
128
|
-
require_paths:
|
143
|
+
require_paths:
|
129
144
|
- lib
|
130
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
146
|
none: false
|
132
|
-
requirements:
|
133
|
-
- -
|
134
|
-
- !ruby/object:Gem::Version
|
135
|
-
|
136
|
-
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
hash: 3
|
151
|
+
segments:
|
152
|
+
- 0
|
153
|
+
version: "0"
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
155
|
none: false
|
138
|
-
requirements:
|
139
|
-
- -
|
140
|
-
- !ruby/object:Gem::Version
|
141
|
-
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
hash: 3
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
142
163
|
requirements: []
|
164
|
+
|
143
165
|
rubyforge_project:
|
144
166
|
rubygems_version: 1.8.10
|
145
167
|
signing_key:
|
146
168
|
specification_version: 3
|
147
169
|
summary: Debug SQL statements in ActiveRecord
|
148
170
|
test_files: []
|
171
|
+
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module SqlSafetyNet
|
2
|
-
module ConnectionAdapter
|
3
|
-
# Logic for analyzing MySQL query plans.
|
4
|
-
module MysqlAdapter
|
5
|
-
def analyze_query(sql, name, *args)
|
6
|
-
if select_statement?(sql)
|
7
|
-
query_plan = select_without_sql_safety_net("EXPLAIN #{sql}", "EXPLAIN", *args)
|
8
|
-
query_plan_flags = analyze_query_plan(query_plan)
|
9
|
-
unless query_plan_flags.empty?
|
10
|
-
@logger.debug("Flagged query plan #{name} (#{query_plan_flags.join(', ')}): #{query_plan.inspect}") if @logger
|
11
|
-
return {:query_plan => query_plan, :flags => query_plan_flags}
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def analyze_query_plan(query_plan)
|
19
|
-
flagged = []
|
20
|
-
query_plan.each do |row|
|
21
|
-
select_type = (row['select_type'] || '').downcase
|
22
|
-
type = (row['type'] || '').downcase
|
23
|
-
rows = row['rows'].to_i
|
24
|
-
extra = (row['Extra'] || '').downcase
|
25
|
-
key = row['key']
|
26
|
-
possible_keys = row['possible_keys']
|
27
|
-
|
28
|
-
flagged << 'table scan' if (type.include?('all') and rows > SqlSafetyNet.config.table_scan_limit)
|
29
|
-
flagged << 'fulltext search' if type.include?('fulltext')
|
30
|
-
flagged << 'no index used' if (key.blank? and rows > SqlSafetyNet.config.table_scan_limit)
|
31
|
-
flagged << 'no indexes possible' if (possible_keys.blank? and rows > SqlSafetyNet.config.table_scan_limit)
|
32
|
-
flagged << 'dependent subquery' if select_type.include?('dependent')
|
33
|
-
flagged << 'uncacheable subquery' if select_type.include?('uncacheable')
|
34
|
-
flagged << 'full scan on null key' if extra.include?('full scan on null key')
|
35
|
-
flagged << "uses temporary table for #{rows} rows" if extra.include?('using temporary') and rows > SqlSafetyNet.config.temporary_table_limit
|
36
|
-
flagged << "uses filesort for #{rows} rows" if extra.include?('filesort') and rows > SqlSafetyNet.config.filesort_limit
|
37
|
-
flagged << "examines #{rows} rows" if rows > SqlSafetyNet.config.examine_rows_limit
|
38
|
-
end
|
39
|
-
return flagged
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
module SqlSafetyNet
|
2
|
-
module ConnectionAdapter
|
3
|
-
# Logic for analyzing a query plan from PostgreSQL. These plans are not terribly useful and sometimes
|
4
|
-
# the statistics are off, so take them with a grain of salt.
|
5
|
-
module PostgreSQLAdapter
|
6
|
-
def analyze_query(sql, name, *args)
|
7
|
-
if select_statement?(sql)
|
8
|
-
query_plan = select_without_sql_safety_net("EXPLAIN #{sql}", "EXPLAIN", *args)
|
9
|
-
query_plan_flags = analyze_query_plan(query_plan)
|
10
|
-
unless query_plan_flags.empty?
|
11
|
-
@logger.debug("Flagged query plan #{name} (#{query_plan_flags.join(', ')}): #{query_plan.inspect}") if @logger
|
12
|
-
return {:query_plan => query_plan, :flags => query_plan_flags}
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def analyze_query_plan(query_plan)
|
20
|
-
query_plan = query_plan.collect{|r| r.values.first}
|
21
|
-
flagged = []
|
22
|
-
limit = nil
|
23
|
-
query_plan.each do |row|
|
24
|
-
row_count = query_plan_rows_value(row)
|
25
|
-
row_count = [limit, row_count].min if limit
|
26
|
-
if row =~ /^(\s|(->))*Limit\s/
|
27
|
-
limit = row_count
|
28
|
-
elsif row =~ /^(\s|(->))*Seq Scan/
|
29
|
-
flagged << 'table scan' if row_count > SqlSafetyNet.config.table_scan_limit
|
30
|
-
elsif row_count > SqlSafetyNet.config.examine_rows_limit
|
31
|
-
flagged << "examines #{row_count} rows"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
return flagged
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def query_plan_rows_value(plan_row)
|
40
|
-
if plan_row.match(/\brows=(\d+)/)
|
41
|
-
return $~[1].to_i
|
42
|
-
else
|
43
|
-
return 0
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|