QueryPerformance.ai.ts140 lines · main
| 1 | import { QueryPerformanceRow } from './QueryPerformance.types' |
| 2 | import type { QueryPlanRow } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.types' |
| 3 | import type { ClassifiedQuery } from '@/components/interfaces/QueryInsights/QueryInsightsHealth/QueryInsightsHealth.types' |
| 4 | import { |
| 5 | getColumnName, |
| 6 | getTableName, |
| 7 | } from '@/components/interfaces/QueryInsights/QueryInsightsTable/QueryInsightsTable.utils' |
| 8 | |
| 9 | export interface QueryExplanationPrompt { |
| 10 | query: string |
| 11 | prompt: string |
| 12 | } |
| 13 | |
| 14 | export function buildQueryExplanationPrompt( |
| 15 | selectedRow: QueryPerformanceRow |
| 16 | ): QueryExplanationPrompt { |
| 17 | const metadata = [ |
| 18 | `Total Time: ${selectedRow.total_time.toLocaleString()}`, |
| 19 | `Calls: ${selectedRow.calls.toLocaleString()}`, |
| 20 | `Mean Time: ${selectedRow.mean_time.toLocaleString()}`, |
| 21 | `Max Time: ${selectedRow.max_time.toLocaleString()}`, |
| 22 | `Min Time: ${selectedRow.min_time.toLocaleString()}`, |
| 23 | `Rows Read: ${selectedRow.rows_read.toLocaleString()}`, |
| 24 | `Cache Hit Rate: ${selectedRow.cache_hit_rate.toLocaleString()}%`, |
| 25 | `Role: ${selectedRow.rolname}`, |
| 26 | ].join('\n') |
| 27 | |
| 28 | let additionalContext = '' |
| 29 | if (selectedRow.index_advisor_result) { |
| 30 | const indexResult = selectedRow.index_advisor_result |
| 31 | |
| 32 | if (indexResult.index_statements.length > 0) { |
| 33 | additionalContext = `\n\nIndex Advisor Recommendations:\n${indexResult.index_statements.join('\n')}\n\nCost Analysis:\n- Startup Cost Before: ${indexResult.startup_cost_before}\n- Startup Cost After: ${indexResult.startup_cost_after}\n- Total Cost Before: ${indexResult.total_cost_before}\n- Total Cost After: ${indexResult.total_cost_after}` |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | const prompt = `Analyze this database query and provide a brief, concise explanation: |
| 38 | |
| 39 | **Performance Metrics:** |
| 40 | ${metadata} |
| 41 | |
| 42 | ${additionalContext} |
| 43 | |
| 44 | Provide a short response covering: |
| 45 | 1. What the query does (1-2 sentences) |
| 46 | 2. Performance assessment (good/concerning and why, keep it brief 2-3 sentences) |
| 47 | 3. Actionable optimization suggestions (if any, keep it brief 2-3 sentences) |
| 48 | |
| 49 | Keep your response concise and focused on actionable insights. We can continue the conversation if needed to get more details.` |
| 50 | |
| 51 | return { |
| 52 | query: selectedRow.query, |
| 53 | prompt, |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | export function buildQueryInsightFixPrompt(item: ClassifiedQuery): QueryExplanationPrompt { |
| 58 | const stats = [ |
| 59 | `Mean time: ${Math.round(item.mean_time).toLocaleString()}ms`, |
| 60 | `Total time: ${Math.round(item.total_time).toLocaleString()}ms`, |
| 61 | `Calls: ${item.calls.toLocaleString()}`, |
| 62 | ].join('\n') |
| 63 | |
| 64 | const issueContext: Record<NonNullable<ClassifiedQuery['issueType']>, string> = { |
| 65 | slow: `This query is running slowly (mean ${Math.round(item.mean_time)}ms). The goal is to reduce mean execution time.`, |
| 66 | index: `This query is missing an index. The planner is doing a sequential scan where an index scan would be faster.`, |
| 67 | error: `This query has a performance error or anti-pattern: ${item.hint}`, |
| 68 | } |
| 69 | |
| 70 | const context = item.issueType ? issueContext[item.issueType] : '' |
| 71 | |
| 72 | const tableName = getTableName(item.query) |
| 73 | const columnName = getColumnName(item.query) |
| 74 | const queryContext = [tableName && `Table: ${tableName}`, columnName && `Column: ${columnName}`] |
| 75 | .filter(Boolean) |
| 76 | .join('\n') |
| 77 | |
| 78 | const prompt = `You are a PostgreSQL performance expert. A query has been flagged in our Query Insights triage view. |
| 79 | |
| 80 | **Issue:** ${item.hint} |
| 81 | ${context} |
| 82 | |
| 83 | **Performance stats:** |
| 84 | ${stats} |
| 85 | ${queryContext ? `\n**Query context:**\n${queryContext}` : ''} |
| 86 | |
| 87 | **Full query:** |
| 88 | \`\`\`sql |
| 89 | ${item.query} |
| 90 | \`\`\` |
| 91 | |
| 92 | Your task is to provide a concrete fix the user can run directly in their SQL Editor. |
| 93 | |
| 94 | Respond with: |
| 95 | 1. A short diagnosis (1-2 sentences max) explaining why the query is slow or problematic |
| 96 | 2. The exact SQL to fix it — this could be a \`CREATE INDEX\` statement, a rewritten version of the query, or another SQL command |
| 97 | 3. A brief explanation of why the fix helps and what improvement to expect |
| 98 | |
| 99 | Format the SQL as a runnable code block. Do not add caveats or lengthy explanations — focus on the fix. |
| 100 | |
| 101 | **Important:** The SQL Editor runs statements inside a transaction block. Do not use \`CREATE INDEX CONCURRENTLY\` — use plain \`CREATE INDEX\` instead. |
| 102 | |
| 103 | **Important:** Use table and column names exactly as they appear in the full query above, including their schema prefix (e.g. \`auth.users\`, not \`public.users\`). Do not guess or change schema names.` |
| 104 | |
| 105 | return { query: item.query, prompt } |
| 106 | } |
| 107 | |
| 108 | export function buildExplainOptimizationPrompt( |
| 109 | query: string, |
| 110 | planRows: QueryPlanRow[], |
| 111 | stats?: Pick<QueryPerformanceRow, 'mean_time' | 'calls' | 'total_time'> |
| 112 | ): QueryExplanationPrompt { |
| 113 | const explainText = planRows.map((r) => r['QUERY PLAN']).join('\n') |
| 114 | |
| 115 | const statLines = stats |
| 116 | ? [ |
| 117 | `Mean time: ${stats.mean_time.toLocaleString()}ms`, |
| 118 | `Total time: ${stats.total_time.toLocaleString()}ms`, |
| 119 | `Calls: ${stats.calls.toLocaleString()}`, |
| 120 | ].join('\n') |
| 121 | : null |
| 122 | |
| 123 | const prompt = `You are a PostgreSQL performance expert. Analyze the EXPLAIN ANALYZE output below and suggest specific optimizations. |
| 124 | |
| 125 | ${statLines ? `**Performance stats:**\n${statLines}\n` : ''} |
| 126 | **EXPLAIN ANALYZE output:** |
| 127 | \`\`\` |
| 128 | ${explainText} |
| 129 | \`\`\` |
| 130 | |
| 131 | Focus on: |
| 132 | 1. The most expensive nodes (widest bars / highest actual time) |
| 133 | 2. Any Seq Scans on large tables that could be replaced with an Index Scan |
| 134 | 3. Large gaps between estimated rows and actual rows (stale statistics) |
| 135 | 4. Inefficient join strategies (Nested Loop on large tables) |
| 136 | |
| 137 | For each issue found, suggest a concrete fix (e.g. the exact \`CREATE INDEX\` statement, or \`ANALYZE table_name\`). Be concise and actionable.` |
| 138 | |
| 139 | return { query, prompt } |
| 140 | } |