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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12302c0e4fc56d8c8fc0d14dc6e10a5ae84aeaac2c328937b78b9e14b8122faa
4
- data.tar.gz: d0ae437286d3c4975630c5dab8dc5aa109eb38f3ed0f6e606cded20a1201b09f
3
+ metadata.gz: '0035975f2585607e2aa44f84064fa8ae1bcec4cfbfbf78831595d9de8c690779'
4
+ data.tar.gz: 3e7bc106fc1c90d3551837411d34f282814f3e8334e215b59dbb67485ff30461
5
5
  SHA512:
6
- metadata.gz: 92d5a42d2b68381a8f07025dec47e289d75c642a2d53823658966c8032e0e1f2a826f6285b1834747dda798fa25968f27f05c00029f60345303dddc9e2b20c2a
7
- data.tar.gz: f4911585fe1a38695b587e11b908265d480523b6e2fcb8966780d80f34ab9612c1a798f612f0a3262f77349d5686a27f4c3da3886824753b36021354c33b267e
6
+ metadata.gz: c4dff81f128bfc76958f7fc71f26861e5e1594d64c79ec32ea8da8d2421d0a30574e24ad8c2255d38d45f5f0bf6ad30ec2248864b159ab30b6ebcba2c0cb352f
7
+ data.tar.gz: 4d1f746c8c7d5eedfe9708f4fea54b154c17e79c7c6bdbd91fa41994b6f4f32c1227a4511f9ad04d5a8d56747281835f9a47125a8c5b974366d27dd0ee62f4ce
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiba (0.9.2)
4
+ shiba (0.9.3)
5
5
  activesupport
6
6
 
7
7
  GEM
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
- @shiba_done = false
77
+ shiba_done = false
78
78
  if defined?(Minitest.after_run)
79
79
  MiniTest.after_run do
80
- yield unless @shiba_done
81
- @shiba_done = true
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 @shiba_done
90
- @shiba_done = true
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["Join Type"]) do
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" => "ref",
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
- conds = Parsers::PostgresExplainIndexConditions.new(cond)
71
- conds.fields
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
- conds = Parsers::PostgresExplainIndexConditions.new(cond)
76
- conds.join_fields
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)
@@ -0,0 +1,9 @@
1
+ module Shiba
2
+ module Parsers
3
+ class BadParse < StandardError
4
+ def initialize(location, parse_string)
5
+ super("Parse Error @ '#{location}' while parsing '#{parse_string}'")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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+\S+\s+/) # operator
116
- parse_value(sc)
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 "bad scan; #{sc.inspect}"
134
+ raise BadParse.new(@sc.peek(15), @string)
120
135
  end
121
136
  end
122
137
  end
@@ -1,3 +1,3 @@
1
1
  module Shiba
2
- VERSION = "0.9.2"
2
+ VERSION = "0.9.3"
3
3
  end
@@ -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{A gem that attempts to find bad queries before you shoot self in foot}
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.2
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-03-26 00:00:00.000000000 Z
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: A gem that attempts to find bad queries before you shoot self in foot
204
+ summary: Catch bad SQL queries before they cause problems in production
202
205
  test_files: []