sql_genius 0.9.0

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +195 -0
  3. data/LICENSE.txt +65 -0
  4. data/README.md +178 -0
  5. data/Rakefile +8 -0
  6. data/app/controllers/concerns/sql_genius/ai_features.rb +332 -0
  7. data/app/controllers/concerns/sql_genius/database_analysis.rb +67 -0
  8. data/app/controllers/concerns/sql_genius/query_execution.rb +87 -0
  9. data/app/controllers/concerns/sql_genius/shared_view_helpers.rb +76 -0
  10. data/app/controllers/sql_genius/base_controller.rb +29 -0
  11. data/app/controllers/sql_genius/queries_controller.rb +94 -0
  12. data/app/views/layouts/sql_genius/application.html.erb +285 -0
  13. data/config/routes.rb +34 -0
  14. data/docs/guides/ai-features.md +115 -0
  15. data/docs/guides/getting-started-rails.md +118 -0
  16. data/docs/guides/ssh-tunnel-connections.md +151 -0
  17. data/docs/screenshots/ai_tools.png +0 -0
  18. data/docs/screenshots/dashboard.png +0 -0
  19. data/docs/screenshots/duplicate_indexes.png +0 -0
  20. data/docs/screenshots/query_explore.png +0 -0
  21. data/docs/screenshots/query_stats.png +0 -0
  22. data/docs/screenshots/server.png +0 -0
  23. data/docs/screenshots/table_sizes.png +0 -0
  24. data/lib/generators/sql_genius/install/install_generator.rb +19 -0
  25. data/lib/generators/sql_genius/install/templates/initializer.rb +56 -0
  26. data/lib/sql_genius/configuration.rb +114 -0
  27. data/lib/sql_genius/core/ai/client.rb +155 -0
  28. data/lib/sql_genius/core/ai/config.rb +47 -0
  29. data/lib/sql_genius/core/ai/connection_advisor.rb +96 -0
  30. data/lib/sql_genius/core/ai/describe_query.rb +41 -0
  31. data/lib/sql_genius/core/ai/dialect_hints.rb +35 -0
  32. data/lib/sql_genius/core/ai/index_advisor.rb +43 -0
  33. data/lib/sql_genius/core/ai/index_planner.rb +91 -0
  34. data/lib/sql_genius/core/ai/innodb_interpreter.rb +78 -0
  35. data/lib/sql_genius/core/ai/migration_risk.rb +51 -0
  36. data/lib/sql_genius/core/ai/optimization.rb +81 -0
  37. data/lib/sql_genius/core/ai/pattern_grouper.rb +94 -0
  38. data/lib/sql_genius/core/ai/rewrite_query.rb +51 -0
  39. data/lib/sql_genius/core/ai/schema_context_builder.rb +82 -0
  40. data/lib/sql_genius/core/ai/schema_review.rb +46 -0
  41. data/lib/sql_genius/core/ai/suggestion.rb +74 -0
  42. data/lib/sql_genius/core/ai/variable_reviewer.rb +113 -0
  43. data/lib/sql_genius/core/ai/workload_digest.rb +86 -0
  44. data/lib/sql_genius/core/analysis/columns.rb +63 -0
  45. data/lib/sql_genius/core/analysis/duplicate_indexes.rb +85 -0
  46. data/lib/sql_genius/core/analysis/query_history.rb +50 -0
  47. data/lib/sql_genius/core/analysis/query_stats.rb +76 -0
  48. data/lib/sql_genius/core/analysis/server_overview.rb +294 -0
  49. data/lib/sql_genius/core/analysis/stats_collector.rb +118 -0
  50. data/lib/sql_genius/core/analysis/stats_history.rb +42 -0
  51. data/lib/sql_genius/core/analysis/table_sizes.rb +52 -0
  52. data/lib/sql_genius/core/analysis/unused_indexes.rb +62 -0
  53. data/lib/sql_genius/core/column_definition.rb +30 -0
  54. data/lib/sql_genius/core/connection/active_record_adapter.rb +75 -0
  55. data/lib/sql_genius/core/connection/fake_adapter.rb +114 -0
  56. data/lib/sql_genius/core/connection.rb +37 -0
  57. data/lib/sql_genius/core/execution_result.rb +27 -0
  58. data/lib/sql_genius/core/index_definition.rb +23 -0
  59. data/lib/sql_genius/core/query_builders/mysql.rb +169 -0
  60. data/lib/sql_genius/core/query_builders/postgresql.rb +185 -0
  61. data/lib/sql_genius/core/query_builders.rb +27 -0
  62. data/lib/sql_genius/core/query_explainer.rb +113 -0
  63. data/lib/sql_genius/core/query_runner/config.rb +21 -0
  64. data/lib/sql_genius/core/query_runner.rb +123 -0
  65. data/lib/sql_genius/core/result.rb +43 -0
  66. data/lib/sql_genius/core/server_info.rb +54 -0
  67. data/lib/sql_genius/core/sql_validator.rb +149 -0
  68. data/lib/sql_genius/core/views/sql_genius/queries/_shared_results.html.erb +59 -0
  69. data/lib/sql_genius/core/views/sql_genius/queries/_tab_ai_tools.html.erb +43 -0
  70. data/lib/sql_genius/core/views/sql_genius/queries/_tab_dashboard.html.erb +97 -0
  71. data/lib/sql_genius/core/views/sql_genius/queries/_tab_duplicate_indexes.html.erb +35 -0
  72. data/lib/sql_genius/core/views/sql_genius/queries/_tab_query_explorer.html.erb +110 -0
  73. data/lib/sql_genius/core/views/sql_genius/queries/_tab_query_stats.html.erb +43 -0
  74. data/lib/sql_genius/core/views/sql_genius/queries/_tab_server.html.erb +59 -0
  75. data/lib/sql_genius/core/views/sql_genius/queries/_tab_slow_queries.html.erb +17 -0
  76. data/lib/sql_genius/core/views/sql_genius/queries/_tab_table_sizes.html.erb +33 -0
  77. data/lib/sql_genius/core/views/sql_genius/queries/_tab_unused_indexes.html.erb +54 -0
  78. data/lib/sql_genius/core/views/sql_genius/queries/dashboard.html.erb +1826 -0
  79. data/lib/sql_genius/core/views/sql_genius/queries/query_detail.html.erb +465 -0
  80. data/lib/sql_genius/core.rb +72 -0
  81. data/lib/sql_genius/engine.rb +31 -0
  82. data/lib/sql_genius/slow_query_monitor.rb +43 -0
  83. data/lib/sql_genius/version.rb +5 -0
  84. data/lib/sql_genius.rb +29 -0
  85. data/sql_genius.gemspec +47 -0
  86. metadata +171 -0
@@ -0,0 +1,285 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>SQLGenius</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="csrf-token" content="<%= form_authenticity_token %>">
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #f5f5f5; color: #333; margin: 0; font-size: 14px; line-height: 1.5; }
10
+ .mg-container { max-width: 1400px; margin: 0 auto; padding: 20px; }
11
+ h4 { margin: 0 0 16px; font-size: 18px; }
12
+
13
+ /* Tabs */
14
+ .mg-tabs { display: flex; border-bottom: 2px solid #dee2e6; margin-bottom: 0; }
15
+ .mg-tab { padding: 8px 16px; cursor: pointer; border: 1px solid transparent; border-bottom: none; margin-bottom: -2px; border-radius: 4px 4px 0 0; background: none; font-size: 14px; color: #555; }
16
+ .mg-tab:hover { color: #000; }
17
+ .mg-tab.active { background: #fff; border-color: #dee2e6; border-bottom-color: #fff; color: #000; font-weight: 500; }
18
+ .mg-tab-content { display: none; background: #fff; border: 1px solid #dee2e6; border-top: none; padding: 16px; }
19
+ .mg-tab-content.active { display: block; }
20
+
21
+ /* Forms */
22
+ label { display: block; font-weight: 500; margin-bottom: 4px; font-size: 13px; }
23
+ select, input[type="number"], input[type="text"], textarea { border: 1px solid #ced4da; border-radius: 4px; padding: 6px 10px; font-size: 13px; width: 100%; font-family: inherit; }
24
+ select:focus, input:focus, textarea:focus { outline: none; border-color: #80bdff; box-shadow: 0 0 0 2px rgba(0,123,255,.15); }
25
+ textarea { font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; resize: vertical; }
26
+ textarea[readonly] { background: #f8f9fa; }
27
+
28
+ /* Grid helpers */
29
+ .mg-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
30
+ .mg-col-1 { flex: 0 0 80px; }
31
+ .mg-col-2 { flex: 0 0 160px; }
32
+ .mg-col-3 { flex: 0 0 240px; }
33
+ .mg-col-4 { flex: 0 0 320px; }
34
+ .mg-col-grow { flex: 1 1 200px; }
35
+ .mg-field { margin-bottom: 12px; }
36
+
37
+ /* Buttons */
38
+ .mg-btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; border: 1px solid transparent; border-radius: 4px; font-size: 13px; cursor: pointer; font-family: inherit; white-space: nowrap; }
39
+ .mg-btn:disabled { opacity: .5; cursor: not-allowed; }
40
+ .mg-btn-primary { background: #007bff; color: #fff; border-color: #007bff; }
41
+ .mg-btn-primary:hover:not(:disabled) { background: #0069d9; }
42
+ .mg-btn-outline { background: #fff; color: #007bff; border-color: #007bff; }
43
+ .mg-btn-outline:hover:not(:disabled) { background: #e7f1ff; }
44
+ .mg-btn-outline-secondary { background: #fff; color: #6c757d; border-color: #6c757d; }
45
+ .mg-btn-outline-secondary:hover:not(:disabled) { background: #f8f9fa; }
46
+ .mg-btn-outline-danger { background: #fff; color: #dc3545; border-color: #dc3545; padding: 4px 8px; }
47
+ .mg-btn-outline-danger:hover:not(:disabled) { background: #ffeef0; }
48
+ .mg-btn-sm { padding: 3px 8px; font-size: 12px; }
49
+
50
+ /* Badge */
51
+ .mg-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; }
52
+ .mg-badge-info { background: #d1ecf1; color: #0c5460; }
53
+ .mg-badge-warning { background: #fff3cd; color: #856404; }
54
+ .mg-badge-danger { background: #f8d7da; color: #721c24; }
55
+ .mg-badge-secondary { background: #e2e3e5; color: #383d41; }
56
+ .mg-badge-success { background: #d4edda; color: #155724; }
57
+
58
+ /* Alert */
59
+ .mg-alert { padding: 10px 14px; border-radius: 4px; margin-bottom: 12px; font-size: 13px; }
60
+ .mg-alert-danger { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
61
+ .mg-alert-warning { background: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
62
+ .mg-alert-info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
63
+
64
+ /* Card */
65
+ .mg-card { border: 1px solid #dee2e6; border-radius: 4px; margin-bottom: 12px; }
66
+ .mg-card-header { padding: 8px 12px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; font-size: 13px; }
67
+ .mg-card-body { padding: 12px; }
68
+ .mg-card-toggle { cursor: pointer; color: #007bff; text-decoration: none; font-size: 13px; }
69
+ .mg-card-toggle:hover { text-decoration: underline; }
70
+
71
+ /* Stat grid */
72
+ .mg-stat-grid { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; font-size: 13px; }
73
+ .mg-stat-grid .mg-stat-label { color: #666; }
74
+ .mg-stat-grid .mg-stat-value { text-align: right; font-weight: 500; }
75
+ .mg-usage-bar { background: #e9ecef; border-radius: 4px; height: 20px; position: relative; overflow: hidden; }
76
+ .mg-usage-bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
77
+ .mg-usage-bar-text { position: absolute; top: 0; left: 0; right: 0; text-align: center; line-height: 20px; font-size: 11px; font-weight: 500; }
78
+
79
+ /* Table */
80
+ .mg-table-wrap { overflow-x: auto; }
81
+ table.mg-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
82
+ .mg-table th { padding: 10px 12px; text-align: left; white-space: nowrap; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #5a6770; background: #f1f3f5; border-bottom: 2px solid #d0d7de; user-select: none; }
83
+ .mg-table th.mg-sortable { cursor: pointer; position: relative; padding-right: 20px; }
84
+ .mg-table th.mg-sortable:hover { color: #333; }
85
+ .mg-table th.mg-sortable::after { content: '\2195'; position: absolute; right: 6px; opacity: 0.3; font-size: 10px; }
86
+ .mg-table th.mg-sort-asc::after { content: '\2191'; opacity: 0.8; }
87
+ .mg-table th.mg-sort-desc::after { content: '\2193'; opacity: 0.8; }
88
+ .mg-table th:first-child { border-top-left-radius: 6px; }
89
+ .mg-table th:last-child { border-top-right-radius: 6px; }
90
+ .mg-table td { padding: 8px 12px; border-bottom: 1px solid #eaecef; vertical-align: top; }
91
+ .mg-table tbody tr { transition: background-color 0.15s ease; }
92
+ .mg-table tbody tr:hover { background: #f0f6ff; }
93
+ .mg-table tbody tr:nth-child(even) { background: #fafbfc; }
94
+ .mg-table tbody tr:nth-child(even):hover { background: #f0f6ff; }
95
+ .mg-table tbody tr:last-child td { border-bottom: none; }
96
+ .mg-table em.null { color: #999; font-style: italic; }
97
+ .mg-table .redacted { color: #999; }
98
+
99
+ /* Numeric table cells */
100
+ .mg-table td.mg-num { text-align: right; font-variant-numeric: tabular-nums; font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; font-size: 12px; white-space: nowrap; }
101
+
102
+ /* SQL code blocks in tables */
103
+ .mg-sql-block { display: block; padding: 6px 8px; background: #1e1e2e; color: #cdd6f4; border-radius: 5px; font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; font-size: 11.5px; line-height: 1.5; word-break: break-word; white-space: pre-wrap; max-height: 120px; overflow-y: auto; border: 1px solid #313244; }
104
+ .mg-sql-block::-webkit-scrollbar { width: 4px; }
105
+ .mg-sql-block::-webkit-scrollbar-track { background: transparent; }
106
+ .mg-sql-block::-webkit-scrollbar-thumb { background: #585b70; border-radius: 2px; }
107
+
108
+ /* SQL syntax colors (Catppuccin Mocha inspired) */
109
+ .mg-sql-kw { color: #cba6f7; font-weight: 600; }
110
+ .mg-sql-fn { color: #89b4fa; }
111
+ .mg-sql-str { color: #a6e3a1; }
112
+ .mg-sql-num { color: #fab387; }
113
+ .mg-sql-op { color: #89dceb; }
114
+ .mg-sql-comment { color: #6c7086; font-style: italic; }
115
+ .mg-sql-tbl { color: #f9e2af; }
116
+ .mg-sql-star { color: #f38ba8; font-weight: 700; }
117
+ .mg-sql-punc { color: #9399b2; }
118
+ .mg-sql-placeholder { color: #74c7ec; font-style: italic; }
119
+
120
+ /* Duration color coding */
121
+ .mg-dur-fast { color: #1a7f37; }
122
+ .mg-dur-moderate { color: #9a6700; }
123
+ .mg-dur-slow { color: #cf222e; font-weight: 600; }
124
+
125
+ /* Checkbox grid */
126
+ .mg-checks { display: flex; flex-wrap: wrap; gap: 4px 16px; }
127
+ .mg-check { display: flex; align-items: center; gap: 4px; font-size: 13px; cursor: pointer; }
128
+ .mg-check input { margin: 0; }
129
+ .mg-check .type-hint { color: #999; font-size: 11px; }
130
+
131
+ /* Inline links */
132
+ .mg-link { color: #007bff; cursor: pointer; text-decoration: none; font-size: 12px; margin-left: 8px; }
133
+ .mg-link:hover { text-decoration: underline; }
134
+
135
+ /* Spinner */
136
+ @keyframes mg-spin { to { transform: rotate(360deg); } }
137
+ .mg-spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid #ccc; border-top-color: #007bff; border-radius: 50%; animation: mg-spin .6s linear infinite; }
138
+
139
+ /* Utilities */
140
+ .mg-hidden { display: none !important; }
141
+ .mg-mt { margin-top: 16px; }
142
+ .mg-mb { margin-bottom: 12px; }
143
+ .mg-text-muted { color: #888; font-size: 13px; }
144
+ .mg-text-center { text-align: center; padding: 24px 0; }
145
+ code { font-size: 12px; word-break: break-all; background: #f0f1f3; padding: 2px 6px; border-radius: 3px; color: #24292f; }
146
+ pre.mg-pre { background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
147
+
148
+ /* AI result sections */
149
+ .mg-ai-section { border-radius: 4px; border: 1px solid #dee2e6; border-left-width: 4px; margin-bottom: 12px; }
150
+ .mg-ai-section-header { padding: 8px 12px; border-bottom: 1px solid #dee2e6; font-size: 13px; }
151
+ .mg-ai-section-body { padding: 12px; font-size: 13px; }
152
+ .mg-ai-danger { border-left-color: #dc3545; background: #fff5f5; }
153
+ .mg-ai-danger .mg-ai-section-header { border-bottom-color: #f5c6cb; }
154
+ .mg-ai-warning { border-left-color: #ffc107; background: #fffbeb; }
155
+ .mg-ai-warning .mg-ai-section-header { border-bottom-color: #ffeeba; }
156
+ .mg-ai-info { border-left-color: #17a2b8; background: #f0f9ff; }
157
+ .mg-ai-info .mg-ai-section-header { border-bottom-color: #bee5eb; }
158
+
159
+ /* Theme toggle */
160
+ .mg-theme-toggle { background: none; border: 1px solid #ced4da; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 14px; line-height: 1; color: inherit; }
161
+ .mg-theme-toggle:hover { background: #e9ecef; }
162
+
163
+ /* --- Dark Theme --- */
164
+ [data-theme="dark"] body,
165
+ [data-theme="dark"] { background: #161b22; color: #c9d1d9; }
166
+ [data-theme="dark"] .mg-container { color: #c9d1d9; }
167
+
168
+ /* Tabs */
169
+ [data-theme="dark"] .mg-tabs { border-bottom-color: #30363d; }
170
+ [data-theme="dark"] .mg-tab { color: #8b949e; }
171
+ [data-theme="dark"] .mg-tab:hover { color: #c9d1d9; }
172
+ [data-theme="dark"] .mg-tab.active { background: #0d1117; border-color: #30363d; border-bottom-color: #0d1117; color: #c9d1d9; }
173
+ [data-theme="dark"] .mg-tab-content { background: #0d1117; border-color: #30363d; }
174
+
175
+ /* Forms */
176
+ [data-theme="dark"] select,
177
+ [data-theme="dark"] input[type="number"],
178
+ [data-theme="dark"] input[type="text"],
179
+ [data-theme="dark"] textarea { background: #0d1117; color: #c9d1d9; border-color: #30363d; }
180
+ [data-theme="dark"] select:focus,
181
+ [data-theme="dark"] input:focus,
182
+ [data-theme="dark"] textarea:focus { border-color: #58a6ff; box-shadow: 0 0 0 2px rgba(88,166,255,.15); }
183
+ [data-theme="dark"] textarea[readonly] { background: #161b22; }
184
+ [data-theme="dark"] label { color: #c9d1d9; }
185
+
186
+ /* Buttons */
187
+ [data-theme="dark"] .mg-btn-primary { background: #238636; border-color: #238636; }
188
+ [data-theme="dark"] .mg-btn-primary:hover:not(:disabled) { background: #2ea043; }
189
+ [data-theme="dark"] .mg-btn-outline { background: #0d1117; color: #58a6ff; border-color: #30363d; }
190
+ [data-theme="dark"] .mg-btn-outline:hover:not(:disabled) { background: #161b22; border-color: #58a6ff; }
191
+ [data-theme="dark"] .mg-btn-outline-secondary { background: #0d1117; color: #8b949e; border-color: #30363d; }
192
+ [data-theme="dark"] .mg-btn-outline-secondary:hover:not(:disabled) { background: #161b22; }
193
+ [data-theme="dark"] .mg-btn-outline-danger { background: #0d1117; color: #f85149; border-color: #30363d; }
194
+ [data-theme="dark"] .mg-btn-outline-danger:hover:not(:disabled) { background: #1c0d0d; }
195
+
196
+ /* Badges */
197
+ [data-theme="dark"] .mg-badge-info { background: #0d2a3a; color: #58a6ff; }
198
+ [data-theme="dark"] .mg-badge-warning { background: #2d2000; color: #d29922; }
199
+ [data-theme="dark"] .mg-badge-danger { background: #2d0d0d; color: #f85149; }
200
+ [data-theme="dark"] .mg-badge-secondary { background: #21262d; color: #8b949e; }
201
+ [data-theme="dark"] .mg-badge-success { background: #0d2d1a; color: #3fb950; }
202
+
203
+ /* Alerts */
204
+ [data-theme="dark"] .mg-alert-danger { background: #2d0d0d; color: #f85149; border-color: #3d1414; }
205
+ [data-theme="dark"] .mg-alert-warning { background: #2d2000; color: #d29922; border-color: #3d2e00; }
206
+ [data-theme="dark"] .mg-alert-info { background: #0d2a3a; color: #58a6ff; border-color: #0d3a5a; }
207
+
208
+ /* Cards */
209
+ [data-theme="dark"] .mg-card { border-color: #30363d; }
210
+ [data-theme="dark"] .mg-card-header { background: #161b22; border-bottom-color: #30363d; color: #c9d1d9; }
211
+ [data-theme="dark"] .mg-card-toggle { color: #58a6ff; }
212
+
213
+ /* Stat grid */
214
+ [data-theme="dark"] .mg-stat-grid .mg-stat-label { color: #8b949e; }
215
+ [data-theme="dark"] .mg-usage-bar { background: #21262d; }
216
+
217
+ /* Tables */
218
+ [data-theme="dark"] .mg-table th { background: #161b22; color: #8b949e; border-bottom-color: #30363d; }
219
+ [data-theme="dark"] .mg-table th.mg-sortable:hover { color: #c9d1d9; }
220
+ [data-theme="dark"] .mg-table td { border-bottom-color: #21262d; }
221
+ [data-theme="dark"] .mg-table tbody tr:hover { background: #1c2128; }
222
+ [data-theme="dark"] .mg-table tbody tr:nth-child(even) { background: #0d1117; }
223
+ [data-theme="dark"] .mg-table tbody tr:nth-child(even):hover { background: #1c2128; }
224
+ [data-theme="dark"] .mg-table em.null { color: #484f58; }
225
+ [data-theme="dark"] .mg-table .redacted { color: #484f58; }
226
+
227
+ /* Duration colors (dark) */
228
+ [data-theme="dark"] .mg-dur-fast { color: #3fb950; }
229
+ [data-theme="dark"] .mg-dur-moderate { color: #d29922; }
230
+ [data-theme="dark"] .mg-dur-slow { color: #f85149; }
231
+
232
+ /* Inline code */
233
+ [data-theme="dark"] code { background: #21262d; color: #c9d1d9; }
234
+ [data-theme="dark"] pre { background: #161b22; color: #c9d1d9; border-color: #30363d; }
235
+ [data-theme="dark"] pre.mg-pre { background: #161b22; color: #c9d1d9; }
236
+ [data-theme="dark"] .mg-card-body { color: #c9d1d9; }
237
+ [data-theme="dark"] .mg-card-body h1, [data-theme="dark"] .mg-card-body h2, [data-theme="dark"] .mg-card-body h3, [data-theme="dark"] .mg-card-body h4 { color: #c9d1d9; }
238
+ [data-theme="dark"] .mg-card-body p, [data-theme="dark"] .mg-card-body li, [data-theme="dark"] .mg-card-body td { color: #c9d1d9; }
239
+ [data-theme="dark"] .mg-card-body strong { color: #e6edf3; }
240
+ [data-theme="dark"] .mg-card-body a { color: #58a6ff; }
241
+
242
+ /* Links */
243
+ [data-theme="dark"] .mg-link { color: #58a6ff; }
244
+
245
+ /* Spinner */
246
+ [data-theme="dark"] .mg-spinner { border-color: #30363d; border-top-color: #58a6ff; }
247
+
248
+ /* Text */
249
+ [data-theme="dark"] .mg-text-muted { color: #8b949e; }
250
+
251
+ /* Checkboxes */
252
+ [data-theme="dark"] .mg-check .type-hint { color: #484f58; }
253
+
254
+ /* AI result sections (dark) */
255
+ [data-theme="dark"] .mg-ai-section { border-color: #30363d; }
256
+ [data-theme="dark"] .mg-ai-danger { background: #2d0d0d; border-left-color: #f85149; }
257
+ [data-theme="dark"] .mg-ai-danger .mg-ai-section-header { border-bottom-color: #3d1414; color: #f85149; }
258
+ [data-theme="dark"] .mg-ai-warning { background: #2d2000; border-left-color: #d29922; }
259
+ [data-theme="dark"] .mg-ai-warning .mg-ai-section-header { border-bottom-color: #3d2e00; color: #d29922; }
260
+ [data-theme="dark"] .mg-ai-info { background: #0d2a3a; border-left-color: #58a6ff; }
261
+ [data-theme="dark"] .mg-ai-info .mg-ai-section-header { border-bottom-color: #0d3a5a; color: #58a6ff; }
262
+ [data-theme="dark"] .mg-ai-section-body { color: #c9d1d9; }
263
+
264
+ /* Theme toggle (dark) */
265
+ [data-theme="dark"] .mg-theme-toggle { border-color: #30363d; color: #c9d1d9; }
266
+ [data-theme="dark"] .mg-theme-toggle:hover { background: #21262d; }
267
+
268
+ /* Database switcher (dark) */
269
+ [data-theme="dark"] .mg-db-switcher select { background: #0d1117; color: #c9d1d9; border-color: #30363d; }
270
+ [data-theme="dark"] .mg-db-badge { background: #0d2a3a; color: #58a6ff; }
271
+ </style>
272
+ </head>
273
+ <body>
274
+ <script>
275
+ (function() {
276
+ var saved = localStorage.getItem('mg-theme');
277
+ var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
278
+ document.documentElement.setAttribute('data-theme', theme);
279
+ })();
280
+ </script>
281
+ <div class="mg-container">
282
+ <%= yield %>
283
+ </div>
284
+ </body>
285
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ SqlGenius::Engine.routes.draw do
4
+ root to: "queries#index"
5
+
6
+ get "columns", to: "queries#columns"
7
+ post "execute", to: "queries#execute"
8
+ post "explain", to: "queries#explain"
9
+ get "queries/:digest", to: "queries#query_detail", as: "query_detail"
10
+ get "api/query_history/:digest", to: "queries#query_history", as: "query_history"
11
+ post "suggest", to: "queries#suggest"
12
+ post "optimize", to: "queries#optimize"
13
+ get "slow_queries", to: "queries#slow_queries"
14
+ get "duplicate_indexes", to: "queries#duplicate_indexes"
15
+ get "table_sizes", to: "queries#table_sizes"
16
+ get "query_stats", to: "queries#query_stats"
17
+ get "unused_indexes", to: "queries#unused_indexes"
18
+ get "server_overview", to: "queries#server_overview"
19
+
20
+ # AI features
21
+ post "describe_query", to: "queries#describe_query"
22
+ post "schema_review", to: "queries#schema_review"
23
+ post "rewrite_query", to: "queries#rewrite_query"
24
+ post "index_advisor", to: "queries#index_advisor"
25
+ post "anomaly_detection", to: "queries#anomaly_detection"
26
+ post "root_cause", to: "queries#root_cause"
27
+ post "migration_risk", to: "queries#migration_risk"
28
+ post "variable_review", to: "queries#variable_review"
29
+ post "connection_advisor", to: "queries#connection_advisor"
30
+ post "workload_digest", to: "queries#workload_digest"
31
+ post "innodb_health", to: "queries#innodb_health"
32
+ post "index_planner", to: "queries#index_planner"
33
+ post "pattern_grouper", to: "queries#pattern_grouper"
34
+ end
@@ -0,0 +1,115 @@
1
+ # AI Features Guide
2
+
3
+ SqlGenius integrates with OpenAI-compatible LLM APIs to provide AI-powered database analysis. All AI features are optional — the dashboard works fully without them.
4
+
5
+ ## Supported providers
6
+
7
+ | Provider | Endpoint | Auth Style |
8
+ |---|---|---|
9
+ | OpenAI | `https://api.openai.com/v1/chat/completions` | Bearer |
10
+ | Anthropic | `https://api.anthropic.com/v1/messages` | x-api-key |
11
+ | Google Gemini | `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions` | Bearer |
12
+ | Azure OpenAI | `https://YOUR-RESOURCE.openai.azure.com/...` | api-key |
13
+ | DeepSeek | `https://api.deepseek.com/chat/completions` | Bearer |
14
+ | Groq | `https://api.groq.com/openai/v1/chat/completions` | Bearer |
15
+ | Ollama (local) | `http://localhost:11434/v1/chat/completions` | Bearer |
16
+ | OpenRouter | `https://openrouter.ai/api/v1/chat/completions` | Bearer |
17
+ | Perplexity | `https://api.perplexity.ai/chat/completions` | Bearer |
18
+ | Any OpenAI-compatible | Your endpoint URL | Bearer or api-key |
19
+
20
+ ## Configuration
21
+
22
+ In your Rails initializer (`config/initializers/sql_genius.rb`):
23
+
24
+ ```ruby
25
+ SqlGenius.configure do |config|
26
+ config.ai_endpoint = "https://api.openai.com/v1/chat/completions"
27
+ config.ai_api_key = ENV["OPENAI_API_KEY"]
28
+ config.ai_model = "gpt-4o-mini"
29
+ config.ai_auth_style = :bearer # or :api_key for Azure, :x_api_key for Anthropic
30
+ end
31
+ ```
32
+
33
+ ## Available features
34
+
35
+ ### Query Explorer AI
36
+
37
+ | Feature | What it does | Where |
38
+ |---|---|---|
39
+ | **AI Suggest** | Generate SQL from natural language | Query Explorer tab |
40
+ | **AI Optimization** | Analyze EXPLAIN output, suggest improvements | After running EXPLAIN |
41
+ | **Index Advisor** | Recommend indexes for a specific query | After running EXPLAIN |
42
+ | **Describe Query** | Explain what a SQL query does in plain English | Query Explorer tab |
43
+ | **Rewrite Query** | Suggest an optimized version of the SQL | Query Explorer tab |
44
+
45
+ ### Schema & Migration
46
+
47
+ | Feature | What it does | Where |
48
+ |---|---|---|
49
+ | **Schema Review** | Find anti-patterns across your schema | AI Tools tab |
50
+ | **Migration Risk** | Assess safety of a DDL/migration before deploying | AI Tools tab |
51
+ | **AI Optimize** | Review a specific table's schema (appears on fragmented tables) | Tables tab |
52
+
53
+ ### Server Analysis
54
+
55
+ | Feature | What it does | Where |
56
+ |---|---|---|
57
+ | **Variable Config Review** | Review my.cnf settings against your workload | Server tab |
58
+ | **Connection Advisor** | Diagnose connection pool issues | Server tab |
59
+ | **InnoDB Health** | Interpret SHOW ENGINE INNODB STATUS | Server tab |
60
+
61
+ ### Workload Analysis
62
+
63
+ | Feature | What it does | Where |
64
+ |---|---|---|
65
+ | **Workload Digest** | Executive summary of your query workload | Query Stats tab |
66
+ | **Pattern Grouper** | Group slow queries by shared root cause | Query Stats tab |
67
+ | **Index Planner** | Holistic index optimization plan | Indexes tabs |
68
+
69
+ ## Settings
70
+
71
+ ### Max Tokens
72
+
73
+ Controls the maximum response length from the LLM. Default: 4096. Adjust with the slider in AI Configuration.
74
+
75
+ - **Lower (256-1024)** — faster responses, may truncate complex analyses
76
+ - **Higher (4096-16384)** — complete analyses, slower and more expensive
77
+
78
+ ### System Prompt
79
+
80
+ Optional context injected into every AI request. Use it to describe your application:
81
+
82
+ > "This is an e-commerce platform with 50M orders. The database handles 5000 QPS during peak hours."
83
+
84
+ ### Domain Prompt
85
+
86
+ Optional instructions for the AI's recommendations:
87
+
88
+ > "Prefer window functions over correlated subqueries. Don't recommend foreign keys — we handle referential integrity in the application layer."
89
+
90
+ ## Using with Ollama (free, local)
91
+
92
+ 1. Install Ollama: `brew install ollama`
93
+ 2. Pull a model: `ollama pull llama3.2`
94
+ 3. Start Ollama: `ollama serve`
95
+ 4. In SqlGenius, select **Ollama (local)** as the provider
96
+ 5. The endpoint and model auto-fill — just click **Save**
97
+
98
+ No API key needed. All data stays on your machine.
99
+
100
+ ## Copying AI responses
101
+
102
+ Every AI response has a **Copy response** button at the bottom right. Click it to copy the plain text to your clipboard — useful for sharing with team members or pasting into tickets.
103
+
104
+ ## Cost considerations
105
+
106
+ Each AI feature makes one API call per invocation. Typical costs with OpenAI gpt-4o-mini:
107
+
108
+ | Feature | ~Input tokens | ~Output tokens | ~Cost |
109
+ |---|---|---|---|
110
+ | Schema Review (all tables) | 2000-5000 | 500-2000 | $0.001-0.005 |
111
+ | Migration Risk | 500-1000 | 500-1000 | $0.001 |
112
+ | Query Optimization | 1000-2000 | 500-1000 | $0.001 |
113
+ | Workload Digest | 3000-5000 | 1000-2000 | $0.003 |
114
+
115
+ Using Ollama or other local models eliminates API costs entirely.
@@ -0,0 +1,118 @@
1
+ # Getting Started with SqlGenius (Rails)
2
+
3
+ ## Installation
4
+
5
+ Add to your Gemfile:
6
+
7
+ ```ruby
8
+ gem "sql_genius"
9
+ ```
10
+
11
+ ```bash
12
+ bundle install
13
+ ```
14
+
15
+ ## Mount the engine
16
+
17
+ In `config/routes.rb`:
18
+
19
+ ```ruby
20
+ Rails.application.routes.draw do
21
+ mount SqlGenius::Engine, at: "/sql_genius"
22
+ # ... your other routes
23
+ end
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ Create an initializer at `config/initializers/sql_genius.rb`:
29
+
30
+ ```ruby
31
+ SqlGenius.configure do |config|
32
+ # Authentication (required in production)
33
+ config.authenticate = ->(controller) {
34
+ # Example: restrict to admin users
35
+ controller.current_user&.admin?
36
+ }
37
+
38
+ # Tables to hide from the dashboard
39
+ config.blocked_tables = %w[
40
+ schema_migrations
41
+ ar_internal_metadata
42
+ ]
43
+
44
+ # Columns to mask in query results
45
+ config.masked_column_patterns = %w[
46
+ password
47
+ token
48
+ secret
49
+ ssn
50
+ ]
51
+
52
+ # Query limits
53
+ config.default_row_limit = 100
54
+ config.max_row_limit = 10_000
55
+ config.query_timeout_ms = 10_000
56
+
57
+ # AI features (optional)
58
+ config.ai_endpoint = ENV["SQL_GENIUS_AI_ENDPOINT"]
59
+ config.ai_api_key = ENV["SQL_GENIUS_AI_KEY"]
60
+ config.ai_model = "gpt-4o-mini"
61
+ config.ai_auth_style = :bearer
62
+
63
+ # Stats collection (background thread, default: true)
64
+ config.stats_collection = true
65
+
66
+ # Slow query monitoring via Redis (optional)
67
+ # config.redis_url = ENV["REDIS_URL"]
68
+ end
69
+ ```
70
+
71
+ ## Visit the dashboard
72
+
73
+ Start your Rails server and navigate to:
74
+
75
+ ```
76
+ http://localhost:3000/sql_genius
77
+ ```
78
+
79
+ ## Features
80
+
81
+ ### Dashboard
82
+ Overview of server health, top slow queries, expensive queries, duplicate/unused index counts.
83
+
84
+ ### Query Explorer
85
+ Run SELECT queries against your database with syntax highlighting, EXPLAIN output, and AI-powered suggestions.
86
+
87
+ ### Query Stats
88
+ Top queries from `performance_schema.events_statements_summary_by_digest`. Click any query to see a detail page with time-series performance charts.
89
+
90
+ ### Server
91
+ Server status, connections, InnoDB buffer pool, query activity.
92
+
93
+ ### Tables
94
+ Table sizes with fragmentation detection. Tables needing optimization get an "AI Optimize" button.
95
+
96
+ ### Indexes
97
+ Duplicate and unused index detection.
98
+
99
+ ### AI Tools (when configured)
100
+ - **Schema Review** — find anti-patterns in your schema
101
+ - **Migration Risk** — assess DDL safety before deploying
102
+ - **Query Description** — explain what a query does in plain English
103
+ - **Query Rewrite** — suggest optimized versions of slow queries
104
+ - **Index Advisor** — recommend indexes for specific queries
105
+
106
+ ## Security
107
+
108
+ **Always configure authentication in production.** Without it, anyone who can reach the mounted path can view your database schema and run SELECT queries.
109
+
110
+ The `blocked_tables` and `masked_column_patterns` settings provide defense-in-depth but are not a substitute for authentication.
111
+
112
+ ## Supported databases
113
+
114
+ - MySQL 5.7+
115
+ - MySQL 8.0+
116
+ - MariaDB 10.3+
117
+
118
+ Some features (query stats, unused indexes) require `performance_schema` to be enabled.