Home / Function/ parseExplainOutput() — supabase Function Reference

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
}

Subdomains

Called By

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