rails-pg-extras 5.6.10 → 5.6.12
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 +4 -4
- data/README.md +27 -17
- data/app/views/rails_pg_extras/web/queries/show.html.erb +2 -4
- data/app/views/rails_pg_extras/web/shared/_queries_selector.html.erb +4 -4
- data/lib/rails-pg-extras.rb +8 -1
- data/lib/rails_pg_extras/mcp_app.rb +102 -0
- data/lib/rails_pg_extras/version.rb +1 -1
- data/pg-extras-mcp.png +0 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ab5f408e9c0a7f70fea16a4d8d2e5079b130c8bb3536a2da760aa8cc509b1cf
|
4
|
+
data.tar.gz: 39a83a3ce09da9710884277245c2306ab70b3584f32885651f4553a7faf0ebbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23302e3cf2afab6463cf306dc9d7274d0da0364080e105559260b47f93ec3c55bfb7faed12185f7119a6b4a64fb64568ce637e03a8cef4188fe983039753218d
|
7
|
+
data.tar.gz: 00bf2ca5b4c857ba53063f3063501d5d2d4a5b561256aef79de379b921461d041e5e50bf4667480d06a2aafad986c8c95b2437ca0cd0f87b457f62e6388104e2
|
data/README.md
CHANGED
@@ -12,6 +12,10 @@ Optionally you can enable a visual interface:
|
|
12
12
|
|
13
13
|

|
14
14
|
|
15
|
+
[rails-pg-extras-mcp gem](https://github.com/pawurb/rails-pg-extras-mcp) provides an MCP (Model Context Protocol) interface enabling PostgreSQL metadata and performance analysis with an LLM support.
|
16
|
+
|
17
|
+

|
18
|
+
|
15
19
|
Alternative versions:
|
16
20
|
|
17
21
|
- Core dependency - [Ruby](https://github.com/pawurb/ruby-pg-extras)
|
@@ -59,6 +63,12 @@ By default a primary ActiveRecord database connection is used for running metada
|
|
59
63
|
ENV["RAILS_PG_EXTRAS_DATABASE_URL"] = "postgresql://postgres:secret@localhost:5432/database_name"
|
60
64
|
```
|
61
65
|
|
66
|
+
Alternatively, you can specify database URL with a method call:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
RailsPgExtras.database_url = "postgresql://postgres:secret@localhost:5432/database_name"
|
70
|
+
```
|
71
|
+
|
62
72
|
## Usage
|
63
73
|
|
64
74
|
Each command can be used as a rake task, or a directly from the Ruby code.
|
@@ -453,18 +463,18 @@ RailsPgExtras.outliers(args: { limit: 20 })
|
|
453
463
|
|
454
464
|
$ rake pg_extras:outliers
|
455
465
|
|
456
|
-
qry | exec_time | prop_exec_time | ncalls | sync_io_time
|
457
|
-
|
458
|
-
SELECT * FROM archivable_usage_events.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
|
459
|
-
COPY public.archivable_usage_events (.. | 50:38:33.198418 | 23.6% | 13 | 13:34:21.
|
460
|
-
COPY public.usage_events (id, reporte.. | 02:32:16.335233 | 1.2% | 13 | 00:34:19.
|
461
|
-
INSERT INTO usage_events (id, retaine.. | 01:42:59.436532 | 0.8% | 12,328,187 | 00:00:00
|
462
|
-
SELECT * FROM usage_events WHERE (alp.. | 01:18:10.754354 | 0.6% | 102,114,301 | 00:00:00
|
463
|
-
UPDATE usage_events SET reporter_id =.. | 00:52:35.683254 | 0.4% | 23,786,348 | 00:00:00
|
466
|
+
qry | exec_time | prop_exec_time | ncalls | sync_io_time | avg_exec_ms
|
467
|
+
-----------------------------------------+------------------+----------------+-------------+--------------+-------------
|
468
|
+
SELECT * FROM archivable_usage_events.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00 | 16.24
|
469
|
+
COPY public.archivable_usage_events (.. | 50:38:33.198418 | 23.6% | 13 | 13:34:21.008 | 233845.63
|
470
|
+
COPY public.usage_events (id, reporte.. | 02:32:16.335233 | 1.2% | 13 | 00:34:19.718 | 11689.72
|
471
|
+
INSERT INTO usage_events (id, retaine.. | 01:42:59.436532 | 0.8% | 12,328,187 | 00:00:00 | 0.84
|
472
|
+
SELECT * FROM usage_events WHERE (alp.. | 01:18:10.754354 | 0.6% | 102,114,301 | 00:00:00 | 0.77
|
473
|
+
UPDATE usage_events SET reporter_id =.. | 00:52:35.683254 | 0.4% | 23,786,348 | 00:00:00 | 0.13
|
464
474
|
(truncated results for brevity)
|
465
475
|
```
|
466
476
|
|
467
|
-
This command displays statements, obtained from `pg_stat_statements`, ordered by the amount of time to execute in aggregate. This includes the statement itself, the total execution time for that statement, the proportion of total execution time for all statements that statement has taken up, the number of times that statement has been called,
|
477
|
+
This command displays statements, obtained from `pg_stat_statements`, ordered by the amount of time to execute in aggregate. This includes the statement itself, the total execution time for that statement, the proportion of total execution time for all statements that statement has taken up, the number of times that statement has been called, the amount of time that statement spent on synchronous I/O (reading/writing from the file system), and the average execution time per call in milliseconds.
|
468
478
|
|
469
479
|
Typically, an efficient query will have an appropriate ratio of calls to total execution time, with as little time spent on I/O as possible. Queries that have a high total execution time but low call count should be investigated to improve their performance. Queries that have a high proportion of execution time being spent on synchronous I/O should also be investigated.
|
470
480
|
|
@@ -477,17 +487,17 @@ RailsPgExtras.calls(args: { limit: 10 })
|
|
477
487
|
|
478
488
|
$ rake pg_extras:calls
|
479
489
|
|
480
|
-
qry | exec_time | prop_exec_time | ncalls | sync_io_time
|
481
|
-
|
482
|
-
SELECT * FROM usage_events WHERE (alp.. | 01:18:11.073333 | 0.6% | 102,120,780 | 00:00:00
|
483
|
-
BEGIN | 00:00:51.285988 | 0.0% | 47,288,662 | 00:00:00
|
484
|
-
COMMIT | 00:00:52.31724 | 0.0% | 47,288,615 | 00:00:00
|
485
|
-
SELECT * FROM archivable_usage_event.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
|
486
|
-
UPDATE usage_events SET reporter_id =.. | 00:52:35.986167 | 0.4% | 23,788,388 | 00:00:00
|
490
|
+
qry | exec_time | prop_exec_time | ncalls | sync_io_time | avg_exec_ms
|
491
|
+
-----------------------------------------+------------------+----------------+-------------+--------------+-------------
|
492
|
+
SELECT * FROM usage_events WHERE (alp.. | 01:18:11.073333 | 0.6% | 102,120,780 | 00:00:00 | 1
|
493
|
+
BEGIN | 00:00:51.285988 | 0.0% | 47,288,662 | 00:00:00 | 0
|
494
|
+
COMMIT | 00:00:52.31724 | 0.0% | 47,288,615 | 00:00:00 | 0
|
495
|
+
SELECT * FROM archivable_usage_event.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00 | 16
|
496
|
+
UPDATE usage_events SET reporter_id =.. | 00:52:35.986167 | 0.4% | 23,788,388 | 00:00:00 | 0
|
487
497
|
(truncated results for brevity)
|
488
498
|
```
|
489
499
|
|
490
|
-
This command is much like `pg:outliers`, but ordered by the number of times a statement has been called.
|
500
|
+
This command is much like `pg:outliers`, but ordered by the number of times a statement has been called.
|
491
501
|
|
492
502
|
[More info](https://pawelurbanek.com/postgresql-fix-performance#missing-indexes)
|
493
503
|
|
@@ -3,10 +3,8 @@
|
|
3
3
|
|
4
4
|
<br>
|
5
5
|
|
6
|
-
<%=
|
7
|
-
|
8
|
-
class: "inline-block bg-blue-500 text-white font-medium px-4 py-2 rounded-lg shadow-md hover:bg-blue-600 transition"
|
9
|
-
%>
|
6
|
+
<%= link_to "← Back to Diagnose", queries_path,
|
7
|
+
class: "inline-block bg-blue-500 text-white font-medium px-4 py-2 rounded-lg shadow-md hover:bg-blue-600 transition" %>
|
10
8
|
|
11
9
|
<% if @error %>
|
12
10
|
<div class="text-red-500 p-3 font-mono my-5"><%= @error %></div>
|
@@ -1,7 +1,7 @@
|
|
1
|
-
<%= form_tag queries_path, id: "queries", method: :get do |f| %>
|
2
|
-
<%= select_tag :query_name, options_for_select(@all_queries, params[:query_name]),
|
3
|
-
{ prompt: "diagnose", class: "border p-2 font-bold", autofocus: true } %>
|
4
|
-
<% end %>
|
1
|
+
<%= form_tag queries_path, id: "queries", method: :get do |f| %>
|
2
|
+
<%= select_tag :query_name, options_for_select(@all_queries, params[:query_name]),
|
3
|
+
{ prompt: "diagnose", class: "border p-2 font-bold", autofocus: true } %>
|
4
|
+
<% end %>
|
5
5
|
|
6
6
|
<%= javascript_tag nonce: true do -%>
|
7
7
|
document.getElementById('queries').addEventListener('change', (e) => {
|
data/lib/rails-pg-extras.rb
CHANGED
@@ -12,6 +12,7 @@ require "rails_pg_extras/table_info"
|
|
12
12
|
require "rails_pg_extras/table_info_print"
|
13
13
|
|
14
14
|
module RailsPgExtras
|
15
|
+
@@database_url = nil
|
15
16
|
QUERIES = RubyPgExtras::QUERIES
|
16
17
|
DEFAULT_ARGS = RubyPgExtras::DEFAULT_ARGS
|
17
18
|
NEW_PG_STAT_STATEMENTS = RubyPgExtras::NEW_PG_STAT_STATEMENTS
|
@@ -142,8 +143,14 @@ module RailsPgExtras
|
|
142
143
|
RubyPgExtras.display_result(result, title: "Missing foreign key constraints", in_format: in_format)
|
143
144
|
end
|
144
145
|
|
146
|
+
def self.database_url=(value)
|
147
|
+
@@database_url = value
|
148
|
+
end
|
149
|
+
|
145
150
|
def self.connection
|
146
|
-
|
151
|
+
db_url = @@database_url || ENV["RAILS_PG_EXTRAS_DATABASE_URL"]
|
152
|
+
|
153
|
+
if db_url.present?
|
147
154
|
connector = ActiveRecord::Base.establish_connection(db_url)
|
148
155
|
|
149
156
|
if connector.respond_to?(:connection)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fast_mcp"
|
4
|
+
require "rack"
|
5
|
+
require "rails_pg_extras/version"
|
6
|
+
|
7
|
+
SKIP_QUERIES = %i[
|
8
|
+
add_extensions
|
9
|
+
pg_stat_statements_reset
|
10
|
+
kill_pid
|
11
|
+
kill_all
|
12
|
+
mandelbrot
|
13
|
+
]
|
14
|
+
|
15
|
+
QUERY_TOOL_CLASSES = RubyPgExtras::QUERIES.reject { |q| SKIP_QUERIES.include?(q) }.map do |query_name|
|
16
|
+
Class.new(FastMcp::Tool) do
|
17
|
+
description RubyPgExtras.description_for(query_name: query_name)
|
18
|
+
|
19
|
+
define_method :call do
|
20
|
+
RailsPgExtras.public_send(query_name, in_format: :hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
define_singleton_method :name do
|
24
|
+
query_name.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class MissingFkConstraintsTool < FastMcp::Tool
|
30
|
+
description "Shows missing foreign key constraints"
|
31
|
+
|
32
|
+
def call
|
33
|
+
RailsPgExtras.missing_fk_constraints(in_format: :hash)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.name
|
37
|
+
"missing_fk_constraints"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class MissingFkIndexesTool < FastMcp::Tool
|
42
|
+
description "Shows missing foreign key indexes"
|
43
|
+
|
44
|
+
def call
|
45
|
+
RailsPgExtras.missing_fk_indexes(in_format: :hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.name
|
49
|
+
"missing_fk_indexes"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class DiagnoseTool < FastMcp::Tool
|
54
|
+
description "Performs a health check of the database"
|
55
|
+
|
56
|
+
def call
|
57
|
+
RailsPgExtras.diagnose(in_format: :hash)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.name
|
61
|
+
"diagnose"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ReadmeResource < FastMcp::Resource
|
66
|
+
uri "file://README.md"
|
67
|
+
resource_name "README"
|
68
|
+
description "The README for RailsPgExtras"
|
69
|
+
mime_type "text/plain"
|
70
|
+
|
71
|
+
def content
|
72
|
+
File.read(uri)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module RailsPgExtras
|
77
|
+
class MCPApp
|
78
|
+
def self.build
|
79
|
+
app = lambda do |_env|
|
80
|
+
[200, { "Content-Type" => "text/html" },
|
81
|
+
["<html><body><h1>Hello from Rack!</h1><p>This is a simple Rack app with MCP middleware.</p></body></html>"]]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create the MCP middleware
|
85
|
+
mcp_app = FastMcp.rack_middleware(
|
86
|
+
app,
|
87
|
+
name: "rails-pg-extras", version: RailsPgExtras::VERSION,
|
88
|
+
path_prefix: "/pg-extras-mcp",
|
89
|
+
logger: Logger.new($stdout),
|
90
|
+
) do |server|
|
91
|
+
server.register_tools(DiagnoseTool)
|
92
|
+
server.register_tools(MissingFkConstraintsTool)
|
93
|
+
server.register_tools(MissingFkIndexesTool)
|
94
|
+
server.register_tools(*QUERY_TOOL_CLASSES)
|
95
|
+
|
96
|
+
server.register_resource(ReadmeResource)
|
97
|
+
|
98
|
+
Rack::Builder.new { run mcp_app }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/pg-extras-mcp.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-pg-extras
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.6.
|
4
|
+
version: 5.6.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-pg-extras
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.6.
|
19
|
+
version: 5.6.12
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.6.
|
26
|
+
version: 5.6.12
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- lib/rails_pg_extras/diagnose_print.rb
|
117
117
|
- lib/rails_pg_extras/index_info.rb
|
118
118
|
- lib/rails_pg_extras/index_info_print.rb
|
119
|
+
- lib/rails_pg_extras/mcp_app.rb
|
119
120
|
- lib/rails_pg_extras/missing_fk_constraints.rb
|
120
121
|
- lib/rails_pg_extras/missing_fk_indexes.rb
|
121
122
|
- lib/rails_pg_extras/railtie.rb
|
@@ -126,6 +127,7 @@ files:
|
|
126
127
|
- lib/rails_pg_extras/web.rb
|
127
128
|
- lib/rails_pg_extras/web/engine.rb
|
128
129
|
- marginalia-logs.png
|
130
|
+
- pg-extras-mcp.png
|
129
131
|
- pg-extras-ui-3.png
|
130
132
|
- rails-pg-extras-diagnose.png
|
131
133
|
- rails-pg-extras.gemspec
|