pghero 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +2 -0
- data/app/controllers/pg_hero/home_controller.rb +38 -4
- data/app/views/layouts/pg_hero/application.html.erb +9 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +2 -2
- data/app/views/pg_hero/home/_queries_table.html.erb +10 -0
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +23 -2
- data/app/views/pg_hero/home/_suggested_index.html.erb +1 -0
- data/app/views/pg_hero/home/connections.html.erb +1 -1
- data/app/views/pg_hero/home/explain.html.erb +3 -0
- data/app/views/pg_hero/home/index.html.erb +92 -2
- data/app/views/pg_hero/home/maintenance.html.erb +32 -0
- data/app/views/pg_hero/home/queries.html.erb +4 -0
- data/app/views/pg_hero/home/system.html.erb +3 -0
- data/config/routes.rb +2 -0
- data/guides/Rails.md +17 -0
- data/guides/Suggested-Indexes.md +19 -0
- data/lib/pghero.rb +445 -22
- data/lib/pghero/tasks.rb +5 -0
- data/lib/pghero/version.rb +1 -1
- data/test/best_index_test.rb +131 -0
- data/test/explain_test.rb +18 -0
- data/test/suggested_indexes_test.rb +14 -0
- data/test/test_helper.rb +44 -2
- metadata +12 -4
- data/test/pghero_test.rb +0 -18
data/lib/pghero/tasks.rb
CHANGED
data/lib/pghero/version.rb
CHANGED
@@ -0,0 +1,131 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class BestIndexTest < Minitest::Test
|
4
|
+
def test_where
|
5
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = 1"
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_all_values
|
9
|
+
index = PgHero.best_index("SELECT * FROM users WHERE login_attempts = 1 ORDER BY created_at")
|
10
|
+
expected = {
|
11
|
+
found: true,
|
12
|
+
structure: {table: "users", where: [{column: "login_attempts", op: "="}], sort: [{column: "created_at", direction: "asc"}]},
|
13
|
+
index: {table: "users", columns: ["login_attempts", "created_at"]},
|
14
|
+
rows: 10000,
|
15
|
+
row_estimates: {"login_attempts" => 333, "created_at" => 1},
|
16
|
+
row_progression: [10000, 333, 0]
|
17
|
+
}
|
18
|
+
assert_equal expected, index
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_where_multiple_columns
|
22
|
+
assert_best_index ({table: "users", columns: ["city_id", "login_attempts"]}), "SELECT * FROM users WHERE city_id = 1 and login_attempts = 2"
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_where_unique
|
26
|
+
assert_best_index ({table: "users", columns: ["email"]}), "SELECT * FROM users WHERE city_id = 1 AND email = 'person2@example.org'"
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_order
|
30
|
+
assert_best_index ({table: "users", columns: ["created_at"]}), "SELECT * FROM users ORDER BY created_at"
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_order_multiple
|
34
|
+
assert_best_index ({table: "users", columns: ["login_attempts", "created_at"]}), "SELECT * FROM users ORDER BY login_attempts, created_at"
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_order_multiple_direction
|
38
|
+
assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users ORDER BY login_attempts DESC, created_at"
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_order_multiple_unique
|
42
|
+
assert_best_index ({table: "users", columns: ["id"]}), "SELECT * FROM users ORDER BY id, created_at"
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_where_unique_order
|
46
|
+
assert_best_index ({table: "users", columns: ["email"]}), "SELECT * FROM users WHERE email = 'person2@example.org' ORDER BY created_at"
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_where_order
|
50
|
+
assert_best_index ({table: "users", columns: ["login_attempts", "created_at"]}), "SELECT * FROM users WHERE login_attempts = 1 ORDER BY created_at"
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_where_in
|
54
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id IN (1, 2)"
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_between
|
58
|
+
skip
|
59
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id BETWEEN 1 AND 2"
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_where_prepared
|
63
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = $1"
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_where_normalized
|
67
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = ?"
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_is_null
|
71
|
+
assert_best_index ({table: "users", columns: ["zip_code"]}), "SELECT * FROM users WHERE zip_code IS NULL"
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_is_null_equal
|
75
|
+
assert_best_index ({table: "users", columns: ["zip_code", "login_attempts"]}), "SELECT * FROM users WHERE zip_code IS NULL AND login_attempts = ?"
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_is_not_null
|
79
|
+
assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users WHERE zip_code IS NOT NULL AND login_attempts = ?"
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_update
|
83
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "UPDATE users SET email = 'test' WHERE city_id = 1"
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_delete
|
87
|
+
assert_best_index ({table: "users", columns: ["city_id"]}), "DELETE FROM users WHERE city_id = 1"
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_parse_error
|
91
|
+
assert_no_index "Parse error", "SELECT *123'"
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_multiple_tables
|
95
|
+
assert_no_index "Unknown structure", "SELECT * FROM users INNER JOIN cities ON cities.id = users.city_id"
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_no_columns
|
99
|
+
assert_no_index "No columns to index", "SELECT * FROM users"
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_small_table
|
103
|
+
assert_no_index "No index needed if less than 500 rows", "SELECT * FROM states WHERE name = 'State 1'"
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_system_table
|
107
|
+
assert_no_index "System table", "SELECT COUNT(*) AS count FROM pg_extension WHERE extname = ?"
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_insert
|
111
|
+
assert_no_index "INSERT statement", "INSERT INTO users (login_attempts) VALUES (1)"
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_set
|
115
|
+
assert_no_index "SET statement", "set client_encoding to 'UTF8'"
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
def assert_best_index(expected, statement)
|
121
|
+
index = PgHero.best_index(statement)
|
122
|
+
assert index[:found]
|
123
|
+
assert_equal expected, index[:index]
|
124
|
+
end
|
125
|
+
|
126
|
+
def assert_no_index(explanation, statement)
|
127
|
+
index = PgHero.best_index(statement)
|
128
|
+
assert !index[:found]
|
129
|
+
assert_equal explanation, index[:explanation]
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class ExplainTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
City.delete_all
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_explain
|
9
|
+
City.create!
|
10
|
+
PgHero.explain("ANALYZE DELETE FROM cities")
|
11
|
+
assert_equal 1, City.count
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_explain_multiple_statements
|
15
|
+
City.create!
|
16
|
+
assert_raises(ActiveRecord::StatementInvalid) { PgHero.explain("ANALYZE DELETE FROM cities; DELETE FROM cities; COMMIT") }
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class SuggestedIndexesTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
PgHero.reset_query_stats
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_basic
|
9
|
+
User.where(email: "person1@example.org").first
|
10
|
+
# User.where(email: "person1@example.org", city_id: 1).first
|
11
|
+
# User.where(city_id: 1).to_a
|
12
|
+
assert_equal [{table: "users", columns: ["email"]}], PgHero.suggested_indexes.map { |q| q.except(:queries) }
|
13
|
+
end
|
14
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -2,15 +2,57 @@ require "bundler/setup"
|
|
2
2
|
Bundler.require(:default)
|
3
3
|
require "minitest/autorun"
|
4
4
|
require "minitest/pride"
|
5
|
+
require "pg_query"
|
5
6
|
|
6
7
|
# for Minitest < 5
|
7
8
|
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
|
8
9
|
|
9
10
|
ActiveRecord::Base.establish_connection adapter: "postgresql", database: "pghero_test"
|
10
11
|
|
11
|
-
ActiveRecord::Migration.create_table :
|
12
|
-
|
12
|
+
ActiveRecord::Migration.create_table :cities, force: true do |t|
|
13
|
+
t.string :name
|
14
|
+
end
|
15
|
+
|
16
|
+
class City < ActiveRecord::Base
|
17
|
+
end
|
18
|
+
|
19
|
+
class State < ActiveRecord::Base
|
13
20
|
end
|
14
21
|
|
15
22
|
class User < ActiveRecord::Base
|
16
23
|
end
|
24
|
+
|
25
|
+
if ENV["SEED"]
|
26
|
+
ActiveRecord::Migration.create_table :users, force: true do |t|
|
27
|
+
t.integer :city_id
|
28
|
+
t.integer :login_attempts
|
29
|
+
t.string :email
|
30
|
+
t.string :zip_code
|
31
|
+
t.timestamp :created_at
|
32
|
+
end
|
33
|
+
|
34
|
+
User.transaction do
|
35
|
+
10000.times do |i|
|
36
|
+
city_id = i % 100
|
37
|
+
User.create!(
|
38
|
+
city_id: city_id,
|
39
|
+
email: "person#{i}@example.org",
|
40
|
+
login_attempts: rand(30),
|
41
|
+
zip_code: i % 40 == 0 ? nil : "12345",
|
42
|
+
created_at: Time.now - rand(50).days
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
ActiveRecord::Base.connection.execute("VACUUM ANALYZE users")
|
47
|
+
|
48
|
+
ActiveRecord::Migration.create_table :states, force: true do |t|
|
49
|
+
t.string :name
|
50
|
+
end
|
51
|
+
|
52
|
+
State.transaction do
|
53
|
+
50.times do |i|
|
54
|
+
State.create!(name: "State #{i}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
ActiveRecord::Base.connection.execute("VACUUM ANALYZE states")
|
58
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pghero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -99,11 +99,13 @@ files:
|
|
99
99
|
- app/views/pg_hero/home/_live_queries_table.html.erb
|
100
100
|
- app/views/pg_hero/home/_queries_table.html.erb
|
101
101
|
- app/views/pg_hero/home/_query_stats_slider.html.erb
|
102
|
+
- app/views/pg_hero/home/_suggested_index.html.erb
|
102
103
|
- app/views/pg_hero/home/connections.html.erb
|
103
104
|
- app/views/pg_hero/home/explain.html.erb
|
104
105
|
- app/views/pg_hero/home/index.html.erb
|
105
106
|
- app/views/pg_hero/home/index_usage.html.erb
|
106
107
|
- app/views/pg_hero/home/live_queries.html.erb
|
108
|
+
- app/views/pg_hero/home/maintenance.html.erb
|
107
109
|
- app/views/pg_hero/home/queries.html.erb
|
108
110
|
- app/views/pg_hero/home/space.html.erb
|
109
111
|
- app/views/pg_hero/home/system.html.erb
|
@@ -114,6 +116,7 @@ files:
|
|
114
116
|
- guides/Linux.md
|
115
117
|
- guides/Query-Stats.md
|
116
118
|
- guides/Rails.md
|
119
|
+
- guides/Suggested-Indexes.md
|
117
120
|
- lib/generators/pghero/query_stats_generator.rb
|
118
121
|
- lib/generators/pghero/templates/query_stats.rb
|
119
122
|
- lib/pghero.rb
|
@@ -122,10 +125,12 @@ files:
|
|
122
125
|
- lib/pghero/tasks.rb
|
123
126
|
- lib/pghero/version.rb
|
124
127
|
- pghero.gemspec
|
128
|
+
- test/best_index_test.rb
|
129
|
+
- test/explain_test.rb
|
125
130
|
- test/gemfiles/activerecord31.gemfile
|
126
131
|
- test/gemfiles/activerecord32.gemfile
|
127
132
|
- test/gemfiles/activerecord40.gemfile
|
128
|
-
- test/
|
133
|
+
- test/suggested_indexes_test.rb
|
129
134
|
- test/test_helper.rb
|
130
135
|
homepage: https://github.com/ankane/pghero
|
131
136
|
licenses:
|
@@ -157,8 +162,11 @@ test_files:
|
|
157
162
|
- guides/Linux.md
|
158
163
|
- guides/Query-Stats.md
|
159
164
|
- guides/Rails.md
|
165
|
+
- guides/Suggested-Indexes.md
|
166
|
+
- test/best_index_test.rb
|
167
|
+
- test/explain_test.rb
|
160
168
|
- test/gemfiles/activerecord31.gemfile
|
161
169
|
- test/gemfiles/activerecord32.gemfile
|
162
170
|
- test/gemfiles/activerecord40.gemfile
|
163
|
-
- test/
|
171
|
+
- test/suggested_indexes_test.rb
|
164
172
|
- test/test_helper.rb
|
data/test/pghero_test.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require_relative "test_helper"
|
2
|
-
|
3
|
-
class TestPgHero < Minitest::Test
|
4
|
-
def setup
|
5
|
-
User.delete_all
|
6
|
-
end
|
7
|
-
|
8
|
-
def test_explain
|
9
|
-
User.create!
|
10
|
-
PgHero.explain("ANALYZE DELETE FROM users")
|
11
|
-
assert_equal 1, User.count
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_explain_multiple_statements
|
15
|
-
User.create!
|
16
|
-
assert_raises(ActiveRecord::StatementInvalid) { PgHero.explain("ANALYZE DELETE FROM users; DELETE FROM users; COMMIT") }
|
17
|
-
end
|
18
|
-
end
|