mysql_genius 0.1.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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +53 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/CHANGELOG.md +13 -0
  6. data/Gemfile +15 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +295 -0
  9. data/Rakefile +6 -0
  10. data/app/controllers/concerns/mysql_genius/ai_features.rb +360 -0
  11. data/app/controllers/concerns/mysql_genius/database_analysis.rb +259 -0
  12. data/app/controllers/concerns/mysql_genius/query_execution.rb +129 -0
  13. data/app/controllers/mysql_genius/base_controller.rb +18 -0
  14. data/app/controllers/mysql_genius/queries_controller.rb +54 -0
  15. data/app/services/mysql_genius/ai_client.rb +84 -0
  16. data/app/services/mysql_genius/ai_optimization_service.rb +56 -0
  17. data/app/services/mysql_genius/ai_suggestion_service.rb +56 -0
  18. data/app/views/layouts/mysql_genius/application.html.erb +116 -0
  19. data/app/views/mysql_genius/queries/_shared_results.html.erb +56 -0
  20. data/app/views/mysql_genius/queries/_tab_ai_tools.html.erb +43 -0
  21. data/app/views/mysql_genius/queries/_tab_duplicate_indexes.html.erb +24 -0
  22. data/app/views/mysql_genius/queries/_tab_query_stats.html.erb +36 -0
  23. data/app/views/mysql_genius/queries/_tab_server.html.erb +54 -0
  24. data/app/views/mysql_genius/queries/_tab_slow_queries.html.erb +17 -0
  25. data/app/views/mysql_genius/queries/_tab_sql_query.html.erb +40 -0
  26. data/app/views/mysql_genius/queries/_tab_table_sizes.html.erb +31 -0
  27. data/app/views/mysql_genius/queries/_tab_unused_indexes.html.erb +25 -0
  28. data/app/views/mysql_genius/queries/_tab_visual_builder.html.erb +61 -0
  29. data/app/views/mysql_genius/queries/index.html.erb +1185 -0
  30. data/bin/console +14 -0
  31. data/bin/setup +8 -0
  32. data/config/routes.rb +24 -0
  33. data/docs/screenshots/ai_tools.png +0 -0
  34. data/docs/screenshots/duplicate_indexes.png +0 -0
  35. data/docs/screenshots/query_stats.png +0 -0
  36. data/docs/screenshots/server.png +0 -0
  37. data/docs/screenshots/sql_query.png +0 -0
  38. data/docs/screenshots/table_sizes.png +0 -0
  39. data/docs/screenshots/visual_builder.png +0 -0
  40. data/lib/mysql_genius/configuration.rb +96 -0
  41. data/lib/mysql_genius/engine.rb +12 -0
  42. data/lib/mysql_genius/slow_query_monitor.rb +38 -0
  43. data/lib/mysql_genius/sql_validator.rb +55 -0
  44. data/lib/mysql_genius/version.rb +3 -0
  45. data/lib/mysql_genius.rb +23 -0
  46. data/mysql_genius.gemspec +34 -0
  47. metadata +122 -0
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mysql_genius"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/config/routes.rb ADDED
@@ -0,0 +1,24 @@
1
+ MysqlGenius::Engine.routes.draw do
2
+ root to: "queries#index"
3
+
4
+ get "columns", to: "queries#columns"
5
+ post "execute", to: "queries#execute"
6
+ post "explain", to: "queries#explain"
7
+ post "suggest", to: "queries#suggest"
8
+ post "optimize", to: "queries#optimize"
9
+ get "slow_queries", to: "queries#slow_queries"
10
+ get "duplicate_indexes", to: "queries#duplicate_indexes"
11
+ get "table_sizes", to: "queries#table_sizes"
12
+ get "query_stats", to: "queries#query_stats"
13
+ get "unused_indexes", to: "queries#unused_indexes"
14
+ get "server_overview", to: "queries#server_overview"
15
+
16
+ # AI features
17
+ post "describe_query", to: "queries#describe_query"
18
+ post "schema_review", to: "queries#schema_review"
19
+ post "rewrite_query", to: "queries#rewrite_query"
20
+ post "index_advisor", to: "queries#index_advisor"
21
+ post "anomaly_detection", to: "queries#anomaly_detection"
22
+ post "root_cause", to: "queries#root_cause"
23
+ post "migration_risk", to: "queries#migration_risk"
24
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,96 @@
1
+ module MysqlGenius
2
+ class Configuration
3
+ # Tables to feature in the visual builder dropdown (array of strings).
4
+ # When empty, all non-blocked tables are shown.
5
+ attr_accessor :featured_tables
6
+
7
+ # Tables that must never be queried (auth, sessions, internal Rails tables).
8
+ attr_accessor :blocked_tables
9
+
10
+ # Column name patterns to mask with [REDACTED] in query results.
11
+ # Matched case-insensitively via String#include?.
12
+ attr_accessor :masked_column_patterns
13
+
14
+ # Default columns to check in the visual builder, keyed by table name.
15
+ # Example: { "users" => %w[id name email created_at] }
16
+ attr_accessor :default_columns
17
+
18
+ # Maximum rows a single query can return.
19
+ attr_accessor :max_row_limit
20
+
21
+ # Default row limit when none is specified.
22
+ attr_accessor :default_row_limit
23
+
24
+ # Query timeout in milliseconds.
25
+ attr_accessor :query_timeout_ms
26
+
27
+ # Proc that receives the controller instance and returns true if the user
28
+ # is authorized. Example:
29
+ # config.authenticate = ->(controller) { controller.current_user&.admin? }
30
+ attr_accessor :authenticate
31
+
32
+ # AI configuration — set to nil to disable AI features entirely.
33
+ # Must respond to :call(messages:, response_format:, temperature:)
34
+ # and return a Hash with "choices" in OpenAI-compatible format,
35
+ # OR set ai_endpoint + ai_api_key for a direct OpenAI-compatible HTTP API.
36
+ attr_accessor :ai_client
37
+ attr_accessor :ai_endpoint
38
+ attr_accessor :ai_api_key
39
+
40
+ # AI model name to pass in the request body (e.g. "gpt-4o", "gpt-3.5-turbo").
41
+ # Optional — if nil, the API default or deployment model is used.
42
+ attr_accessor :ai_model
43
+
44
+ # AI auth style: :bearer (OpenAI, Ollama Cloud) or :api_key (Azure OpenAI).
45
+ # Defaults to :api_key for backwards compatibility.
46
+ attr_accessor :ai_auth_style
47
+
48
+ # Custom system prompt prepended to AI suggestions. Use this to describe
49
+ # your domain, table relationships, and naming conventions.
50
+ attr_accessor :ai_system_context
51
+
52
+ # Slow query threshold in milliseconds. Queries slower than this are logged.
53
+ attr_accessor :slow_query_threshold_ms
54
+
55
+ # Redis URL for slow query storage. Set to nil to disable slow query monitoring.
56
+ attr_accessor :redis_url
57
+
58
+ # Logger instance for audit logging. Defaults to a file logger.
59
+ # Set to nil to disable audit logging.
60
+ attr_accessor :audit_logger
61
+
62
+ # Base controller class for the engine to inherit from.
63
+ # Set to "ApplicationController" to get current_user and other app helpers.
64
+ # Defaults to "ActionController::Base".
65
+ attr_accessor :base_controller
66
+
67
+ def initialize
68
+ @featured_tables = []
69
+ @blocked_tables = %w[
70
+ sessions
71
+ ar_internal_metadata
72
+ schema_migrations
73
+ ]
74
+ @masked_column_patterns = %w[password secret digest token]
75
+ @default_columns = {}
76
+ @max_row_limit = 1000
77
+ @default_row_limit = 25
78
+ @query_timeout_ms = 30_000
79
+ @authenticate = ->(controller) { true }
80
+ @ai_client = nil
81
+ @ai_endpoint = nil
82
+ @ai_api_key = nil
83
+ @ai_model = nil
84
+ @ai_auth_style = :api_key
85
+ @ai_system_context = nil
86
+ @slow_query_threshold_ms = 250
87
+ @redis_url = nil
88
+ @audit_logger = nil
89
+ @base_controller = "ActionController::Base"
90
+ end
91
+
92
+ def ai_enabled?
93
+ !ai_client.nil? || (!ai_endpoint.nil? && !ai_endpoint.empty? && !ai_api_key.nil? && !ai_api_key.empty?)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,12 @@
1
+ module MysqlGenius
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace MysqlGenius
4
+
5
+ config.after_initialize do
6
+ if MysqlGenius.configuration.redis_url.present?
7
+ require "mysql_genius/slow_query_monitor"
8
+ MysqlGenius::SlowQueryMonitor.subscribe!
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ require "json"
2
+
3
+ module MysqlGenius
4
+ class SlowQueryMonitor
5
+ def self.redis_key
6
+ "mysql_genius:slow_queries"
7
+ end
8
+
9
+ def self.subscribe!
10
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, start, finish, _id, payload|
11
+ duration_ms = ((finish - start) * 1000).round(1)
12
+ sql = payload[:sql].to_s
13
+ threshold = MysqlGenius.configuration.slow_query_threshold_ms
14
+
15
+ next unless duration_ms >= threshold
16
+ next unless sql.match?(/\ASELECT\b/i)
17
+ next if sql.include?("SCHEMA")
18
+ next if sql.include?("EXPLAIN")
19
+ next if payload[:name] == "SCHEMA"
20
+
21
+ begin
22
+ redis = Redis.new(url: MysqlGenius.configuration.redis_url)
23
+ entry = {
24
+ sql: sql.truncate(10_000),
25
+ duration_ms: duration_ms,
26
+ timestamp: Time.current.iso8601,
27
+ name: payload[:name]
28
+ }.to_json
29
+
30
+ redis.lpush(redis_key, entry)
31
+ redis.ltrim(redis_key, 0, 199)
32
+ rescue => e
33
+ Rails.logger.debug("[mysql_genius] Slow query logger error: #{e.message}") if defined?(Rails)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ module MysqlGenius
2
+ module SqlValidator
3
+ FORBIDDEN_KEYWORDS = %w[INSERT UPDATE DELETE DROP ALTER CREATE TRUNCATE GRANT REVOKE].freeze
4
+
5
+ module_function
6
+
7
+ def validate(sql, blocked_tables:, connection:)
8
+ return "Please enter a query." if sql.nil? || sql.strip.empty?
9
+
10
+ normalized = sql.gsub(/--.*$/, "").gsub(%r{/\*.*?\*/}m, "").strip
11
+
12
+ unless normalized.match?(/\ASELECT\b/i) || normalized.match?(/\AWITH\b/i)
13
+ return "Only SELECT queries are allowed."
14
+ end
15
+
16
+ return "Access to system schemas is not allowed." if normalized.match?(/\b(information_schema|mysql|performance_schema|sys)\b/i)
17
+
18
+ FORBIDDEN_KEYWORDS.each do |keyword|
19
+ return "#{keyword} statements are not allowed." if normalized.match?(/\b#{keyword}\b/i)
20
+ end
21
+
22
+ tables_in_query = extract_table_references(normalized, connection)
23
+ blocked = tables_in_query & blocked_tables
24
+ if blocked.any?
25
+ return "Access denied for table(s): #{blocked.join(', ')}."
26
+ end
27
+
28
+ nil
29
+ end
30
+
31
+ def extract_table_references(sql, connection)
32
+ tables = []
33
+ sql.scan(/\bFROM\s+((?:`?\w+`?(?:\s*,\s*`?\w+`?)*)+)/i) { |m| m[0].scan(/`?(\w+)`?/) { |t| tables << t[0] } }
34
+ sql.scan(/\bJOIN\s+`?(\w+)`?/i) { |m| tables << m[0] }
35
+ sql.scan(/\b(?:INTO|UPDATE)\s+`?(\w+)`?/i) { |m| tables << m[0] }
36
+ tables.uniq.map(&:downcase) & connection.tables
37
+ end
38
+
39
+ def apply_row_limit(sql, limit)
40
+ if sql.match?(/\bLIMIT\s+\d+\s*,\s*\d+/i)
41
+ sql.gsub(/\bLIMIT\s+(\d+)\s*,\s*(\d+)/i) do
42
+ "LIMIT #{::Regexp.last_match(1).to_i}, #{[::Regexp.last_match(2).to_i, limit].min}"
43
+ end
44
+ elsif sql.match?(/\bLIMIT\s+\d+/i)
45
+ sql.gsub(/\bLIMIT\s+(\d+)/i) { "LIMIT #{[::Regexp.last_match(1).to_i, limit].min}" }
46
+ else
47
+ "#{sql.gsub(/;\s*\z/, '')} LIMIT #{limit}"
48
+ end
49
+ end
50
+
51
+ def masked_column?(column_name, patterns)
52
+ patterns.any? { |pattern| column_name.downcase.include?(pattern) }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module MysqlGenius
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ require "mysql_genius/version"
2
+ require "mysql_genius/configuration"
3
+ require "mysql_genius/sql_validator"
4
+
5
+ module MysqlGenius
6
+ class Error < StandardError; end
7
+
8
+ class << self
9
+ def configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ def configure
14
+ yield(configuration)
15
+ end
16
+
17
+ def reset_configuration!
18
+ @configuration = Configuration.new
19
+ end
20
+ end
21
+ end
22
+
23
+ require "mysql_genius/engine" if defined?(Rails)
@@ -0,0 +1,34 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "mysql_genius/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mysql_genius"
7
+ spec.version = MysqlGenius::VERSION
8
+ spec.authors = ["Antarr Byrd"]
9
+ spec.email = ["antarr.t.byrd@uth.tmc.edu"]
10
+
11
+ spec.summary = "A MySQL performance dashboard and query explorer for Rails — like PgHero, but for MySQL."
12
+ spec.description = "MysqlGenius gives Rails apps a mountable admin dashboard for MySQL databases. " \
13
+ "Includes a safe SQL query explorer with visual builder, EXPLAIN analysis, " \
14
+ "slow query monitoring, audit logging, and optional AI-powered query suggestions and optimization."
15
+ spec.homepage = "https://github.com/antarr/mysql_genius"
16
+ spec.license = "MIT"
17
+
18
+ spec.required_ruby_version = ">= 2.6.0"
19
+
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = spec.homepage
22
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
23
+
24
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "railties", ">= 5.2"
32
+ spec.add_dependency "activerecord", ">= 5.2"
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysql_genius
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Antarr Byrd
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-04-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '5.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '5.2'
41
+ description: MysqlGenius gives Rails apps a mountable admin dashboard for MySQL databases.
42
+ Includes a safe SQL query explorer with visual builder, EXPLAIN analysis, slow query
43
+ monitoring, audit logging, and optional AI-powered query suggestions and optimization.
44
+ email:
45
+ - antarr.t.byrd@uth.tmc.edu
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".github/workflows/ci.yml"
51
+ - ".gitignore"
52
+ - ".rspec"
53
+ - CHANGELOG.md
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - app/controllers/concerns/mysql_genius/ai_features.rb
59
+ - app/controllers/concerns/mysql_genius/database_analysis.rb
60
+ - app/controllers/concerns/mysql_genius/query_execution.rb
61
+ - app/controllers/mysql_genius/base_controller.rb
62
+ - app/controllers/mysql_genius/queries_controller.rb
63
+ - app/services/mysql_genius/ai_client.rb
64
+ - app/services/mysql_genius/ai_optimization_service.rb
65
+ - app/services/mysql_genius/ai_suggestion_service.rb
66
+ - app/views/layouts/mysql_genius/application.html.erb
67
+ - app/views/mysql_genius/queries/_shared_results.html.erb
68
+ - app/views/mysql_genius/queries/_tab_ai_tools.html.erb
69
+ - app/views/mysql_genius/queries/_tab_duplicate_indexes.html.erb
70
+ - app/views/mysql_genius/queries/_tab_query_stats.html.erb
71
+ - app/views/mysql_genius/queries/_tab_server.html.erb
72
+ - app/views/mysql_genius/queries/_tab_slow_queries.html.erb
73
+ - app/views/mysql_genius/queries/_tab_sql_query.html.erb
74
+ - app/views/mysql_genius/queries/_tab_table_sizes.html.erb
75
+ - app/views/mysql_genius/queries/_tab_unused_indexes.html.erb
76
+ - app/views/mysql_genius/queries/_tab_visual_builder.html.erb
77
+ - app/views/mysql_genius/queries/index.html.erb
78
+ - bin/console
79
+ - bin/setup
80
+ - config/routes.rb
81
+ - docs/screenshots/ai_tools.png
82
+ - docs/screenshots/duplicate_indexes.png
83
+ - docs/screenshots/query_stats.png
84
+ - docs/screenshots/server.png
85
+ - docs/screenshots/sql_query.png
86
+ - docs/screenshots/table_sizes.png
87
+ - docs/screenshots/visual_builder.png
88
+ - lib/mysql_genius.rb
89
+ - lib/mysql_genius/configuration.rb
90
+ - lib/mysql_genius/engine.rb
91
+ - lib/mysql_genius/slow_query_monitor.rb
92
+ - lib/mysql_genius/sql_validator.rb
93
+ - lib/mysql_genius/version.rb
94
+ - mysql_genius.gemspec
95
+ homepage: https://github.com/antarr/mysql_genius
96
+ licenses:
97
+ - MIT
98
+ metadata:
99
+ homepage_uri: https://github.com/antarr/mysql_genius
100
+ source_code_uri: https://github.com/antarr/mysql_genius
101
+ changelog_uri: https://github.com/antarr/mysql_genius/blob/main/CHANGELOG.md
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.6.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.0.3.1
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: A MySQL performance dashboard and query explorer for Rails — like PgHero,
121
+ but for MySQL.
122
+ test_files: []