QueryConsole
A Rails engine that provides a secure, mountable web interface for running SQL queries against your application's database. Read-only by default with optional DML support.
Modern, responsive SQL query interface with schema explorer, query management, and real-time execution
Table of Contents
- Features
- Screenshots
- Security Features
- Installation
- Configuration
- Usage
- Security Considerations
- Development
- Troubleshooting
- Contributing
- Changelog
Features
Security & Control
- đ Security First: Read-only by default with multi-layer enforcement
- đĻ Environment Gating: Disabled by default in production
- đ Flexible Authorization: Integrate with your existing auth system
- ⥠Resource Protection: Configurable row limits and query timeouts
- đ Comprehensive Audit Logging: All queries logged with actor information and metadata
- đ Optional DML Support: Enable INSERT/UPDATE/DELETE with confirmation dialogs
Query Execution
- đ EXPLAIN Query Plans: Analyze query execution plans for performance debugging
- â Smart Validation: SQL validation with keyword blocking and statement isolation
- đ¯ Accurate Results: Proper row counts for both SELECT and DML operations
- âąī¸ Query Timeout: Configurable timeout to prevent long-running queries
User Interface
- đ Modern UI: Clean, responsive interface with real-time updates
- đī¸ Schema Explorer: Browse tables, columns, types with quick actions
- đž Query Management: Save, organize, import/export queries (client-side)
- đ Query History: Client-side history stored in browser localStorage
- đ¨ Tabbed Navigation: Switch between History, Schema, and Saved Queries seamlessly
- đ Quick Actions: Generate queries from schema, copy names, insert WHERE clauses
- ⥠Hotwire-Powered: Turbo Frames and Stimulus for smooth, SPA-like experience
- đ¨ Zero Build Step: CDN-hosted dependencies, no asset compilation needed
Screenshots
Query Execution with Results
Execute SQL queries with real-time results, execution time, and row counts displayed in a clean, scrollable table
Schema Explorer
Browse database tables, columns with data types, nullable status, and quick-action buttons (Insert, WHERE, Copy Table Name)
DML Operations with Safety Features
DML operations show "Data Modified" banner, accurate "Rows Affected" count, and permanent change confirmation. A browser confirmation dialog appears before execution (not shown - browser native UI).
Query History
Access recent queries with timestamps - click any query to load it into the editor instantly
Saved Queries Management
Save important queries with names and tags, then load, export, or import them with one click
Security Features
QueryConsole implements multiple layers of security:
- Environment Gating: Only enabled in configured environments (development by default)
- Authorization Hook: Requires explicit authorization configuration
- Read-Only by Default: Only SELECT and WITH (CTE) queries allowed by default
- Optional DML with Safeguards: INSERT/UPDATE/DELETE available when explicitly enabled, with mandatory user confirmation dialogs
- Keyword Blocking: Always blocks DDL operations (DROP, ALTER, CREATE, TRUNCATE, 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
- Comprehensive Audit Trail: All queries logged with actor, query type, and execution metadata
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
# Advanced 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 |
enable_dml |
false |
Enable DML queries (INSERT, UPDATE, DELETE) |
enable_explain |
true |
Enable EXPLAIN query plans |
enable_explain_analyze |
false |
Enable EXPLAIN ANALYZE (use with caution) |
schema_explorer |
true |
Enable schema browser |
schema_cache_seconds |
60 |
Schema cache duration in seconds |
DML (Data Manipulation Language) Support
By default, Query Console is read-only. To enable DML operations (INSERT, UPDATE, DELETE):
QueryConsole.configure do |config|
config.enable_dml = true
# Recommended: Restrict to specific environments
config.enabled_environments = ["development", "staging"]
endImportant Security Notes
- DML is disabled by default for safety
- When enabled, INSERT, UPDATE, DELETE, and MERGE queries are permitted
- All DML operations are logged with actor information and query type
- No transaction support - queries auto-commit immediately
- Consider additional application-level authorization for production use
What's Still Blocked
Even with DML enabled, these operations remain forbidden:
-
DROP,ALTER,CREATE(schema changes) -
TRUNCATE(bulk deletion) -
GRANT,REVOKE(permission changes) -
EXECUTE,EXEC(stored procedures) -
TRANSACTION,COMMIT,ROLLBACK(manual transaction control) - System procedures (
sp_,xp_)
UI Behavior with DML
When DML is enabled and a DML query is detected:
- Before execution: A confirmation dialog appears with a clear warning about permanent data modifications
- User must explicitly confirm to proceed (can click "Cancel" to abort)
- After execution: An informational banner shows: "âšī¸ Data Modified: This query has modified the database"
- Rows Affected count is displayed (e.g., "3 row(s) affected") showing how many rows were inserted/updated/deleted
- The security banner reflects DML status
- All changes are permanent and logged
Database Support
DML operations work on all supported databases:
- SQLite: INSERT, UPDATE, DELETE
- PostgreSQL: INSERT, UPDATE, DELETE, MERGE (via INSERT ... ON CONFLICT)
- MySQL: INSERT, UPDATE, DELETE, REPLACE
Enhanced Audit Logging
DML queries are logged with additional metadata:
{
component: "query_console",
actor: "user@example.com",
sql: "UPDATE users SET active = true WHERE id = 123",
duration_ms: 12.5,
rows: 1,
status: "ok",
query_type: "UPDATE", # NEW: Query type classification
is_dml: true # NEW: DML flag
}Example DML Queries
-- Insert a new record
INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com');
-- Update existing records
UPDATE users SET active = true WHERE id = 123;
-- Delete specific records
DELETE FROM sessions WHERE expires_at < NOW();
-- PostgreSQL upsert
INSERT INTO settings (key, value) VALUES ('theme', 'dark')
ON CONFLICT (key) DO UPDATE SET value = 'dark';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 (by default):
-
UPDATE users SET name = 'test'(unlessenable_dml = true) -
DELETE FROM users(unlessenable_dml = true) -
INSERT INTO users VALUES (...)(unlessenable_dml = true) -
DROP TABLE users(always blocked) -
TRUNCATE TABLE users(always blocked) -
SELECT * FROM users; DELETE FROM users(multiple statements always blocked) - Any query containing forbidden keywords
Note: With config.enable_dml = true, INSERT, UPDATE, DELETE, and MERGE queries become allowed.
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
See CHANGELOG.md for detailed version history.
Recent Updates
Latest (DML Support)
- ⨠Optional DML Support: INSERT/UPDATE/DELETE with mandatory confirmation dialogs
- đ¯ Accurate Row Counts: Proper affected rows tracking for DML operations
- đ Enhanced Security: Pre-execution confirmation with detailed warnings
- đ Enhanced Audit Logging: Query type classification and DML flags
- đī¸ Multi-Database Support: SQLite, PostgreSQL, MySQL compatibility
v0.2.0 (January 2026)
- đ EXPLAIN Plans: Query execution plan analysis
- đī¸ Schema Explorer: Interactive table/column browser with quick actions
- đž Saved Queries: Client-side query management with import/export
- đ¨ Modern UI: Tabbed navigation and collapsible sections
- đ Quick Actions: Generate queries from schema explorer
v0.1.0 (Initial Release)
- đ Read-only query console with security enforcement
- đĻ Environment gating and authorization hooks
- â SQL validation and row limiting
- âąī¸ Query timeout protection
- đ Client-side history with localStorage
- â Comprehensive test suite and audit logging