shiba 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +113 -63
- data/bin/review +25 -135
- data/lib/shiba.rb +4 -0
- data/lib/shiba/activerecord_integration.rb +21 -19
- data/lib/shiba/analyzer.rb +1 -1
- data/lib/shiba/configure.rb +12 -6
- data/lib/shiba/connection/mysql.rb +72 -3
- data/lib/shiba/connection/postgres.rb +4 -1
- data/lib/shiba/console.rb +165 -0
- data/lib/shiba/explain.rb +22 -7
- data/lib/shiba/fuzzer.rb +5 -0
- data/lib/shiba/index_stats.rb +20 -1
- data/lib/shiba/output/tags.yaml +1 -1
- data/lib/shiba/parsers/mysql_select_fields.rb +64 -0
- data/lib/shiba/parsers/shiba_string_scanner.rb +27 -0
- data/lib/shiba/review/cli.rb +196 -0
- data/lib/shiba/review/comment_renderer.rb +19 -2
- data/lib/shiba/review/diff.rb +227 -0
- data/lib/shiba/review/explain_diff.rb +117 -0
- data/lib/shiba/reviewer.rb +20 -19
- data/lib/shiba/table_stats.rb +4 -0
- data/lib/shiba/version.rb +1 -1
- data/web/results.html.erb +15 -1
- metadata +10 -5
- data/lib/shiba/checker.rb +0 -165
- data/lib/shiba/diff.rb +0 -129
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
require 'shiba/review/diff'
|
5
|
+
require 'shiba/backtrace'
|
6
|
+
|
7
|
+
module Shiba
|
8
|
+
module Review
|
9
|
+
# Given an explain log and a diff, returns any explain logs
|
10
|
+
# that appear to be caused by the diff.
|
11
|
+
class ExplainDiff
|
12
|
+
Result = Struct.new(:status, :message)
|
13
|
+
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
def initialize(log, options)
|
17
|
+
@log = log
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def diff_requested_by_user?
|
22
|
+
[ "staged", "unstaged", "branch", "diff" ].any? { |key| @options.key?(key) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns detected problem queries with their line numbers.
|
26
|
+
# Query problem format is [ [ "path:lineno", explain ]... ]
|
27
|
+
def problems
|
28
|
+
return @problems if @problems
|
29
|
+
|
30
|
+
@problems = explains_with_backtrace_in_diff.select do |explain|
|
31
|
+
explain["severity"] && explain["severity"] != 'none'
|
32
|
+
end
|
33
|
+
|
34
|
+
if options["verbose"]
|
35
|
+
$stderr.puts @problems
|
36
|
+
$stderr.puts "Updated lines: #{updated_lines}"
|
37
|
+
end
|
38
|
+
|
39
|
+
@problems.map! do |problem|
|
40
|
+
line = diff_line_from_backtrace(problem["backtrace"])
|
41
|
+
next if line.nil?
|
42
|
+
|
43
|
+
[ line, problem ]
|
44
|
+
end
|
45
|
+
@problems.compact!
|
46
|
+
|
47
|
+
@problems
|
48
|
+
end
|
49
|
+
|
50
|
+
def result
|
51
|
+
msg = nil
|
52
|
+
|
53
|
+
if changed_files.empty?
|
54
|
+
if options['verbose']
|
55
|
+
msg = "No changes found. Are you sure you specified the correct branch?"
|
56
|
+
end
|
57
|
+
return Result.new(:pass, msg)
|
58
|
+
end
|
59
|
+
|
60
|
+
if problems.empty?
|
61
|
+
msg = "No problems found caused by the diff"
|
62
|
+
return Result.new(:pass, msg)
|
63
|
+
end
|
64
|
+
|
65
|
+
return Result.new(:fail, "Potential problems")
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
# file.rb:32:in `hello'",
|
71
|
+
LINE_NUMBER_PATTERN = /:(\d+):/
|
72
|
+
|
73
|
+
def diff_line_from_backtrace(backtrace)
|
74
|
+
backtrace.each do |bl|
|
75
|
+
updated_lines.each do |path, lines|
|
76
|
+
next if !bl.start_with?(path)
|
77
|
+
bl =~ LINE_NUMBER_PATTERN
|
78
|
+
next if !lines.include?($1.to_i)
|
79
|
+
|
80
|
+
return "#{path}:#{$1}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# All explains from the log file with a backtrace that contains a changed file.
|
88
|
+
def explains_with_backtrace_in_diff
|
89
|
+
patterns = changed_files.map { |path| "-e #{path}" }.join(" ")
|
90
|
+
cmd = "grep #{@log} #{patterns}"
|
91
|
+
$stderr.puts cmd if options["verbose"]
|
92
|
+
|
93
|
+
json_lines = `#{cmd}`
|
94
|
+
json_lines.each_line.map { |line| JSON.parse(line) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def changed_files
|
98
|
+
@changed_files ||= diff.paths
|
99
|
+
end
|
100
|
+
|
101
|
+
def updated_lines
|
102
|
+
return @updated_lines if @updated_lines
|
103
|
+
|
104
|
+
diff_file = diff.file(context: 0, ignore_deletions: true)
|
105
|
+
@updated_lines = Diff::Parser.new(diff_file).updated_lines
|
106
|
+
@updated_lines.map! do |path, lines|
|
107
|
+
[ Shiba::Backtrace.clean!(path), lines ]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def diff
|
112
|
+
@diff ||= options["diff"] ? Diff::FileDiff.new(options["diff"]) : Diff::GitDiff.new(options)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/shiba/reviewer.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'open3'
|
2
2
|
require 'shiba'
|
3
|
-
require 'shiba/diff'
|
3
|
+
require 'shiba/review/diff'
|
4
4
|
require 'shiba/review/api'
|
5
5
|
require 'shiba/review/comment_renderer'
|
6
6
|
|
@@ -10,7 +10,6 @@ module Shiba
|
|
10
10
|
# 2. May make sense to edit the comment on a commit line when the code
|
11
11
|
# is semi-corrected but still a problem
|
12
12
|
class Reviewer
|
13
|
-
TEMPLATE_FILE = File.join(Shiba.root, 'lib/shiba/output/tags.yaml')
|
14
13
|
MESSAGE_FILTER_THRESHOLD = 0.005
|
15
14
|
|
16
15
|
attr_reader :repo_url, :problems, :options
|
@@ -20,7 +19,9 @@ module Shiba
|
|
20
19
|
@problems = problems
|
21
20
|
@options = options
|
22
21
|
@commit_id = options.fetch("branch") do
|
23
|
-
|
22
|
+
if options["submit"]
|
23
|
+
raise Shiba::Error.new("Must specify a branch")
|
24
|
+
end
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
@@ -33,7 +34,11 @@ module Shiba
|
|
33
34
|
raise Shiba::Error.new("Bad path received: #{line_number}")
|
34
35
|
end
|
35
36
|
|
36
|
-
position =
|
37
|
+
position = if path == "none:-1"
|
38
|
+
nil
|
39
|
+
else
|
40
|
+
diff_parser.find_position(file, line_number.to_i)
|
41
|
+
end
|
37
42
|
|
38
43
|
explain = keep_only_dangerous_messages(explain)
|
39
44
|
|
@@ -97,22 +102,18 @@ module Shiba
|
|
97
102
|
explain_b
|
98
103
|
end
|
99
104
|
|
100
|
-
def
|
101
|
-
|
102
|
-
output = options['diff'] ? file_diff : git_diff
|
103
|
-
@diff = Shiba::Diff.new(output)
|
104
|
-
end
|
105
|
-
|
106
|
-
def git_diff
|
107
|
-
cmd ="git diff origin/HEAD..#{@commit_id}"
|
108
|
-
report("Finding PR position using: #{cmd}")
|
109
|
-
|
110
|
-
output = StringIO.new(`#{cmd}`)
|
105
|
+
def diff_parser
|
106
|
+
@diff_parser ||= Review::Diff::Parser.new(diff.file)
|
111
107
|
end
|
112
108
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
109
|
+
def diff
|
110
|
+
@diff ||= if options['diff']
|
111
|
+
report("Finding PR position using file: #{options['diff']}")
|
112
|
+
Review::Diff::FileDiff.new(options['diff'])
|
113
|
+
else
|
114
|
+
report("Finding PR position using git")
|
115
|
+
Review::Diff::GitDiff.new(options)
|
116
|
+
end
|
116
117
|
end
|
117
118
|
|
118
119
|
def api
|
@@ -130,7 +131,7 @@ module Shiba
|
|
130
131
|
end
|
131
132
|
|
132
133
|
def tags
|
133
|
-
@tags ||=
|
134
|
+
@tags ||= YAML.load_file(Shiba::TEMPLATE_FILE)
|
134
135
|
end
|
135
136
|
|
136
137
|
end
|
data/lib/shiba/table_stats.rb
CHANGED
@@ -18,6 +18,10 @@ module Shiba
|
|
18
18
|
ask_each(:table_count, table)
|
19
19
|
end
|
20
20
|
|
21
|
+
def get_column_size(table_name, column)
|
22
|
+
ask_each(:get_column_size, table_name, column)
|
23
|
+
end
|
24
|
+
|
21
25
|
def fuzzed?(table)
|
22
26
|
!@dump_stats.tables[table] && !@manual_stats.tables[table] && @db_stats.tables[table]
|
23
27
|
end
|
data/lib/shiba/version.rb
CHANGED
data/web/results.html.erb
CHANGED
@@ -206,6 +206,20 @@
|
|
206
206
|
return (this.running_cost * 100).toFixed() + "ms";
|
207
207
|
else
|
208
208
|
return this.running_cost.toFixed(1) + "s";
|
209
|
+
},
|
210
|
+
formatted_result: function() {
|
211
|
+
var rb = this.result_bytes;
|
212
|
+
var result;
|
213
|
+
if ( rb == 0 )
|
214
|
+
return "" + this.result_size + " rows";
|
215
|
+
else if ( rb < 1000 )
|
216
|
+
result = rb + " bytes ";
|
217
|
+
else if ( rb < 1000000 )
|
218
|
+
result = (rb / 1000).toFixed() + "kb ";
|
219
|
+
else
|
220
|
+
result = (rb / 1000000 ).toFixed(1) + "mb ";
|
221
|
+
|
222
|
+
return result + " (" + this.result_size.toLocaleString() + " rows)";
|
209
223
|
}
|
210
224
|
}
|
211
225
|
</script>
|
@@ -226,7 +240,7 @@
|
|
226
240
|
<script>
|
227
241
|
Vue.component('tag-<%= tag %>', {
|
228
242
|
template: '#tag-<%= tag %>-template',
|
229
|
-
props: [ 'table_size', 'result_size', 'table', 'cost', 'index', 'join_to', 'index_used', 'running_cost', 'tables', 'rows_read' ],
|
243
|
+
props: [ 'table_size', 'result_size', 'table', 'cost', 'index', 'join_to', 'index_used', 'running_cost', 'tables', 'rows_read', 'result_bytes' ],
|
230
244
|
computed: templateComputedFunctions,
|
231
245
|
data: function () {
|
232
246
|
return { lastRunnningCost: undefined };
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shiba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Osheroff
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-03-
|
12
|
+
date: 2019-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -121,12 +121,11 @@ files:
|
|
121
121
|
- lib/shiba/activerecord_integration.rb
|
122
122
|
- lib/shiba/analyzer.rb
|
123
123
|
- lib/shiba/backtrace.rb
|
124
|
-
- lib/shiba/checker.rb
|
125
124
|
- lib/shiba/configure.rb
|
126
125
|
- lib/shiba/connection.rb
|
127
126
|
- lib/shiba/connection/mysql.rb
|
128
127
|
- lib/shiba/connection/postgres.rb
|
129
|
-
- lib/shiba/
|
128
|
+
- lib/shiba/console.rb
|
130
129
|
- lib/shiba/explain.rb
|
131
130
|
- lib/shiba/explain/check_support.rb
|
132
131
|
- lib/shiba/explain/checks.rb
|
@@ -139,10 +138,15 @@ files:
|
|
139
138
|
- lib/shiba/index_stats.rb
|
140
139
|
- lib/shiba/output.rb
|
141
140
|
- lib/shiba/output/tags.yaml
|
141
|
+
- lib/shiba/parsers/mysql_select_fields.rb
|
142
|
+
- lib/shiba/parsers/shiba_string_scanner.rb
|
142
143
|
- lib/shiba/query.rb
|
143
144
|
- lib/shiba/query_watcher.rb
|
144
145
|
- lib/shiba/review/api.rb
|
146
|
+
- lib/shiba/review/cli.rb
|
145
147
|
- lib/shiba/review/comment_renderer.rb
|
148
|
+
- lib/shiba/review/diff.rb
|
149
|
+
- lib/shiba/review/explain_diff.rb
|
146
150
|
- lib/shiba/reviewer.rb
|
147
151
|
- lib/shiba/setup.rb
|
148
152
|
- lib/shiba/table_stats.rb
|
@@ -177,7 +181,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
181
|
- !ruby/object:Gem::Version
|
178
182
|
version: '0'
|
179
183
|
requirements: []
|
180
|
-
|
184
|
+
rubyforge_project:
|
185
|
+
rubygems_version: 2.7.6
|
181
186
|
signing_key:
|
182
187
|
specification_version: 4
|
183
188
|
summary: A gem that attempts to find bad queries before you shoot self in foot
|
data/lib/shiba/checker.rb
DELETED
@@ -1,165 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'open3'
|
3
|
-
|
4
|
-
require 'shiba/diff'
|
5
|
-
require 'shiba/backtrace'
|
6
|
-
|
7
|
-
module Shiba
|
8
|
-
# Given an explain log and a diff, returns any explain logs
|
9
|
-
# that appear to be caused by the diff.
|
10
|
-
class Checker
|
11
|
-
Result = Struct.new(:status, :message, :problems)
|
12
|
-
|
13
|
-
attr_reader :options
|
14
|
-
|
15
|
-
def initialize(options)
|
16
|
-
@options = options
|
17
|
-
end
|
18
|
-
|
19
|
-
# Returns a Result object with a status, message, and any problem queries detected.
|
20
|
-
# Query problem format is [ [ "path:lineno", explain ]... ]
|
21
|
-
def run(log)
|
22
|
-
msg = nil
|
23
|
-
|
24
|
-
if options['verbose']
|
25
|
-
puts cmd
|
26
|
-
end
|
27
|
-
|
28
|
-
if changed_files.empty?
|
29
|
-
if options['verbose']
|
30
|
-
msg = "No changes found. Are you sure you specified the correct branch?"
|
31
|
-
end
|
32
|
-
return Result.new(:pass, msg)
|
33
|
-
end
|
34
|
-
|
35
|
-
explains = select_lines_with_changed_files(log)
|
36
|
-
problems = explains.select { |explain| explain["severity"] && explain["severity"] != 'none' }
|
37
|
-
|
38
|
-
|
39
|
-
if options["verbose"]
|
40
|
-
puts problems
|
41
|
-
puts "Updated lines: #{updated_lines}"
|
42
|
-
end
|
43
|
-
|
44
|
-
if problems.empty?
|
45
|
-
msg = "No problems found caused by the diff"
|
46
|
-
return Result.new(:pass, msg)
|
47
|
-
end
|
48
|
-
|
49
|
-
problems.map! do |problem|
|
50
|
-
line = updated_line_from_backtrace(problem["backtrace"], updated_lines)
|
51
|
-
next if line.nil?
|
52
|
-
|
53
|
-
[ line, problem ]
|
54
|
-
end
|
55
|
-
problems.compact!
|
56
|
-
|
57
|
-
if problems.empty?
|
58
|
-
msg = "No problems found caused by the diff"
|
59
|
-
return Result.new(:pass, msg)
|
60
|
-
end
|
61
|
-
|
62
|
-
return Result.new(:fail, "Potential problems", problems)
|
63
|
-
end
|
64
|
-
|
65
|
-
protected
|
66
|
-
|
67
|
-
def updated_line_from_backtrace(backtrace, updates)
|
68
|
-
backtrace.each do |bl|
|
69
|
-
updates.each do |path, lines|
|
70
|
-
next if !bl.start_with?(path)
|
71
|
-
bl =~ /:(\d+):/
|
72
|
-
next if !lines.include?($1.to_i)
|
73
|
-
|
74
|
-
return "#{path}:#{$1}"
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
return nil
|
79
|
-
end
|
80
|
-
|
81
|
-
def select_lines_with_changed_files(log)
|
82
|
-
patterns = changed_files.map { |path| "-e #{path}" }.join(" ")
|
83
|
-
cmd = "grep #{log} #{patterns}"
|
84
|
-
$stderr.puts cmd if options["verbose"]
|
85
|
-
|
86
|
-
json_lines = `#{cmd}`
|
87
|
-
json_lines.each_line.map { |line| JSON.parse(line) }
|
88
|
-
end
|
89
|
-
|
90
|
-
def changed_files
|
91
|
-
@changed_files ||= begin
|
92
|
-
options['diff'] ? file_diff_names : git_diff_names
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def updated_lines
|
97
|
-
return @updated_lines if @updated_lines
|
98
|
-
|
99
|
-
|
100
|
-
out = options['diff'] ? file_diff_lines : git_diff_lines
|
101
|
-
@updated_lines = Shiba::Diff.new(out).updated_lines
|
102
|
-
|
103
|
-
|
104
|
-
@updated_lines.map! do |path, lines|
|
105
|
-
[ Shiba::Backtrace.clean!(path), lines ]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def file_diff_lines
|
110
|
-
File.open(options['diff'])
|
111
|
-
end
|
112
|
-
|
113
|
-
def git_diff_lines
|
114
|
-
run = "git diff#{cmd} --unified=0 --diff-filter=d"
|
115
|
-
if options[:verbose]
|
116
|
-
$stderr.puts run
|
117
|
-
end
|
118
|
-
|
119
|
-
_, out,_,_ = Open3.popen3(run)
|
120
|
-
out
|
121
|
-
end
|
122
|
-
|
123
|
-
# index ade9b24..661d522 100644
|
124
|
-
# --- a/test/app/app.rb
|
125
|
-
# +++ b/test/app/app.rb
|
126
|
-
# @@ -24,4 +24,4 @@ ActiveRecord::Base...
|
127
|
-
# org = Organization.create!(name: 'test')
|
128
|
-
#
|
129
|
-
# file_diff_lines
|
130
|
-
# => test/app/app.rb
|
131
|
-
def file_diff_names
|
132
|
-
file_name_pattern = /^\+\+\+ b\/(.*?)$/
|
133
|
-
f = File.open(options['diff'])
|
134
|
-
f.grep(file_name_pattern) { $1 }
|
135
|
-
end
|
136
|
-
|
137
|
-
def git_diff_names
|
138
|
-
run = "git diff#{cmd} --name-only --diff-filter=d"
|
139
|
-
|
140
|
-
if options[:verbose]
|
141
|
-
$stderr.puts run
|
142
|
-
end
|
143
|
-
result = `#{run}`
|
144
|
-
if $?.exitstatus != 0
|
145
|
-
$stderr.puts result
|
146
|
-
raise Shiba::Error.new "Failed to read changes"
|
147
|
-
end
|
148
|
-
|
149
|
-
result.split("\n")
|
150
|
-
end
|
151
|
-
|
152
|
-
def cmd
|
153
|
-
cmd = case
|
154
|
-
when options["staged"]
|
155
|
-
" --staged"
|
156
|
-
when options["unstaged"]
|
157
|
-
""
|
158
|
-
else
|
159
|
-
commit = " origin/HEAD"
|
160
|
-
commit << "...#{options["branch"]}" if options["branch"]
|
161
|
-
commit
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|