QueryWise 0.2.1 → 0.2.2
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/Dockerfile +1 -1
- data/Dockerfile.cloudrun +10 -1
- data/app/controllers/api/v1/analysis_controller.rb +60 -47
- data/app/controllers/api/v1/base_controller.rb +12 -1
- data/app/models/app_profile.rb +3 -1
- data/cloudbuild.yaml +7 -5
- data/config/application.rb +1 -1
- data/config/database.yml +9 -6
- data/config/environments/production.rb +5 -3
- data/lib/query_optimizer_client/version.rb +1 -1
- data/query_optimizer_client.gemspec +3 -3
- metadata +17 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 675e9af8542844fa7945366477cc652486a47e4279c6345cb9f89ea0dfd4db1c
|
4
|
+
data.tar.gz: 239b2d8ca7a00b8f8285d3d13711eb2b634aafb91ed9a749ffbe1b886791549b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6847d97425bea4c34d5f6ba4850c09d11def44263eda5d83e38ec3b0f8a020159f866abd4231cdc75230bcce07dd3dbda803446a81bd385278a0206f16d12e2b
|
7
|
+
data.tar.gz: 609cd7a899723f32607f7eeb6f75479faa49d0d9d8e84eeb7a4b7a7f0eceafefc36d6da85f4e34478d9e92c5677cd417bedd14332bee74fae67e426894c91e67
|
data/Dockerfile
CHANGED
data/Dockerfile.cloudrun
CHANGED
@@ -46,6 +46,15 @@ COPY . .
|
|
46
46
|
# Precompile bootsnap code for faster boot times
|
47
47
|
RUN bundle exec bootsnap precompile app/ lib/
|
48
48
|
|
49
|
+
# Create a script to run migrations and start the server
|
50
|
+
RUN echo '#!/bin/bash\n\
|
51
|
+
set -e\n\
|
52
|
+
echo "Running database migrations..."\n\
|
53
|
+
bundle exec rails db:migrate RAILS_ENV=production\n\
|
54
|
+
echo "Starting Rails server..."\n\
|
55
|
+
exec bundle exec puma -C config/puma.rb' > /rails/start.sh && \
|
56
|
+
chmod +x /rails/start.sh
|
57
|
+
|
49
58
|
# Final stage for app image
|
50
59
|
FROM base
|
51
60
|
|
@@ -73,4 +82,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
73
82
|
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
|
74
83
|
|
75
84
|
# Start server optimized for Cloud Run
|
76
|
-
CMD ["
|
85
|
+
CMD ["/rails/start.sh"]
|
@@ -1,59 +1,65 @@
|
|
1
1
|
class Api::V1::AnalysisController < Api::V1::BaseController
|
2
2
|
def analyze
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
begin
|
4
|
+
if params[:queries].nil?
|
5
|
+
render_error('Missing required parameter: queries')
|
6
|
+
return
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
+
queries_data = params[:queries]
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
11
|
+
# Validate queries using the validator
|
12
|
+
validation_errors = QueryDataValidator.validate_queries(queries_data)
|
13
|
+
if validation_errors.any?
|
14
|
+
render_error('Validation failed', :bad_request, errors: validation_errors)
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create QueryAnalysis records
|
19
|
+
query_analyses = []
|
20
|
+
queries_data.each do |query_data|
|
21
|
+
begin
|
22
|
+
analysis = create_query_analysis(query_data)
|
23
|
+
query_analyses << analysis if analysis
|
24
|
+
rescue => e
|
25
|
+
Rails.logger.error "Error creating query analysis: #{e.message}"
|
26
|
+
Rails.logger.error e.backtrace.join("\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if query_analyses.empty?
|
31
|
+
render_error('No valid queries provided')
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
# Run analysis
|
20
36
|
begin
|
21
|
-
|
22
|
-
query_analyses << analysis if analysis
|
37
|
+
analysis_results = perform_analysis(query_analyses)
|
23
38
|
rescue => e
|
24
|
-
Rails.logger.error "Error
|
39
|
+
Rails.logger.error "Error performing analysis: #{e.message}"
|
25
40
|
Rails.logger.error e.backtrace.join("\n")
|
41
|
+
render_error('Analysis failed')
|
42
|
+
return
|
26
43
|
end
|
27
|
-
end
|
28
44
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
45
|
+
# Store optimization suggestions
|
46
|
+
begin
|
47
|
+
store_suggestions(query_analyses, analysis_results)
|
48
|
+
rescue => e
|
49
|
+
Rails.logger.error "Failed to store suggestions: #{e.message}"
|
50
|
+
Rails.logger.error e.backtrace.join("\n")
|
51
|
+
# Continue without storing suggestions for now
|
52
|
+
end
|
33
53
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
Rails.logger.error "Error performing analysis: #{e.message}"
|
39
|
-
Rails.logger.error e.backtrace.join("\n")
|
40
|
-
render_error('Analysis failed')
|
41
|
-
return
|
42
|
-
end
|
43
|
-
|
44
|
-
# Store optimization suggestions
|
45
|
-
begin
|
46
|
-
store_suggestions(query_analyses, analysis_results)
|
54
|
+
# Format response
|
55
|
+
response_data = format_analysis_response(analysis_results)
|
56
|
+
|
57
|
+
render_success(response_data, message: "Analyzed #{query_analyses.length} queries")
|
47
58
|
rescue => e
|
48
|
-
Rails.logger.error "
|
59
|
+
Rails.logger.error "API Error: #{e.message}"
|
49
60
|
Rails.logger.error e.backtrace.join("\n")
|
50
|
-
|
61
|
+
render_error('Internal server error')
|
51
62
|
end
|
52
|
-
|
53
|
-
# Format response
|
54
|
-
response_data = format_analysis_response(analysis_results)
|
55
|
-
|
56
|
-
render_success(response_data, message: "Analyzed #{query_analyses.length} queries")
|
57
63
|
end
|
58
64
|
|
59
65
|
def analyze_ci
|
@@ -331,10 +337,17 @@ class Api::V1::AnalysisController < Api::V1::BaseController
|
|
331
337
|
|
332
338
|
def calculate_severity_breakdown(results)
|
333
339
|
breakdown = { low: 0, medium: 0, high: 0, critical: 0 }
|
334
|
-
|
335
|
-
results[:n_plus_one].each
|
336
|
-
|
337
|
-
|
340
|
+
|
341
|
+
results[:n_plus_one].each do |p|
|
342
|
+
severity_key = p[:severity]&.to_sym
|
343
|
+
breakdown[severity_key] += 1 if breakdown.key?(severity_key)
|
344
|
+
end
|
345
|
+
|
346
|
+
results[:slow_queries].each do |i|
|
347
|
+
severity_key = i[:severity]&.to_sym
|
348
|
+
breakdown[severity_key] += 1 if breakdown.key?(severity_key)
|
349
|
+
end
|
350
|
+
|
338
351
|
breakdown
|
339
352
|
end
|
340
353
|
end
|
@@ -18,12 +18,23 @@ class Api::V1::BaseController < ApplicationController
|
|
18
18
|
return
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
# Find app profile by checking the BCrypt hash
|
22
|
+
@current_app_profile = AppProfile.all.find do |profile|
|
23
|
+
begin
|
24
|
+
BCrypt::Password.new(profile.api_key_digest) == api_key
|
25
|
+
rescue BCrypt::Errors::InvalidHash
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
22
29
|
|
23
30
|
unless @current_app_profile
|
24
31
|
render_error('Invalid API key', :unauthorized)
|
25
32
|
return
|
26
33
|
end
|
34
|
+
rescue => e
|
35
|
+
Rails.logger.error "Authentication error: #{e.message}"
|
36
|
+
Rails.logger.error e.backtrace.join("\n")
|
37
|
+
render_error('Authentication failed', :unauthorized)
|
27
38
|
end
|
28
39
|
|
29
40
|
def set_default_response_format
|
data/app/models/app_profile.rb
CHANGED
@@ -6,7 +6,9 @@ class AppProfile < ApplicationRecord
|
|
6
6
|
validates :api_key_digest, presence: true, uniqueness: true
|
7
7
|
|
8
8
|
def self.authenticate_with_api_key(api_key)
|
9
|
-
|
9
|
+
all.find do |profile|
|
10
|
+
BCrypt::Password.new(profile.api_key_digest) == api_key
|
11
|
+
end
|
10
12
|
end
|
11
13
|
|
12
14
|
def generate_api_key!
|
data/cloudbuild.yaml
CHANGED
@@ -4,14 +4,15 @@ steps:
|
|
4
4
|
- name: 'gcr.io/cloud-builders/docker'
|
5
5
|
args: [
|
6
6
|
'build',
|
7
|
-
'-
|
7
|
+
'-f', 'Dockerfile.cloudrun',
|
8
|
+
'-t', 'gcr.io/$PROJECT_ID/query-optimizer-api:$BUILD_ID',
|
8
9
|
'-t', 'gcr.io/$PROJECT_ID/query-optimizer-api:latest',
|
9
10
|
'.'
|
10
11
|
]
|
11
12
|
|
12
13
|
# Push the container image to Container Registry
|
13
14
|
- name: 'gcr.io/cloud-builders/docker'
|
14
|
-
args: ['push', 'gcr.io/$PROJECT_ID/query-optimizer-api:$
|
15
|
+
args: ['push', 'gcr.io/$PROJECT_ID/query-optimizer-api:$BUILD_ID']
|
15
16
|
|
16
17
|
- name: 'gcr.io/cloud-builders/docker'
|
17
18
|
args: ['push', 'gcr.io/$PROJECT_ID/query-optimizer-api:latest']
|
@@ -21,21 +22,22 @@ steps:
|
|
21
22
|
entrypoint: gcloud
|
22
23
|
args: [
|
23
24
|
'run', 'deploy', 'query-optimizer-api',
|
24
|
-
'--image', 'gcr.io/$PROJECT_ID/query-optimizer-api
|
25
|
+
'--image', 'gcr.io/$PROJECT_ID/query-optimizer-api:latest',
|
25
26
|
'--region', 'us-central1',
|
26
27
|
'--platform', 'managed',
|
27
28
|
'--allow-unauthenticated',
|
28
|
-
'--port', '
|
29
|
+
'--port', '8080',
|
29
30
|
'--memory', '512Mi',
|
30
31
|
'--cpu', '1',
|
31
32
|
'--max-instances', '10',
|
32
33
|
'--set-env-vars', 'RAILS_ENV=production',
|
34
|
+
'--set-secrets', 'DATABASE_PASSWORD=database-password:latest',
|
33
35
|
'--set-cloudsql-instances', '$PROJECT_ID:us-central1:query-optimizer-db'
|
34
36
|
]
|
35
37
|
|
36
38
|
# Store images in Container Registry
|
37
39
|
images:
|
38
|
-
- 'gcr.io/$PROJECT_ID/query-optimizer-api:$
|
40
|
+
- 'gcr.io/$PROJECT_ID/query-optimizer-api:$BUILD_ID'
|
39
41
|
- 'gcr.io/$PROJECT_ID/query-optimizer-api:latest'
|
40
42
|
|
41
43
|
# Build options
|
data/config/application.rb
CHANGED
@@ -14,7 +14,7 @@ module QueryOptimizerApi
|
|
14
14
|
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
15
15
|
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
16
16
|
# Common ones are `templates`, `generators`, or `middleware`, for example.
|
17
|
-
config.autoload_lib(ignore: %w[assets tasks])
|
17
|
+
config.autoload_lib(ignore: %w[assets tasks query_optimizer_client])
|
18
18
|
|
19
19
|
# Configuration for the application, engines, and railties goes here.
|
20
20
|
#
|
data/config/database.yml
CHANGED
@@ -52,18 +52,21 @@ test:
|
|
52
52
|
production:
|
53
53
|
primary: &primary_production
|
54
54
|
<<: *default
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
adapter: postgresql
|
56
|
+
database: query_optimizer_production
|
57
|
+
username: rails
|
58
|
+
password: <%= ENV["DATABASE_PASSWORD"] %>
|
59
|
+
host: <%= ENV["DB_HOST"] || "/cloudsql/vital-form-469501-s9:us-central1:query-optimizer-db" %>
|
60
|
+
port: <%= ENV["DB_PORT"] || 5432 %>
|
58
61
|
cache:
|
59
62
|
<<: *primary_production
|
60
|
-
database:
|
63
|
+
database: query_optimizer_production
|
61
64
|
migrations_paths: db/cache_migrate
|
62
65
|
queue:
|
63
66
|
<<: *primary_production
|
64
|
-
database:
|
67
|
+
database: query_optimizer_production
|
65
68
|
migrations_paths: db/queue_migrate
|
66
69
|
cable:
|
67
70
|
<<: *primary_production
|
68
|
-
database:
|
71
|
+
database: query_optimizer_production
|
69
72
|
migrations_paths: db/cable_migrate
|
@@ -44,11 +44,13 @@ Rails.application.configure do
|
|
44
44
|
config.active_support.report_deprecations = false
|
45
45
|
|
46
46
|
# Replace the default in-process memory cache store with a durable alternative.
|
47
|
-
config.cache_store = :solid_cache_store
|
47
|
+
# config.cache_store = :solid_cache_store
|
48
|
+
config.cache_store = :memory_store
|
48
49
|
|
49
50
|
# Replace the default in-process and non-durable queuing backend for Active Job.
|
50
|
-
config.active_job.queue_adapter = :solid_queue
|
51
|
-
config.solid_queue.connects_to = { database: { writing: :queue } }
|
51
|
+
# config.active_job.queue_adapter = :solid_queue
|
52
|
+
# config.solid_queue.connects_to = { database: { writing: :queue } }
|
53
|
+
config.active_job.queue_adapter = :async
|
52
54
|
|
53
55
|
# Ignore bad email addresses and do not raise email delivery errors.
|
54
56
|
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "QueryWise is a lightweight, developer-friendly tool that helps Ruby on Rails teams detect, analyze, and fix inefficient database queries. Automatically detect N+1 queries, slow queries, and missing indexes without needing heavy, expensive Application Performance Monitoring (APM) software."
|
13
13
|
spec.homepage = "https://github.com/BlairLane22/QueryOptimizerAPI"
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = ">= 3.
|
15
|
+
spec.required_ruby_version = ">= 3.2.2"
|
16
16
|
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
18
|
spec.metadata["homepage_uri"] = spec.homepage
|
@@ -44,7 +44,7 @@ Gem::Specification.new do |spec|
|
|
44
44
|
|
45
45
|
# Runtime dependencies
|
46
46
|
spec.add_dependency "httparty", "~> 0.21"
|
47
|
-
spec.add_dependency "activesupport", "~>
|
47
|
+
spec.add_dependency "activesupport", "~> 8.0", ">= 8.0.2.1"
|
48
48
|
|
49
49
|
# Development dependencies
|
50
50
|
spec.add_development_dependency "rake", "~> 13.0"
|
@@ -52,7 +52,7 @@ Gem::Specification.new do |spec|
|
|
52
52
|
spec.add_development_dependency "rubocop", "~> 1.21"
|
53
53
|
spec.add_development_dependency "webmock", "~> 3.18"
|
54
54
|
spec.add_development_dependency "vcr", "~> 6.1"
|
55
|
-
spec.add_development_dependency "rails", "~>
|
55
|
+
spec.add_development_dependency "rails", "~> 8.0"
|
56
56
|
spec.add_development_dependency "sqlite3", "~> 1.4"
|
57
57
|
|
58
58
|
# For more information and examples about making a new gem, check out our
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: QueryWise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blair Lane
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -30,14 +30,20 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '8.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 8.0.2.1
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
41
|
- - "~>"
|
39
42
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
43
|
+
version: '8.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 8.0.2.1
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: rake
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,14 +120,14 @@ dependencies:
|
|
114
120
|
requirements:
|
115
121
|
- - "~>"
|
116
122
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
123
|
+
version: '8.0'
|
118
124
|
type: :development
|
119
125
|
prerelease: false
|
120
126
|
version_requirements: !ruby/object:Gem::Requirement
|
121
127
|
requirements:
|
122
128
|
- - "~>"
|
123
129
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
130
|
+
version: '8.0'
|
125
131
|
- !ruby/object:Gem::Dependency
|
126
132
|
name: sqlite3
|
127
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -244,7 +250,7 @@ metadata:
|
|
244
250
|
source_code_uri: https://github.com/BlairLane22/QueryWise
|
245
251
|
changelog_uri: https://github.com/BlairLane22/QueryWise/blob/main/CHANGELOG.md
|
246
252
|
documentation_uri: https://github.com/BlairLane22/QueryWise/blob/main/README.md
|
247
|
-
post_install_message:
|
253
|
+
post_install_message:
|
248
254
|
rdoc_options: []
|
249
255
|
require_paths:
|
250
256
|
- lib
|
@@ -252,15 +258,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
252
258
|
requirements:
|
253
259
|
- - ">="
|
254
260
|
- !ruby/object:Gem::Version
|
255
|
-
version: 3.
|
261
|
+
version: 3.2.2
|
256
262
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
257
263
|
requirements:
|
258
264
|
- - ">="
|
259
265
|
- !ruby/object:Gem::Version
|
260
266
|
version: '0'
|
261
267
|
requirements: []
|
262
|
-
rubygems_version: 3.
|
263
|
-
signing_key:
|
268
|
+
rubygems_version: 3.4.10
|
269
|
+
signing_key:
|
264
270
|
specification_version: 4
|
265
271
|
summary: QueryWise - Rails Database Query Optimizer
|
266
272
|
test_files: []
|