shiba 0.1.1 → 0.1.2

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
  SHA1:
3
- metadata.gz: 0eff935dfd9607d514fc16e2615f5af7d415602d
4
- data.tar.gz: c7aa551bccf103087df9848638462d8b3b2d0c30
3
+ metadata.gz: f6436b087e61bcce7488d2fbb04003c3fd32fb36
4
+ data.tar.gz: 348c1fa32744f1879e489fa5530294a4ea70fe4e
5
5
  SHA512:
6
- metadata.gz: 6f0d6576e905ced52788b0ea247f7f0d51c5ea997f1064114028ab4cc3f1811b6adf8e75f9f7b97344b58eb2d579953bfab77a32fc6fcae33c755cb8a8832a2a
7
- data.tar.gz: a16096e13a86dc7a8b977c21334590f46701bc1ef9e77c06e8a23f334fa36bcad78a52f2716892881c852569232c9fbb79341c93ea3b311867a4da6f7eb8bf6a
6
+ metadata.gz: e84dcdbd7cb06d3a2dfa7fa87e9c0bf40e8a3059a8396284c8503599d3d94cbb4c45681b081e8074abc700a7285f64239777cd30c5d1f5eed17f6302756ac98a
7
+ data.tar.gz: be47f54c87cad50e7260d64468b2c0489f55163a57db59173c3302da9e82a9f62380b239a65eecab6d6db849b044b38d1c0821fdb66457d8497d73f29a6b34f0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiba (0.1.1)
4
+ shiba (0.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -11,17 +11,20 @@ You can run shiba either as a gem in your test suite, or as a standalone utility
11
11
 
12
12
  ### Gem
13
13
 
14
- Add this line to your application's Gemfile:
14
+ Install
15
15
 
16
16
  ```ruby
17
- group :test do
18
- gem 'shiba'
19
- end
17
+ bundle add shiba --group "development, test"
18
+ ```
20
19
 
21
- bundle
20
+ Run shiba
22
21
 
22
+ ```ruby
23
23
  # Run some some code using shiba to generate a SQL report
24
24
  bundle exec shiba analyze rake test:functional
25
+
26
+ # Or run a single test
27
+ bundle exec shiba analyze rails test test/controllers/users_controller_test.rb
25
28
  ```
26
29
 
27
30
  ### Standalone:
@@ -50,4 +53,4 @@ local:$ bin/analyze.rb -h 127.0.0.1 -d TESTDB -u MYSQLUSER -p MYSQLPASS -s shiba
50
53
 
51
54
  local:$ jq -C -s 'sort_by(.cost) | reverse' results.json | less -R
52
55
 
53
- ```
56
+ ```
data/bin/analyze CHANGED
@@ -4,55 +4,14 @@
4
4
 
5
5
  require 'bundler/setup'
6
6
  require 'optionparser'
7
+ require 'shiba'
7
8
  require 'shiba/configure'
8
9
  require 'shiba/output'
9
10
 
10
11
  options = {}
11
- parser = OptionParser.new do |opts|
12
- opts.banner = "analyze <command>. Creates report of SQL from the running process."
13
-
14
- opts.on("-h","--host HOST", "sql host") do |h|
15
- options["host"] = h
16
- end
17
-
18
- opts.on("-d","--database DATABASE", "sql database") do |d|
19
- options["database"] = d
20
- end
21
-
22
- opts.on("-u","--user USER", "sql user") do |u|
23
- options["user"] = u
24
- end
25
-
26
- opts.on("-p","--password PASSWORD", "sql password") do |p|
27
- options["password"] = p
28
- end
29
-
30
- opts.on("-i","--index INDEX", "index of query to inspect") do |i|
31
- options["index"] = i.to_i
32
- end
33
-
34
- opts.on("-l", "--limit NUM", "stop after processing NUM queries") do |l|
35
- options["limit"] = l.to_i
36
- end
37
-
38
- opts.on("-s","--stats FILES", "location of index statistics tsv file") do |f|
39
- options["stats"] = f
40
- end
41
-
42
- opts.on("-f", "--file FILE", "location of file containing queries") do |f|
43
- options["file"] = f
44
- end
45
-
46
- opts.on("-o", "--output FILE", "write to file instead of stdout") do |f|
47
- options["output"] = f
48
- end
49
-
50
- opts.on("-t", "--test", "analyze queries at --file instead of analyzing a process") do |f|
51
- options["test"] = true
52
- end
53
-
54
- end
55
12
 
13
+ parser = Shiba::Configure.make_options_parser(options)
14
+ parser.banner = "analyze <command>. Creates report of SQL from the running process."
56
15
  parser.parse!
57
16
 
58
17
  if options["test"] && !options["file"]
@@ -63,24 +22,26 @@ end
63
22
 
64
23
  # Automagic configuration goes here
65
24
  if !options["database"]
66
- config = Shiba::Configure.activerecord_configuration
67
-
68
- if tc = config && config['test']
69
- $stderr.puts "Reading configuration from '#{`pwd`.chomp}/config/database.yml'[:test]."
70
- options['host'] ||= tc['host']
71
- options['database'] ||= tc['database']
72
- options['user'] ||= tc['username']
73
- options['password'] ||= tc['password']
74
- end
25
+ config = Shiba::Configure.activerecord_configuration
26
+
27
+ if tc = config && config['test']
28
+ $stderr.puts "Reading configuration from '#{`pwd`.chomp}/config/database.yml'[:test]."
29
+ options['host'] ||= tc['host']
30
+ options['database'] ||= tc['database']
31
+ options['username'] ||= tc['username']
32
+ options['password'] ||= tc['password']
75
33
  end
34
+ end
76
35
 
77
- if !options["file"]
78
- options["file"] = `mktemp /tmp/shiba-query.log-#{Time.now.to_i}`.chomp
79
- end
36
+ Shiba.configure(options)
80
37
 
81
- if !options["output"]
82
- options["output"] = `mktemp /tmp/shiba-explain.log-#{Time.now.to_i}`.chomp
83
- end
38
+ if !options["file"]
39
+ options["file"] = `mktemp /tmp/shiba-query.log-#{Time.now.to_i}`.chomp
40
+ end
41
+
42
+ if !options["explain"]
43
+ options["explain"] = `mktemp /tmp/shiba-explain.log-#{Time.now.to_i}`.chomp
44
+ end
84
45
 
85
46
  # Log process queries
86
47
  if !options.delete("test")
@@ -105,7 +66,7 @@ if !options.delete("test")
105
66
  end
106
67
 
107
68
  # Explain
108
- $stderr.puts "Analyzing SQL to '#{options["output"]}'..."
69
+ $stderr.puts "Analyzing SQL to '#{options["explain"]}'..."
109
70
  path = "#{File.dirname(__FILE__)}/explain"
110
71
  args = options.select { |_,v| !v.nil? }.map { |k,v| [ "--#{k}", v ] }.flatten
111
72
 
data/bin/explain CHANGED
@@ -10,50 +10,9 @@ require 'shiba/output'
10
10
  require 'optionparser'
11
11
 
12
12
  options = {}
13
- parser = OptionParser.new do |opts|
14
- opts.banner = "Usage: explain -h HOST -d DB -u USER -p PASS [-f QUERY_FILE] [-s STATS_FILE] "
15
-
16
- opts.on("-h","--host HOST", "sql host") do |h|
17
- options["host"] = h
18
- end
19
-
20
- opts.on("-d","--database DATABASE", "sql database") do |d|
21
- options["database"] = d
22
- end
23
-
24
- opts.on("-u","--user USER", "sql user") do |u|
25
- options["username"] = u
26
- end
27
-
28
- opts.on("-p","--password PASSWORD", "sql password") do |p|
29
- options["password"] = p
30
- end
31
-
32
- opts.on("-i","--index INDEX", "index of query to inspect") do |i|
33
- options["index"] = i.to_i
34
- end
35
-
36
- opts.on("-l", "--limit NUM", "stop after processing NUM queries") do |l|
37
- options["limit"] = l.to_i
38
- end
39
-
40
- opts.on("-s","--stats FILES", "location of index statistics tsv file") do |f|
41
- options["stats_file"] = f
42
- end
43
-
44
- opts.on("-f", "--file FILE", "location of file containing queries") do |f|
45
- options["file"] = f
46
- end
47
-
48
- opts.on("-o", "--output FILE", "write to file instead of stdout") do |f|
49
- options["output"] = f
50
- end
51
-
52
- opts.on("--debug") do
53
- options["debug"] = true
54
- end
55
- end
56
13
 
14
+ parser = Shiba::Configure.make_options_parser(options)
15
+ parser.banner = "Run a list of queries through shiba's analyzer."
57
16
  parser.parse!
58
17
 
59
18
  ["database", "username"].each do |opt|
@@ -67,12 +26,12 @@ end
67
26
  file = options.delete("file")
68
27
  file = File.open(file, "r") if file
69
28
 
70
- output = options.delete("output")
29
+ output = options.delete("explain")
71
30
  output = File.open(output, 'w') if output
72
31
 
73
32
  Shiba.configure(options)
74
33
 
75
- schema_stats_fname = options["stats_file"]
34
+ schema_stats_fname = options["stats"]
76
35
 
77
36
  if schema_stats_fname && !File.exist?(schema_stats_fname)
78
37
  $stderr.puts "No such file: #{schema_stats_fname}"
@@ -102,4 +61,4 @@ file = $stdin if file.nil?
102
61
  output = $stdout if output.nil?
103
62
 
104
63
  queries = Shiba::Analyzer.analyze(file, output, schema_stats, options)
105
- Shiba::Output.new(queries).make_web!
64
+ Shiba::Output.new(queries, { 'output' => options['output'] }).make_web!
@@ -45,7 +45,15 @@ end
45
45
 
46
46
  class Watcher < Redmine; end
47
47
  class Relation < Redmine; end
48
- class User < Redmine; end
48
+ class User < Redmine
49
+ def enhance(hash, owner)
50
+ if rand < 0.01
51
+ hash['type'] = 'admin'
52
+ else
53
+ hash['type'] = 'user'
54
+ end
55
+ end
56
+ end
49
57
 
50
58
  class Sampler
51
59
  def initialize(interested_in)
@@ -105,9 +113,17 @@ class Sampler
105
113
  def sample_model(model, params = {})
106
114
  # go exponentially up from 1.
107
115
  last = find_max(model, 0, 1, params)
116
+
108
117
  # go up from the last hit value.
109
- if last
110
- find_max(model, last.id, 1, params)
118
+ last = find_max(model, last.id, 1, params)
119
+
120
+ max_id = last.id
121
+ num_to_sample = (max_id * 0.05).to_i
122
+
123
+ num_to_sample.times do
124
+ instance = get_instance(model, rand(max_id), params)
125
+ extract_hash(instance) if instance
126
+ sleep(1)
111
127
  end
112
128
  end
113
129
 
data/lib/shiba.rb CHANGED
@@ -1,11 +1,22 @@
1
1
  require "shiba/version"
2
+ require "shiba/configure"
2
3
  require "mysql2"
3
4
 
4
5
  module Shiba
5
6
  class Error < StandardError; end
6
7
 
7
- def self.configure(connection_hash)
8
- @connection_hash = connection_hash
8
+ def self.configure(options)
9
+ @connection_hash = options.select { |k, v| ['username', 'database', 'host', 'password'].include?(k) }
10
+ @main_config = Configure.read_config_file(options['config'], "config/shiba.yml")
11
+ @index_config = Configure.read_config_file(options['index'], "config/shiba_index.yml")
12
+ end
13
+
14
+ def self.config
15
+ @main_config
16
+ end
17
+
18
+ def self.index_config
19
+ @index_config
9
20
  end
10
21
 
11
22
  def self.connection
@@ -18,4 +29,4 @@ module Shiba
18
29
  end
19
30
 
20
31
  # This goes at the end so that Shiba.root is defined.
21
- require "shiba/railtie" if defined?(Rails)
32
+ require "shiba/railtie" if defined?(Rails)
@@ -1,31 +1,103 @@
1
1
  require 'pathname'
2
-
2
+ require 'pp'
3
3
  module Shiba
4
- module Configure
5
-
6
- # avoiding Rails dependency on the cli tools for now.
7
- # yanked from https://github.com/rails/rails/blob/v5.0.5/railties/lib/rails/application/configuration.rb
8
- def self.activerecord_configuration
9
- yaml = Pathname.new("config/database.yml")
10
-
11
- config = if yaml && yaml.exist?
12
- require "yaml"
13
- require "erb"
14
- YAML.load(ERB.new(yaml.read).result) || {}
15
- elsif ENV['DATABASE_URL']
16
- # Value from ENV['DATABASE_URL'] is set to default database connection
17
- # by Active Record.
18
- {}
19
- end
20
-
21
- config
22
- rescue Psych::SyntaxError => e
23
- raise "YAML syntax error occurred while parsing #{yaml.to_s}. " \
24
- "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
25
- "Error: #{e.message}"
26
- rescue => e
27
- raise e, "Cannot load `#{path}`:\n#{e.message}", e.backtrace
4
+ module Configure
5
+
6
+ # avoiding Rails dependency on the cli tools for now.
7
+ # yanked from https://github.com/rails/rails/blob/v5.0.5/railties/lib/rails/application/configuration.rb
8
+ def self.activerecord_configuration
9
+ yaml = Pathname.new("config/database.yml")
10
+
11
+ config = if yaml && yaml.exist?
12
+ require "yaml"
13
+ require "erb"
14
+ YAML.load(ERB.new(yaml.read).result) || {}
15
+ elsif ENV['DATABASE_URL']
16
+ # Value from ENV['DATABASE_URL'] is set to default database connection
17
+ # by Active Record.
18
+ {}
19
+ end
20
+
21
+ config
22
+ rescue Psych::SyntaxError => e
23
+ raise "YAML syntax error occurred while parsing #{yaml.to_s}. " \
24
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
25
+ "Error: #{e.message}"
26
+ rescue => e
27
+ raise e, "Cannot load `#{path}`:\n#{e.message}", e.backtrace
28
+ end
29
+
30
+ def self.read_config_file(option_file, default)
31
+ file_to_read = nil
32
+ if option_file
33
+ if !File.exist?(option_file)
34
+ $stderr.puts "no such file: #{option_file}"
35
+ exit 1
36
+ end
37
+ file_to_read = option_file
38
+ elsif File.exist?(default)
39
+ file_to_read = default
40
+ end
41
+
42
+ if file_to_read
43
+ YAML.load_file(file_to_read)
44
+ else
45
+ {}
46
+ end
47
+ end
48
+
49
+ def self.make_options_parser(options)
50
+ parser = OptionParser.new do |opts|
51
+ # note that the key to the hash needs to stay the same as the
52
+ # option name since we re-pass them
53
+ opts.on("-h","--host HOST", "sql host") do |h|
54
+ options["host"] = h
55
+ end
56
+
57
+ opts.on("-d","--database DATABASE", "sql database") do |d|
58
+ options["database"] = d
59
+ end
60
+
61
+ opts.on("-u","--username USER", "sql user") do |u|
62
+ options["username"] = u
63
+ end
64
+
65
+ opts.on("-p","--password PASSWORD", "sql password") do |p|
66
+ options["password"] = p
28
67
  end
29
68
 
69
+ opts.on("-c","--config FILE", "location of shiba.yml") do |f|
70
+ options["config"] = f
71
+ end
72
+
73
+ opts.on("-i","--index INDEX", "location of shiba_index.yml") do |i|
74
+ options["index"] = i.to_i
75
+ end
76
+
77
+ opts.on("-l", "--limit NUM", "stop after processing NUM queries") do |l|
78
+ options["limit"] = l.to_i
79
+ end
80
+
81
+ opts.on("-s","--stats FILES", "location of index statistics tsv file") do |f|
82
+ options["stats"] = f
83
+ end
84
+
85
+ opts.on("-f", "--file FILE", "location of file containing queries") do |f|
86
+ options["file"] = f
87
+ end
88
+
89
+ opts.on("-e", "--explain FILE", "write explain JSON to file. default: stdout") do |f|
90
+ options["explain"] = f
91
+ end
92
+
93
+ opts.on("-o", "--output PATH", "path to put generated report in. default: /tmp") do |p|
94
+ options["output"] = p
95
+ end
96
+
97
+ opts.on("-t", "--test", "analyze queries at --file instead of analyzing a process") do |f|
98
+ options["test"] = true
99
+ end
100
+ end
30
101
  end
31
- end
102
+ end
103
+ end
data/lib/shiba/explain.rb CHANGED
@@ -3,10 +3,10 @@ require 'shiba/index'
3
3
 
4
4
  module Shiba
5
5
  class Explain
6
- def initialize(sql, stats, options = {})
6
+ def initialize(sql, stats, backtrace, options = {})
7
7
  @sql = sql
8
+ @backtrace = backtrace
8
9
 
9
- @sql, _, @backtrace = @sql.partition(" /*shiba")
10
10
  if options[:force_key]
11
11
  @sql = @sql.sub(/(FROM\s*\S+)/i, '\1' + " FORCE INDEX(`#{options[:force_key]}`)")
12
12
  end
@@ -20,8 +20,6 @@ module Shiba
20
20
  end
21
21
 
22
22
  def as_json
23
- @backtrace.chomp!("*/")
24
-
25
23
  {
26
24
  sql: @sql,
27
25
  table: get_table,
@@ -30,7 +28,7 @@ module Shiba
30
28
  cost: @cost,
31
29
  used_key_parts: first['used_key_parts'],
32
30
  possible_keys: first['possible_keys'],
33
- backtrace: JSON.parse(@backtrace)
31
+ backtrace: @backtrace
34
32
  }
35
33
  end
36
34
 
@@ -217,7 +215,7 @@ module Shiba
217
215
  end
218
216
 
219
217
  def estimate_row_count_with_key(key)
220
- Explain.new(@sql, @stats, force_key: key).estimate_row_count
218
+ Explain.new(@sql, @stats, @backtrace, force_key: key).estimate_row_count
221
219
  rescue Mysql2::Error => e
222
220
  if /Key .+? doesn't exist in table/ =~ e.message
223
221
  return nil
@@ -226,7 +224,32 @@ module Shiba
226
224
  raise e
227
225
  end
228
226
 
227
+ def ignore?
228
+ !!ignore_line_and_backtrace_line
229
+ end
230
+
231
+ def ignore_line_and_backtrace_line
232
+ ignore_files = Shiba.config['ignore']
233
+ if ignore_files
234
+ ignore_files.each do |i|
235
+ file, method = i.split('#')
236
+ @backtrace.each do |b|
237
+ next unless b.include?(file)
238
+ next if method && !b.include?(method)
239
+ return [i, b]
240
+ end
241
+ end
242
+ end
243
+ nil
244
+ end
245
+
229
246
  def run_checks!
247
+ if ignore?
248
+ @cost = 0
249
+ messages << "ignored"
250
+ return
251
+ end
252
+
230
253
  @cost = estimate_row_count
231
254
  end
232
255
  end
data/lib/shiba/output.rb CHANGED
@@ -5,28 +5,26 @@ require 'erb'
5
5
 
6
6
  module Shiba
7
7
  class Output
8
-
9
8
  OUTPUT_PATH = "/tmp/shiba_results"
10
- JS_PATH = OUTPUT_PATH + "/js"
11
9
 
12
10
  WEB_PATH = File.dirname(__FILE__) + "/../../web"
13
11
  def self.tags
14
12
  @tags ||= YAML.load_file(File.dirname(__FILE__) + "/output/tags.yaml")
15
13
  end
16
14
 
17
- def self.from_file(fname)
18
- queries = []
19
- File.open(fname, "r") do |f|
20
- while line = f.gets
21
- queries << JSON.parse(line)
22
- end
23
- end
24
- new(queries)
15
+ def initialize(queries, options = {})
16
+ @queries = queries
17
+ @options = options
25
18
  end
26
19
 
20
+ def output_path
21
+ path ||= File.join(@options['output'], "shiba_results") if @options['output']
22
+ path ||= Dir.pwd + "/log/shiba_results" if File.exist?(Dir.pwd + "/log")
23
+ path ||= OUTPUT_PATH
24
+ end
27
25
 
28
- def initialize(queries)
29
- @queries = queries
26
+ def js_path
27
+ File.join(output_path, "js")
30
28
  end
31
29
 
32
30
  def remote_url
@@ -40,11 +38,11 @@ module Shiba
40
38
  end
41
39
 
42
40
  def make_web!
43
- FileUtils.mkdir_p(JS_PATH)
41
+ FileUtils.mkdir_p(js_path)
44
42
 
45
43
  js = Dir.glob(WEB_PATH + "/dist/*.js").map { |f| File.basename(f) }
46
44
  js.each do |f|
47
- system("cp #{WEB_PATH}/dist/#{f} #{JS_PATH}")
45
+ system("cp #{WEB_PATH}/dist/#{f} #{js_path}")
48
46
  end
49
47
 
50
48
  data = {
@@ -54,14 +52,14 @@ module Shiba
54
52
  url: remote_url
55
53
  }
56
54
 
57
- system("cp #{WEB_PATH}/*.css #{OUTPUT_PATH}")
55
+ system("cp #{WEB_PATH}/*.css #{output_path}")
58
56
 
59
57
  erb = ERB.new(File.read(WEB_PATH + "/../web/results.html.erb"))
60
- File.open(OUTPUT_PATH + "/results.html", "w+") do |f|
58
+ File.open(output_path + "/results.html", "w+") do |f|
61
59
  f.write(erb.result(binding))
62
60
  end
63
61
 
64
- puts "done, results are in " + "/tmp/shiba_results/results.html"
62
+ puts "done, results are in " + File.join(output_path, "results.html")
65
63
  end
66
64
  end
67
65
  end
@@ -38,7 +38,12 @@ access_type_tablescan:
38
38
  fuzzed_data:
39
39
  title: Guessed Table Size
40
40
  description: |
41
- There wasn't enough data in your local environment to adequately measure this query, so we
42
- pretended this table had a bunch of data in it. To improve results, please dump your
43
- production index statistics into Shiba.
41
+ We're not sure how much data this table will hold in the future, so we've pretended
42
+ there's 6000 rows in it. This can lead to a lot of false positives. To
43
+ improve results, please give shiba your index statistics.
44
+ level: info
45
+ ignored:
46
+ title: Ignored Query
47
+ description: |
48
+ This query matched an "ignore" rule in shiba.yml, so we skipped any further analysis on it.
44
49
  level: info
data/lib/shiba/query.rb CHANGED
@@ -15,7 +15,15 @@ module Shiba
15
15
  end
16
16
 
17
17
  def initialize(sql, stats)
18
- @sql = sql
18
+ @sql, _, @backtrace = sql.partition(" /*shiba")
19
+
20
+ if @backtrace.length > 0
21
+ @backtrace.chomp!("*/")
22
+ @backtrace = JSON.parse(@backtrace)
23
+ else
24
+ @backtrace = []
25
+ end
26
+
19
27
  @stats = stats
20
28
  @@index += 1
21
29
  @index = @@index
@@ -23,12 +31,17 @@ module Shiba
23
31
 
24
32
  attr_reader :sql, :index
25
33
 
34
+
26
35
  def fingerprint
27
36
  @fingerprint ||= self.class.get_fingerprint(@sql)
28
37
  end
29
38
 
30
39
  def explain
31
- Explain.new(@sql, @stats)
40
+ Explain.new(@sql, @stats, @backtrace)
41
+ end
42
+
43
+ def backtrace
44
+ @backtrace
32
45
  end
33
46
  end
34
47
  end
@@ -4,13 +4,14 @@ require 'rails'
4
4
 
5
5
  module Shiba
6
6
  class QueryWatcher
7
- FINGERPRINTS = {}
8
7
  IGNORE = /\.rvm|gem|vendor\/|rbenv|seed|db|shiba|test|spec/
9
8
 
10
9
  def self.watch(file)
11
10
  new(file).watch
12
11
  end
13
12
 
13
+ attr_reader :queries
14
+
14
15
  def initialize(file)
15
16
  @file = file
16
17
  # fixme mem growth on this is kinda nasty
@@ -23,11 +24,13 @@ module Shiba
23
24
  sql = payload[:sql]
24
25
 
25
26
  if sql.start_with?("SELECT")
26
- lines = app_backtrace
27
- if lines && !@queries[sql]
28
- @file.puts("#{sql} /*shiba#{lines}*/" )
27
+ fingerprint = Query.get_fingerprint(sql)
28
+ if !@queries[fingerprint]
29
+ if lines = app_backtrace
30
+ @file.puts("#{sql} /*shiba#{lines}*/")
31
+ end
29
32
  end
30
- @queries[sql] = true
33
+ @queries[fingerprint] = true
31
34
  end
32
35
  end
33
36
  end
data/lib/shiba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Shiba
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
data/web/results.html.erb CHANGED
@@ -94,11 +94,13 @@
94
94
  <query v-for="query in queries" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags"></query>
95
95
  </div>
96
96
  <div class="row">
97
- <div class="col-12">We also found {{ queriesLow.length }} queries that look fine.</div>
97
+ <div class="col-12">We also found <a href="#" v-on:click.prevent="lowExpanded = !lowExpanded">{{ queriesLow.length }} queries</a> that look fine.</div>
98
98
  </div>
99
- <div class="queries">
99
+ <a name="lowExapnded"></a>
100
+ <div class="queries" v-if="lowExpanded">
100
101
  <query v-for="query in queriesLow" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags"></query>
101
102
  </div>
103
+ <div style="height:50px"></div>
102
104
  </div>
103
105
  </div>
104
106
 
@@ -188,7 +190,8 @@
188
190
  data: {
189
191
  queries: queriesByTable,
190
192
  queriesLow: queriesByTableLow,
191
- tags: data.tags
193
+ tags: data.tags,
194
+ lowExpanded: false
192
195
  },
193
196
  methods: { }
194
197
  });
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.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Osheroff