LibreDB Studio includes enterprise-grade query optimization features to prevent system freezes and provide performance insights for DBAs, data engineers, and developers.
- Query Pagination System
- Silent Auto-Limiting
- Load More Functionality
- Query EXPLAIN Integration
- Performance Insights
- Architecture
All SELECT queries are automatically paginated to prevent browser freezes when dealing with large datasets. This is handled transparently without interrupting the user workflow.
| Constant | Value | Description |
|---|---|---|
DEFAULT_QUERY_LIMIT |
500 | Default rows per page |
MAX_UNLIMITED_ROWS |
100,000 | Maximum rows for "Load All" |
- User executes a SELECT query
- System automatically adds
LIMIT 500 OFFSET 0if no LIMIT exists - If user already specified a LIMIT, it's preserved (no override)
- Results display with pagination metadata
Instead of showing warning popups for large datasets, LibreDB Studio silently limits results to 500 rows. This provides:
- Uninterrupted workflow - No confirmation dialogs
- Safe defaults - System never freezes
- User control - Load More when needed
When auto-limiting is applied:
┌─────────────────────────────────────────────────────┐
│ Results 500 rows │ AUTO-LIMITED │ Load More │
└─────────────────────────────────────────────────────┘
- AUTO-LIMITED badge - Shows when the system added a LIMIT
- Row count - Displays actual returned rows
- Load More button - Appears when more data is available
Location: src/lib/db/utils/query-limiter.ts
import { analyzeQuery, applyQueryLimit } from '@/lib/db/utils/query-limiter';
// Analyze a query
const info = analyzeQuery('SELECT * FROM users WHERE active = true');
// Returns: { type: 'SELECT', hasLimit: false, hasOffset: false, ... }
// Apply limit to query
const result = applyQueryLimit('SELECT * FROM users', 500, 0);
// Returns: { sql: 'SELECT * FROM users LIMIT 500 OFFSET 0', wasLimited: true, ... }| Query Type | Auto-Limit Applied |
|---|---|
| SELECT | Yes |
| SELECT with LIMIT | No (preserved) |
| SELECT with UNION | Yes (wrapped) |
| SELECT with CTE | Yes |
| INSERT/UPDATE/DELETE | No |
| DDL (CREATE, ALTER) | No |
- Execute query → 500 rows displayed
- Click "Load More" → Next 500 rows appended
- Repeat until all data loaded or satisfied
// Initial query
POST /api/db/query
{
"connection": {...},
"sql": "SELECT * FROM orders",
"options": { "limit": 500, "offset": 0 }
}
// Load More
POST /api/db/query
{
"connection": {...},
"sql": "SELECT * FROM orders",
"options": { "limit": 500, "offset": 500 }
}{
"rows": [...],
"fields": ["id", "name", ...],
"rowCount": 500,
"executionTime": 45,
"pagination": {
"limit": 500,
"offset": 0,
"hasMore": true, // More rows available
"totalReturned": 500,
"wasLimited": true // System added LIMIT
}
}For advanced users, a "Load All" button triggers an unlimited query (max 100K rows) with a confirmation dialog:
┌──────────────────────────────────────┐
│ Load all results? │
│ │
│ This may slow down your browser. │
│ Max 100K rows will be loaded. │
│ │
│ [Cancel] [Load All] │
└──────────────────────────────────────┘
Every SELECT query automatically runs EXPLAIN in the background (parallel execution). This provides instant performance insights without user action.
| Database | EXPLAIN Format |
|---|---|
| PostgreSQL | EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) |
| MySQL | EXPLAIN FORMAT=JSON |
| SQLite | Not supported (no JSON format) |
User executes: SELECT * FROM orders WHERE status = 'pending'
┌─────────────────────────────────────────────────────────────┐
│ Parallel Execution │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ Main Query │ │ Background EXPLAIN │ │
│ │ (with LIMIT) │ │ (no LIMIT, ANALYZE) │ │
│ └────────┬─────────┘ └────────────┬─────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ Results Tab Explain Tab │
└─────────────────────────────────────────────────────────────┘
Click the "Explain" tab in the results panel to view:
- Performance Insights
- Execution Plan Tree
- Raw JSON
Location: src/components/VisualExplain.tsx
The VisualExplain component analyzes execution plans and provides actionable insights.
| Warning | Trigger | Severity |
|---|---|---|
| Sequential Scan | Seq Scan on >10K rows | Warning |
| Estimate Mismatch | Actual/Planned ratio >10x | Info |
| Expensive Sort | Sort operation >100ms | Warning |
| High Loop Count | Nested Loop >1000 iterations | Critical |
Sequential Scan Warning:
⚠️ Sequential Scan
Full table scan on "orders" (15.2K rows). Consider adding an index.
N+1 Problem Detection:
🔴 High Loop Count
Nested loop executed 5.2K times. This could indicate an N+1 problem.
Estimate Mismatch:
ℹ️ Estimate Mismatch
Expected 100 rows, got 15.2K. Statistics may be outdated.
| Metric | Description |
|---|---|
| Cache Hit Rate | Buffer cache efficiency (>95% is good) |
| Operations | Number of plan nodes |
| Execution Time | Total query time |
Interactive, collapsible execution plan with:
- Node type icons (Seq Scan, Index Scan, Join, Sort, etc.)
- Time bars showing relative cost
- Row counts and costs
- Filter conditions
- Index usage
▼ Limit (0.12ms, 500 rows)
└─▼ Sort (45.2ms, 500 rows)
└─▼ Seq Scan on orders (120.5ms, 15.2K rows)
Filter: status = 'pending'
src/
├── lib/db/utils/
│ └── query-limiter.ts # Query parsing and LIMIT injection
├── app/api/db/
│ └── query/route.ts # Query API with pagination
├── components/
│ ├── Studio.tsx # Query execution orchestration
│ ├── ResultsGrid.tsx # Results display with Load More
│ └── VisualExplain.tsx # EXPLAIN visualization
└── lib/types.ts # QueryPagination interface
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Dashboard │────▶│ /api/db/query │────▶│ DB Provider │
│ │ │ │ │ │
│ executeQuery│ │ - Parse query │ │ - Execute SQL │
│ │ │ - Apply LIMIT │ │ - Return rows │
└─────────────┘ │ - Add pagination │ └─────────────────┘
│ └──────────────────┘
│
▼
┌─────────────┐ ┌──────────────────┐
│ ResultsGrid │ │ VisualExplain │
│ │ │ │
│ - Show rows │ │ - Parse plan │
│ - Load More │ │ - Show warnings │
│ - Stats bar │ │ - Render tree │
└─────────────┘ └──────────────────┘
// Query pagination metadata
interface QueryPagination {
limit: number;
offset: number;
hasMore: boolean;
totalReturned: number;
wasLimited: boolean;
}
// Query result with pagination
interface QueryResult {
rows: any[];
fields: string[];
rowCount: number;
executionTime: number;
explainPlan?: any;
pagination?: QueryPagination;
}
// Query tab state
interface QueryTab {
id: string;
name: string;
query: string;
result: QueryResult | null;
explainPlan?: any;
currentOffset?: number;
isLoadingMore?: boolean;
allRows?: any[];
}- Use WHERE clauses - Filter data at the database level
- Add LIMIT when known - If you only need 10 rows, add
LIMIT 10 - Check Explain tab - Review performance before running in production
- Use indexes - Add indexes for frequently filtered columns
- Never bypass the limiter - Always use the query API
- Handle pagination - Support
hasMorein custom implementations - Parse EXPLAIN - Use the analyzePlan function for custom analysis
Currently, limits are hardcoded. Future versions may support configuration:
// Future: .env configuration
QUERY_DEFAULT_LIMIT=500
QUERY_MAX_UNLIMITED=100000
EXPLAIN_AUTO_RUN=true| Version | Changes |
|---|---|
| 0.7.0 | Initial query optimization system |
| 0.7.1 | Removed Large Dataset popup (silent limiting) |
| 0.7.1 | Added automatic background EXPLAIN |
| 0.7.1 | Added VisualExplain with Performance Insights |