shiba 0.9.2 → 0.9.3
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 +31 -1
- data/lib/shiba/activerecord_integration.rb +5 -5
- data/lib/shiba/explain/postgres_explain.rb +34 -13
- data/lib/shiba/parsers/bad_parse.rb +9 -0
- data/lib/shiba/parsers/postgres_explain_index_conditions.rb +20 -5
- data/lib/shiba/version.rb +1 -1
- data/shiba.gemspec +3 -2
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0035975f2585607e2aa44f84064fa8ae1bcec4cfbfbf78831595d9de8c690779'
|
4
|
+
data.tar.gz: 3e7bc106fc1c90d3551837411d34f282814f3e8334e215b59dbb67485ff30461
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4dff81f128bfc76958f7fc71f26861e5e1594d64c79ec32ea8da8d2421d0a30574e24ad8c2255d38d45f5f0bf6ad30ec2248864b159ab30b6ebcba2c0cb352f
|
7
|
+
data.tar.gz: 4d1f746c8c7d5eedfe9708f4fea54b154c17e79c7c6bdbd91fa41994b6f4f32c1227a4511f9ad04d5a8d56747281835f9a47125a8c5b974366d27dd0ee62f4ce
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Shiba is a tool (currently in alpha) that automatically reviews SQL queries befo
|
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
11
|
-
Install using bundler. Note: this gem is not designed to be run on production. It should be required after minitest/rspec.
|
11
|
+
Install in a Rails / ActiveRecord project using bundler. Note: this gem is not designed to be run on production. It should be required after minitest/rspec.
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
# Gemfile
|
@@ -39,11 +39,16 @@ SHIBA_DEBUG=true ruby test/controllers/users_controller_test.rb
|
|
39
39
|
# Report available at /tmp/shiba-explain.log-1550099512
|
40
40
|
```
|
41
41
|
|
42
|
+
### Postgres Support
|
43
|
+
|
44
|
+
Note: Postgres support is under development. For hopefully reliable results, test tables should have at least 1,000 rows.
|
45
|
+
|
42
46
|
## Next steps
|
43
47
|
* [Integrate with Github pull requests](#automatic-pull-request-reviews)
|
44
48
|
* [Add production stats for realistic analysis](#going-beyond-table-scans)
|
45
49
|
* [Preview queries from the developer console](#analyze-queries-from-the-developer-console)
|
46
50
|
* [Read more about typical query problems](#typical-query-problems)
|
51
|
+
* [Using with languages other than Ruby](#language-support)
|
47
52
|
|
48
53
|
|
49
54
|
|
@@ -206,3 +211,28 @@ users.size
|
|
206
211
|
Normally a query like this would only become a problem as the app grows in popularity. Fixes include adding `limit` or `find_each`.
|
207
212
|
|
208
213
|
With more data, Shiba can help detect this issue when it appears in a pull request.
|
214
|
+
|
215
|
+
### Language support
|
216
|
+
|
217
|
+
Shiba commands can be used to analyze non Ruby / Rails projects when given a query log file. The log file is a list of queries with the query's backtrace as a SQL comment. The backtrace comment must begin with the word 'shiba' followed by a JSON array of backtrace lines:
|
218
|
+
|
219
|
+
```
|
220
|
+
SELECT `users`.* FROM `users` WHERE `users`.`email` = 'squirrel@example.com' /*shiba["test/app/app.rb:29:in `<main>'", "Rakefile:0:in `<run>'"]*/
|
221
|
+
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 /*shiba["test/app/app.rb:30:in `<main>'", "Rakefile:0:in `<run>'"]*/
|
222
|
+
```
|
223
|
+
|
224
|
+
The generated log file can then be analyzed after installing Ruby.
|
225
|
+
|
226
|
+
```console
|
227
|
+
gem install bundler
|
228
|
+
|
229
|
+
git clone https://github.com/burrito-brothers/shiba.git
|
230
|
+
cd shiba
|
231
|
+
bundle
|
232
|
+
bin/explain -f query.log --database <DB_NAME> --server mysql --json explain.log.json
|
233
|
+
bin/review -f explain.log.json
|
234
|
+
|
235
|
+
# When no problem queries are found, the command will exit with status 0.
|
236
|
+
$ echo $?
|
237
|
+
$ 0
|
238
|
+
```
|
@@ -74,11 +74,11 @@ module Shiba
|
|
74
74
|
|
75
75
|
# define both minitest and rspec hooks -- it can be
|
76
76
|
# unclear in some envs which one is active. maybe even both could run in one process? not sure.
|
77
|
-
|
77
|
+
shiba_done = false
|
78
78
|
if defined?(Minitest.after_run)
|
79
79
|
MiniTest.after_run do
|
80
|
-
yield unless
|
81
|
-
|
80
|
+
yield unless shiba_done
|
81
|
+
shiba_done = true
|
82
82
|
end
|
83
83
|
@done_hook = :minitest
|
84
84
|
end
|
@@ -86,8 +86,8 @@ module Shiba
|
|
86
86
|
if defined?(RSpec.configure)
|
87
87
|
RSpec.configure do |config|
|
88
88
|
config.after(:suite) do
|
89
|
-
yield unless
|
90
|
-
|
89
|
+
yield unless shiba_done
|
90
|
+
shiba_done = true
|
91
91
|
end
|
92
92
|
end
|
93
93
|
@done_hook = :rspec
|
@@ -17,21 +17,20 @@ module Shiba
|
|
17
17
|
|
18
18
|
def transform_node(node, array)
|
19
19
|
case node['Node Type']
|
20
|
-
when "Limit", "LockRows", "Aggregate", "Unique", "Sort", "Hash", "ProjectSet"
|
20
|
+
when "Limit", "LockRows", "Aggregate", "Unique", "Sort", "Hash", "ProjectSet", "Materialize"
|
21
21
|
recurse_plans(node, array)
|
22
|
-
when "Nested Loop"
|
23
|
-
with_state(join_type: node[
|
24
|
-
recurse_plans(node, array)
|
25
|
-
end
|
26
|
-
when "Hash Join"
|
27
|
-
join_fields = extract_join_key_parts(node['Hash Cond'])
|
28
|
-
with_state(join_fields: join_fields, join_type: "Hash") do
|
22
|
+
when "Hash Join", "Merge Join", "Nested Loop", "BitmapOr"
|
23
|
+
with_state(join_type: node['Node Type']) do
|
29
24
|
recurse_plans(node, array)
|
30
25
|
end
|
31
26
|
when "Bitmap Heap Scan"
|
32
27
|
with_state(table: node['Relation Name']) do
|
33
28
|
recurse_plans(node, array)
|
34
29
|
end
|
30
|
+
when "Subquery Scan"
|
31
|
+
with_state(subquery: true) do
|
32
|
+
recurse_plans(node, array)
|
33
|
+
end
|
35
34
|
when "Seq Scan"
|
36
35
|
array << {
|
37
36
|
"table" => node["Relation Name"],
|
@@ -48,9 +47,15 @@ module Shiba
|
|
48
47
|
used_key_parts = []
|
49
48
|
end
|
50
49
|
|
50
|
+
if @state[:join_type] == 'BitmapOr'
|
51
|
+
access_type = "intersect"
|
52
|
+
else
|
53
|
+
access_type = "ref"
|
54
|
+
end
|
55
|
+
|
51
56
|
h = {
|
52
57
|
"table" => node["Relation Name"] || @state[:table],
|
53
|
-
"access_type" =>
|
58
|
+
"access_type" => access_type,
|
54
59
|
"key" => node["Index Name"],
|
55
60
|
"used_key_parts" => used_key_parts
|
56
61
|
}
|
@@ -60,20 +65,36 @@ module Shiba
|
|
60
65
|
end
|
61
66
|
|
62
67
|
array << h
|
68
|
+
when "Result"
|
69
|
+
# TBD: What the hell is here? seems like queries that short-circuit end up here?
|
70
|
+
array << {
|
71
|
+
"extra" => "No tables used"
|
72
|
+
}
|
63
73
|
else
|
74
|
+
debugger
|
64
75
|
raise "unhandled node: #{node}"
|
65
76
|
end
|
66
77
|
array
|
67
78
|
end
|
68
79
|
|
69
80
|
def extract_used_key_parts(cond)
|
70
|
-
|
71
|
-
|
81
|
+
begin
|
82
|
+
conds = Parsers::PostgresExplainIndexConditions.new(cond)
|
83
|
+
conds.fields
|
84
|
+
rescue Parsers::BadParse => e
|
85
|
+
debugger
|
86
|
+
{}
|
87
|
+
end
|
72
88
|
end
|
73
89
|
|
74
90
|
def extract_join_key_parts(cond)
|
75
|
-
|
76
|
-
|
91
|
+
begin
|
92
|
+
conds = Parsers::PostgresExplainIndexConditions.new(cond)
|
93
|
+
conds.join_fields
|
94
|
+
rescue Parsers::BadParse => e
|
95
|
+
debugger
|
96
|
+
{}
|
97
|
+
end
|
77
98
|
end
|
78
99
|
|
79
100
|
def recurse_plans(node, array)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'shiba/parsers/shiba_string_scanner'
|
2
|
+
require 'shiba/parsers/bad_parse'
|
2
3
|
|
3
4
|
module Shiba
|
4
5
|
module Parsers
|
@@ -43,6 +44,7 @@ module Shiba
|
|
43
44
|
def parse_string(sc)
|
44
45
|
quote_char = sc.peek(1)
|
45
46
|
v = sc.match_quoted_double_escape(quote_char)
|
47
|
+
sc.scan(/::timestamp without time zone/)
|
46
48
|
sc.scan(/::\w+(\[\])?/)
|
47
49
|
v
|
48
50
|
end
|
@@ -73,13 +75,22 @@ module Shiba
|
|
73
75
|
peek = sc.peek(1)
|
74
76
|
if peek == "("
|
75
77
|
sc.getch
|
78
|
+
ident = sc.scan(/[^\(\)]+/)
|
79
|
+
|
76
80
|
# typed column like (name)::text = 'ben'
|
77
|
-
ident = sc.scan(/[^\)]+/)
|
78
81
|
sc.scan(/\)::\S+/)
|
79
82
|
elsif peek == '"'
|
80
83
|
ident = parse_string(sc)
|
81
84
|
else
|
82
|
-
ident = sc.scan(/[^ \.\)\[]+/)
|
85
|
+
ident = sc.scan(/[^ \(\.\)\[]+/)
|
86
|
+
|
87
|
+
# function, lower((name)::text)
|
88
|
+
if sc.scan(/\(/)
|
89
|
+
ident = nil
|
90
|
+
parse_ident(sc)
|
91
|
+
sc.scan(/\)/)
|
92
|
+
end
|
93
|
+
|
83
94
|
# field[1] for array fields, not bothering to do brace matching here yet, oy vey
|
84
95
|
sc.scan(/\[.*?\]/)
|
85
96
|
end
|
@@ -112,11 +123,15 @@ module Shiba
|
|
112
123
|
table = nil
|
113
124
|
|
114
125
|
parse_field(sc)
|
115
|
-
sc.scan(/\s
|
116
|
-
|
126
|
+
sc.scan(/\s+/)
|
127
|
+
|
128
|
+
if !sc.scan(/IS NOT NULL/)
|
129
|
+
sc.scan(/\S+\s+/) # operator
|
130
|
+
parse_value(sc)
|
131
|
+
end
|
117
132
|
|
118
133
|
if sc.scan(RPAREN).nil?
|
119
|
-
raise
|
134
|
+
raise BadParse.new(@sc.peek(15), @string)
|
120
135
|
end
|
121
136
|
end
|
122
137
|
end
|
data/lib/shiba/version.rb
CHANGED
data/shiba.gemspec
CHANGED
@@ -9,8 +9,9 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Ben Osheroff", "Eric Chapweske"]
|
10
10
|
spec.email = ["ben@gimbo.net", "ben.osheroff@gmail.com", "ericis@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description = %q{
|
12
|
+
spec.summary = %q{Catch bad SQL queries before they cause problems in production}
|
13
|
+
spec.description = %q{Use production statistics for realistic SQL query analysis. Finds code that may take down production, including missing indexes, overly broad indexes, and queries that return too much data.
|
14
|
+
}
|
14
15
|
spec.homepage = "https://github.com/burrito-brothers/shiba"
|
15
16
|
|
16
17
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
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.9.
|
4
|
+
version: 0.9.3
|
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-
|
12
|
+
date: 2019-04-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -81,7 +81,9 @@ dependencies:
|
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '10.0'
|
84
|
-
description:
|
84
|
+
description: "Use production statistics for realistic SQL query analysis. Finds code
|
85
|
+
that may take down production, including missing indexes, overly broad indexes,
|
86
|
+
and queries that return too much data.\n "
|
85
87
|
email:
|
86
88
|
- ben@gimbo.net
|
87
89
|
- ben.osheroff@gmail.com
|
@@ -139,6 +141,7 @@ files:
|
|
139
141
|
- lib/shiba/index_stats.rb
|
140
142
|
- lib/shiba/output.rb
|
141
143
|
- lib/shiba/output/tags.yaml
|
144
|
+
- lib/shiba/parsers/bad_parse.rb
|
142
145
|
- lib/shiba/parsers/mysql_select_fields.rb
|
143
146
|
- lib/shiba/parsers/postgres_explain_index_conditions.rb
|
144
147
|
- lib/shiba/parsers/shiba_string_scanner.rb
|
@@ -198,5 +201,5 @@ rubyforge_project:
|
|
198
201
|
rubygems_version: 2.7.6
|
199
202
|
signing_key:
|
200
203
|
specification_version: 4
|
201
|
-
summary:
|
204
|
+
summary: Catch bad SQL queries before they cause problems in production
|
202
205
|
test_files: []
|