pghero_fork 2.7.3
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/CHANGELOG.md +391 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +3 -0
- data/app/assets/images/pghero/favicon.png +0 -0
- data/app/assets/javascripts/pghero/Chart.bundle.js +20755 -0
- data/app/assets/javascripts/pghero/application.js +158 -0
- data/app/assets/javascripts/pghero/chartkick.js +2436 -0
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/javascripts/pghero/jquery.js +10872 -0
- data/app/assets/javascripts/pghero/nouislider.js +2672 -0
- data/app/assets/stylesheets/pghero/application.css +514 -0
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/assets/stylesheets/pghero/nouislider.css +310 -0
- data/app/controllers/pg_hero/home_controller.rb +449 -0
- data/app/helpers/pg_hero/home_helper.rb +30 -0
- data/app/views/layouts/pg_hero/application.html.erb +68 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +16 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +51 -0
- data/app/views/pg_hero/home/_queries_table.html.erb +72 -0
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +16 -0
- data/app/views/pg_hero/home/_suggested_index.html.erb +18 -0
- data/app/views/pg_hero/home/connections.html.erb +32 -0
- data/app/views/pg_hero/home/explain.html.erb +27 -0
- data/app/views/pg_hero/home/index.html.erb +518 -0
- data/app/views/pg_hero/home/index_bloat.html.erb +72 -0
- data/app/views/pg_hero/home/live_queries.html.erb +11 -0
- data/app/views/pg_hero/home/maintenance.html.erb +55 -0
- data/app/views/pg_hero/home/queries.html.erb +33 -0
- data/app/views/pg_hero/home/relation_space.html.erb +14 -0
- data/app/views/pg_hero/home/show_query.html.erb +106 -0
- data/app/views/pg_hero/home/space.html.erb +83 -0
- data/app/views/pg_hero/home/system.html.erb +34 -0
- data/app/views/pg_hero/home/tune.html.erb +53 -0
- data/config/routes.rb +32 -0
- data/lib/generators/pghero/config_generator.rb +13 -0
- data/lib/generators/pghero/query_stats_generator.rb +18 -0
- data/lib/generators/pghero/space_stats_generator.rb +18 -0
- data/lib/generators/pghero/templates/config.yml.tt +46 -0
- data/lib/generators/pghero/templates/query_stats.rb.tt +15 -0
- data/lib/generators/pghero/templates/space_stats.rb.tt +13 -0
- data/lib/pghero.rb +246 -0
- data/lib/pghero/connection.rb +5 -0
- data/lib/pghero/database.rb +175 -0
- data/lib/pghero/engine.rb +16 -0
- data/lib/pghero/methods/basic.rb +160 -0
- data/lib/pghero/methods/connections.rb +77 -0
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/explain.rb +29 -0
- data/lib/pghero/methods/indexes.rb +332 -0
- data/lib/pghero/methods/kill.rb +28 -0
- data/lib/pghero/methods/maintenance.rb +93 -0
- data/lib/pghero/methods/queries.rb +75 -0
- data/lib/pghero/methods/query_stats.rb +349 -0
- data/lib/pghero/methods/replication.rb +74 -0
- data/lib/pghero/methods/sequences.rb +124 -0
- data/lib/pghero/methods/settings.rb +37 -0
- data/lib/pghero/methods/space.rb +141 -0
- data/lib/pghero/methods/suggested_indexes.rb +329 -0
- data/lib/pghero/methods/system.rb +287 -0
- data/lib/pghero/methods/tables.rb +68 -0
- data/lib/pghero/methods/users.rb +87 -0
- data/lib/pghero/query_stats.rb +5 -0
- data/lib/pghero/space_stats.rb +5 -0
- data/lib/pghero/stats.rb +6 -0
- data/lib/pghero/version.rb +3 -0
- data/lib/tasks/pghero.rake +27 -0
- data/licenses/LICENSE-chart.js.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +22 -0
- data/licenses/LICENSE-highlight.js.txt +29 -0
- data/licenses/LICENSE-jquery.txt +20 -0
- data/licenses/LICENSE-moment.txt +22 -0
- data/licenses/LICENSE-nouislider.txt +21 -0
- metadata +130 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
module PgHero
|
2
|
+
module Methods
|
3
|
+
module System
|
4
|
+
def system_stats_enabled?
|
5
|
+
!system_stats_provider.nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
# TODO remove defined checks in 3.0
|
9
|
+
def system_stats_provider
|
10
|
+
if aws_db_instance_identifier && (defined?(Aws) || defined?(AWS))
|
11
|
+
:aws
|
12
|
+
elsif gcp_database_id
|
13
|
+
:gcp
|
14
|
+
elsif azure_resource_id
|
15
|
+
:azure
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def cpu_usage(**options)
|
20
|
+
system_stats(:cpu, **options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection_stats(**options)
|
24
|
+
system_stats(:connections, **options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def replication_lag_stats(**options)
|
28
|
+
system_stats(:replication_lag, **options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_iops_stats(**options)
|
32
|
+
system_stats(:read_iops, **options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_iops_stats(**options)
|
36
|
+
system_stats(:write_iops, **options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def free_space_stats(**options)
|
40
|
+
system_stats(:free_space, **options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def rds_stats(metric_name, duration: nil, period: nil, offset: nil, series: false)
|
44
|
+
if system_stats_enabled?
|
45
|
+
aws_options = {region: region}
|
46
|
+
if access_key_id
|
47
|
+
aws_options[:access_key_id] = access_key_id
|
48
|
+
aws_options[:secret_access_key] = secret_access_key
|
49
|
+
end
|
50
|
+
|
51
|
+
client =
|
52
|
+
if defined?(Aws)
|
53
|
+
Aws::CloudWatch::Client.new(aws_options)
|
54
|
+
else
|
55
|
+
AWS::CloudWatch.new(aws_options).client
|
56
|
+
end
|
57
|
+
|
58
|
+
duration = (duration || 1.hour).to_i
|
59
|
+
period = (period || 1.minute).to_i
|
60
|
+
offset = (offset || 0).to_i
|
61
|
+
end_time = Time.at(((Time.now - offset).to_f / period).ceil * period)
|
62
|
+
start_time = end_time - duration
|
63
|
+
|
64
|
+
resp = client.get_metric_statistics(
|
65
|
+
namespace: "AWS/RDS",
|
66
|
+
metric_name: metric_name,
|
67
|
+
dimensions: [{name: "DBInstanceIdentifier", value: aws_db_instance_identifier}],
|
68
|
+
start_time: start_time.iso8601,
|
69
|
+
end_time: end_time.iso8601,
|
70
|
+
period: period,
|
71
|
+
statistics: ["Average"]
|
72
|
+
)
|
73
|
+
data = {}
|
74
|
+
resp[:datapoints].sort_by { |d| d[:timestamp] }.each do |d|
|
75
|
+
data[d[:timestamp]] = d[:average]
|
76
|
+
end
|
77
|
+
|
78
|
+
add_missing_data(data, start_time, end_time, period) if series
|
79
|
+
|
80
|
+
data
|
81
|
+
else
|
82
|
+
raise NotEnabled, "System stats not enabled"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def azure_stats(metric_name, duration: nil, period: nil, offset: nil, series: false)
|
87
|
+
# TODO DRY with RDS stats
|
88
|
+
duration = (duration || 1.hour).to_i
|
89
|
+
period = (period || 1.minute).to_i
|
90
|
+
offset = (offset || 0).to_i
|
91
|
+
end_time = Time.at(((Time.now - offset).to_f / period).ceil * period)
|
92
|
+
start_time = end_time - duration
|
93
|
+
|
94
|
+
interval =
|
95
|
+
case period
|
96
|
+
when 60
|
97
|
+
"PT1M"
|
98
|
+
when 300
|
99
|
+
"PT5M"
|
100
|
+
when 900
|
101
|
+
"PT15M"
|
102
|
+
when 1800
|
103
|
+
"PT30M"
|
104
|
+
when 3600
|
105
|
+
"PT1H"
|
106
|
+
else
|
107
|
+
raise Error, "Unsupported period"
|
108
|
+
end
|
109
|
+
|
110
|
+
client = Azure::Monitor::Profiles::Latest::Mgmt::Client.new
|
111
|
+
timespan = "#{start_time.iso8601}/#{end_time.iso8601}"
|
112
|
+
results = client.metrics.list(
|
113
|
+
azure_resource_id,
|
114
|
+
metricnames: metric_name,
|
115
|
+
aggregation: "Average",
|
116
|
+
timespan: timespan,
|
117
|
+
interval: interval
|
118
|
+
)
|
119
|
+
|
120
|
+
data = {}
|
121
|
+
result = results.value.first
|
122
|
+
if result
|
123
|
+
result.timeseries.first.data.each do |point|
|
124
|
+
data[point.time_stamp.to_time] = point.average
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
add_missing_data(data, start_time, end_time, period) if series
|
129
|
+
|
130
|
+
data
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def gcp_stats(metric_name, duration: nil, period: nil, offset: nil, series: false)
|
136
|
+
require "google/cloud/monitoring/v3"
|
137
|
+
|
138
|
+
# TODO DRY with RDS stats
|
139
|
+
duration = (duration || 1.hour).to_i
|
140
|
+
period = (period || 1.minute).to_i
|
141
|
+
offset = (offset || 0).to_i
|
142
|
+
end_time = Time.at(((Time.now - offset).to_f / period).ceil * period)
|
143
|
+
start_time = end_time - duration
|
144
|
+
|
145
|
+
# validate input since we need to interpolate below
|
146
|
+
raise Error, "Invalid metric name" unless metric_name =~ /\A[a-z\/_]+\z/i
|
147
|
+
raise Error, "Invalid database id" unless gcp_database_id =~ /\A[a-z0-9\-:]+\z/i
|
148
|
+
|
149
|
+
# we handle three situations:
|
150
|
+
# 1. google-cloud-monitoring-v3
|
151
|
+
# 2. google-cloud-monitoring >= 1
|
152
|
+
# 3. google-cloud-monitoring < 1
|
153
|
+
|
154
|
+
# for situations 1 and 2
|
155
|
+
# Google::Cloud::Monitoring.metric_service is documented
|
156
|
+
# but doesn't work for situation 1
|
157
|
+
if defined?(Google::Cloud::Monitoring::V3::MetricService::Client)
|
158
|
+
client = Google::Cloud::Monitoring::V3::MetricService::Client.new
|
159
|
+
|
160
|
+
interval = Google::Cloud::Monitoring::V3::TimeInterval.new
|
161
|
+
interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
|
162
|
+
# subtract period to make sure we get first data point
|
163
|
+
interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
|
164
|
+
|
165
|
+
aggregation = Google::Cloud::Monitoring::V3::Aggregation.new
|
166
|
+
# may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
|
167
|
+
# stick with average for now to match AWS
|
168
|
+
aggregation.per_series_aligner = Google::Cloud::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
|
169
|
+
aggregation.alignment_period = period
|
170
|
+
|
171
|
+
results = client.list_time_series({
|
172
|
+
name: "projects/#{gcp_database_id.split(":").first}",
|
173
|
+
filter: "metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
|
174
|
+
interval: interval,
|
175
|
+
view: Google::Cloud::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
|
176
|
+
aggregation: aggregation
|
177
|
+
})
|
178
|
+
else
|
179
|
+
require "google/cloud/monitoring"
|
180
|
+
|
181
|
+
client = Google::Cloud::Monitoring::Metric.new
|
182
|
+
|
183
|
+
interval = Google::Monitoring::V3::TimeInterval.new
|
184
|
+
interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
|
185
|
+
# subtract period to make sure we get first data point
|
186
|
+
interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
|
187
|
+
|
188
|
+
aggregation = Google::Monitoring::V3::Aggregation.new
|
189
|
+
# may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
|
190
|
+
# stick with average for now to match AWS
|
191
|
+
aggregation.per_series_aligner = Google::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
|
192
|
+
aggregation.alignment_period = period
|
193
|
+
|
194
|
+
results = client.list_time_series(
|
195
|
+
"projects/#{gcp_database_id.split(":").first}",
|
196
|
+
"metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
|
197
|
+
interval,
|
198
|
+
Google::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
|
199
|
+
aggregation: aggregation
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
data = {}
|
204
|
+
result = results.first
|
205
|
+
if result
|
206
|
+
result.points.each do |point|
|
207
|
+
time = Time.at(point.interval.start_time.seconds)
|
208
|
+
value = point.value.double_value
|
209
|
+
value *= 100 if metric_name == "cpu/utilization"
|
210
|
+
data[time] = value
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
add_missing_data(data, start_time, end_time, period) if series
|
215
|
+
|
216
|
+
data
|
217
|
+
end
|
218
|
+
|
219
|
+
def system_stats(metric_key, **options)
|
220
|
+
case system_stats_provider
|
221
|
+
when :aws
|
222
|
+
metrics = {
|
223
|
+
cpu: "CPUUtilization",
|
224
|
+
connections: "DatabaseConnections",
|
225
|
+
replication_lag: "ReplicaLag",
|
226
|
+
read_iops: "ReadIOPS",
|
227
|
+
write_iops: "WriteIOPS",
|
228
|
+
free_space: "FreeStorageSpace"
|
229
|
+
}
|
230
|
+
rds_stats(metrics[metric_key], **options)
|
231
|
+
when :gcp
|
232
|
+
if metric_key == :free_space
|
233
|
+
quota = gcp_stats("disk/quota", **options)
|
234
|
+
used = gcp_stats("disk/bytes_used", **options)
|
235
|
+
free_space(quota, used)
|
236
|
+
else
|
237
|
+
metrics = {
|
238
|
+
cpu: "cpu/utilization",
|
239
|
+
connections: "postgresql/num_backends",
|
240
|
+
replication_lag: "replication/replica_lag",
|
241
|
+
read_iops: "disk/read_ops_count",
|
242
|
+
write_iops: "disk/write_ops_count"
|
243
|
+
}
|
244
|
+
gcp_stats(metrics[metric_key], **options)
|
245
|
+
end
|
246
|
+
when :azure
|
247
|
+
if metric_key == :free_space
|
248
|
+
quota = azure_stats("storage_limit", **options)
|
249
|
+
used = azure_stats("storage_used", **options)
|
250
|
+
free_space(quota, used)
|
251
|
+
else
|
252
|
+
# no read_iops, write_iops
|
253
|
+
# could add io_consumption_percent
|
254
|
+
metrics = {
|
255
|
+
cpu: "cpu_percent",
|
256
|
+
connections: "active_connections",
|
257
|
+
replication_lag: "pg_replica_log_delay_in_seconds"
|
258
|
+
}
|
259
|
+
raise Error, "Metric not supported" unless metrics[metric_key]
|
260
|
+
azure_stats(metrics[metric_key], **options)
|
261
|
+
end
|
262
|
+
else
|
263
|
+
raise NotEnabled, "System stats not enabled"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# only use data points included in both series
|
268
|
+
# this also eliminates need to align Time.now
|
269
|
+
def free_space(quota, used)
|
270
|
+
data = {}
|
271
|
+
quota.each do |k, v|
|
272
|
+
data[k] = v - used[k] if v && used[k]
|
273
|
+
end
|
274
|
+
data
|
275
|
+
end
|
276
|
+
|
277
|
+
def add_missing_data(data, start_time, end_time, period)
|
278
|
+
time = start_time
|
279
|
+
end_time = end_time
|
280
|
+
while time < end_time
|
281
|
+
data[time] ||= nil
|
282
|
+
time += period
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module PgHero
|
2
|
+
module Methods
|
3
|
+
module Tables
|
4
|
+
def table_hit_rate
|
5
|
+
select_one(<<-SQL
|
6
|
+
SELECT
|
7
|
+
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) AS rate
|
8
|
+
FROM
|
9
|
+
pg_statio_user_tables
|
10
|
+
SQL
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def table_caching
|
15
|
+
select_all <<-SQL
|
16
|
+
SELECT
|
17
|
+
schemaname AS schema,
|
18
|
+
relname AS table,
|
19
|
+
CASE WHEN heap_blks_hit + heap_blks_read = 0 THEN
|
20
|
+
0
|
21
|
+
ELSE
|
22
|
+
ROUND(1.0 * heap_blks_hit / (heap_blks_hit + heap_blks_read), 2)
|
23
|
+
END AS hit_rate
|
24
|
+
FROM
|
25
|
+
pg_statio_user_tables
|
26
|
+
ORDER BY
|
27
|
+
2 DESC, 1
|
28
|
+
SQL
|
29
|
+
end
|
30
|
+
|
31
|
+
def unused_tables
|
32
|
+
select_all <<-SQL
|
33
|
+
SELECT
|
34
|
+
schemaname AS schema,
|
35
|
+
relname AS table,
|
36
|
+
n_live_tup AS estimated_rows
|
37
|
+
FROM
|
38
|
+
pg_stat_user_tables
|
39
|
+
WHERE
|
40
|
+
idx_scan = 0
|
41
|
+
ORDER BY
|
42
|
+
n_live_tup DESC,
|
43
|
+
relname ASC
|
44
|
+
SQL
|
45
|
+
end
|
46
|
+
|
47
|
+
def table_stats(schema: nil, table: nil)
|
48
|
+
select_all <<-SQL
|
49
|
+
SELECT
|
50
|
+
nspname AS schema,
|
51
|
+
relname AS table,
|
52
|
+
reltuples::bigint AS estimated_rows,
|
53
|
+
pg_total_relation_size(pg_class.oid) AS size_bytes
|
54
|
+
FROM
|
55
|
+
pg_class
|
56
|
+
INNER JOIN
|
57
|
+
pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
58
|
+
WHERE
|
59
|
+
relkind = 'r'
|
60
|
+
#{schema ? "AND nspname = #{quote(schema)}" : nil}
|
61
|
+
#{table ? "AND relname IN (#{Array(table).map { |t| quote(t) }.join(", ")})" : nil}
|
62
|
+
ORDER BY
|
63
|
+
1, 2
|
64
|
+
SQL
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module PgHero
|
2
|
+
module Methods
|
3
|
+
module Users
|
4
|
+
# documented as unsafe to pass user input
|
5
|
+
# TODO quote in 3.0, but still not officially supported
|
6
|
+
def create_user(user, password: nil, schema: "public", database: nil, readonly: false, tables: nil)
|
7
|
+
password ||= random_password
|
8
|
+
database ||= PgHero.connection_config(connection_model)[:database]
|
9
|
+
|
10
|
+
commands =
|
11
|
+
[
|
12
|
+
"CREATE ROLE #{user} LOGIN PASSWORD #{quote(password)}",
|
13
|
+
"GRANT CONNECT ON DATABASE #{database} TO #{user}",
|
14
|
+
"GRANT USAGE ON SCHEMA #{schema} TO #{user}"
|
15
|
+
]
|
16
|
+
if readonly
|
17
|
+
if tables
|
18
|
+
commands.concat table_grant_commands("SELECT", tables, user)
|
19
|
+
else
|
20
|
+
commands << "GRANT SELECT ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
|
21
|
+
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT SELECT ON TABLES TO #{user}"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
if tables
|
25
|
+
commands.concat table_grant_commands("ALL PRIVILEGES", tables, user)
|
26
|
+
else
|
27
|
+
commands << "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
|
28
|
+
commands << "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} TO #{user}"
|
29
|
+
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON TABLES TO #{user}"
|
30
|
+
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON SEQUENCES TO #{user}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# run commands
|
35
|
+
connection_model.transaction do
|
36
|
+
commands.each do |command|
|
37
|
+
execute command
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
{password: password}
|
42
|
+
end
|
43
|
+
|
44
|
+
# documented as unsafe to pass user input
|
45
|
+
# TODO quote in 3.0, but still not officially supported
|
46
|
+
def drop_user(user, schema: "public", database: nil)
|
47
|
+
database ||= PgHero.connection_config(connection_model)[:database]
|
48
|
+
|
49
|
+
# thanks shiftb
|
50
|
+
commands =
|
51
|
+
[
|
52
|
+
"REVOKE CONNECT ON DATABASE #{database} FROM #{user}",
|
53
|
+
"REVOKE USAGE ON SCHEMA #{schema} FROM #{user}",
|
54
|
+
"REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} FROM #{user}",
|
55
|
+
"REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} FROM #{user}",
|
56
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE SELECT ON TABLES FROM #{user}",
|
57
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE SELECT ON SEQUENCES FROM #{user}",
|
58
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE ALL ON SEQUENCES FROM #{user}",
|
59
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE ALL ON TABLES FROM #{user}",
|
60
|
+
"DROP ROLE #{user}"
|
61
|
+
]
|
62
|
+
|
63
|
+
# run commands
|
64
|
+
connection_model.transaction do
|
65
|
+
commands.each do |command|
|
66
|
+
execute command
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def random_password
|
76
|
+
require "securerandom"
|
77
|
+
SecureRandom.base64(40).delete("+/=")[0...24]
|
78
|
+
end
|
79
|
+
|
80
|
+
def table_grant_commands(privilege, tables, user)
|
81
|
+
tables.map do |table|
|
82
|
+
"GRANT #{privilege} ON TABLE #{table} TO #{user}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|