pghero 1.7.0 → 2.0.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/.travis.yml +2 -0
- data/CHANGELOG.md +31 -0
- data/README.md +2 -2
- data/app/assets/javascripts/pghero/Chart.bundle.js +7512 -5661
- data/app/assets/javascripts/pghero/application.js +9 -0
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/stylesheets/pghero/application.css +54 -2
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/controllers/pg_hero/home_controller.rb +148 -52
- data/app/helpers/pg_hero/base_helper.rb +15 -0
- data/app/views/layouts/pg_hero/application.html.erb +1 -1
- data/app/views/pg_hero/home/_connections_table.html.erb +2 -2
- data/app/views/pg_hero/home/_live_queries_table.html.erb +11 -7
- data/app/views/pg_hero/home/_queries_table.html.erb +21 -10
- data/app/views/pg_hero/home/_suggested_index.html.erb +1 -1
- data/app/views/pg_hero/home/connections.html.erb +2 -14
- data/app/views/pg_hero/home/explain.html.erb +1 -1
- data/app/views/pg_hero/home/index.html.erb +58 -22
- data/app/views/pg_hero/home/index_bloat.html.erb +69 -0
- data/app/views/pg_hero/home/maintenance.html.erb +7 -7
- data/app/views/pg_hero/home/queries.html.erb +10 -0
- data/app/views/pg_hero/home/relation_space.html.erb +9 -0
- data/app/views/pg_hero/home/show_query.html.erb +107 -0
- data/app/views/pg_hero/home/space.html.erb +64 -10
- data/config/routes.rb +4 -2
- data/guides/Rails.md +28 -1
- data/guides/Suggested-Indexes.md +1 -1
- data/lib/pghero.rb +25 -36
- data/lib/pghero/database.rb +5 -1
- data/lib/pghero/methods/basic.rb +78 -13
- data/lib/pghero/methods/connections.rb +16 -56
- data/lib/pghero/methods/explain.rb +2 -6
- data/lib/pghero/methods/indexes.rb +173 -18
- data/lib/pghero/methods/kill.rb +2 -2
- data/lib/pghero/methods/maintenance.rb +23 -26
- data/lib/pghero/methods/queries.rb +1 -23
- data/lib/pghero/methods/query_stats.rb +95 -96
- data/lib/pghero/methods/{replica.rb → replication.rb} +17 -4
- data/lib/pghero/methods/sequences.rb +4 -5
- data/lib/pghero/methods/space.rb +101 -8
- data/lib/pghero/methods/suggested_indexes.rb +49 -108
- data/lib/pghero/methods/system.rb +14 -10
- data/lib/pghero/methods/tables.rb +8 -8
- data/lib/pghero/methods/users.rb +10 -12
- data/lib/pghero/version.rb +1 -1
- data/lib/tasks/pghero.rake +1 -1
- data/test/basic_test.rb +38 -0
- data/test/best_index_test.rb +3 -3
- data/test/suggested_indexes_test.rb +0 -2
- data/test/test_helper.rb +38 -40
- metadata +11 -6
- data/app/views/pg_hero/home/index_usage.html.erb +0 -27
- data/test/explain_test.rb +0 -18
@@ -2,29 +2,29 @@ module PgHero
|
|
2
2
|
module Methods
|
3
3
|
module SuggestedIndexes
|
4
4
|
def suggested_indexes_enabled?
|
5
|
-
defined?(PgQuery) && query_stats_enabled?
|
5
|
+
defined?(PgQuery) && Gem::Version.new(PgQuery::VERSION) >= Gem::Version.new("0.9.0") && query_stats_enabled?
|
6
6
|
end
|
7
7
|
|
8
8
|
# TODO clean this mess
|
9
|
-
def suggested_indexes_by_query(
|
9
|
+
def suggested_indexes_by_query(queries: nil, query_stats: nil, indexes: nil)
|
10
10
|
best_indexes = {}
|
11
11
|
|
12
12
|
if suggested_indexes_enabled?
|
13
13
|
# get most time-consuming queries
|
14
|
-
queries
|
14
|
+
queries ||= (query_stats || self.query_stats(historical: true, start_at: 24.hours.ago)).map { |qs| qs[:query] }
|
15
15
|
|
16
16
|
# get best indexes for queries
|
17
17
|
best_indexes = best_index_helper(queries)
|
18
18
|
|
19
19
|
if best_indexes.any?
|
20
20
|
existing_columns = Hash.new { |hash, key| hash[key] = Hash.new { |hash2, key2| hash2[key2] = [] } }
|
21
|
-
indexes
|
22
|
-
indexes.group_by { |g| g[
|
21
|
+
indexes ||= self.indexes
|
22
|
+
indexes.group_by { |g| g[:using] }.each do |group, inds|
|
23
23
|
inds.each do |i|
|
24
|
-
existing_columns[group][i[
|
24
|
+
existing_columns[group][i[:table]] << i[:columns]
|
25
25
|
end
|
26
26
|
end
|
27
|
-
indexes_by_table = indexes.group_by { |i| i[
|
27
|
+
indexes_by_table = indexes.group_by { |i| i[:table] }
|
28
28
|
|
29
29
|
best_indexes.each do |_query, best_index|
|
30
30
|
if best_index[:found]
|
@@ -38,15 +38,17 @@ module PgHero
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
+
else
|
42
|
+
raise NotEnabled, "Suggested indexes not enabled"
|
41
43
|
end
|
42
44
|
|
43
45
|
best_indexes
|
44
46
|
end
|
45
47
|
|
46
|
-
def suggested_indexes(
|
48
|
+
def suggested_indexes(suggested_indexes_by_query: nil, **options)
|
47
49
|
indexes = []
|
48
50
|
|
49
|
-
(
|
51
|
+
(suggested_indexes_by_query || self.suggested_indexes_by_query(options)).select { |_s, i| i[:found] && !i[:covering_index] }.group_by { |_s, i| i[:index] }.each do |index, group|
|
50
52
|
details = {}
|
51
53
|
group.map(&:second).each do |g|
|
52
54
|
details = details.except(:index).deep_merge(g)
|
@@ -57,25 +59,16 @@ module PgHero
|
|
57
59
|
indexes.sort_by { |i| [i[:table], i[:columns]] }
|
58
60
|
end
|
59
61
|
|
60
|
-
def autoindex(
|
62
|
+
def autoindex(create: false)
|
61
63
|
suggested_indexes.each do |index|
|
62
64
|
p index
|
63
|
-
if
|
65
|
+
if create
|
64
66
|
connection.execute("CREATE INDEX CONCURRENTLY ON #{quote_table_name(index[:table])} (#{index[:columns].map { |c| quote_table_name(c) }.join(",")})")
|
65
67
|
end
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
69
|
-
def
|
70
|
-
config["databases"].keys.each do |database|
|
71
|
-
with(database) do
|
72
|
-
puts "Autoindexing #{database}..."
|
73
|
-
autoindex(options)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def best_index(statement, _options = {})
|
71
|
+
def best_index(statement)
|
79
72
|
best_index_helper([statement])[statement]
|
80
73
|
end
|
81
74
|
|
@@ -95,8 +88,8 @@ module PgHero
|
|
95
88
|
# TODO get schema from query structure, then try search path
|
96
89
|
schema = connection_model.connection_config[:schema] || "public"
|
97
90
|
if tables.any?
|
98
|
-
row_stats = Hash[table_stats(table: tables, schema: schema).map { |i| [i[
|
99
|
-
col_stats = column_stats(table: tables, schema: schema).group_by { |i| i[
|
91
|
+
row_stats = Hash[table_stats(table: tables, schema: schema).map { |i| [i[:table], i[:estimated_rows]] }]
|
92
|
+
col_stats = column_stats(table: tables, schema: schema).group_by { |i| i[:table] }
|
100
93
|
end
|
101
94
|
|
102
95
|
# find best index based on query structure and column stats
|
@@ -117,7 +110,7 @@ module PgHero
|
|
117
110
|
total_rows = row_stats[table].to_i
|
118
111
|
index[:rows] = total_rows
|
119
112
|
|
120
|
-
ranks = Hash[col_stats[table].to_a.map { |r| [r[
|
113
|
+
ranks = Hash[col_stats[table].to_a.map { |r| [r[:column], r] }]
|
121
114
|
columns = (where + sort).map { |c| c[:column] }.uniq
|
122
115
|
|
123
116
|
if columns.any?
|
@@ -188,9 +181,7 @@ module PgHero
|
|
188
181
|
return {error: "Too large"} if statement.to_s.length > 10000
|
189
182
|
|
190
183
|
begin
|
191
|
-
|
192
|
-
v2 = parsed_statement.respond_to?(:tree)
|
193
|
-
tree = v2 ? parsed_statement.tree : parsed_statement.parsetree
|
184
|
+
tree = PgQuery.parse(statement).tree
|
194
185
|
rescue PgQuery::ParseError
|
195
186
|
return {error: "Parse error"}
|
196
187
|
end
|
@@ -201,27 +192,23 @@ module PgHero
|
|
201
192
|
unless table
|
202
193
|
error =
|
203
194
|
case tree.keys.first
|
204
|
-
when "InsertStmt"
|
195
|
+
when "InsertStmt"
|
205
196
|
"INSERT statement"
|
206
|
-
when "VariableSetStmt"
|
197
|
+
when "VariableSetStmt"
|
207
198
|
"SET statement"
|
208
199
|
when "SelectStmt"
|
209
200
|
if (tree["SelectStmt"]["fromClause"].first["JoinExpr"] rescue false)
|
210
201
|
"JOIN not supported yet"
|
211
202
|
end
|
212
|
-
when "SELECT"
|
213
|
-
if (tree["SELECT"]["fromClause"].first["JOINEXPR"] rescue false)
|
214
|
-
"JOIN not supported yet"
|
215
|
-
end
|
216
203
|
end
|
217
204
|
return {error: error || "Unknown structure"}
|
218
205
|
end
|
219
206
|
|
220
207
|
select = tree.values.first
|
221
|
-
where = (select["whereClause"] ? parse_where(select["whereClause"]
|
208
|
+
where = (select["whereClause"] ? parse_where(select["whereClause"]) : []) rescue nil
|
222
209
|
return {error: "Unknown structure"} unless where
|
223
210
|
|
224
|
-
sort = (select["sortClause"] ? parse_sort(select["sortClause"]
|
211
|
+
sort = (select["sortClause"] ? parse_sort(select["sortClause"]) : []) rescue []
|
225
212
|
|
226
213
|
{table: table, where: where, sort: sort}
|
227
214
|
end
|
@@ -235,22 +222,22 @@ module PgHero
|
|
235
222
|
def row_estimates(stats, total_rows, rows_left, op)
|
236
223
|
case op
|
237
224
|
when "null"
|
238
|
-
rows_left * stats[
|
225
|
+
rows_left * stats[:null_frac].to_f
|
239
226
|
when "not_null"
|
240
|
-
rows_left * (1 - stats[
|
227
|
+
rows_left * (1 - stats[:null_frac].to_f)
|
241
228
|
else
|
242
|
-
rows_left *= (1 - stats[
|
229
|
+
rows_left *= (1 - stats[:null_frac].to_f)
|
243
230
|
ret =
|
244
|
-
if stats[
|
231
|
+
if stats[:n_distinct].to_f == 0
|
245
232
|
0
|
246
|
-
elsif stats[
|
233
|
+
elsif stats[:n_distinct].to_f < 0
|
247
234
|
if total_rows > 0
|
248
|
-
(-1 / stats[
|
235
|
+
(-1 / stats[:n_distinct].to_f) * (rows_left / total_rows.to_f)
|
249
236
|
else
|
250
237
|
0
|
251
238
|
end
|
252
239
|
else
|
253
|
-
rows_left / stats[
|
240
|
+
rows_left / stats[:n_distinct].to_f
|
254
241
|
end
|
255
242
|
|
256
243
|
case op
|
@@ -272,85 +259,39 @@ module PgHero
|
|
272
259
|
tree["DeleteStmt"]["relation"]["RangeVar"]["relname"]
|
273
260
|
when "UpdateStmt"
|
274
261
|
tree["UpdateStmt"]["relation"]["RangeVar"]["relname"]
|
275
|
-
when "SELECT"
|
276
|
-
tree["SELECT"]["fromClause"].first["RANGEVAR"]["relname"]
|
277
|
-
when "DELETE FROM"
|
278
|
-
tree["DELETE FROM"]["relation"]["RANGEVAR"]["relname"]
|
279
|
-
when "UPDATE"
|
280
|
-
tree["UPDATE"]["relation"]["RANGEVAR"]["relname"]
|
281
262
|
end
|
282
263
|
end
|
283
264
|
|
284
265
|
# TODO capture values
|
285
|
-
def parse_where(tree
|
286
|
-
|
287
|
-
aexpr = tree["A_Expr"]
|
266
|
+
def parse_where(tree)
|
267
|
+
aexpr = tree["A_Expr"]
|
288
268
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
else
|
293
|
-
raise "Not Implemented"
|
294
|
-
end
|
295
|
-
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr["name"].first["String"]["str"])
|
296
|
-
[{column: aexpr["lexpr"]["ColumnRef"]["fields"].last["String"]["str"], op: aexpr["name"].first["String"]["str"]}]
|
297
|
-
elsif tree["NullTest"]
|
298
|
-
op = tree["NullTest"]["nulltesttype"] == 1 ? "not_null" : "null"
|
299
|
-
[{column: tree["NullTest"]["arg"]["ColumnRef"]["fields"].last["String"]["str"], op: op}]
|
269
|
+
if tree["BoolExpr"]
|
270
|
+
if tree["BoolExpr"]["boolop"] == 0
|
271
|
+
tree["BoolExpr"]["args"].flat_map { |v| parse_where(v) }
|
300
272
|
else
|
301
273
|
raise "Not Implemented"
|
302
274
|
end
|
275
|
+
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr["name"].first["String"]["str"])
|
276
|
+
[{column: aexpr["lexpr"]["ColumnRef"]["fields"].last["String"]["str"], op: aexpr["name"].first["String"]["str"]}]
|
277
|
+
elsif tree["NullTest"]
|
278
|
+
op = tree["NullTest"]["nulltesttype"] == 1 ? "not_null" : "null"
|
279
|
+
[{column: tree["NullTest"]["arg"]["ColumnRef"]["fields"].last["String"]["str"], op: op}]
|
303
280
|
else
|
304
|
-
|
305
|
-
|
306
|
-
if tree["BOOLEXPR"]
|
307
|
-
if tree["BOOLEXPR"]["boolop"] == 0
|
308
|
-
tree["BOOLEXPR"]["args"].flat_map { |v| parse_where(v) }
|
309
|
-
else
|
310
|
-
raise "Not Implemented"
|
311
|
-
end
|
312
|
-
elsif tree["AEXPR AND"]
|
313
|
-
left = parse_where(tree["AEXPR AND"]["lexpr"])
|
314
|
-
right = parse_where(tree["AEXPR AND"]["rexpr"])
|
315
|
-
if left && right
|
316
|
-
left + right
|
317
|
-
else
|
318
|
-
raise "Not Implemented"
|
319
|
-
end
|
320
|
-
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr["name"].first)
|
321
|
-
[{column: aexpr["lexpr"]["COLUMNREF"]["fields"].last, op: aexpr["name"].first}]
|
322
|
-
elsif tree["AEXPR IN"] && ["=", "<>"].include?(tree["AEXPR IN"]["name"].first)
|
323
|
-
[{column: tree["AEXPR IN"]["lexpr"]["COLUMNREF"]["fields"].last, op: tree["AEXPR IN"]["name"].first}]
|
324
|
-
elsif tree["NULLTEST"]
|
325
|
-
op = tree["NULLTEST"]["nulltesttype"] == 1 ? "not_null" : "null"
|
326
|
-
[{column: tree["NULLTEST"]["arg"]["COLUMNREF"]["fields"].last, op: op}]
|
327
|
-
else
|
328
|
-
raise "Not Implemented"
|
329
|
-
end
|
281
|
+
raise "Not Implemented"
|
330
282
|
end
|
331
283
|
end
|
332
284
|
|
333
|
-
def parse_sort(sort_clause
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
}
|
340
|
-
end
|
341
|
-
else
|
342
|
-
sort_clause.map do |v|
|
343
|
-
{
|
344
|
-
column: v["SORTBY"]["node"]["COLUMNREF"]["fields"].last,
|
345
|
-
direction: v["SORTBY"]["sortby_dir"] == 2 ? "desc" : "asc"
|
346
|
-
}
|
347
|
-
end
|
285
|
+
def parse_sort(sort_clause)
|
286
|
+
sort_clause.map do |v|
|
287
|
+
{
|
288
|
+
column: v["SortBy"]["node"]["ColumnRef"]["fields"].last["String"]["str"],
|
289
|
+
direction: v["SortBy"]["sortby_dir"] == 2 ? "desc" : "asc"
|
290
|
+
}
|
348
291
|
end
|
349
292
|
end
|
350
293
|
|
351
|
-
def column_stats(
|
352
|
-
schema = options[:schema]
|
353
|
-
tables = options[:table] ? Array(options[:table]) : nil
|
294
|
+
def column_stats(schema: nil, table: nil)
|
354
295
|
select_all <<-SQL
|
355
296
|
SELECT
|
356
297
|
schemaname AS schema,
|
@@ -361,8 +302,8 @@ module PgHero
|
|
361
302
|
FROM
|
362
303
|
pg_stats
|
363
304
|
WHERE
|
364
|
-
|
365
|
-
AND
|
305
|
+
schemaname = #{quote(schema)}
|
306
|
+
#{table ? "AND tablename IN (#{Array(table).map { |t| quote(t) }.join(", ")})" : ""}
|
366
307
|
ORDER BY
|
367
308
|
1, 2, 3
|
368
309
|
SQL
|
@@ -1,27 +1,31 @@
|
|
1
1
|
module PgHero
|
2
2
|
module Methods
|
3
3
|
module System
|
4
|
-
def cpu_usage(options
|
4
|
+
def cpu_usage(**options)
|
5
5
|
rds_stats("CPUUtilization", options)
|
6
6
|
end
|
7
7
|
|
8
|
-
def connection_stats(options
|
8
|
+
def connection_stats(**options)
|
9
9
|
rds_stats("DatabaseConnections", options)
|
10
10
|
end
|
11
11
|
|
12
|
-
def replication_lag_stats(options
|
12
|
+
def replication_lag_stats(**options)
|
13
13
|
rds_stats("ReplicaLag", options)
|
14
14
|
end
|
15
15
|
|
16
|
-
def read_iops_stats(options
|
16
|
+
def read_iops_stats(**options)
|
17
17
|
rds_stats("ReadIOPS", options)
|
18
18
|
end
|
19
19
|
|
20
|
-
def write_iops_stats(options
|
20
|
+
def write_iops_stats(**options)
|
21
21
|
rds_stats("WriteIOPS", options)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def free_space_stats(**options)
|
25
|
+
rds_stats("FreeStorageSpace", options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def rds_stats(metric_name, duration: nil, period: nil, offset: nil)
|
25
29
|
if system_stats_enabled?
|
26
30
|
aws_options = {region: region}
|
27
31
|
if access_key_id
|
@@ -36,9 +40,9 @@ module PgHero
|
|
36
40
|
AWS::CloudWatch.new(aws_options).client
|
37
41
|
end
|
38
42
|
|
39
|
-
duration = (
|
40
|
-
period = (
|
41
|
-
offset = (
|
43
|
+
duration = (duration || 1.hour).to_i
|
44
|
+
period = (period || 1.minute).to_i
|
45
|
+
offset = (offset || 0).to_i
|
42
46
|
|
43
47
|
end_time = (Time.now - offset)
|
44
48
|
# ceil period
|
@@ -59,7 +63,7 @@ module PgHero
|
|
59
63
|
end
|
60
64
|
data
|
61
65
|
else
|
62
|
-
|
66
|
+
raise NotEnabled, "System stats not enabled"
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
@@ -2,18 +2,19 @@ module PgHero
|
|
2
2
|
module Methods
|
3
3
|
module Tables
|
4
4
|
def table_hit_rate
|
5
|
-
|
5
|
+
select_one(<<-SQL
|
6
6
|
SELECT
|
7
7
|
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) AS rate
|
8
8
|
FROM
|
9
9
|
pg_statio_user_tables
|
10
10
|
SQL
|
11
|
-
)
|
11
|
+
)
|
12
12
|
end
|
13
13
|
|
14
14
|
def table_caching
|
15
15
|
select_all <<-SQL
|
16
16
|
SELECT
|
17
|
+
schemaname AS schema,
|
17
18
|
relname AS table,
|
18
19
|
CASE WHEN heap_blks_hit + heap_blks_read = 0 THEN
|
19
20
|
0
|
@@ -32,7 +33,7 @@ module PgHero
|
|
32
33
|
SELECT
|
33
34
|
schemaname AS schema,
|
34
35
|
relname AS table,
|
35
|
-
n_live_tup
|
36
|
+
n_live_tup AS estimated_rows
|
36
37
|
FROM
|
37
38
|
pg_stat_user_tables
|
38
39
|
WHERE
|
@@ -43,14 +44,13 @@ module PgHero
|
|
43
44
|
SQL
|
44
45
|
end
|
45
46
|
|
46
|
-
def table_stats(
|
47
|
-
schema = options[:schema]
|
48
|
-
tables = options[:table] ? Array(options[:table]) : nil
|
47
|
+
def table_stats(schema: nil, table: nil)
|
49
48
|
select_all <<-SQL
|
50
49
|
SELECT
|
51
50
|
nspname AS schema,
|
52
51
|
relname AS table,
|
53
|
-
reltuples::bigint
|
52
|
+
reltuples::bigint AS estimated_rows,
|
53
|
+
pg_total_relation_size(pg_class.oid) AS size_bytes
|
54
54
|
FROM
|
55
55
|
pg_class
|
56
56
|
INNER JOIN
|
@@ -58,7 +58,7 @@ module PgHero
|
|
58
58
|
WHERE
|
59
59
|
relkind = 'r'
|
60
60
|
#{schema ? "AND nspname = #{quote(schema)}" : nil}
|
61
|
-
#{
|
61
|
+
#{table ? "AND relname IN (#{Array(table).map { |t| quote(t) }.join(", ")})" : nil}
|
62
62
|
ORDER BY
|
63
63
|
1, 2
|
64
64
|
SQL
|
data/lib/pghero/methods/users.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
module PgHero
|
2
2
|
module Methods
|
3
3
|
module Users
|
4
|
-
def create_user(user,
|
5
|
-
password
|
6
|
-
|
7
|
-
database = options[:database] || connection_model.connection_config[:database]
|
4
|
+
def create_user(user, password: nil, schema: "public", database: nil, readonly: false, tables: nil)
|
5
|
+
password ||= random_password
|
6
|
+
database ||= connection_model.connection_config[:database]
|
8
7
|
|
9
8
|
commands =
|
10
9
|
[
|
@@ -12,16 +11,16 @@ module PgHero
|
|
12
11
|
"GRANT CONNECT ON DATABASE #{database} TO #{user}",
|
13
12
|
"GRANT USAGE ON SCHEMA #{schema} TO #{user}"
|
14
13
|
]
|
15
|
-
if
|
16
|
-
if
|
17
|
-
commands.concat table_grant_commands("SELECT",
|
14
|
+
if readonly
|
15
|
+
if tables
|
16
|
+
commands.concat table_grant_commands("SELECT", tables, user)
|
18
17
|
else
|
19
18
|
commands << "GRANT SELECT ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
|
20
19
|
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT SELECT ON TABLES TO #{user}"
|
21
20
|
end
|
22
21
|
else
|
23
|
-
if
|
24
|
-
commands.concat table_grant_commands("ALL PRIVILEGES",
|
22
|
+
if tables
|
23
|
+
commands.concat table_grant_commands("ALL PRIVILEGES", tables, user)
|
25
24
|
else
|
26
25
|
commands << "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
|
27
26
|
commands << "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} TO #{user}"
|
@@ -40,9 +39,8 @@ module PgHero
|
|
40
39
|
{password: password}
|
41
40
|
end
|
42
41
|
|
43
|
-
def drop_user(user,
|
44
|
-
|
45
|
-
database = options[:database] || connection_model.connection_config[:database]
|
42
|
+
def drop_user(user, schema: "public", database: nil)
|
43
|
+
database ||= connection_model.connection_config[:database]
|
46
44
|
|
47
45
|
# thanks shiftb
|
48
46
|
commands =
|