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.
@@ -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 add a flagged query" do
8
- analysis.add_query("sql", "test", 10, 0.01, :query_plan => "this sucks", :flags => ["write better sql"])
9
- analysis.non_flagged_queries.should be_empty
10
- query = analysis.flagged_queries.first
11
- query[:sql].should == "sql"
12
- query[:name].should == "test"
13
- query[:rows].should == 10
14
- query[:elapsed_time].should == 0.01
15
- query[:query_plan].should == "this sucks"
16
- query[:flags].should == ["write better sql"]
17
- query[:cached].should == false
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 add a non-flagged query" do
21
- analysis.add_query("sql", "test", 10, 0.01, nil)
22
- analysis.flagged_queries.should be_empty
23
- query = analysis.non_flagged_queries.first
24
- query[:sql].should == "sql"
25
- query[:name].should == "test"
26
- query[:rows].should == 10
27
- query[:elapsed_time].should == 0.01
28
- query[:cached].should == false
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 determine if any query is flagged" do
32
- analysis.add_query("sql", "test", 10, 0.01, :query_plan => "this sucks", :flags => ["write better sql"])
33
- analysis.add_query("sql", "test", 10, 0.01, nil)
34
- analysis.should be_flagged
35
- analysis.flagged_queries?.should == true
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 determine if the rows selected is flagged" do
39
- analysis.rows = 1000000
40
- analysis.should be_flagged
41
- analysis.too_many_rows?.should == true
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 determine if the number of selects is flagged" do
45
- analysis.selects = 100000
46
- analysis.should be_flagged
47
- analysis.too_many_selects?.should == true
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
- it "should set the analysis object within a block" do
51
- SqlSafetyNet::QueryAnalysis.analyze do
52
- SqlSafetyNet::QueryAnalysis.current.should be_a(SqlSafetyNet::QueryAnalysis)
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
- SqlSafetyNet::QueryAnalysis.current.should == nil
55
- end
56
-
57
- it "should determine if a query is happening in a cache block" do
58
- cache = ActiveSupport::Cache::MemoryStore.new
59
- analysis = nil
60
- SqlSafetyNet::QueryAnalysis.analyze do
61
- val = cache.fetch("key") do
62
- analysis = SqlSafetyNet::QueryAnalysis.current
63
- analysis.add_query("sql", "test", 10, 0.01, nil)
64
- "woot"
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'] || ">=2.2.2"
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.create_tables
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
- SqlSafetyNet.config.enable_on(SqlSafetyNet::MysqlTestModel.connection.class)
53
- SqlSafetyNet.config.enable_on(SqlSafetyNet::PostgresqlTestModel.connection.class)
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
- version: 1.1.11
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
- date: 2012-06-06 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2012-06-14 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: activesupport
16
- requirement: &70157641410240 !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
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
- prerelease: false
24
- version_requirements: *70157641410240
25
- - !ruby/object:Gem::Dependency
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
26
37
  name: activerecord
27
- requirement: &70157641409720 !ruby/object:Gem::Requirement
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
28
40
  none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: 2.2.2
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
- prerelease: false
35
- version_requirements: *70157641409720
36
- - !ruby/object:Gem::Dependency
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
37
53
  name: actionpack
38
- requirement: &70157641409100 !ruby/object:Gem::Requirement
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
39
56
  none: false
40
- requirements:
41
- - - ! '>='
42
- - !ruby/object:Gem::Version
43
- version: '0'
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
- prerelease: false
46
- version_requirements: *70157641409100
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
- version_requirements: *70157641408000
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
- version: '0'
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
- prerelease: false
79
- version_requirements: *70157641407460
80
- - !ruby/object:Gem::Dependency
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
81
85
  name: sqlite3-ruby
82
- requirement: &70157641406900 !ruby/object:Gem::Requirement
86
+ prerelease: false
87
+ requirement: &id005 !ruby/object:Gem::Requirement
83
88
  none: false
84
- requirements:
85
- - - ! '>='
86
- - !ruby/object:Gem::Version
87
- version: '0'
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
88
96
  type: :development
89
- prerelease: false
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
- extra_rdoc_files:
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/connection_adapter/mysql_adapter.rb
108
- - lib/sql_safety_net/connection_adapter/postgresql_adapter.rb
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/rack_handler.rb
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/example_database.yml
115
- - spec/mysql_connection_adapter_spec.rb
116
- - spec/postgres_connection_adapter_spec.rb
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/rack_handler_spec.rb
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
- version: '0'
136
- required_rubygems_version: !ruby/object:Gem::Requirement
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
- version: '0'
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