shiba 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/CODE_OF_CONDUCT.md +3 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/README.md +53 -0
- data/Rakefile +2 -0
- data/TODO +12 -0
- data/bin/analyze +116 -0
- data/bin/check +0 -0
- data/bin/console +14 -0
- data/bin/explain +105 -0
- data/bin/fingerprint +10 -0
- data/bin/inspect +0 -0
- data/bin/parse +0 -0
- data/bin/redmine/sample_redmine.rb +165 -0
- data/bin/release +6 -0
- data/bin/setup +8 -0
- data/bin/shiba +40 -0
- data/bin/watch.rb +19 -0
- data/cmd/builds/fingerprint.darwin-amd64 +0 -0
- data/cmd/builds/fingerprint.linux-amd64 +0 -0
- data/cmd/check.go +138 -0
- data/cmd/fingerprint.go +28 -0
- data/cmd/inspect.go +92 -0
- data/cmd/parse.go +79 -0
- data/lib/shiba.rb +21 -0
- data/lib/shiba/analyzer.rb +100 -0
- data/lib/shiba/configure.rb +31 -0
- data/lib/shiba/explain.rb +234 -0
- data/lib/shiba/index.rb +159 -0
- data/lib/shiba/output.rb +67 -0
- data/lib/shiba/output/tags.yaml +44 -0
- data/lib/shiba/query.rb +34 -0
- data/lib/shiba/query_watcher.rb +79 -0
- data/lib/shiba/railtie.rb +20 -0
- data/lib/shiba/version.rb +3 -0
- data/shiba.gemspec +38 -0
- data/web/bootstrap.min.css +7 -0
- data/web/dist/bundle.js +167 -0
- data/web/main.css +18 -0
- data/web/main.js +5 -0
- data/web/package-lock.json +4100 -0
- data/web/package.json +19 -0
- data/web/results.html.erb +199 -0
- data/web/vue.js +11055 -0
- data/web/webpack.config.js +14 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e14e046d4bcec0e2025d671461517dabd547bc0a
|
4
|
+
data.tar.gz: 1e5b9e2043c8ecb2f2a46d2dc581be196c2daf75
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51f1c7929ab2306a7889d37f11d9fcfc73091c7b9325a70cf17e9f57293e4463a9780ce04702ad1ef8392b3c325c0f9a82a7d0e999779d9a989eaeacbc4c6e3d
|
7
|
+
data.tar.gz: 4e7ddb0dd6e3f5e555995e2ab0a9505428770dc335489c7929bdab4b7138ac28b94c9e5b91aad2b7a75d3568c3fe9b80ef4b655274c42b6c1ecdcb52fece5efa
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
shiba (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (10.0.2)
|
10
|
+
mysql2 (0.5.2)
|
11
|
+
rake (10.5.0)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
bundler (~> 2.0)
|
18
|
+
byebug
|
19
|
+
mysql2
|
20
|
+
rake (~> 10.0)
|
21
|
+
shiba!
|
22
|
+
|
23
|
+
BUNDLED WITH
|
24
|
+
2.0.1
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Shiba
|
2
|
+
|
3
|
+
Shiba is a tool that helps you to understand and write better SQL. Integrate
|
4
|
+
the gem into your test suite, give Shiba a bit of data about your indexes, and Shiba
|
5
|
+
will let you know the impact of your queries on production, with the goal of catching
|
6
|
+
poorly performing queries before they hit production.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
You can run shiba either as a gem in your test suite, or as a standalone utility.
|
11
|
+
|
12
|
+
### Gem
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
group :test do
|
18
|
+
gem 'shiba'
|
19
|
+
end
|
20
|
+
|
21
|
+
bundle
|
22
|
+
|
23
|
+
# Run some some code using shiba to generate a SQL report
|
24
|
+
bundle exec shiba analyze rake test:functional
|
25
|
+
```
|
26
|
+
|
27
|
+
### Standalone:
|
28
|
+
|
29
|
+
```
|
30
|
+
# 1. Get shiba.
|
31
|
+
local:$ git clone git@github.com:burrito-brothers/shiba.git
|
32
|
+
|
33
|
+
# 2. Get production data.
|
34
|
+
# Shiba *can* work without any further data, but it's really best if you can
|
35
|
+
# dump index statistics from a production database, or a staging database with
|
36
|
+
# that resembles production.
|
37
|
+
|
38
|
+
local:$ ssh production_host
|
39
|
+
production_host:$ mysql -ABe "select * from information_schema.statistics where table_schema = 'DATABASE'" > shiba_schema_stats.tsv
|
40
|
+
local:$ scp production_host:shiba_schema_stats.tsv shiba/
|
41
|
+
|
42
|
+
# 3. Analyze your queries.
|
43
|
+
# set shiba loose on your queries!
|
44
|
+
# If you can't do step #2, just leave off the '-s' option
|
45
|
+
|
46
|
+
local:$ cd shiba
|
47
|
+
local:$ bin/analyze.rb -h 127.0.0.1 -d TESTDB -u MYSQLUSER -p MYSQLPASS -s shiba_schema_stats.tsv -f ~/src/MYPROJECT/log/test.log > results.json
|
48
|
+
|
49
|
+
# analyze the results with `jq`, whynot
|
50
|
+
|
51
|
+
local:$ jq -C -s 'sort_by(.cost) | reverse' results.json | less -R
|
52
|
+
|
53
|
+
```
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
===
|
2
|
+
this query is throwing the optimizer for a serious loop;
|
3
|
+
it says it can use an index on `ipb_address` but when we force key it
|
4
|
+
still table scans. Not clear on whether the OR in there is fucking us over or
|
5
|
+
if it's a test-data issue.
|
6
|
+
|
7
|
+
|
8
|
+
# SELECT ipb_id,ipb_address,ipb_timestamp,ipb_auto,ipb_anon_only,ipb_create_account,ipb_enable_autoblock,ipb_expiry,ipb_deleted,ipb_block_email,ipb_allow_usertalk,ipb_parent_block_id,ipb_sitewide,comment_ipb_reason.comment_text AS `ipb_reason_text`,comment_ipb_reason.comment_data AS `ipb_reason_data`,comment_ipb_reason.comment_id AS `ipb_reason_cid`,ipb_by,ipb_by_text,NULL AS `ipb_by_actor` FROM `ipblocks` FORCE KEY(`ipb_address`) JOIN `comment` `comment_ipb_reason` ON ((comment_ipb_reason.comment_id = ipb_reason_id)) WHERE ipb_address = '127.0.0.1' OR ((ipb_range_start LIKE '7F00%' ESCAPE '`' ) AND (ipb_range_start <= '7F000001') AND (ipb_range_end >= '7F000001'));
|
9
|
+
|
10
|
+
===
|
11
|
+
need to use format=json and see how much of an index we're using.
|
12
|
+
|
data/bin/analyze
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#!/usr/bin/env ruby
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'optionparser'
|
7
|
+
require 'shiba/configure'
|
8
|
+
require 'shiba/output'
|
9
|
+
|
10
|
+
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
|
+
|
56
|
+
parser.parse!
|
57
|
+
|
58
|
+
if options["test"] && !options["file"]
|
59
|
+
$stderr.puts "--file <query log> is required for test mode"
|
60
|
+
$stderr.puts parser.banner
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# Automagic configuration goes here
|
65
|
+
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
|
75
|
+
end
|
76
|
+
|
77
|
+
if !options["file"]
|
78
|
+
options["file"] = `mktemp /tmp/shiba-query.log-#{Time.now.to_i}`.chomp
|
79
|
+
end
|
80
|
+
|
81
|
+
if !options["output"]
|
82
|
+
options["output"] = `mktemp /tmp/shiba-explain.log-#{Time.now.to_i}`.chomp
|
83
|
+
end
|
84
|
+
|
85
|
+
# Log process queries
|
86
|
+
if !options.delete("test")
|
87
|
+
if ARGV.empty?
|
88
|
+
$stderr.puts "The name of a command must be passed in to generate SQL logs."
|
89
|
+
$stderr.puts "Example: shiba analyze rake spec"
|
90
|
+
$stderr.puts ""
|
91
|
+
$stderr.puts "For static analysis, try the --test option."
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
|
95
|
+
path = "#{File.dirname(__FILE__)}/watch.rb"
|
96
|
+
watch_args = ARGV + [ "--file", options["file"] ]
|
97
|
+
pid = fork do
|
98
|
+
Signal.trap("INT") { exit 1 }
|
99
|
+
exec(path, *watch_args)
|
100
|
+
end
|
101
|
+
|
102
|
+
Signal.trap("INT", "IGNORE")
|
103
|
+
Process.wait(pid)
|
104
|
+
Signal.trap("INT", "DEFAULT")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Explain
|
108
|
+
$stderr.puts "Analyzing SQL to '#{options["output"]}'..."
|
109
|
+
path = "#{File.dirname(__FILE__)}/explain"
|
110
|
+
args = options.select { |_,v| !v.nil? }.map { |k,v| [ "--#{k}", v ] }.flatten
|
111
|
+
|
112
|
+
$stderr.puts ([path] + args).join(" ")
|
113
|
+
if !system(path, *args)
|
114
|
+
exit 1
|
115
|
+
end
|
116
|
+
|
data/bin/check
ADDED
Binary file
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "shiba"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/explain
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'shiba'
|
5
|
+
require 'shiba/analyzer'
|
6
|
+
require 'shiba/index'
|
7
|
+
require 'shiba/configure'
|
8
|
+
require 'shiba/output'
|
9
|
+
|
10
|
+
require 'optionparser'
|
11
|
+
|
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
|
+
|
57
|
+
parser.parse!
|
58
|
+
|
59
|
+
["database", "username"].each do |opt|
|
60
|
+
if !options[opt]
|
61
|
+
$stderr.puts "Required: #{opt}"
|
62
|
+
$stderr.puts parser.banner
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
file = options.delete("file")
|
68
|
+
file = File.open(file, "r") if file
|
69
|
+
|
70
|
+
output = options.delete("output")
|
71
|
+
output = File.open(output, 'w') if output
|
72
|
+
|
73
|
+
Shiba.configure(options)
|
74
|
+
|
75
|
+
schema_stats_fname = options["stats_file"]
|
76
|
+
|
77
|
+
if schema_stats_fname && !File.exist?(schema_stats_fname)
|
78
|
+
$stderr.puts "No such file: #{schema_stats_fname}"
|
79
|
+
exit 1
|
80
|
+
end
|
81
|
+
|
82
|
+
if schema_stats_fname
|
83
|
+
schema_stats = Shiba::Index.parse(schema_stats_fname)
|
84
|
+
|
85
|
+
local_db_stats = Shiba::Index.query(Shiba.connection)
|
86
|
+
Shiba::Index.fuzz!(local_db_stats)
|
87
|
+
local_db_stats.each do |table, values|
|
88
|
+
schema_stats[table] = values unless schema_stats[table]
|
89
|
+
end
|
90
|
+
else
|
91
|
+
schema_stats = Shiba::Index.query(Shiba.connection)
|
92
|
+
|
93
|
+
if Shiba::Index.insufficient_stats?(schema_stats)
|
94
|
+
$stderr.puts "WARN: insufficient stats available in the #{options["database"]} database, guessing at numbers."
|
95
|
+
$stderr.puts "To get better analysis please specify an index statistics file."
|
96
|
+
sleep 0.5
|
97
|
+
Shiba::Index.fuzz!(schema_stats)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
file = $stdin if file.nil?
|
102
|
+
output = $stdout if output.nil?
|
103
|
+
|
104
|
+
queries = Shiba::Analyzer.analyze(file, output, schema_stats, options)
|
105
|
+
Shiba::Output.new(queries).make_web!
|
data/bin/fingerprint
ADDED
data/bin/inspect
ADDED
Binary file
|
data/bin/parse
ADDED
Binary file
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'activeresource'
|
4
|
+
require 'logger'
|
5
|
+
require 'pp'
|
6
|
+
require 'mysql2'
|
7
|
+
|
8
|
+
ActiveResource::Base.logger = Logger.new($stdout)
|
9
|
+
|
10
|
+
class Redmine < ActiveResource::Base
|
11
|
+
self.site = 'http://www.redmine.org/'
|
12
|
+
self.user = 'osheroff'
|
13
|
+
self.password = `cat ~/.redmine_pass`.chomp
|
14
|
+
def enhance(hash, owner)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Attachment < Redmine
|
19
|
+
def enhance(hash, owner)
|
20
|
+
hash['disk_filename'] = self.attributes['content_url']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Comment < Redmine; end
|
25
|
+
|
26
|
+
class Changeset < Redmine
|
27
|
+
@@changeset_id = 1
|
28
|
+
|
29
|
+
def enhance(hash, owner)
|
30
|
+
hash['id'] = @@changeset_id
|
31
|
+
@@changeset_id += 1
|
32
|
+
hash['repository_id'] = 1
|
33
|
+
hash['scmid'] = 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Issue < Redmine; end
|
38
|
+
|
39
|
+
class Journal < Redmine
|
40
|
+
def enhance(hash, owner)
|
41
|
+
hash['journalized_type'] = 'Issue'
|
42
|
+
hash['journalized_id'] = owner.id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Watcher < Redmine; end
|
47
|
+
class Relation < Redmine; end
|
48
|
+
class User < Redmine; end
|
49
|
+
|
50
|
+
class Sampler
|
51
|
+
def initialize(interested_in)
|
52
|
+
@results = {}
|
53
|
+
@interested_in = interested_in
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_hash(instance, owner = nil)
|
57
|
+
h = {}
|
58
|
+
table_name = instance.class.name.tableize
|
59
|
+
attrs = @interested_in[table_name]
|
60
|
+
|
61
|
+
instance.attributes.each do |k, v|
|
62
|
+
if k == "id" || attrs.include?(k)
|
63
|
+
h[k] = v
|
64
|
+
elsif attrs.include?("#{k}_id")
|
65
|
+
h[k + "_id"] = v.id
|
66
|
+
elsif v.is_a?(Array) && @interested_in[k]
|
67
|
+
v.each do |obj|
|
68
|
+
extract_hash(obj, instance)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
instance.enhance(h, owner)
|
74
|
+
|
75
|
+
@results[table_name] ||= []
|
76
|
+
@results[table_name] << h
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_instance(model, id, params = {})
|
80
|
+
model.find(id, params: params) rescue nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_max(model, start, offset, params)
|
84
|
+
last_instance = nil
|
85
|
+
while instance = get_instance(model, start + offset, params)
|
86
|
+
last_instance = instance
|
87
|
+
extract_hash(instance)
|
88
|
+
offset *= 2
|
89
|
+
sleep(0.5)
|
90
|
+
end
|
91
|
+
|
92
|
+
1.upto(6) do |i|
|
93
|
+
# check for gaps
|
94
|
+
instance = get_instance(model, start + offset + (i**2), params)
|
95
|
+
if instance
|
96
|
+
last_instance = instance
|
97
|
+
extract_hash(instance)
|
98
|
+
return find_max(model, start + offset + (i**2), offset, params) || last_instance
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
last_instance
|
103
|
+
end
|
104
|
+
|
105
|
+
def sample_model(model, params = {})
|
106
|
+
# go exponentially up from 1.
|
107
|
+
last = find_max(model, 0, 1, params)
|
108
|
+
# go up from the last hit value.
|
109
|
+
if last
|
110
|
+
find_max(model, last.id, 1, params)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def output
|
115
|
+
@results.each do |table, res|
|
116
|
+
File.open("/tmp/redmine/#{table}.json", "w+") do |f|
|
117
|
+
res.each do |row|
|
118
|
+
f.puts(row.to_json)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
client = Mysql2::Client.new(username: 'root', password: '123456', database: 'redmine_test', host: '127.0.0.1')
|
126
|
+
|
127
|
+
|
128
|
+
tables = {
|
129
|
+
'attachments' => {
|
130
|
+
model: Attachment
|
131
|
+
},
|
132
|
+
'issues' => {
|
133
|
+
model: Issue,
|
134
|
+
params: { include: ['journals', 'comments', 'changesets', 'watchers'] },
|
135
|
+
},
|
136
|
+
'users' => {
|
137
|
+
model: User
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
|
142
|
+
all_tables = tables.map do |k, v|
|
143
|
+
a = [k]
|
144
|
+
a += v[:params][:include] if v[:params] && v[:params][:include]
|
145
|
+
a
|
146
|
+
end.flatten
|
147
|
+
|
148
|
+
table_indexes = {}
|
149
|
+
all_tables.each do |table|
|
150
|
+
indexes = client.query("show indexes from #{table}")
|
151
|
+
interested_columns = indexes.to_a.map { |i| i["Column_name"] }.flatten.uniq
|
152
|
+
table_indexes[table] = interested_columns
|
153
|
+
end
|
154
|
+
|
155
|
+
sampler = Sampler.new(table_indexes)
|
156
|
+
|
157
|
+
tables.each do |table, hash|
|
158
|
+
sampler.sample_model(hash[:model], hash[:params])
|
159
|
+
end
|
160
|
+
|
161
|
+
sampler.output
|
162
|
+
|
163
|
+
#sample_model(Issue, %w(project_id status_id category_id priority_id author_id))
|
164
|
+
|
165
|
+
|