parseExplainOutput() — supabase Function Reference
Architecture documentation for the parseExplainOutput() function in ExplainVisualizer.parser.ts from the supabase codebase.
Entity Profile
Dependency Diagram
graph TD ac30de94_dc70_4332_43a2_049c7d7d4364["parseExplainOutput()"] 1354dff2_a00a_a836_6d23_6b19e617f15f["createNodeTree()"] 1354dff2_a00a_a836_6d23_6b19e617f15f -->|calls| ac30de94_dc70_4332_43a2_049c7d7d4364 0cb11d5b_0044_bc0f_7964_0620c11647b6["createNode()"] ac30de94_dc70_4332_43a2_049c7d7d4364 -->|calls| 0cb11d5b_0044_bc0f_7964_0620c11647b6 a7bd362f_7510_af20_72c2_e65f3cc2830a["addNodeToTree()"] ac30de94_dc70_4332_43a2_049c7d7d4364 -->|calls| a7bd362f_7510_af20_72c2_e65f3cc2830a style ac30de94_dc70_4332_43a2_049c7d7d4364 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
apps/studio/components/interfaces/ExplainVisualizer/ExplainVisualizer.parser.ts lines 23–126
export function parseExplainOutput(rows: readonly QueryPlanRow[]): ExplainNode[] {
const lines = rows.map((row) => row['QUERY PLAN'] || '').filter(Boolean)
const root: ExplainNode[] = []
const stack: { node: ExplainNode; indent: number }[] = []
// Detail line patterns that should be attached to the previous node
const detailPatterns =
/^(Filter|Sort Key|Group Key|Hash Cond|Join Filter|Index Cond|Recheck Cond|Rows Removed by Filter|Rows Removed by Index Recheck|Output|Merge Cond|Sort Method|Worker \d+|Buffers|Planning Time|Execution Time|One-Time Filter|InitPlan|SubPlan):/
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
// Skip empty lines
if (!line.trim()) continue
// Calculate the indentation (number of leading spaces)
const leadingMatch = line.match(/^(\s*)/)
const leadingSpaces = leadingMatch ? leadingMatch[1].length : 0
// Check if this line has an arrow (indicates a child operation node)
const hasArrow = line.includes('->')
// Extract the content after any arrow
let content = line
let effectiveIndent = leadingSpaces
if (hasArrow) {
// Find position of -> and use that for indent calculation
const arrowIndex = line.indexOf('->')
effectiveIndent = arrowIndex
content = line.substring(arrowIndex + 2).trim()
} else {
content = line.trim()
}
// Skip Planning Time and Execution Time summary lines (at root level)
if (
content.startsWith('Planning Time:') ||
content.startsWith('Execution Time:') ||
content.startsWith('Planning:') ||
content.startsWith('Execution:')
) {
continue
}
// Check if this is a detail line (like Filter:, Sort Key:, etc.)
if (detailPatterns.test(content) && stack.length > 0) {
// Attach to the most recent node at or above this indentation
const currentNode = stack[stack.length - 1].node
currentNode.details += (currentNode.details ? '\n' : '') + content
continue
}
// Check if this is a continuation of details (indented text without operation pattern)
// These are typically wrapped condition expressions
if (!hasArrow && stack.length > 0 && leadingSpaces > 0) {
const lastItem = stack[stack.length - 1]
// If it's more indented than the last node and doesn't look like an operation
if (leadingSpaces > lastItem.indent && !content.match(/^\w+.*\(cost=/)) {
lastItem.node.details += (lastItem.node.details ? '\n' : '') + content
continue
}
}
// Parse main operation line: "Operation on table (metrics)"
// Match operation with optional metrics in parentheses
// Handle multiple metric groups like (cost=...) (actual time=...)
const metricsMatch = content.match(/^(.+?)\s*(\([^)]*cost=[^)]+\)(?:\s*\([^)]+\))*)?\s*$/)
if (!metricsMatch) {
continue
}
const [, operationPart, metricsStr] = metricsMatch
const metrics = metricsStr
? metricsStr.replace(/^\(|\)$/g, '').replace(/\)\s*\(/g, ' ')
: undefined
// Split operation and object name (e.g., "Seq Scan on users" -> operation: "Seq Scan", details: "users")
let operation = operationPart.trim()
let details = ''
// Check for "on tablename" or "using indexname" patterns
const onMatch = operationPart.match(/^(.+?)\s+on\s+(.+)$/i)
const usingMatch = operationPart.match(/^(.+?)\s+using\s+(.+)$/i)
if (onMatch) {
operation = onMatch[1].trim()
details = 'on ' + onMatch[2].trim()
} else if (usingMatch) {
operation = usingMatch[1].trim()
details = 'using ' + usingMatch[2].trim()
}
// Calculate the tree level based on indentation
// PostgreSQL typically uses 6 spaces per level for -> nodes
const level = hasArrow ? Math.floor(effectiveIndent / 6) + 1 : 0
const node = createNode(operation, details, metrics, level, line)
addNodeToTree(node, effectiveIndent, root, stack)
}
return root
}
Domain
Subdomains
Called By
Source
Frequently Asked Questions
What does parseExplainOutput() do?
parseExplainOutput() is a function in the supabase codebase.
What does parseExplainOutput() call?
parseExplainOutput() calls 2 function(s): addNodeToTree, createNode.
What calls parseExplainOutput()?
parseExplainOutput() is called by 1 function(s): createNodeTree.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free