pg_peek 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edfdc4a86128d1eec3f1c69e731780b9ef71afdd071d76cde2756eee57d55ff8
4
- data.tar.gz: 666c7f0204c05e295f6c6950e32362720e273d579ec3eec6c557fd2769ab14c0
3
+ metadata.gz: eb99f16f0d2715eb1e038f98b4c7a43077678408b4f7f216f1238a823540f448
4
+ data.tar.gz: 2bda074f80788e6ab201e49948ce76539cb2e62ce2d0f06276ec22262ed32fda
5
5
  SHA512:
6
- metadata.gz: 4ecde1526a25bd02fe30c47dbbe62ff2058ce2d8f6b1ff741ffd50b2eea26236e102646f98b67dabf4d9f6d566717a838c04c15be895d5d3b335a59bde342945
7
- data.tar.gz: 44fa0fbf8d8ccd408c9e967b78ec29503fc94f605beb961cdc8480137773e7b6145af740990c039ac4f09be1c7664d03c204290190c0f9cd94a1e674b9517d15
6
+ metadata.gz: cbd81cbdb7abff990c656557f38e5d889e96c4b80a632a9c55d48b49b0363dd3443ee3ca13adc8ced93edcd5fde0b9c5fdeb4d202f8f64a755aeebacbcffe37d
7
+ data.tar.gz: 0f60578fbe2b529b6fc076d087a2908a0806e23f77597e70d57a8abc8166e8aa89509805f493af20a7495aba75c683a395ec4af1e1e6b032891491d20ae7b1c5
@@ -280,6 +280,12 @@ button, .button {
280
280
  button:hover { border-color: var(--fg); }
281
281
  button.danger { color: var(--danger); border-color: var(--danger); }
282
282
  button.danger:hover { background: var(--danger); color: var(--bg); }
283
+ button:disabled, button:disabled:hover {
284
+ background: var(--panel);
285
+ color: var(--dim);
286
+ border-color: var(--line);
287
+ cursor: not-allowed;
288
+ }
283
289
 
284
290
  form.inline { display: inline; margin: 0; }
285
291
 
@@ -297,6 +303,13 @@ form.inline { display: inline; margin: 0; }
297
303
  margin: 0.75rem 0;
298
304
  }
299
305
 
306
+ .alert {
307
+ border: 1px solid var(--danger);
308
+ color: var(--danger);
309
+ padding: 0.4rem 0.75rem;
310
+ margin: 0.75rem 0;
311
+ }
312
+
300
313
  mark { background: var(--accent); color: var(--bg); }
301
314
 
302
315
  .grid {
@@ -3,9 +3,15 @@
3
3
  class PgPeek::PgStatStatementsController < PgPeek::ApplicationController
4
4
  def reset
5
5
  @database = PgPeek::Database.find(params[:database_id]) or raise ActiveRecord::RecordNotFound, "Database not found"
6
- PgPeek::PgStatStatements.new(database: @database).reset!
6
+ pg_stat_statements = PgPeek::PgStatStatements.new(database: @database)
7
7
 
8
- redirect_to database_path(@database, anchor: "pg_stat_statements"),
9
- notice: "pg_stat_statements has been reset."
8
+ if pg_stat_statements.resettable?
9
+ pg_stat_statements.reset!
10
+ flash[:notice] = "pg_stat_statements has been reset."
11
+ else
12
+ flash[:alert] = "This role may not reset pg_stat_statements; the statistics were left as they are."
13
+ end
14
+
15
+ redirect_to database_path(@database, anchor: "pg_stat_statements")
10
16
  end
11
17
  end
@@ -24,16 +24,34 @@ class PgPeek::PgStatStatements
24
24
  # The extension can be created in the database while the module itself was
25
25
  # never loaded at server start. Querying the view then raises
26
26
  # PG::ObjectNotInPrerequisiteState, so every read has to check this first.
27
+ #
28
+ # Roles without pg_read_all_settings (dokku's postgres plugin, most managed
29
+ # hosts) cannot read shared_preload_libraries, so then the only way to tell
30
+ # is to ask the module itself.
27
31
  def preloaded?
28
- shared_preload_libraries.split(",").map(&:strip).include?(LIBRARY_NAME)
32
+ if shared_preload_libraries
33
+ shared_preload_libraries.split(",").map(&:strip).include?(LIBRARY_NAME)
34
+ else
35
+ installed? && module_loaded?
36
+ end
29
37
  end
30
38
 
31
39
  def usable?
32
40
  installed? && preloaded?
33
41
  end
34
42
 
43
+ # nil when this role may not examine the setting, as opposed to "" when
44
+ # nothing is preloaded.
35
45
  def shared_preload_libraries
36
- @shared_preload_libraries ||= connection.select_value(PgPeek::QueryLoader.mark("SHOW shared_preload_libraries")).to_s
46
+ return @shared_preload_libraries if defined?(@shared_preload_libraries)
47
+
48
+ @shared_preload_libraries = without_aborting_transaction do
49
+ connection.select_value(PgPeek::QueryLoader.mark("SHOW shared_preload_libraries")).to_s
50
+ end
51
+ rescue ActiveRecord::StatementInvalid => e
52
+ raise unless e.cause.is_a?(PG::InsufficientPrivilege)
53
+
54
+ @shared_preload_libraries = nil
37
55
  end
38
56
 
39
57
  def outdated?
@@ -42,12 +60,23 @@ class PgPeek::PgStatStatements
42
60
  @installed_version != @default_version
43
61
  end
44
62
 
63
+ # The extension revokes EXECUTE on the reset from PUBLIC, so only superusers
64
+ # and roles granted it may run one.
65
+ def resettable?
66
+ return @resettable if defined?(@resettable)
67
+
68
+ @resettable = usable? && connection.select_value(PgPeek::QueryLoader.mark(<<~SQL)) == true
69
+ SELECT bool_and(has_function_privilege(oid, 'EXECUTE'))
70
+ FROM pg_proc WHERE proname = 'pg_stat_statements_reset'
71
+ SQL
72
+ end
73
+
45
74
  # Server-wide on purpose. pg_stat_statements_info keeps a single stats_reset
46
75
  # for the whole server and only moves it when every entry is removed, so a
47
76
  # reset scoped to this database would leave "stats since" pointing at the
48
77
  # old time.
49
78
  def reset!
50
- return unless usable?
79
+ return unless resettable?
51
80
 
52
81
  connection.execute PgPeek::QueryLoader.mark(<<-SQL)
53
82
  SELECT pg_stat_statements_reset();
@@ -119,6 +148,27 @@ class PgPeek::PgStatStatements
119
148
 
120
149
  private
121
150
 
151
+ # pg_stat_statements_info() raises PG::ObjectNotInPrerequisiteState unless
152
+ # the module was loaded at server start, and is readable by any role.
153
+ def module_loaded?
154
+ return @module_loaded if defined?(@module_loaded)
155
+
156
+ @module_loaded = without_aborting_transaction do
157
+ connection.select_value(PgPeek::QueryLoader.mark("SELECT 1 FROM pg_stat_statements_info"))
158
+ true
159
+ end
160
+ rescue ActiveRecord::StatementInvalid => e
161
+ raise unless e.cause.is_a?(PG::ObjectNotInPrerequisiteState)
162
+
163
+ @module_loaded = false
164
+ end
165
+
166
+ # A failed statement inside an open transaction (a host app's, or a
167
+ # transactional test) would poison it; a savepoint confines the failure.
168
+ def without_aborting_transaction(&)
169
+ connection.transaction(requires_new: true, &)
170
+ end
171
+
122
172
  def verify_installation
123
173
  result = connection.execute PgPeek::QueryLoader.mark(<<-SQL)
124
174
  SELECT default_version, installed_version FROM pg_available_extensions WHERE name = 'pg_stat_statements';
@@ -15,6 +15,7 @@
15
15
  <body>
16
16
  <main>
17
17
  <% if flash[:notice] %><p class="notice"><%= flash[:notice] %></p><% end %>
18
+ <% if flash[:alert] %><p class="alert"><%= flash[:alert] %></p><% end %>
18
19
  <nav>
19
20
  <ul>
20
21
  <li><%= link_to "pg_peek", databases_path, class: "wordmark" %></li>
@@ -142,8 +142,16 @@ end</code></pre>
142
142
  reset_database_pg_stat_statements_path(@database),
143
143
  method: :delete,
144
144
  class: "danger",
145
+ disabled: !@pg_stat_statements.resettable?,
145
146
  form_class: "inline",
146
147
  form: { data: { confirm: "Reset pg_stat_statements? This clears statement statistics for every database on this server." } } %>
148
+ <% unless @pg_stat_statements.resettable? %>
149
+ <p class="dim">
150
+ <%= @connection_summary["username"] %> may not reset the statistics: the extension
151
+ allows it only to superusers and roles granted it. To allow it:
152
+ </p>
153
+ <pre><code>GRANT EXECUTE ON FUNCTION pg_stat_statements_reset TO <%= @connection_summary["username"] %>;</code></pre>
154
+ <% end %>
147
155
  <% if @pg_stat_statements.outdated? %>
148
156
  <p class="dim">
149
157
  Version <%= @pg_stat_statements.default_version %> is available:
@@ -11,7 +11,11 @@
11
11
  <footer>
12
12
  <small>
13
13
  A server restart is required — this setting cannot be reloaded.
14
- Currently loaded: <code><%= pg_stat_statements.shared_preload_libraries.presence || "(none)" %></code>
14
+ <% if pg_stat_statements.shared_preload_libraries %>
15
+ Currently loaded: <code><%= pg_stat_statements.shared_preload_libraries.presence || "(none)" %></code>
16
+ <% else %>
17
+ This role may not read <code>shared_preload_libraries</code>; the module itself reported it is not loaded.
18
+ <% end %>
15
19
  </small>
16
20
  </footer>
17
21
  </article>
@@ -1,3 +1,3 @@
1
1
  module PgPeek
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_peek
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomasz Mazur
@@ -171,6 +171,7 @@ licenses:
171
171
  metadata:
172
172
  homepage_uri: https://github.com/trix/pg_peek
173
173
  source_code_uri: https://github.com/trix/pg_peek
174
+ changelog_uri: https://github.com/trix/pg_peek/releases
174
175
  rubygems_mfa_required: 'true'
175
176
  rdoc_options: []
176
177
  require_paths: