shiba 0.1.0
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 +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
|
+
|