shiba 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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: []