QueryConsole
A Rails engine that provides a secure, mountable web interface for running read-only SQL queries against your application's database.
Features
Core Features (v0.1.0)
- 🔒 Security First: Read-only queries enforced at multiple levels
- 🚦 Environment Gating: Disabled by default in production
- 🔑 Flexible Authorization: Integrate with your existing auth system
- 📊 Modern UI: Clean, responsive interface with query history
- 📝 Audit Logging: All queries logged with actor information
- ⚡ Resource Protection: Configurable row limits and query timeouts
- 💾 Client-Side History: Query history stored in browser localStorage
- ⚡ Hotwire-Powered: Uses Turbo Frames and Stimulus for smooth, SPA-like experience
- 🎨 Zero Build Step: CDN-hosted Hotwire, no asset compilation needed
New in v0.2.0 🚀
- 📊 EXPLAIN Query Plans: Analyze query execution plans for performance debugging
- 🗂️ Schema Explorer: Browse tables, columns, types with quick actions
- 💾 Saved Queries: Save, organize, import/export your important queries (client-side)
- 🎨 Tabbed UI: Switch between History and Schema views seamlessly
- 🔍 Quick Actions: Generate queries from schema, copy names, insert WHERE clauses
Security Features
QueryConsole implements multiple layers of security:
- Environment Gating: Only enabled in configured environments (development by default)
- Authorization Hook: Requires explicit authorization configuration
- SQL Validation: Only SELECT and WITH (CTE) queries allowed
- Keyword Blocking: Blocks all write operations (UPDATE, DELETE, INSERT, DROP, etc.)
- Statement Isolation: Prevents multiple statement execution
- Row Limiting: Automatic result limiting to prevent resource exhaustion
- Query Timeout: Configurable timeout to prevent long-running queries
- Audit Trail: All queries logged with structured data
Installation
Add this line to your application's Gemfile:
gem 'query_console'And then execute:
bundle install
rails generate query_console:installRequirements:
- Ruby 3.1+
- Rails 7.0+
- Works with Rails 8+
- Hotwire (Turbo Rails + Stimulus) - automatically included
Configuration
The generator creates config/initializers/query_console.rb. You MUST configure the authorization hook:
QueryConsole.configure do |config|
# Required: Set up authorization
config.authorize = ->(controller) {
controller.current_user&.admin?
}
# Track who runs queries
config.current_actor = ->(controller) {
controller.current_user&.email || "anonymous"
}
# Optional: Enable in additional environments (use with caution!)
# config.enabled_environments = %w[development staging]
# Optional: Adjust limits
# config.max_rows = 1000
# config.timeout_ms = 5000
# v0.2.0+ Features
# EXPLAIN feature (default: enabled)
# config.enable_explain = true
# config.enable_explain_analyze = false # Disabled by default for safety
# Schema Explorer (default: enabled)
# config.schema_explorer = true
# config.schema_cache_seconds = 60
# config.schema_table_denylist = ["schema_migrations", "ar_internal_metadata"]
# config.schema_allowlist = [] # Optional: whitelist specific tables
endAuthorization Examples
With Devise
config.authorize = ->(controller) {
controller.current_user&.admin?
}With HTTP Basic Auth
config.authorize = ->(controller) {
controller.authenticate_or_request_with_http_basic do |username, password|
username == "admin" && password == Rails.application.credentials.query_console_password
end
}For Development (NOT for production!)
config.authorize = ->(_controller) { true }Configuration Options
| Option | Default | Description |
|---|---|---|
enabled_environments |
["development"] |
Environments where console is accessible |
authorize |
nil |
Lambda/proc that receives controller and returns true/false |
current_actor |
->(_) { "unknown" } |
Lambda/proc to identify who's running queries |
max_rows |
500 |
Maximum rows returned per query |
timeout_ms |
3000 |
Query timeout in milliseconds |
forbidden_keywords |
See code | SQL keywords that are blocked |
allowed_starts_with |
["select", "with"] |
Allowed query starting keywords |
Mounting
Add to your config/routes.rb:
Rails.application.routes.draw do
mount QueryConsole::Engine, at: "/query_console"
endThen visit: http://localhost:3000/query_console
Usage
Running Queries
- Enter your SELECT query in the editor
- Click "Run Query" or press Ctrl/Cmd+Enter
- View results in the table below
- Query is automatically saved to history
Query History
- Stored locally in your browser (not on server)
- Click any history item to load it into the editor
- Stores up to 20 recent queries
- Clear history with the "Clear" button
Allowed Queries
✅ Allowed:
SELECT * FROM usersSELECT id, name FROM users WHERE active = trueWITH active_users AS (SELECT * FROM users WHERE active = true) SELECT * FROM active_users- Queries with JOINs, ORDER BY, GROUP BY, etc.
❌ Blocked:
UPDATE users SET name = 'test'DELETE FROM usersINSERT INTO users VALUES (...)DROP TABLE users-
SELECT * FROM users; DELETE FROM users(multiple statements) - Any query containing forbidden keywords
Example Queries
-- List recent users
SELECT id, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT 10;
-- Count by status
SELECT status, COUNT(*) as count
FROM orders
GROUP BY status;
-- Join with aggregation
SELECT u.email, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.email
ORDER BY order_count DESC
LIMIT 20;
-- Common Table Expression (CTE)
WITH active_users AS (
SELECT * FROM users WHERE active = true
)
SELECT * FROM active_users WHERE created_at > DATE('now', '-30 days');Security Considerations
Environment Configuration
⚠️ Important: QueryConsole is disabled by default in production. To enable in non-development environments:
config.enabled_environments = %w[development staging]Never enable in production without:
- Strong authentication
- Network restrictions (VPN, IP whitelist)
- Audit monitoring
- Database read-only user (recommended)
Authorization
The authorization hook is called on every request. Ensure it:
- Returns
falseornilto deny access - Is performant (avoid N+1 queries)
- Handles edge cases (logged out users, etc.)
Audit Logs
All queries are logged to Rails.logger.info with:
{
component: "query_console",
actor: "user@example.com",
sql: "SELECT * FROM users LIMIT 10",
duration_ms: 45.2,
rows: 10,
status: "ok", # or "error"
truncated: false
}Monitor these logs for:
- Unusual query patterns
- Unauthorized access attempts
- Performance issues
- Data access patterns
Database Permissions
For production environments, consider using a dedicated read-only database user:
# config/database.yml
production_readonly:
<<: *production
username: readonly_user
password: <%= ENV['READONLY_DB_PASSWORD'] %>
# In your initializer
QueryConsole.configure do |config|
config.database_config = :production_readonly
endDevelopment
Testing Without a Rails App
You can test the gem in isolation without needing a full Rails application:
Option 1: Run automated tests
cd query_console
bundle install
bundle exec rspecOption 2: Start the test server
cd query_console
bundle install
./bin/test_serverThen visit: http://localhost:9292/query_console
The test server includes sample data and is pre-configured for easy testing.
See TESTING.md for detailed testing instructions.
Test Coverage
The test suite includes:
- SQL validator specs (security rules)
- SQL limiter specs (result limiting)
- Runner specs (integration tests)
- Controller specs (authorization & routing)
Frontend Technology Stack
QueryConsole uses Hotwire (Turbo + Stimulus), the modern Rails-native frontend framework:
What's Included
- Turbo Frames: Query results update without page reloads (SPA-like experience)
- Stimulus Controllers: Organized JavaScript for collapsible sections, history, and editor
- CDN Delivery: Hotwire loaded from CDN (no asset compilation needed)
- Zero Build Step: No webpack, esbuild, or other bundlers required
Architecture
Frontend Stack
├── HTML: ERB Templates
├── CSS: Vanilla CSS (inline)
├── JavaScript:
│ ├── Turbo Frames (results updates)
│ └── Stimulus Controllers
│ ├── collapsible_controller (section toggling)
│ ├── history_controller (localStorage management)
│ └── editor_controller (query execution)
└── Storage: localStorage API
Benefits
✅ No Build Step: Works out of the box, no compilation needed
✅ Rails-Native: Standard Rails 7+ approach
✅ Lightweight: ~50KB total (vs React's 200KB+)
✅ Fast: No page reloads, instant interactions
✅ Progressive: Degrades gracefully without JavaScript
Why Hotwire?
- Rails Standard: Default frontend stack for Rails 7+
- Simple: Fewer moving parts than SPA frameworks
- Productive: Write less JavaScript, more HTML
- Modern: All the benefits of SPAs without the complexity
- Maintainable: Standard Rails patterns throughout
Troubleshooting
Console returns 404
Possible causes:
- Environment not in
enabled_environments - Authorization hook returns
false - Authorization hook not configured (defaults to deny)
Solution: Check your initializer configuration.
Query times out
Causes:
- Query is too complex
- Database is slow
- Timeout setting too aggressive
Solutions:
- Increase
timeout_ms - Optimize query
- Add indexes to database
"Multiple statements" error
Cause: Query contains semicolon (;) in the middle
Solution: Remove extra semicolons. Only one trailing semicolon is allowed.
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-feature) - Write tests for your changes
- Ensure tests pass (
bundle exec rspec) - Commit your changes (
git commit -am 'Add feature') - Push to the branch (
git push origin feature/my-feature) - Create a Pull Request
License
The gem is available as open source under the terms of the MIT License.
Credits
Created by Johnson Gnanasekar
Changelog
0.1.0 (Initial Release)
- Basic query console with read-only enforcement
- Environment gating and authorization hooks
- SQL validation and row limiting
- Query timeout protection
- Client-side history with localStorage
- Comprehensive test suite
- Audit logging