query_guard 0.4.2 โ†’ 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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -1
  3. data/DESIGN.md +420 -0
  4. data/INDEX.md +309 -0
  5. data/README.md +579 -30
  6. data/exe/queryguard +23 -0
  7. data/lib/query_guard/action_controller_subscriber.rb +27 -0
  8. data/lib/query_guard/analysis/query_risk_classifier.rb +124 -0
  9. data/lib/query_guard/analysis/risk_detectors.rb +258 -0
  10. data/lib/query_guard/analysis/risk_level.rb +35 -0
  11. data/lib/query_guard/analyzers/base.rb +30 -0
  12. data/lib/query_guard/analyzers/query_count_analyzer.rb +31 -0
  13. data/lib/query_guard/analyzers/query_risk_analyzer.rb +146 -0
  14. data/lib/query_guard/analyzers/registry.rb +57 -0
  15. data/lib/query_guard/analyzers/select_star_analyzer.rb +42 -0
  16. data/lib/query_guard/analyzers/slow_query_analyzer.rb +39 -0
  17. data/lib/query_guard/budget.rb +148 -0
  18. data/lib/query_guard/cli/batch_report_formatter.rb +129 -0
  19. data/lib/query_guard/cli/command.rb +93 -0
  20. data/lib/query_guard/cli/commands/analyze.rb +52 -0
  21. data/lib/query_guard/cli/commands/check.rb +58 -0
  22. data/lib/query_guard/cli/formatter.rb +278 -0
  23. data/lib/query_guard/cli/json_reporter.rb +247 -0
  24. data/lib/query_guard/cli/paged_report_formatter.rb +137 -0
  25. data/lib/query_guard/cli/source_metadata_collector.rb +297 -0
  26. data/lib/query_guard/cli.rb +197 -0
  27. data/lib/query_guard/client.rb +4 -6
  28. data/lib/query_guard/config.rb +145 -6
  29. data/lib/query_guard/core/context.rb +80 -0
  30. data/lib/query_guard/core/finding.rb +162 -0
  31. data/lib/query_guard/core/finding_builders.rb +152 -0
  32. data/lib/query_guard/core/query.rb +40 -0
  33. data/lib/query_guard/explain/adapter_interface.rb +89 -0
  34. data/lib/query_guard/explain/explain_enricher.rb +367 -0
  35. data/lib/query_guard/explain/plan_signals.rb +385 -0
  36. data/lib/query_guard/explain/postgresql_adapter.rb +208 -0
  37. data/lib/query_guard/exporter.rb +124 -0
  38. data/lib/query_guard/fingerprint.rb +96 -0
  39. data/lib/query_guard/middleware.rb +101 -15
  40. data/lib/query_guard/migrations/database_adapter.rb +88 -0
  41. data/lib/query_guard/migrations/migration_analyzer.rb +100 -0
  42. data/lib/query_guard/migrations/migration_risk_detectors.rb +390 -0
  43. data/lib/query_guard/migrations/postgresql_adapter.rb +157 -0
  44. data/lib/query_guard/migrations/table_risk_analyzer.rb +154 -0
  45. data/lib/query_guard/migrations/table_size_resolver.rb +152 -0
  46. data/lib/query_guard/publish.rb +38 -0
  47. data/lib/query_guard/rspec.rb +119 -0
  48. data/lib/query_guard/security.rb +99 -0
  49. data/lib/query_guard/store.rb +38 -0
  50. data/lib/query_guard/subscriber.rb +46 -15
  51. data/lib/query_guard/suggest/index_suggester.rb +176 -0
  52. data/lib/query_guard/suggest/pattern_extractors.rb +137 -0
  53. data/lib/query_guard/trace.rb +106 -0
  54. data/lib/query_guard/uploader/http_uploader.rb +166 -0
  55. data/lib/query_guard/uploader/interface.rb +79 -0
  56. data/lib/query_guard/uploader/no_op_uploader.rb +46 -0
  57. data/lib/query_guard/uploader/registry.rb +37 -0
  58. data/lib/query_guard/uploader/upload_service.rb +80 -0
  59. data/lib/query_guard/version.rb +1 -1
  60. data/lib/query_guard.rb +54 -7
  61. metadata +78 -10
  62. data/.rspec +0 -3
  63. data/Rakefile +0 -21
  64. data/config/initializers/query_guard.rb +0 -9
data/README.md CHANGED
@@ -1,63 +1,612 @@
1
- # query_guard
1
+ # QueryGuard: Migration Safety for Rails
2
2
 
3
- Guardrails for ActiveRecord queries per request: maximum query count, slow query flagging, and optional `SELECT *` blocking.
3
+ **Catch risky database changes before they reach production.**
4
+
5
+ QueryGuard automatically analyzes your Rails migrations in CI to detect safety issues, preventing schema problems from entering your codebase. Built with deep query intelligence and security features for comprehensive database safety.
6
+
7
+ ## ๐ŸŽฏ What Makes QueryGuard Different?
8
+
9
+ **v1.0 Focus: Migration Safety + Query Security**
10
+
11
+ QueryGuard runs in your CI pipeline to:
12
+ - **Analyze Migrations**: Detect risky patterns like non-nullable columns without defaults, missing rollbacks, performance issues
13
+ - **Detect Security Issues**: Monitor for SQL injection, unusual query patterns, data exfiltration, mass assignment attacks
14
+ - **Monitor Query Performance**: Track slow queries and unusual database activity
4
15
 
5
16
  ## Installation
6
17
 
7
- Add to your Gemfile:
18
+ Add QueryGuard to your Gemfile:
8
19
 
9
20
  ```ruby
10
- gem "query_guard"
21
+ group :development, :test do
22
+ gem 'query_guard'
23
+ end
11
24
  ```
12
25
 
13
- ## โš™๏ธ Configuration File
26
+ Then install and generate the initializer:
14
27
 
15
- To enable and configure `QueryGuard` in your Rails application,
16
- you need to create an initializer file with the configuration options below.
28
+ ```bash
29
+ bundle install
30
+ bundle exec rails generate query_guard:install
31
+ ```
17
32
 
18
- ---
33
+ This creates `config/initializers/query_guard.rb` with essential configuration.
34
+
35
+ ## Configuration
36
+
37
+ The default configuration works for most Rails applications. Edit `config/initializers/query_guard.rb` to customize:
38
+
39
+ ```ruby
40
+ QueryGuard.configure do |config|
41
+ # Directory where migrations live (default: db/migrate)
42
+ config.migrations_directory = 'db/migrate'
43
+
44
+ # Environments to analyze (default: [:development, :test])
45
+ config.enabled_environments = [:development, :test]
46
+
47
+ # Max queries to check per request (prevent timeouts)
48
+ config.max_queries_per_request = 100
49
+
50
+ # Max query duration in ms before flagging as slow
51
+ config.max_duration_ms_per_query = 5000
52
+ end
53
+ ```
54
+
55
+ ## Using QueryGuard
56
+
57
+ ### In CI (Recommended)
58
+
59
+ Add to your CI workflow to check migrations automatically:
60
+
61
+ ```yaml
62
+ # .github/workflows/db-safety.yml
63
+ name: Database Safety
64
+ on: [pull_request]
19
65
 
20
- ### 1๏ธโƒฃ Create the configuration file
66
+ jobs:
67
+ analyze:
68
+ runs-on: ubuntu-latest
69
+ services:
70
+ postgres:
71
+ image: postgres:14
72
+ options: >-
73
+ --health-cmd pg_isready
74
+ --health-interval 10s
75
+ --health-timeout 5s
76
+ --health-retries 5
77
+ env:
78
+ POSTGRES_PASSWORD: postgres
21
79
 
22
- Run this command inside your Rails app:
80
+ steps:
81
+ - uses: actions/checkout@v3
82
+ - uses: ruby/setup-ruby@v1
83
+ with:
84
+ ruby-version: 3.3.0
85
+ bundler-cache: true
86
+
87
+ - name: Analyze migrations
88
+ run: bundle exec queryguard analyze
89
+ env:
90
+ DATABASE_URL: postgres://postgres:postgres@localhost/test_db
91
+ ```
92
+
93
+ ### Locally
23
94
 
24
95
  ```bash
25
- touch config/initializers/query_guard.rb
96
+ # Analyze migrations in your current environment
97
+ bundle exec queryguard analyze
98
+
99
+ # Get JSON output for integration with CI dashboards
100
+ bundle exec queryguard analyze --format json
26
101
  ```
27
102
 
28
- ### 2๏ธโƒฃ Add the following code inside that file:
103
+ ## JSON Output
104
+
105
+ QueryGuard can output analysis results as JSON for integration with CI dashboards, security tools, or custom workflows:
106
+
107
+ ```bash
108
+ bundle exec queryguard analyze --format json
109
+ ```
110
+
111
+ ### JSON Schema
112
+
113
+ ```json
114
+ {
115
+ "status": "success",
116
+ "findings": [
117
+ {
118
+ "type": "risky_migration",
119
+ "severity": "high",
120
+ "message": "Adding non-nullable column without default",
121
+ "file": "db/migrate/20240101120000_add_user_status.rb",
122
+ "line": 4,
123
+ "context": {
124
+ "migration_name": "AddUserStatus",
125
+ "environment": "test"
126
+ }
127
+ }
128
+ ],
129
+ "metadata": {
130
+ "environment": "test",
131
+ "database": "postgresql",
132
+ "schema_version": "20240101120000"
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## What's Coming Next
138
+
139
+ **v2.0 (Future)**
140
+ - Query analysis for N+1 problems and missing indexes
141
+ - Live application query monitoring
142
+ - Performance recommendations based on actual query patterns
143
+ - SaaS dashboard integration
144
+
145
+ **v1.0** focuses exclusively on migration safety because:
146
+ 1. Migrations are deterministic and analyzable in CI
147
+ 2. Migration problems are expensive to fix in production
148
+ 3. This gives you immediate, measurable value
149
+
150
+ For query monitoring in your application, see QueryGuard's query analysis (coming v2.0) or integrate with tools like:
151
+ - [New Relic APM](https://newrelic.com)
152
+ - [DataDog APM](https://www.datadoghq.com)
153
+ - [Sentry Performance](https://sentry.io)
154
+
155
+ ## Development
156
+
157
+ To set up the development environment:
158
+
159
+ ```bash
160
+ git clone https://github.com/yourusername/query_guard.git
161
+ cd query_guard
162
+ bundle install
163
+ bundle exec rake spec
164
+ ```
165
+
166
+ All 186+ tests should pass. QueryGuard maintains 100% test coverage for core analysis logic.
167
+
168
+ ## Contributing
169
+
170
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
171
+
172
+ ## License
173
+
174
+ MIT License - See [LICENSE.txt](LICENSE.txt) for details.
175
+
176
+ ## Support
177
+
178
+ - **Issues**: [GitHub Issues](https://github.com/yourusername/query_guard/issues)
179
+ - **Discussions**: [GitHub Discussions](https://github.com/yourusername/query_guard/discussions)
180
+ - **Docs**: See [INDEX.md](INDEX.md) for complete documentation
181
+
182
+ ---
183
+
184
+ ### Why QueryGuard?
185
+
186
+ Database migrations are a critical part of your deployment pipeline. A single unsafe migration can:
187
+ - Lock production tables for hours
188
+ - Cause deployment failures requiring emergency rollbacks
189
+ - Add months of technical debt to your schema
190
+
191
+ QueryGuard catches these issues in CI, before they reach production.
192
+
193
+ **Stop deploying database surprises.** Start using QueryGuard.
194
+ =======
195
+ QueryGuard fills a unique niche between traditional APMs and Rails profilers:
196
+
197
+ - **vs Datadog APM**: Datadog excels at distributed tracing, logs/metrics correlation, and infrastructure monitoring, but is generic across languages. QueryGuard is **deeply Rails and ActiveRecord-aware**, providing schema intelligence and query budgets that Datadog doesn't offer.
198
+
199
+ - **vs Skylight**: Skylight is an excellent Rails profiler with request timelines, deploy tracking, and background job support. QueryGuard complements this by adding **query budgets/SLOs per endpoint**, **schema-aware linting** (missing indexes, wide scans), and **first-class test/CI support** via RSpec matchers.
200
+
201
+ - **vs Grafana**: Grafana provides powerful multi-signal visualization and metricsโ†”traces workflows via exemplars, but isn't Rails-specific. QueryGuard offers **Rails-native tooling** that works in development, tests, and production with minimal configuration.
202
+
203
+ **QueryGuard's Focus**: SQL + Schema + Budget enforcement + Developer UX (console, tests, CI).
204
+
205
+ ## ๐Ÿ“ฆ Installation
206
+
207
+ Add to your Gemfile:
29
208
 
30
209
  ```ruby
31
- # config/initializers/query_guard.rb
210
+ gem "query_guard"
211
+ ```
212
+
213
+ Then run:
32
214
 
33
- # Configure QueryGuard settings
215
+ ```bash
216
+ bundle install
217
+ ```
218
+
219
+ ## โš™๏ธ Configuration
220
+
221
+ Create an initializer at `config/initializers/query_guard.rb`:
222
+
223
+ ```ruby
34
224
  QueryGuard.configure do |config|
35
225
  # Environments where QueryGuard should be active
36
- # By default: [:development, :test]
37
- config.enabled_environments = %i[development test]
226
+ config.enabled_environments = %i[development test production]
38
227
 
39
- # Maximum number of SQL queries allowed per request
40
- # Use nil to disable this limit
41
- config.max_queries_per_request = 100
228
+ # === Budget System (New in v2) ===
229
+ # Define query budgets/SLOs for specific endpoints or jobs
230
+
231
+ # Controller actions
232
+ config.budget.for("users#index", count: 10, duration_ms: 500)
233
+ config.budget.for("posts#show", count: 5, duration_ms: 200)
234
+ config.budget.for("admin/reports#dashboard", count: 50, duration_ms: 2000)
235
+
236
+ # Background jobs
237
+ config.budget.for_job("EmailJob", count: 20, duration_ms: 1000)
238
+ config.budget.for_job("DataExportJob", count: 100, duration_ms: 5000)
239
+
240
+ # Budget enforcement mode
241
+ config.budget.mode = :log # :log (warn only), :notify (callback), :raise (exception)
242
+
243
+ # Optional: callback for :notify mode
244
+ config.budget.on_violation = ->(key, violation) {
245
+ # Send to error tracker, metrics service, etc.
246
+ Honeybadger.notify("Budget violation", context: { key: key, violation: violation })
247
+ }
42
248
 
43
- # Maximum duration (milliseconds) for a single SQL query
44
- # Logs as a slow query if exceeded
249
+ # === Legacy Limits (Still Supported) ===
250
+ config.max_queries_per_request = 100
45
251
  config.max_duration_ms_per_query = 100.0
46
-
47
- # Whether to flag or block SELECT * statements
48
252
  config.block_select_star = true
49
-
50
- # Ignore certain SQL patterns (e.g., schema and transaction queries)
253
+
254
+ # Ignore certain SQL patterns
51
255
  config.ignored_sql = [
52
- /^PRAGMA /i, # SQLite schema queries
256
+ /^PRAGMA /i,
53
257
  /^BEGIN/i,
54
- /^COMMIT/i
258
+ /^COMMIT/i,
259
+ /^SHOW /i
55
260
  ]
56
261
 
57
- # Raise exception on violation instead of just logging
58
- config.raise_on_violation = false
262
+ # === Security Features ===
263
+ config.enable_security = true
264
+ config.detect_sql_injection = true
265
+ config.detect_unusual_query_pattern = true
266
+ config.detect_data_exfiltration = true
267
+ config.detect_mass_assignment = true
59
268
 
60
- # Prefix for log messages in Rails logs
269
+ # === Export Configuration ===
270
+ config.base_url = ENV["QUERY_GUARD_API_URL"]
271
+ config.api_key = ENV["QUERY_GUARD_API_KEY"]
272
+ config.project = "my_app"
273
+ config.env = Rails.env
274
+
275
+ # Logging
276
+ config.raise_on_violation = false # Set to true in CI
61
277
  config.log_prefix = "[QueryGuard]"
62
278
  end
63
279
  ```
280
+
281
+ ## ๐Ÿš€ Features
282
+
283
+ ### 1. Query Budgets & SLOs
284
+
285
+ Define query budgets for specific endpoints or background jobs:
286
+
287
+ ```ruby
288
+ # In config/initializers/query_guard.rb
289
+ QueryGuard.configure do |config|
290
+ # Set budgets for controller actions
291
+ config.budget.for("users#index", count: 10, duration_ms: 500)
292
+ config.budget.for("posts#show", count: 5, duration_ms: 200)
293
+
294
+ # Set budgets for background jobs
295
+ config.budget.for_job("EmailJob", count: 20)
296
+ config.budget.for_job("ReportJob", count: 100, duration_ms: 5000)
297
+
298
+ # Choose enforcement mode
299
+ config.budget.mode = :log # Options: :log, :notify, :raise
300
+ end
301
+ ```
302
+
303
+ **Enforcement Modes**:
304
+
305
+ - `:log` - Logs warnings when budgets are exceeded (default, safe for production)
306
+ - `:notify` - Calls a custom callback for integration with error trackers
307
+ - `:raise` - Raises an exception (useful in test/CI environments)
308
+
309
+ **With Callbacks**:
310
+
311
+ ```ruby
312
+ config.budget.mode = :notify
313
+ config.budget.on_violation = ->(key, violation) {
314
+ # Send to your monitoring service
315
+ Datadog::Statsd.new.increment("query_guard.budget.exceeded", tags: ["endpoint:#{key}"])
316
+
317
+ # Or send to error tracker
318
+ Sentry.capture_message("Budget exceeded", extra: { key: key, violation: violation })
319
+ }
320
+ ```
321
+
322
+ ### 2. Trace API (Console & Testing)
323
+
324
+ Manually trace query performance in any context:
325
+
326
+ ```ruby
327
+ # In Rails console or tests
328
+ result, report = QueryGuard.trace("load active users") do
329
+ User.where(active: true).limit(100).to_a
330
+ end
331
+
332
+ puts "Queries executed: #{report.query_count}"
333
+ puts "Total duration: #{report.total_duration_ms}ms"
334
+ puts "Violations: #{report.violations.inspect}"
335
+ puts "Queries:"
336
+ report.queries.each do |q|
337
+ puts " #{q[:duration_ms]}ms: #{q[:sql]}"
338
+ end
339
+ ```
340
+
341
+ **With Context**:
342
+
343
+ ```ruby
344
+ result, report = QueryGuard.trace("process batch", context: { batch_id: 123, user_id: 456 }) do
345
+ Batch.find(123).process!
346
+ end
347
+
348
+ # Context is included in the report for correlation
349
+ puts report.context # => { batch_id: 123, user_id: 456 }
350
+ ```
351
+
352
+ ### 3. RSpec Matchers
353
+
354
+ Test query performance in your specs:
355
+
356
+ ```ruby
357
+ require "query_guard/rspec"
358
+
359
+ RSpec.describe UsersController, type: :controller do
360
+ describe "GET #index" do
361
+ it "stays within query budget" do
362
+ expect {
363
+ get :index
364
+ }.to_not exceed_query_budget(count: 10, duration_ms: 500)
365
+ end
366
+
367
+ # Or use named budgets defined in config
368
+ it "respects users#index budget" do
369
+ expect {
370
+ get :index
371
+ }.to_not exceed_query_budget("users#index")
372
+ end
373
+ end
374
+ end
375
+ ```
376
+
377
+ **Helper Method**:
378
+
379
+ ```ruby
380
+ RSpec.describe "batch processing" do
381
+ it "processes batch efficiently" do
382
+ report = within_query_budget(count: 50, duration_ms: 2000) do
383
+ Batch.process_all
384
+ end
385
+
386
+ expect(report.query_count).to be < 50
387
+ end
388
+ end
389
+ ```
390
+
391
+ ### 4. SQL Fingerprinting & Statistics
392
+
393
+ Track query patterns across your application:
394
+
395
+ ```ruby
396
+ # In console or background job
397
+ QueryGuard::Fingerprint.record("SELECT * FROM users WHERE id = 123", 45.2)
398
+ QueryGuard::Fingerprint.record("SELECT * FROM users WHERE id = 456", 32.1)
399
+
400
+ # Get stats for a specific fingerprint
401
+ fp = QueryGuard::Fingerprint.generate("SELECT * FROM users WHERE id = ?")
402
+ stats = QueryGuard::Fingerprint.stats_for(fp)
403
+
404
+ puts stats[:count] # => 2
405
+ puts stats[:total_duration_ms] # => 77.3
406
+ puts stats[:min_duration_ms] # => 32.1
407
+ puts stats[:max_duration_ms] # => 45.2
408
+ puts stats[:first_seen_at]
409
+ puts stats[:last_seen_at]
410
+
411
+ # Get top queries by various metrics
412
+ QueryGuard::Fingerprint.top_by_count(10) # Most frequently executed
413
+ QueryGuard::Fingerprint.top_by_duration(10) # Highest total time
414
+ QueryGuard::Fingerprint.top_by_avg_duration(10) # Slowest on average
415
+ ```
416
+
417
+ **Fingerprinting normalizes SQL**:
418
+ - Removes string and numeric literals
419
+ - Collapses whitespace
420
+ - Normalizes `IN (...)` lists
421
+ - Returns consistent SHA1 hash
422
+
423
+ ```ruby
424
+ # These all produce the same fingerprint:
425
+ QueryGuard::Fingerprint.generate("SELECT * FROM users WHERE id = 1")
426
+ QueryGuard::Fingerprint.generate("SELECT * FROM users WHERE id = 999")
427
+ QueryGuard::Fingerprint.generate("SELECT * FROM users WHERE id = 42")
428
+ # All normalize to: "select * from users where id = ?"
429
+ ```
430
+
431
+ ### 5. Security Features (Existing)
432
+
433
+ QueryGuard includes built-in security detection:
434
+
435
+ - **SQL Injection Detection**: Flags suspicious patterns (OR 1=1, UNION SELECT, etc.)
436
+ - **Unusual Query Patterns**: Rate limiting per actor (IP/user)
437
+ - **Data Exfiltration**: Monitors large responses and suspicious endpoints
438
+ - **Mass Assignment**: Detects unpermitted parameters
439
+
440
+ ```ruby
441
+ config.enable_security = true
442
+ config.detect_sql_injection = true
443
+ config.detect_unusual_query_pattern = true
444
+ config.max_queries_per_minute_per_actor = 300
445
+
446
+ # Custom actor resolver
447
+ config.actor_resolver = ->(env) {
448
+ env["warden"].user&.id || env["action_dispatch.remote_ip"]
449
+ }
450
+ ```
451
+
452
+ ### 6. Export & Monitoring
453
+
454
+ Export query data to external services:
455
+
456
+ ```ruby
457
+ config.base_url = "https://your-monitoring-service.com"
458
+ config.api_key = ENV["MONITORING_API_KEY"]
459
+ config.project = "my_rails_app"
460
+ config.env = Rails.env
461
+ config.export_mode = :async # Don't block requests
462
+ ```
463
+
464
+ Exported data includes:
465
+ - Query statements with fingerprints
466
+ - Durations and timestamps
467
+ - Budget violations
468
+ - Security threat events
469
+ - Request context (controller, action, user, etc.)
470
+
471
+ ## ๐Ÿ“Š Use Cases
472
+
473
+ ### Development
474
+
475
+ ```ruby
476
+ # In Rails console
477
+ result, report = QueryGuard.trace("diagnose N+1") do
478
+ Post.limit(10).each { |post| post.comments.to_a }
479
+ end
480
+
481
+ puts "Queries: #{report.query_count}" # Spot N+1 problems immediately
482
+ report.queries.each { |q| puts q[:sql] }
483
+ ```
484
+
485
+ ### Testing (CI)
486
+
487
+ ```ruby
488
+ # spec/support/query_guard.rb
489
+ RSpec.configure do |config|
490
+ config.around(:each, :query_budget) do |example|
491
+ metadata = example.metadata
492
+ budget = metadata[:query_budget]
493
+
494
+ expect {
495
+ example.run
496
+ }.to_not exceed_query_budget(**budget)
497
+ end
498
+ end
499
+
500
+ # spec/controllers/users_controller_spec.rb
501
+ RSpec.describe UsersController do
502
+ describe "GET #index", :query_budget, query_budget: { count: 5, duration_ms: 200 } do
503
+ it "loads users" do
504
+ get :index
505
+ expect(response).to be_successful
506
+ end
507
+ end
508
+ end
509
+ ```
510
+
511
+ ### Production Monitoring
512
+
513
+ ```ruby
514
+ # config/initializers/query_guard.rb
515
+ QueryGuard.configure do |config|
516
+ config.enabled_environments = [:production]
517
+ config.budget.mode = :notify
518
+
519
+ config.budget.for("api/v1/users#index", count: 10, duration_ms: 100)
520
+ config.budget.for("api/v1/posts#feed", count: 15, duration_ms: 150)
521
+
522
+ config.budget.on_violation = ->(key, violation) {
523
+ # Alert when budgets exceeded in production
524
+ Datadog::Statsd.new.increment("query.budget.exceeded", tags: [
525
+ "endpoint:#{key}",
526
+ "type:#{violation[:type]}"
527
+ ])
528
+ }
529
+ end
530
+ ```
531
+
532
+ ## ๐Ÿ”ง API Reference
533
+
534
+ ### QueryGuard.configure
535
+
536
+ Configure QueryGuard settings. See Configuration section above.
537
+
538
+ ### QueryGuard.trace(label, context: {}, &block)
539
+
540
+ Trace a block of code and capture query statistics.
541
+
542
+ **Arguments**:
543
+ - `label` (String): Descriptive label for the trace
544
+ - `context` (Hash): Additional context (user_id, batch_id, etc.)
545
+ - `&block`: Code to trace
546
+
547
+ **Returns**: `[result, report]` tuple
548
+
549
+ ### QueryGuard::Budget
550
+
551
+ **Methods**:
552
+ - `.for(key, **limits)`: Define budget for controller action
553
+ - `.for_job(job, **limits)`: Define budget for background job
554
+ - `.mode=`: Set enforcement mode (`:log`, `:notify`, `:raise`)
555
+ - `.on_violation=`: Set callback for `:notify` mode
556
+
557
+ ### QueryGuard::Fingerprint
558
+
559
+ **Methods**:
560
+ - `.generate(sql)`: Generate fingerprint for SQL
561
+ - `.normalize(sql)`: Normalize SQL query
562
+ - `.record(sql, duration_ms)`: Record query execution
563
+ - `.stats_for(fingerprint)`: Get stats for fingerprint
564
+ - `.top_by_count(limit)`: Top queries by count
565
+ - `.top_by_duration(limit)`: Top queries by total duration
566
+ - `.top_by_avg_duration(limit)`: Top queries by average duration
567
+ - `.reset!`: Clear all stats
568
+
569
+ ### RSpec Matchers
570
+
571
+ ```ruby
572
+ require "query_guard/rspec"
573
+
574
+ expect { code }.to_not exceed_query_budget(count: 10)
575
+ expect { code }.to_not exceed_query_budget(count: 10, duration_ms: 500)
576
+ expect { code }.to_not exceed_query_budget("users#index")
577
+
578
+ report = within_query_budget(count: 10) { code }
579
+ ```
580
+
581
+ ## ๐Ÿงช Testing
582
+
583
+ Run the test suite:
584
+
585
+ ```bash
586
+ bundle exec rspec
587
+ ```
588
+
589
+ ## ๐Ÿ“„ License
590
+
591
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
592
+
593
+ ## ๐Ÿ™ Contributing
594
+
595
+ Contributions welcome! Please:
596
+
597
+ 1. Fork the repository
598
+ 2. Create a feature branch
599
+ 3. Write tests for your changes
600
+ 4. Submit a pull request
601
+
602
+ ## ๐Ÿ“ฎ Support
603
+
604
+ - Issues: [GitHub Issues](https://github.com/Chitradevi36/query_guard/issues)
605
+ - Documentation: This README
606
+ - Example Rails App: [Coming soon]
607
+
608
+ ---
609
+
610
+ Built with โค๏ธ to make Rails query performance monitoring delightful.
611
+ >>>>>>> 2fd3b3b87eaac58f779bc220c6bff2389c1b1c7b
612
+
data/exe/queryguard ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Handle --version early (before loading Rails)
5
+ if ARGV.first == '--version' || ARGV.first == '-v'
6
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
7
+ require 'query_guard/version'
8
+ puts "QueryGuard v#{QueryGuard::VERSION}"
9
+ exit 0
10
+ end
11
+
12
+ # Now load everything else
13
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
14
+ require 'query_guard'
15
+ require 'query_guard/cli'
16
+
17
+ begin
18
+ QueryGuard::CLI.run(ARGV)
19
+ rescue => e
20
+ puts "Error: #{e.message}"
21
+ puts e.backtrace.join("\n") if ENV['DEBUG']
22
+ exit 1
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require "active_support/notifications"
3
+
4
+ module QueryGuard
5
+ module ActionControllerSubscriber
6
+ EVENT = "unpermitted_parameters.action_controller"
7
+
8
+ def self.install!(config)
9
+ return if @installed
10
+ @subscriber = ActiveSupport::Notifications.subscribe(EVENT) do |_name, _start, _finish, _id, payload|
11
+ stats = Thread.current[:query_guard_stats]
12
+ next unless stats
13
+ next unless config.enable_security && config.detect_mass_assignment
14
+
15
+ keys = Array(payload[:keys]).map(&:to_s)
16
+ sensitive = keys & Array(config.sensitive_param_keys)
17
+
18
+ stats[:violations] << {
19
+ type: :mass_assignment_unpermitted_params,
20
+ keys: keys.take(50),
21
+ sensitive_keys: sensitive.take(50)
22
+ }
23
+ end
24
+ @installed = true
25
+ end
26
+ end
27
+ end