mysql_genius 0.4.1 → 0.5.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: b7858ff8adbac9ab758eee1f82b0302f67cc09357ad087f3a3102a6646307f9d
4
- data.tar.gz: 72053d61ff33d5fbf986a7a60445e36ae821ec9c2368ef4c9a0e4768636cb6ec
3
+ metadata.gz: 48fbc36b280f63255eebb38587db28d24e38150b53faaff13efeddb810de8c70
4
+ data.tar.gz: a55a5115090bc136e99d854dc13c231670293a363f8d441951d98191c794d9e6
5
5
  SHA512:
6
- metadata.gz: 8dd7d033566d1ba8112253768dd491a5083f2daf57f2eb96231f668f229bac16572d97735d86a873efe6d9b44b7fdc649a31647ba10470c877a5c353dd0ebf53
7
- data.tar.gz: b25231d00d367295acc3c84e612bd4345fc3a954a48aa7b93a17c8fec50bb5804936e9605b1663971566542626c93f65fc49522900e36f667ff6d6073143d11c
6
+ metadata.gz: 9d9de9d97d05814ff37b0ed88ee1846656ba3f1ff323f094ff776a3abb4be358d12204810e9068d2f6e99daea88d007f5a406f27681b7c1574a38395b0889c7e
7
+ data.tar.gz: cb5c725008371288dff5e8a32a66851914a39de71f0b647ade81bde743e01f7fe5f7ae2664715da9602b7e024b5609c3df53f37c1817faa73e7a4d801a26f292
data/.rubocop.yml CHANGED
@@ -17,6 +17,9 @@ RSpec/MessageSpies:
17
17
  RSpec/VerifiedDoubles:
18
18
  Enabled: false
19
19
 
20
+ RSpec/VerifiedDoubleReference:
21
+ Enabled: false
22
+
20
23
  RSpec/ExampleLength:
21
24
  Max: 25
22
25
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Fixed
6
+ - **ERB templates missing from `mysql_genius-core` gem package.** The gemspec glob only matched `*.rb` files, excluding the shared ERB templates. The dashboard crashed with `MissingTemplate` error when installed from RubyGems. Fixed by changing the glob to `*.{rb,erb}`.
7
+
8
+ ## 0.5.0
9
+
10
+ ### Changed
11
+ - **ERB templates moved into `mysql_genius-core`.** All 11 view files (`dashboard.html.erb` and 10 partials) have been extracted from `app/views/mysql_genius/queries/` into `gems/mysql_genius-core/lib/mysql_genius/core/views/mysql_genius/queries/`. The index template is renamed to `dashboard.html.erb`. The engine registers `MysqlGenius::Core.views_path` before `:add_view_paths` so Rails finds templates in both view roots. Non-Rails adapters (Phase 2b `mysql_genius-desktop` sidecar) can register this same path with their own view loader and implement `path_for`/`render_partial` to reuse the templates.
12
+ - **`QueriesController#index`** now sets `@framework_version_major` and `@framework_version_minor` instance variables (replacing direct `Rails::VERSION` references in the template) and explicitly renders `"mysql_genius/queries/dashboard"`.
13
+ - **`SharedViewHelpers`** — new concern providing `path_for(name)` and `render_partial(name)` as the 2-method contract the shared templates depend on. `render_partial` delegates to `view_context.render(partial: "mysql_genius/queries/#{name}")`.
14
+ - Extracted 5 AI prompt builders from the `AiFeatures` concern into `MysqlGenius::Core::Ai::{DescribeQuery, SchemaReview, RewriteQuery, IndexAdvisor, MigrationRisk}` plus a shared `Core::Ai::SchemaContextBuilder` helper. `anomaly_detection` and `root_cause` remain in the Rails concern because they depend on the Redis-backed `SlowQueryMonitor`.
15
+ - Extracted `QueriesController#columns` logic into `MysqlGenius::Core::Analysis::Columns` with a tagged-result struct. Retires the `masked_column?` helper added in the 0.4.1 hotfix.
16
+ - `MysqlGenius::Core::Ai::Config` gains a `domain_context:` field. The Rails adapter defaults it to a Rails-specific string; `mysql_genius-desktop` will default to empty.
17
+ - `mysql_genius` now declares runtime dependency on `mysql_genius-core ~> 0.5.0` (was `~> 0.4.0`).
18
+
19
+ ### Added
20
+ - Integration test suite at `spec/dummy/` + `spec/rails_helper.rb` + `spec/requests/`. Boots a minimal Rails engine dummy app and dispatches real HTTP requests against the mounted engine via `Rack::Test`. Dedicated regression specs at `spec/regressions/` pin the two Phase 1b latent bugs (`Core::Connection::ActiveRecordAdapter` boot-order and `QueriesController#masked_column?` helper deletion) so they can never silently return.
21
+ - `CLAUDE.md` updated: the "no Rails boot in tests" rule is relaxed to a two-tier model (unit specs stub AR, integration specs boot Rails via `spec/dummy/`).
22
+
23
+ ### Internal
24
+ - `MysqlGenius::Core.views_path` — new public module method returning the absolute path to the shared ERB template directory.
25
+
3
26
  ## 0.4.1
4
27
 
5
28
  ### Fixed
data/Gemfile CHANGED
@@ -16,6 +16,8 @@ end
16
16
  group :development, :test do
17
17
  gem "rake"
18
18
  gem "rspec", "~> 3.0"
19
+ gem "rspec-rails"
20
+ gem "rack-test"
19
21
  gem "rubocop"
20
22
  gem "rubocop-shopify"
21
23
  gem "rubocop-rspec"
@@ -47,20 +47,7 @@ module MysqlGenius
47
47
  sql = params[:sql].to_s.strip
48
48
  return render(json: { error: "SQL is required." }, status: :unprocessable_entity) if sql.blank?
49
49
 
50
- messages = [
51
- { role: "system", content: <<~PROMPT },
52
- You are a MySQL query explainer. Given a SQL query, explain in plain English:
53
- 1. What the query does (tables involved, joins, filters, aggregations)
54
- 2. How data flows through the query
55
- 3. Any subtle behaviors (implicit type casts, NULL handling in NOT IN, DISTINCT effects, etc.)
56
- 4. Potential performance concerns visible from the SQL structure alone
57
- #{ai_domain_context}
58
- Respond with JSON: {"explanation": "your plain-English explanation using markdown formatting"}
59
- PROMPT
60
- { role: "user", content: sql },
61
- ]
62
-
63
- result = ai_client.chat(messages: messages)
50
+ result = MysqlGenius::Core::Ai::DescribeQuery.new(ai_client, ai_config_for_core).call(sql)
64
51
  render(json: result)
65
52
  rescue StandardError => e
66
53
  render(json: { error: "Explanation failed: #{e.message}" }, status: :unprocessable_entity)
@@ -69,42 +56,7 @@ module MysqlGenius
69
56
  def schema_review
70
57
  return ai_not_configured unless mysql_genius_config.ai_enabled?
71
58
 
72
- table = params[:table].to_s.strip
73
- connection = ActiveRecord::Base.connection
74
-
75
- tables_to_review = table.present? ? [table] : queryable_tables.first(20)
76
- schema_desc = tables_to_review.map do |t|
77
- next unless connection.tables.include?(t)
78
-
79
- cols = connection.columns(t).map { |c| "#{c.name} #{c.sql_type}#{" NOT NULL" unless c.null}#{" DEFAULT #{c.default}" if c.default}" }
80
- pk = connection.primary_key(t)
81
- indexes = connection.indexes(t).map { |idx| "#{"UNIQUE " if idx.unique}INDEX #{idx.name} (#{idx.columns.join(", ")})" }
82
- row_count = connection.exec_query("SELECT TABLE_ROWS FROM information_schema.tables WHERE table_schema = #{connection.quote(connection.current_database)} AND table_name = #{connection.quote(t)}").rows.first&.first
83
- desc = "Table: #{t} (~#{row_count} rows)\n"
84
- desc += "Primary Key: #{pk || "NONE"}\n"
85
- desc += "Columns: #{cols.join(", ")}\n"
86
- desc += "Indexes: #{indexes.any? ? indexes.join(", ") : "NONE"}"
87
- desc
88
- end.compact.join("\n\n")
89
-
90
- messages = [
91
- { role: "system", content: <<~PROMPT },
92
- You are a MySQL schema reviewer for a Ruby on Rails application. Analyze the following schema and identify anti-patterns and improvement opportunities. Look for:
93
- - Inappropriate column types (VARCHAR(255) for short values, TEXT where VARCHAR suffices, INT for booleans)
94
- - Missing indexes on foreign key columns or frequently filtered columns
95
- - Missing NOT NULL constraints where NULLs are unlikely
96
- - ENUM columns that should be lookup tables
97
- - Missing created_at/updated_at timestamps
98
- - Tables without a PRIMARY KEY
99
- - Overly wide indexes or redundant indexes
100
- - Column naming inconsistencies
101
- #{ai_domain_context}
102
- Respond with JSON: {"findings": "markdown-formatted findings organized by severity (Critical, Warning, Suggestion). Include specific ALTER TABLE statements where applicable."}
103
- PROMPT
104
- { role: "user", content: schema_desc },
105
- ]
106
-
107
- result = ai_client.chat(messages: messages)
59
+ result = MysqlGenius::Core::Ai::SchemaReview.new(ai_client, ai_config_for_core, rails_connection).call(params[:table].to_s.strip.presence)
108
60
  render(json: result)
109
61
  rescue StandardError => e
110
62
  render(json: { error: "Schema review failed: #{e.message}" }, status: :unprocessable_entity)
@@ -116,31 +68,7 @@ module MysqlGenius
116
68
  sql = params[:sql].to_s.strip
117
69
  return render(json: { error: "SQL is required." }, status: :unprocessable_entity) if sql.blank?
118
70
 
119
- schema = build_schema_for_query(sql)
120
-
121
- messages = [
122
- { role: "system", content: <<~PROMPT },
123
- You are a MySQL query rewrite expert. Analyze the SQL for anti-patterns and suggest a rewritten version. Look for:
124
- - SELECT * when specific columns would suffice
125
- - Correlated subqueries that could be JOINs
126
- - OR conditions preventing index use (suggest UNION ALL)
127
- - LIKE '%prefix' patterns (leading wildcard)
128
- - Implicit type conversions in WHERE clauses
129
- - NOT IN with NULLable columns (suggest NOT EXISTS)
130
- - ORDER BY on non-indexed columns with LIMIT
131
- - Unnecessary DISTINCT
132
- - Functions on indexed columns in WHERE (e.g., DATE(created_at) instead of range)
133
-
134
- Available schema:
135
- #{schema}
136
- #{ai_domain_context}
137
-
138
- Respond with JSON: {"original": "the original SQL", "rewritten": "the improved SQL", "changes": "markdown list of each change and why it helps"}
139
- PROMPT
140
- { role: "user", content: sql },
141
- ]
142
-
143
- result = ai_client.chat(messages: messages)
71
+ result = MysqlGenius::Core::Ai::RewriteQuery.new(ai_client, ai_config_for_core, rails_connection).call(sql)
144
72
  render(json: result)
145
73
  rescue StandardError => e
146
74
  render(json: { error: "Rewrite failed: #{e.message}" }, status: :unprocessable_entity)
@@ -153,33 +81,7 @@ module MysqlGenius
153
81
  explain_rows = Array(params[:explain_rows]).map { |row| row.respond_to?(:values) ? row.values : Array(row) }
154
82
  return render(json: { error: "SQL and EXPLAIN output are required." }, status: :unprocessable_entity) if sql.blank? || explain_rows.blank?
155
83
 
156
- connection = ActiveRecord::Base.connection
157
- tables_in_query = MysqlGenius::Core::SqlValidator.extract_table_references(sql, connection)
158
-
159
- index_detail = tables_in_query.map do |t|
160
- indexes = connection.indexes(t).map { |idx| "#{"UNIQUE " if idx.unique}INDEX #{idx.name} (#{idx.columns.join(", ")})" }
161
- stats = connection.exec_query("SELECT INDEX_NAME, COLUMN_NAME, CARDINALITY, SEQ_IN_INDEX FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = #{connection.quote(connection.current_database)} AND TABLE_NAME = #{connection.quote(t)} ORDER BY INDEX_NAME, SEQ_IN_INDEX")
162
- cardinality = stats.rows.map { |r| "#{r[0]}.#{r[1]}: cardinality=#{r[2]}" }.join(", ")
163
- row_count = connection.exec_query("SELECT TABLE_ROWS FROM information_schema.tables WHERE table_schema = #{connection.quote(connection.current_database)} AND table_name = #{connection.quote(t)}").rows.first&.first
164
- "Table: #{t} (~#{row_count} rows)\nIndexes: #{indexes.any? ? indexes.join("; ") : "NONE"}\nCardinality: #{cardinality}"
165
- end.join("\n\n")
166
-
167
- messages = [
168
- { role: "system", content: <<~PROMPT },
169
- You are a MySQL index advisor. Given a query, its EXPLAIN output, and current index/cardinality information, suggest optimal indexes. Consider:
170
- - Composite index column ordering (most selective first, or matching query order)
171
- - Covering indexes to avoid table lookups
172
- - Partial indexes for long string columns
173
- - Write-side costs (if this is a high-write table, note the INSERT/UPDATE overhead)
174
- - Whether existing indexes could be extended rather than creating new ones
175
- #{ai_domain_context}
176
-
177
- Respond with JSON: {"indexes": "markdown-formatted recommendations with exact CREATE INDEX statements, rationale for column ordering, and estimated impact. Include any indexes that should be DROPPED as part of the change."}
178
- PROMPT
179
- { role: "user", content: "Query:\n#{sql}\n\nEXPLAIN:\n#{explain_rows.map { |r| r.join(" | ") }.join("\n")}\n\nCurrent Indexes:\n#{index_detail}" },
180
- ]
181
-
182
- result = ai_client.chat(messages: messages)
84
+ result = MysqlGenius::Core::Ai::IndexAdvisor.new(ai_client, ai_config_for_core, rails_connection).call(sql, explain_rows)
183
85
  render(json: result)
184
86
  rescue StandardError => e
185
87
  render(json: { error: "Index advisor failed: #{e.message}" }, status: :unprocessable_entity)
@@ -323,54 +225,7 @@ module MysqlGenius
323
225
  migration_sql = params[:migration].to_s.strip
324
226
  return render(json: { error: "Migration SQL or Ruby code is required." }, status: :unprocessable_entity) if migration_sql.blank?
325
227
 
326
- connection = ActiveRecord::Base.connection
327
-
328
- # Try to identify tables mentioned in the migration
329
- table_names = migration_sql.scan(/(?:create_table|add_column|remove_column|add_index|remove_index|rename_column|change_column|alter\s+table)\s+[:\"]?(\w+)/i).flatten.uniq
330
- table_names += migration_sql.scan(/ALTER\s+TABLE\s+`?(\w+)`?/i).flatten
331
-
332
- table_info = table_names.uniq.map do |t|
333
- next unless connection.tables.include?(t)
334
-
335
- row_count = connection.exec_query("SELECT TABLE_ROWS FROM information_schema.tables WHERE table_schema = #{connection.quote(connection.current_database)} AND table_name = #{connection.quote(t)}").rows.first&.first
336
- indexes = connection.indexes(t).map { |idx| "#{idx.name} (#{idx.columns.join(", ")})" }
337
- "Table: #{t} (~#{row_count} rows, #{indexes.size} indexes)"
338
- end.compact.join("\n")
339
-
340
- # Current active queries on those tables
341
- active = ""
342
- begin
343
- results = connection.exec_query(<<~SQL)
344
- SELECT DIGEST_TEXT, COUNT_STAR AS calls, ROUND(AVG_TIMER_WAIT / 1000000000, 1) AS avg_ms
345
- FROM performance_schema.events_statements_summary_by_digest
346
- WHERE SCHEMA_NAME = #{connection.quote(connection.current_database)}
347
- AND DIGEST_TEXT IS NOT NULL
348
- AND COUNT_STAR > 10
349
- ORDER BY COUNT_STAR DESC LIMIT 20
350
- SQL
351
- matching = results.rows.select { |r| table_names.any? { |t| r[0].to_s.downcase.include?(t.downcase) } }
352
- active = matching.map { |r| "calls=#{r[1]} avg=#{r[2]}ms: #{r[0].to_s.truncate(200)}" }.join("\n")
353
- rescue ActiveRecord::StatementInvalid
354
- # performance_schema may be unavailable
355
- end
356
-
357
- messages = [
358
- { role: "system", content: <<~PROMPT },
359
- You are a MySQL migration risk assessor. Given a Rails migration or DDL, evaluate:
360
- 1. Will this lock the table? For how long given the row count?
361
- 2. Is this safe to run during traffic, or does it need a maintenance window?
362
- 3. Should pt-online-schema-change or gh-ost be used instead?
363
- 4. Will it break or degrade any of the active queries against this table?
364
- 5. Are there any data loss risks?
365
- 6. What is the recommended deployment strategy?
366
- #{ai_domain_context}
367
-
368
- Respond with JSON: {"risk_level": "low|medium|high|critical", "assessment": "markdown-formatted risk assessment with specific recommendations and estimated lock duration"}
369
- PROMPT
370
- { role: "user", content: "Migration:\n#{migration_sql}\n\nAffected Tables:\n#{table_info.presence || "Could not determine"}\n\nActive Queries on These Tables:\n#{active.presence || "None found or performance_schema unavailable"}" },
371
- ]
372
-
373
- result = ai_client.chat(messages: messages)
228
+ result = MysqlGenius::Core::Ai::MigrationRisk.new(ai_client, ai_config_for_core, rails_connection).call(migration_sql)
374
229
  render(json: result)
375
230
  rescue StandardError => e
376
231
  render(json: { error: "Migration risk assessment failed: #{e.message}" }, status: :unprocessable_entity)
@@ -378,6 +233,10 @@ module MysqlGenius
378
233
 
379
234
  private
380
235
 
236
+ RAILS_DOMAIN_CONTEXT = <<~CTX
237
+ This is a Ruby on Rails application. Do NOT recommend adding foreign key constraints (FOREIGN KEY / REFERENCES); Rails handles referential integrity at the application layer. DO recommend indexes on foreign key columns for join performance.
238
+ CTX
239
+
381
240
  def ai_client
382
241
  MysqlGenius::Core::Ai::Client.new(ai_config_for_core)
383
242
  end
@@ -391,6 +250,7 @@ module MysqlGenius
391
250
  model: cfg.ai_model,
392
251
  auth_style: cfg.ai_auth_style,
393
252
  system_context: cfg.ai_system_context,
253
+ domain_context: RAILS_DOMAIN_CONTEXT,
394
254
  )
395
255
  end
396
256
 
@@ -398,21 +258,14 @@ module MysqlGenius
398
258
  render(json: { error: "AI features are not configured." }, status: :not_found)
399
259
  end
400
260
 
401
- def ai_domain_context
402
- parts = []
403
- parts << "This is a Ruby on Rails application. Do NOT recommend adding foreign key constraints (FOREIGN KEY / REFERENCES); Rails handles referential integrity at the application layer. DO recommend indexes on foreign key columns for join performance."
404
- ctx = mysql_genius_config.ai_system_context
405
- parts << "Domain context:\n#{ctx}" if ctx.present?
406
- "\n" + parts.join("\n")
261
+ def rails_connection
262
+ MysqlGenius::Core::Connection::ActiveRecordAdapter.new(ActiveRecord::Base.connection)
407
263
  end
408
264
 
409
- def build_schema_for_query(sql)
410
- connection = ActiveRecord::Base.connection
411
- tables = MysqlGenius::Core::SqlValidator.extract_table_references(sql, connection)
412
- tables.map do |t|
413
- cols = connection.columns(t).map { |c| "#{c.name} (#{c.type})" }
414
- "#{t}: #{cols.join(", ")}"
415
- end.join("\n")
265
+ def ai_domain_context
266
+ cfg = mysql_genius_config
267
+ ctx = cfg.ai_system_context
268
+ ctx.present? ? "\nDomain context:\n#{ctx}" : ""
416
269
  end
417
270
  end
418
271
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlGenius
4
+ module SharedViewHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ helper_method :path_for, :render_partial
9
+ end
10
+
11
+ # URL path helper for shared templates.
12
+ # path_for(:execute) # => "/mysql_genius/execute" (from engine route helpers)
13
+ def path_for(name)
14
+ mysql_genius.public_send("#{name}_path")
15
+ end
16
+
17
+ # Partial renderer for shared templates.
18
+ # render_partial(:tab_dashboard) # => view_context.render partial: "mysql_genius/queries/tab_dashboard"
19
+ def render_partial(name)
20
+ view_context.render(partial: "mysql_genius/queries/#{name}")
21
+ end
22
+ end
23
+ end
@@ -5,6 +5,7 @@ module MysqlGenius
5
5
  include QueryExecution
6
6
  include DatabaseAnalysis
7
7
  include AiFeatures
8
+ include SharedViewHelpers
8
9
 
9
10
  def index
10
11
  @featured_tables = if mysql_genius_config.featured_tables.any?
@@ -14,23 +15,24 @@ module MysqlGenius
14
15
  end
15
16
  @all_tables = queryable_tables.sort
16
17
  @ai_enabled = mysql_genius_config.ai_enabled?
18
+ @framework_version_major = Rails::VERSION::MAJOR
19
+ @framework_version_minor = Rails::VERSION::MINOR
20
+ render("mysql_genius/queries/dashboard")
17
21
  end
18
22
 
19
23
  def columns
20
- table = params[:table]
21
- if mysql_genius_config.blocked_tables.include?(table)
22
- return render(json: { error: "Table '#{table}' is not available for querying." }, status: :forbidden)
23
- end
24
-
25
- unless ActiveRecord::Base.connection.tables.include?(table)
26
- return render(json: { error: "Table '#{table}' does not exist." }, status: :not_found)
27
- end
24
+ result = MysqlGenius::Core::Analysis::Columns.new(
25
+ rails_connection,
26
+ blocked_tables: mysql_genius_config.blocked_tables,
27
+ masked_column_patterns: mysql_genius_config.masked_column_patterns,
28
+ default_columns: mysql_genius_config.default_columns,
29
+ ).call(table: params[:table])
28
30
 
29
- defaults = mysql_genius_config.default_columns[table] || []
30
- cols = ActiveRecord::Base.connection.columns(table).reject { |c| masked_column?(c.name) }.map do |c|
31
- { name: c.name, type: c.type.to_s, default: defaults.empty? || defaults.include?(c.name) }
31
+ case result.status
32
+ when :ok then render(json: result.columns)
33
+ when :blocked then render(json: { error: result.error_message }, status: :forbidden)
34
+ when :not_found then render(json: { error: result.error_message }, status: :not_found)
32
35
  end
33
- render(json: cols)
34
36
  end
35
37
 
36
38
  def slow_queries
@@ -58,14 +60,8 @@ module MysqlGenius
58
60
  ActiveRecord::Base.connection.tables - mysql_genius_config.blocked_tables
59
61
  end
60
62
 
61
- # Delegates to Core::SqlValidator's 2-arg class method. A bare
62
- # `masked_column?(name)` call survives on line 30 because this helper
63
- # reintroduces the 1-arg instance method the controller's `columns`
64
- # action depends on. Without this helper, `columns` raises NoMethodError
65
- # at runtime (Phase 1b regression — Core::SqlValidator.masked_column?
66
- # became a 2-arg class method but the call site wasn't updated).
67
- def masked_column?(name)
68
- MysqlGenius::Core::SqlValidator.masked_column?(name, mysql_genius_config.masked_column_patterns)
63
+ def rails_connection
64
+ MysqlGenius::Core::Connection::ActiveRecordAdapter.new(ActiveRecord::Base.connection)
69
65
  end
70
66
  end
71
67
  end
@@ -4,6 +4,10 @@ module MysqlGenius
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace MysqlGenius
6
6
 
7
+ initializer "mysql_genius.register_core_views", before: :add_view_paths do
8
+ paths["app/views"] << MysqlGenius::Core.views_path
9
+ end
10
+
7
11
  config.after_initialize do
8
12
  if MysqlGenius.configuration.redis_url.present?
9
13
  require "mysql_genius/slow_query_monitor"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MysqlGenius
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.1"
5
5
  end
data/mysql_genius.gemspec CHANGED
@@ -31,6 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.require_paths = ["lib"]
32
32
 
33
33
  spec.add_dependency("activerecord", ">= 5.2", "< 9")
34
- spec.add_dependency("mysql_genius-core", "~> 0.4.0")
34
+ spec.add_dependency("mysql_genius-core", "~> 0.5.0")
35
35
  spec.add_dependency("railties", ">= 5.2", "< 9")
36
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql_genius
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antarr Byrd
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-11 00:00:00.000000000 Z
11
+ date: 2026-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.4.0
39
+ version: 0.5.0
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.4.0
46
+ version: 0.5.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: railties
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -88,20 +88,10 @@ files:
88
88
  - app/controllers/concerns/mysql_genius/ai_features.rb
89
89
  - app/controllers/concerns/mysql_genius/database_analysis.rb
90
90
  - app/controllers/concerns/mysql_genius/query_execution.rb
91
+ - app/controllers/concerns/mysql_genius/shared_view_helpers.rb
91
92
  - app/controllers/mysql_genius/base_controller.rb
92
93
  - app/controllers/mysql_genius/queries_controller.rb
93
94
  - app/views/layouts/mysql_genius/application.html.erb
94
- - app/views/mysql_genius/queries/_shared_results.html.erb
95
- - app/views/mysql_genius/queries/_tab_ai_tools.html.erb
96
- - app/views/mysql_genius/queries/_tab_dashboard.html.erb
97
- - app/views/mysql_genius/queries/_tab_duplicate_indexes.html.erb
98
- - app/views/mysql_genius/queries/_tab_query_explorer.html.erb
99
- - app/views/mysql_genius/queries/_tab_query_stats.html.erb
100
- - app/views/mysql_genius/queries/_tab_server.html.erb
101
- - app/views/mysql_genius/queries/_tab_slow_queries.html.erb
102
- - app/views/mysql_genius/queries/_tab_table_sizes.html.erb
103
- - app/views/mysql_genius/queries/_tab_unused_indexes.html.erb
104
- - app/views/mysql_genius/queries/index.html.erb
105
95
  - bin/console
106
96
  - bin/setup
107
97
  - config/routes.rb
@@ -1,56 +0,0 @@
1
- <!-- Explain Results -->
2
- <div id="explain-results" class="mg-mt mg-hidden">
3
- <div class="mg-card">
4
- <div class="mg-card-header">
5
- <span><strong>&#128270; EXPLAIN Output</strong></span>
6
- <div>
7
- <% if @ai_enabled %>
8
- <button id="explain-optimize" class="mg-btn mg-btn-outline mg-btn-sm">&#9889; AI Optimization</button>
9
- <button id="explain-index-advisor" class="mg-btn mg-btn-outline mg-btn-sm">&#9889; Index Advisor</button>
10
- <% end %>
11
- <button id="explain-close" class="mg-btn mg-btn-outline-secondary mg-btn-sm">&#10005; Close</button>
12
- </div>
13
- </div>
14
- <div class="mg-card-body">
15
- <div class="mg-table-wrap">
16
- <table class="mg-table">
17
- <thead id="explain-thead"></thead>
18
- <tbody id="explain-tbody"></tbody>
19
- </table>
20
- </div>
21
- <div id="optimize-results" class="mg-hidden mg-mt">
22
- <div id="optimize-content" class="mg-alert mg-alert-info"></div>
23
- </div>
24
- </div>
25
- </div>
26
- </div>
27
-
28
- <!-- Results Area -->
29
- <div id="query-results" class="mg-mt">
30
- <div id="results-alert" class="mg-hidden"></div>
31
- <div id="results-stats" class="mg-mb mg-hidden">
32
- <span id="results-row-count" class="mg-badge mg-badge-info"></span>
33
- <span id="results-time" class="mg-badge mg-badge-secondary"></span>
34
- <span id="results-truncated" class="mg-badge mg-badge-warning mg-hidden">Results truncated</span>
35
- </div>
36
- <div id="results-table-wrapper" class="mg-table-wrap mg-hidden">
37
- <table class="mg-table">
38
- <thead id="results-thead"></thead>
39
- <tbody id="results-tbody"></tbody>
40
- </table>
41
- </div>
42
- <div id="results-empty" class="mg-text-center mg-text-muted mg-hidden">No rows returned.</div>
43
- </div>
44
-
45
- <!-- AI Query Analysis Results -->
46
- <div id="ai-query-result" class="mg-mt mg-hidden">
47
- <div class="mg-card">
48
- <div class="mg-card-header">
49
- <span id="ai-query-title"><strong>&#9889; AI Analysis</strong></span>
50
- <button id="ai-query-close" class="mg-btn mg-btn-outline-secondary mg-btn-sm">&#10005;</button>
51
- </div>
52
- <div class="mg-card-body">
53
- <div id="ai-query-content" style="font-size:13px;"></div>
54
- </div>
55
- </div>
56
- </div>
@@ -1,43 +0,0 @@
1
- <!-- AI Tools Tab -->
2
- <div class="mg-tab-content" id="tab-aitools">
3
- <!-- Schema Review -->
4
- <div class="mg-card mg-mb">
5
- <div class="mg-card-header"><strong>&#9889; Schema Review</strong></div>
6
- <div class="mg-card-body">
7
- <div class="mg-text-muted mg-mb" style="font-size:12px;">Analyze your schema for anti-patterns: inappropriate column types, missing indexes, naming inconsistencies, and more.</div>
8
- <div class="mg-row" style="align-items:flex-end;">
9
- <div class="mg-col-4 mg-field">
10
- <label for="schema-table">Table (leave blank for all)</label>
11
- <select id="schema-table">
12
- <option value="">All tables (top 20)</option>
13
- <% @all_tables.each do |table| %>
14
- <option value="<%= table %>"><%= table %></option>
15
- <% end %>
16
- </select>
17
- </div>
18
- <div class="mg-field">
19
- <button id="schema-review-btn" class="mg-btn mg-btn-primary mg-btn-sm">&#9889; Analyze Schema</button>
20
- </div>
21
- </div>
22
- <div id="schema-result" class="mg-mt mg-hidden">
23
- <div id="schema-result-content" style="font-size:13px;"></div>
24
- </div>
25
- </div>
26
- </div>
27
-
28
- <!-- Migration Risk Assessment -->
29
- <div class="mg-card">
30
- <div class="mg-card-header"><strong>&#9889; Migration Risk Assessment</strong></div>
31
- <div class="mg-card-body">
32
- <div class="mg-text-muted mg-mb" style="font-size:12px;">Paste a Rails migration or DDL and get a risk assessment: lock duration, impact on active queries, deployment strategy.</div>
33
- <div class="mg-field">
34
- <textarea id="migration-input" rows="8" placeholder="class AddIndexToUsers < ActiveRecord::Migration[7.0]&#10; def change&#10; add_index :users, :email, unique: true&#10; end&#10;end"></textarea>
35
- </div>
36
- <button id="migration-assess-btn" class="mg-btn mg-btn-primary mg-btn-sm">&#9889; Assess Risk</button>
37
- <div id="migration-result" class="mg-mt mg-hidden">
38
- <div id="migration-risk-badge" style="margin-bottom:8px;"></div>
39
- <div id="migration-result-content" style="font-size:13px;"></div>
40
- </div>
41
- </div>
42
- </div>
43
- </div>